Source code for centerline.geometry
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from numpy import array
from scipy.spatial import Voronoi
from shapely.geometry import LineString, MultiLineString, MultiPolygon, Polygon
from shapely.ops import unary_union
from . import exceptions
[docs]class Centerline:
"""Create a centerline object.
The ``attributes`` are copied and set as the centerline's
attributes.
:param input_geometry: input geometry
:type input_geometry: :py:class:`shapely.geometry.Polygon` or
:py:class:`shapely.geometry.MultiPolygon`
:param interpolation_distance: densify the input geometry's
border by placing additional points at this distance,
defaults to 0.5 [meter]
:type interpolation_distance: float, optional
:raises exceptions.InvalidInputTypeError: input geometry is not
of type :py:class:`shapely.geometry.Polygon` or
:py:class:`shapely.geometry.MultiPolygon`
"""
def __init__(
self, input_geometry, interpolation_distance=0.5, **attributes
):
self._input_geometry = input_geometry
self._interpolation_distance = abs(interpolation_distance)
if not self.input_geometry_is_valid():
raise exceptions.InvalidInputTypeError
self._min_x, self._min_y = self._get_reduced_coordinates()
self.assign_attributes_to_instance(attributes)
self.geometry = MultiLineString(lines=self._construct_centerline())
def _get_reduced_coordinates(self):
min_x = int(min(self._input_geometry.envelope.exterior.xy[0]))
min_y = int(min(self._input_geometry.envelope.exterior.xy[1]))
return min_x, min_y
[docs] def assign_attributes_to_instance(self, attributes):
"""Assign the ``attributes`` to the :py:class:`Centerline` object.
:param attributes: polygon's attributes
:type attributes: dict
"""
for key in attributes:
setattr(self, key, attributes.get(key))
def _construct_centerline(self):
vertices, ridges = self._get_voronoi_vertices_and_ridges()
linestrings = []
for ridge in ridges:
if self._ridge_is_finite(ridge):
starting_point = self._create_point_with_restored_coordinates(
x=vertices[ridge[0]][0], y=vertices[ridge[0]][1]
)
ending_point = self._create_point_with_restored_coordinates(
x=vertices[ridge[1]][0], y=vertices[ridge[1]][1]
)
linestring = LineString((starting_point, ending_point))
if self._linestring_is_within_input_geometry(linestring):
linestrings.append(linestring)
if len(linestrings) < 2:
raise exceptions.TooFewRidgesError
return unary_union(linestrings)
def _get_voronoi_vertices_and_ridges(self):
borders = self._get_densified_borders()
voronoi_diagram = Voronoi(borders)
vertices = voronoi_diagram.vertices
ridges = voronoi_diagram.ridge_vertices
return vertices, ridges
def _ridge_is_finite(self, ridge):
return -1 not in ridge
def _create_point_with_restored_coordinates(self, x, y):
return (x + self._min_x, y + self._min_y)
def _linestring_is_within_input_geometry(self, linestring):
return (
linestring.within(self._input_geometry)
and len(linestring.coords[0]) > 1
)
def _get_densified_borders(self):
polygons = self._extract_polygons_from_input_geometry()
points = []
for polygon in polygons:
points += self._get_interpolated_boundary(polygon.exterior)
if self._polygon_has_interior_rings(polygon):
for interior in polygon.interiors:
points += self._get_interpolated_boundary(interior)
return array(points)
def _extract_polygons_from_input_geometry(self):
if isinstance(self._input_geometry, MultiPolygon):
return (polygon for polygon in self._input_geometry.geoms)
else:
return (self._input_geometry,)
def _polygon_has_interior_rings(self, polygon):
return len(polygon.interiors) > 0
def _get_interpolated_boundary(self, boundary):
line = LineString(boundary)
first_point = self._get_coordinates_of_first_point(line)
last_point = self._get_coordinates_of_last_point(line)
intermediate_points = self._get_coordinates_of_interpolated_points(
line
)
return [first_point] + intermediate_points + [last_point]
def _get_coordinates_of_first_point(self, linestring):
return self._create_point_with_reduced_coordinates(
x=linestring.xy[0][0], y=linestring.xy[1][0]
)
def _get_coordinates_of_last_point(self, linestring):
return self._create_point_with_reduced_coordinates(
x=linestring.xy[0][-1], y=linestring.xy[1][-1]
)
def _get_coordinates_of_interpolated_points(self, linestring):
intermediate_points = []
interpolation_distance = self._interpolation_distance
line_length = linestring.length
while interpolation_distance < line_length:
point = linestring.interpolate(interpolation_distance)
reduced_point = self._create_point_with_reduced_coordinates(
x=point.x, y=point.y
)
intermediate_points.append(reduced_point)
interpolation_distance += self._interpolation_distance
return intermediate_points
def _create_point_with_reduced_coordinates(self, x, y):
return (x - self._min_x, y - self._min_y)