Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MultiPolygon support #19

Merged
merged 1 commit into from
Sep 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 0.5 - 2018-09-09

### Added

- `MultiPolygon` support

## 0.4.2 - 2018-08-22

### Added
Expand Down
2 changes: 1 addition & 1 deletion centerline/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import unicode_literals

__title__ = 'centerline'
__version__ = '0.4.2'
__version__ = '0.5'
__author__ = 'Filip Todic'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2014-present Filip Todic'
Expand Down
6 changes: 3 additions & 3 deletions centerline/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from shapely.geometry import mapping, shape

from .main import Centerline
from .utils import get_ogr_driver, is_polygon
from .utils import get_ogr_driver, is_valid_geometry


def create_centerlines(src, dst, density=0.5):
Expand Down Expand Up @@ -50,11 +50,11 @@ def create_centerlines(src, dst, density=0.5):
encoding=source.encoding) as destination:
for record in source:
geom = record.get('geometry')
input_geom = shape(geom)

if not is_polygon(geometry_type=geom.get('type')):
if not is_valid_geometry(geometry=input_geom):
continue

input_geom = shape(geom)
attributes = record.get('properties')
try:
centerline_obj = Centerline(
Expand Down
34 changes: 22 additions & 12 deletions centerline/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

from numpy import array
from scipy.spatial import Voronoi
from shapely.geometry import LineString, MultiLineString, Polygon
from shapely.geometry import LineString, MultiLineString, MultiPolygon
from shapely.ops import unary_union

from .utils import is_valid_geometry


class Centerline(MultiLineString):
"""
Expand Down Expand Up @@ -40,9 +42,10 @@ def __init__(self, input_geom, interpolation_dist=0.5, **attributes):
ValueError: invalid input geometry

"""
if not isinstance(input_geom, Polygon):
if not is_valid_geometry(input_geom):
raise ValueError(
'Input geometry must be of type shapely.geometry.Polygon!'
'Input geometry must be of type shapely.geometry.Polygon '
'or shapely.geometry.MultiPolygon!'
)

self._input_geom = input_geom
Expand Down Expand Up @@ -112,17 +115,24 @@ def __densify_border(self):
[[X1, Y1], [X2, Y2], ..., [Xn, Yn]

"""
if len(self._input_geom.interiors) == 0:
exterIN = LineString(self._input_geom.exterior)
points = self.__fixed_interpolation(exterIN)

if isinstance(self._input_geom, MultiPolygon):
polygons = [polygon for polygon in self._input_geom]
else:
exterIN = LineString(self._input_geom.exterior)
points = self.__fixed_interpolation(exterIN)
polygons = [self._input_geom]

points = []
for polygon in polygons:
if len(polygon.interiors) == 0:
exterior = LineString(polygon.exterior)
points += self.__fixed_interpolation(exterior)

else:
exterior = LineString(polygon.exterior)
points += self.__fixed_interpolation(exterior)

for j in range(len(self._input_geom.interiors)):
interIN = LineString(self._input_geom.interiors[j])
points += self.__fixed_interpolation(interIN)
for j in range(len(polygon.interiors)):
interior = LineString(polygon.interiors[j])
points += self.__fixed_interpolation(interior)

return points

Expand Down
11 changes: 6 additions & 5 deletions centerline/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@
import os

from osgeo import gdal, ogr
from shapely.geometry import MultiPolygon, Polygon

# Enable GDAL/OGR exceptions
gdal.UseExceptions()


ALLOWED_INPUT_GEOMETRY = 'Polygon'
ALLOWED_INPUT_GEOMETRIES = ('Polygon', 'MultiPolygon')


def is_polygon(geometry_type):
def is_valid_geometry(geometry):
"""
Confirm that the geometry type is of type Polygon.
Confirm that the geometry type is of type Polygon or MultiPolygon.

Args:
geometry_type (str): geometry type
geometry (BaseGeometry): BaseGeometry instance (e.g. Polygon)

Returns:
bool

"""
if geometry_type == ALLOWED_INPUT_GEOMETRY:
if isinstance(geometry, Polygon) or isinstance(geometry, MultiPolygon):
return True
else:
return False
Expand Down
13 changes: 8 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

class TestCenterlineSupportedGeometryTypes(TestCase):
"""
Only Polygons should be supported.
Only Polygons and MultiPolygons should be supported.

For more information about creating the geometry objects (like the
ones used below) see The Shapely User Manual:
Expand All @@ -39,11 +39,14 @@ def test__polygon_with_interior_ring__returns_multilinestring(self):
self.assertIsInstance(centerline, MultiLineString)

def test__multipolygon__raises_valueerror(self):
POLYGONS = [Point(i, 0).buffer(0.1) for i in range(2)]
MULTIPOLYGON = MultiPolygon(POLYGONS)
POLYGON_1 = Polygon([[0, 0], [0, 4], [4, 4], [4, 0]])
POLYGON_2 = Polygon([[5, 5], [5, 9], [9, 9], [9, 5]])

with self.assertRaises(ValueError):
Centerline(MULTIPOLYGON)
MULTIPOLYGON = MultiPolygon([POLYGON_1, POLYGON_2])

centerline = Centerline(MULTIPOLYGON)

self.assertIsInstance(centerline, MultiLineString)

def test__point__raises_valueerror(self):
POINT = Point(0, 0)
Expand Down
29 changes: 23 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,37 @@
import os
from unittest import TestCase

from centerline.utils import get_ogr_driver, is_polygon
from shapely import geometry

from centerline.utils import get_ogr_driver, is_valid_geometry

TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
SHP_DIR = os.path.join(TESTS_DIR, 'data', 'shp')
GEOJSON_DIR = os.path.join(TESTS_DIR, 'data', 'geojson')


class TestIsPolygon(TestCase):
class TestIsValidGeometry(TestCase):

def test_point_returns_false(self):
self.assertFalse(is_valid_geometry(geometry.Point()))

def test_multipoint_returns_false(self):
self.assertFalse(is_valid_geometry(geometry.MultiPoint()))

def test_linestring_returns_false(self):
self.assertFalse(is_valid_geometry(geometry.LineString()))

def test_multilinestring_returns_false(self):
self.assertFalse(is_valid_geometry(geometry.MultiLineString()))

def test_linearring_returns_false(self):
self.assertFalse(is_valid_geometry(geometry.LinearRing()))

def test__returns_true(self):
self.assertTrue(is_polygon('Polygon'))
def test_polygon_returns_true(self):
self.assertTrue(is_valid_geometry(geometry.Polygon()))

def test__returns_false_for_multipolygon(self):
self.assertFalse(is_polygon('MultiPolygon'))
def test_multipolygon_returns_true(self):
self.assertTrue(is_valid_geometry(geometry.MultiPolygon()))


class TestGetOgrDriver(TestCase):
Expand Down