Skip to content

Commit

Permalink
Merge pull request #175 from jbouffard/neighborhood
Browse files Browse the repository at this point in the history
Neighborhood Classes
  • Loading branch information
echeipesh committed May 5, 2017
2 parents dc5afb0 + 8b16628 commit 43d8f17
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 18 deletions.
7 changes: 7 additions & 0 deletions docs/geopyspark.geotrellis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ geopyspark.geotrellis.geotiff_rdd module
:members:
:inherited-members:

geopyspark.geotrellis.neighborhoods module
--------------------------------------------

.. automodule:: geopyspark.geotrellis.neighborhoods
:members:
:inherited-members:

geopyspark.geotrellis.rdd module
---------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ object Constants {
final val NESW = "nesw"
final val SQUARE = "square"
final val WEDGE = "wedge"
final val CIRCLE = "circle"

final val GREATERTHAN = "GreaterThan"
final val GREATERTHANOREQUALTO = "GreaterThanOrEqualTo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object GeoTrellisUtils {
case NESW => Nesw(param1.toInt)
case SQUARE => Square(param1.toInt)
case WEDGE => Wedge(param1, param2, param3)
case _ if (operation == "Aspect" || operation == "Slope") => Square(1)
case CIRCLE => Circle(param1)
}

def getOperation(
Expand Down
6 changes: 4 additions & 2 deletions geopyspark/geotrellis/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
"""Neighborhood type."""
WEDGE = 'wedge'

"""Neighborhood type."""
CIRCLE = "circle"

"""Focal operation type."""
SUM = 'Sum'

Expand Down Expand Up @@ -157,8 +160,7 @@
NESW,
SQUARE,
WEDGE,
ASPECT,
SLOPE
CIRCLE
]

"""The NoData value for ints in GeoTrellis."""
Expand Down
149 changes: 149 additions & 0 deletions geopyspark/geotrellis/neighborhoods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Classes that represent the various neighborhoods used in focal functions.
Note:
Once a parameter has been entered for any one of these classes it gets converted to a
``float`` if it was originally an ``int``.
"""


class Neighborhood(object):
def __init__(self, name, param_1, param_2=None, param_3=None):
"""The base class of the all of the neighborhoods.
Args:
param_1 (int or float): The first argument of the neighborhood.
param_2 (int or float, optional): The second argument of the neighborhood.
param_3 (int or float, optional): The third argument of the neighborhood.
Attributes:
param_1 (float): The first argument.
param_2 (float, optional): The second argument.
param_3 (float, optional): The third argument.
name (str): The name of the neighborhood.
"""

self.name = name
self.param_1 = float(param_1)

if param_2:
self.param_2 = float(param_2)
else:
self.param_2 = 0.0

if param_3:
self.param_3 = float(param_3)
else:
self.param_3 = 0.0


class Square(Neighborhood):
def __init__(self, extent):
"""A square neighborhood.
Args:
extent (int or float): The extent of this neighborhood. This represents the how many
cells past the focus the bounding box goes.
Attributes:
extent (int or float): The extent of this neighborhood. This represents the how many
cells past the focus the bounding box goes.
param_1 (float): Same as ``extent``.
param_2 (float): Unused param for ``Square``. Is 0.0.
param_3 (float): Unused param for ``Square``. Is 0.0.
name (str): The name of the neighborhood which is, "square".
"""

super().__init__(name="square", param_1=extent)
self.extent = extent


class Circle(Neighborhood):
"""A circle neighborhood.
Args:
radius (int or float): The radius of the circle that determines which cells fall within
the bounding box.
Attributes:
radius (int or float): The radius of the circle that determines which cells fall within
the bounding box.
param_1 (float): Same as ``radius``.
param_2 (float): Unused param for ``Circle``. Is 0.0.
param_3 (float): Unused param for ``Circle``. Is 0.0.
name (str): The name of the neighborhood which is, "circle".
Note:
Cells that lie exactly on the radius of the circle are apart of the neighborhood.
"""

def __init__(self, radius):
super().__init__(name="circle", param_1=radius)
self.radius = radius


class Nesw(Neighborhood):
"""A neighborhood that includes a column and row intersection for the focus.
Args:
extent (int or float): The extent of this neighborhood. This represents the how many
cells past the focus the bounding box goes.
Attributes:
extent (int or float): The extent of this neighborhood. This represents the how many
cells past the focus the bounding box goes.
param_1 (float): Same as ``extent``.
param_2 (float): Unused param for ``Nesw``. Is 0.0.
param_3 (float): Unused param for ``Nesw``. Is 0.0.
name (str): The name of the neighborhood which is, "nesw".
"""

def __init__(self, extent):
super().__init__(name="nesw", param_1=extent)
self.extent = extent


class Wedge(Neighborhood):
"""A wedge neighborhood.
Args:
radius (int or float): The radius of the wedge.
start_angle (int or float): The starting angle of the wedge in degrees.
end_angle (int or float): The ending angle of the wedge in degrees.
Attributes:
radius (int or float): The radius of the wedge.
start_angle (int or float): The starting angle of the wedge in degrees.
end_angle (int or float): The ending angle of the wedge in degrees.
param_1 (float): Same as ``radius``.
param_2 (float): Same as ``start_angle``.
param_3 (float): Same as ``end_angle``.
name (str): The name of the neighborhood which is, "wedge".
"""

def __init__(self, radius, start_angle, end_angle):
super().__init__(name="wedge", param_1=radius, param_2=start_angle, param_3=end_angle)
self.radius = radius
self.start_angle = start_angle
self.end_angle = end_angle


class Annulus(Neighborhood):
"""An Annulus neighborhood.
Args:
inner_radius (int or float): The radius of the inner circle.
outer_radius (int or float): The radius of the outer circle.
Attributes:
inner_radius (int or float): The radius of the inner circle.
outer_radius (int or float): The radius of the outer circle.
param_1 (float): Same as ``inner_radius``.
param_2 (float): Same as ``outer_radius``.
param_3 (float): Unused param for ``Annulus``. Is 0.0.
name (str): The name of the neighborhood which is, "annulus".
"""

def __init__(self, inner_radius, outer_radius):
super().__init__(name="annulus", param_1=inner_radius, param_2=outer_radius)
self.inner_radius = inner_radius
self.outer_radius = outer_radius
58 changes: 43 additions & 15 deletions geopyspark/geotrellis/rdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from shapely.wkt import dumps
from geopyspark.geotrellis.constants import (RESAMPLE_METHODS,
OPERATIONS,
SLOPE,
ASPECT,
SQUARE,
NEIGHBORHOODS,
NEARESTNEIGHBOR,
FLOAT,
Expand All @@ -18,6 +21,8 @@
LESSTHANOREQUALTO,
NODATAINT
)
from geopyspark.geotrellis.neighborhoods import Neighborhood


def _reclassify(srdd, value_map, data_type, boundary_strategy, replace_nodata_with):
new_dict = {}
Expand Down Expand Up @@ -543,36 +548,59 @@ def pyramid(self, start_zoom, end_zoom, resample_method=NEARESTNEIGHBOR):

return [TiledRasterRDD(self.geopysc, self.rdd_type, srdd) for srdd in result]

def focal(self, operation, neighborhood, param_1=None, param_2=None, param_3=None):
def focal(self, operation, neighborhood=None, param_1=None, param_2=None, param_3=None):
"""Performs the given focal operation on the layers contained in the RDD.
Args:
operation (str): The focal operation such as SUM, ASPECT, SLOPE, etc.
neighborhood (str): The type of neighborhood to use such as ANNULUS, SQUARE, etc.
param_1 (int, float, optional): If using ``SLOPE``, then this is the zFactor, else it
is the first argument of the ``neighborhood``.
param_2 (int, float, optional): The second argument of the `neighborhood`.
param_3 (int, float, optional): The third argument of the `neighborhood`.
neighborhood (str or :class:`~geopyspark.geotrellis.neighborhoods.Neighborhood`, optional):
The type of neighborhood to use in the focal operation. This can be represented by
either an instance of ``Neighborhood``, or by a constant such as ANNULUS, SQUARE,
etc. Defaults to ``None``.
param_1 (int or float, optional): If using ``SLOPE``, then this is the zFactor, else it
is the first argument of ``neighborhood``.
param_2 (int or float, optional): The second argument of the `neighborhood`.
param_3 (int or float, optional): The third argument of the `neighborhood`.
Note:
``param``s only need to be set if ``neighborhood`` is not an instance of
``Neighborhood`` or if ``neighborhood`` is ``None``.
Any `param` that is not set will default to 0.0.
If ``neighborhood`` is ``None`` then ``operation`` **must** be either ``SLOPE`` or
``ASPECT``.
Returns:
:class:`~geopyspark.geotrellis.rdd.TiledRasterRDD`
"""

if operation not in OPERATIONS:
raise ValueError(operation, " Is not a known operation.")
raise ValueError(operation, "Is not a known operation.")

if isinstance(neighborhood, Neighborhood):
srdd = self.srdd.focal(operation, neighborhood.name, neighborhood.param_1,
neighborhood.param_2, neighborhood.param_3)

if param_1 is None:
param_1 = 0.0
if param_2 is None:
param_2 = 0.0
if param_3 is None:
param_3 = 0.0
elif isinstance(neighborhood, str):
if neighborhood not in NEIGHBORHOODS:
raise ValueError(neighborhood, "is not a known neighborhood.")

srdd = self.srdd.focal(operation, neighborhood, float(param_1), float(param_2),
float(param_3))
if param_1 is None:
param_1 = 0.0
if param_2 is None:
param_2 = 0.0
if param_3 is None:
param_3 = 0.0

srdd = self.srdd.focal(operation, neighborhood, float(param_1), float(param_2),
float(param_3))

elif not neighborhood and operation == SLOPE or operation == ASPECT:
srdd = self.srdd.focal(operation, SQUARE, 1.0, 0.0, 0.0)

else:
raise ValueError("neighborhood must be set or the operation must be SLOPE or ASPECT")

return TiledRasterRDD(self.geopysc, self.rdd_type, srdd)

Expand Down
15 changes: 15 additions & 0 deletions geopyspark/tests/focal_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from geopyspark.geotrellis.rdd import TiledRasterRDD
from geopyspark.tests.base_test_class import BaseTestClass
from geopyspark.geotrellis.constants import SPATIAL, SUM, MIN, SQUARE, ANNULUS
from geopyspark.geotrellis.neighborhoods import Square, Annulus


class FocalTest(BaseTestClass):
Expand Down Expand Up @@ -57,11 +58,25 @@ def test_focal_sum_int(self):

self.assertTrue(result.to_numpy_rdd().first()[1]['data'][0][1][0] >= 6)

def test_focal_sum_square(self):
square = Square(extent=1.0)
result = self.raster_rdd.focal(
operation=SUM,
neighborhood=square)

self.assertTrue(result.to_numpy_rdd().first()[1]['data'][0][1][0] >= 6)

def test_focal_min(self):
result = self.raster_rdd.focal(operation=MIN, neighborhood=ANNULUS, param_1=2.0, param_2=1.0)

self.assertEqual(result.to_numpy_rdd().first()[1]['data'][0][0][0], -1)

def test_focal_min_annulus(self):
annulus = Annulus(inner_radius=2.0, outer_radius=1.0)
result = self.raster_rdd.focal(operation=MIN, neighborhood=annulus)

self.assertEqual(result.to_numpy_rdd().first()[1]['data'][0][0][0], -1)

def test_focal_min_int(self):
result = self.raster_rdd.focal(operation=MIN, neighborhood=ANNULUS, param_1=2, param_2=1)

Expand Down

0 comments on commit 43d8f17

Please sign in to comment.