initial commit
This commit is contained in:
28
billinglayer/python/shapely/geometry/__init__.py
Normal file
28
billinglayer/python/shapely/geometry/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Geometry classes and factories
|
||||
"""
|
||||
|
||||
from .base import CAP_STYLE, JOIN_STYLE
|
||||
from .collection import GeometryCollection
|
||||
from .geo import box, mapping, shape
|
||||
from .linestring import LineString
|
||||
from .multilinestring import MultiLineString
|
||||
from .multipoint import MultiPoint
|
||||
from .multipolygon import MultiPolygon
|
||||
from .point import Point
|
||||
from .polygon import LinearRing, Polygon
|
||||
|
||||
__all__ = [
|
||||
"box",
|
||||
"shape",
|
||||
"mapping",
|
||||
"Point",
|
||||
"LineString",
|
||||
"Polygon",
|
||||
"MultiPoint",
|
||||
"MultiLineString",
|
||||
"MultiPolygon",
|
||||
"GeometryCollection",
|
||||
"LinearRing",
|
||||
"CAP_STYLE",
|
||||
"JOIN_STYLE",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
994
billinglayer/python/shapely/geometry/base.py
Normal file
994
billinglayer/python/shapely/geometry/base.py
Normal file
@@ -0,0 +1,994 @@
|
||||
"""Base geometry class and utilities
|
||||
|
||||
Note: a third, z, coordinate value may be used when constructing
|
||||
geometry objects, but has no effect on geometric analysis. All
|
||||
operations are performed in the x-y plane. Thus, geometries with
|
||||
different z values may intersect or be equal.
|
||||
"""
|
||||
import re
|
||||
from warnings import warn
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shapely
|
||||
from shapely._geometry_helpers import _geom_factory
|
||||
from shapely.constructive import BufferCapStyle, BufferJoinStyle
|
||||
from shapely.coords import CoordinateSequence
|
||||
from shapely.errors import GeometryTypeError, GEOSException, ShapelyDeprecationWarning
|
||||
|
||||
GEOMETRY_TYPES = [
|
||||
"Point",
|
||||
"LineString",
|
||||
"LinearRing",
|
||||
"Polygon",
|
||||
"MultiPoint",
|
||||
"MultiLineString",
|
||||
"MultiPolygon",
|
||||
"GeometryCollection",
|
||||
]
|
||||
|
||||
|
||||
def geom_factory(g, parent=None):
|
||||
"""
|
||||
Creates a Shapely geometry instance from a pointer to a GEOS geometry.
|
||||
|
||||
.. warning::
|
||||
The GEOS library used to create the the GEOS geometry pointer
|
||||
and the GEOS library used by Shapely must be exactly the same, or
|
||||
unexpected results or segfaults may occur.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Deprecated in Shapely 2.0, and will be removed in a future version.
|
||||
"""
|
||||
warn(
|
||||
"The 'geom_factory' function is deprecated in Shapely 2.0, and will be "
|
||||
"removed in a future version",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _geom_factory(g)
|
||||
|
||||
|
||||
def dump_coords(geom):
|
||||
"""Dump coordinates of a geometry in the same order as data packing"""
|
||||
if not isinstance(geom, BaseGeometry):
|
||||
raise ValueError(
|
||||
"Must be instance of a geometry class; found " + geom.__class__.__name__
|
||||
)
|
||||
elif geom.geom_type in ("Point", "LineString", "LinearRing"):
|
||||
return geom.coords[:]
|
||||
elif geom.geom_type == "Polygon":
|
||||
return geom.exterior.coords[:] + [i.coords[:] for i in geom.interiors]
|
||||
elif geom.geom_type.startswith("Multi") or geom.geom_type == "GeometryCollection":
|
||||
# Recursive call
|
||||
return [dump_coords(part) for part in geom.geoms]
|
||||
else:
|
||||
raise GeometryTypeError("Unhandled geometry type: " + repr(geom.geom_type))
|
||||
|
||||
|
||||
def _maybe_unpack(result):
|
||||
if result.ndim == 0:
|
||||
# convert numpy 0-d array / scalar to python scalar
|
||||
return result.item()
|
||||
else:
|
||||
# >=1 dim array
|
||||
return result
|
||||
|
||||
|
||||
class CAP_STYLE:
|
||||
round = BufferCapStyle.round
|
||||
flat = BufferCapStyle.flat
|
||||
square = BufferCapStyle.square
|
||||
|
||||
|
||||
class JOIN_STYLE:
|
||||
round = BufferJoinStyle.round
|
||||
mitre = BufferJoinStyle.mitre
|
||||
bevel = BufferJoinStyle.bevel
|
||||
|
||||
|
||||
class BaseGeometry(shapely.Geometry):
|
||||
"""
|
||||
Provides GEOS spatial predicates and topological operations.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self):
|
||||
warn(
|
||||
"Directly calling the base class 'BaseGeometry()' is deprecated, and "
|
||||
"will raise an error in the future. To create an empty geometry, "
|
||||
"use one of the subclasses instead, for example 'GeometryCollection()'.",
|
||||
ShapelyDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")
|
||||
|
||||
@property
|
||||
def _ndim(self):
|
||||
return shapely.get_coordinate_dimension(self)
|
||||
|
||||
def __bool__(self):
|
||||
return self.is_empty is False
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.__bool__()
|
||||
|
||||
def __format__(self, format_spec):
|
||||
"""Format a geometry using a format specification."""
|
||||
# bypass regexp for simple cases
|
||||
if format_spec == "":
|
||||
return shapely.to_wkt(self, rounding_precision=-1)
|
||||
elif format_spec == "x":
|
||||
return shapely.to_wkb(self, hex=True).lower()
|
||||
elif format_spec == "X":
|
||||
return shapely.to_wkb(self, hex=True)
|
||||
|
||||
# fmt: off
|
||||
format_spec_regexp = (
|
||||
"(?:0?\\.(?P<prec>[0-9]+))?"
|
||||
"(?P<fmt_code>[fFgGxX]?)"
|
||||
)
|
||||
# fmt: on
|
||||
match = re.fullmatch(format_spec_regexp, format_spec)
|
||||
if match is None:
|
||||
raise ValueError(f"invalid format specifier: {format_spec}")
|
||||
|
||||
prec, fmt_code = match.groups()
|
||||
|
||||
if prec:
|
||||
prec = int(prec)
|
||||
else:
|
||||
# GEOS has a default rounding_precision -1
|
||||
prec = -1
|
||||
|
||||
if not fmt_code:
|
||||
fmt_code = "g"
|
||||
|
||||
if fmt_code in ("g", "G"):
|
||||
res = shapely.to_wkt(self, rounding_precision=prec, trim=True)
|
||||
elif fmt_code in ("f", "F"):
|
||||
res = shapely.to_wkt(self, rounding_precision=prec, trim=False)
|
||||
elif fmt_code in ("x", "X"):
|
||||
raise ValueError("hex representation does not specify precision")
|
||||
else:
|
||||
raise NotImplementedError(f"unhandled fmt_code: {fmt_code}")
|
||||
|
||||
if fmt_code.isupper():
|
||||
return res.upper()
|
||||
else:
|
||||
return res
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
wkt = super().__str__()
|
||||
except (GEOSException, ValueError):
|
||||
# we never want a repr() to fail; that can be very confusing
|
||||
return "<shapely.{} Exception in WKT writer>".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
|
||||
# the total length is limited to 80 characters including brackets
|
||||
max_length = 78
|
||||
if len(wkt) > max_length:
|
||||
return f"<{wkt[: max_length - 3]}...>"
|
||||
|
||||
return f"<{wkt}>"
|
||||
|
||||
def __str__(self):
|
||||
return self.wkt
|
||||
|
||||
def __reduce__(self):
|
||||
return (shapely.from_wkb, (shapely.to_wkb(self, include_srid=True),))
|
||||
|
||||
# Operators
|
||||
# ---------
|
||||
|
||||
def __and__(self, other):
|
||||
return self.intersection(other)
|
||||
|
||||
def __or__(self, other):
|
||||
return self.union(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.difference(other)
|
||||
|
||||
def __xor__(self, other):
|
||||
return self.symmetric_difference(other)
|
||||
|
||||
# Coordinate access
|
||||
# -----------------
|
||||
|
||||
@property
|
||||
def coords(self):
|
||||
"""Access to geometry's coordinates (CoordinateSequence)"""
|
||||
coords_array = shapely.get_coordinates(self, include_z=self.has_z)
|
||||
return CoordinateSequence(coords_array)
|
||||
|
||||
@property
|
||||
def xy(self):
|
||||
"""Separate arrays of X and Y coordinate values"""
|
||||
raise NotImplementedError
|
||||
|
||||
# Python feature protocol
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
"""Dictionary representation of the geometry"""
|
||||
raise NotImplementedError
|
||||
|
||||
# Type of geometry and its representations
|
||||
# ----------------------------------------
|
||||
|
||||
def geometryType(self):
|
||||
warn(
|
||||
"The 'GeometryType()' method is deprecated, and will be removed in "
|
||||
"the future. You can use the 'geom_type' attribute instead.",
|
||||
ShapelyDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.geom_type
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
warn(
|
||||
"The 'type' attribute is deprecated, and will be removed in "
|
||||
"the future. You can use the 'geom_type' attribute instead.",
|
||||
ShapelyDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.geom_type
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
"""WKT representation of the geometry"""
|
||||
# TODO(shapely-2.0) keep default of not trimming?
|
||||
return shapely.to_wkt(self, rounding_precision=-1)
|
||||
|
||||
@property
|
||||
def wkb(self):
|
||||
"""WKB representation of the geometry"""
|
||||
return shapely.to_wkb(self)
|
||||
|
||||
@property
|
||||
def wkb_hex(self):
|
||||
"""WKB hex representation of the geometry"""
|
||||
return shapely.to_wkb(self, hex=True)
|
||||
|
||||
def svg(self, scale_factor=1.0, **kwargs):
|
||||
"""Raises NotImplementedError"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _repr_svg_(self):
|
||||
"""SVG representation for iPython notebook"""
|
||||
svg_top = (
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" '
|
||||
'xmlns:xlink="http://www.w3.org/1999/xlink" '
|
||||
)
|
||||
if self.is_empty:
|
||||
return svg_top + "/>"
|
||||
else:
|
||||
# Establish SVG canvas that will fit all the data + small space
|
||||
xmin, ymin, xmax, ymax = self.bounds
|
||||
if xmin == xmax and ymin == ymax:
|
||||
# This is a point; buffer using an arbitrary size
|
||||
xmin, ymin, xmax, ymax = self.buffer(1).bounds
|
||||
else:
|
||||
# Expand bounds by a fraction of the data ranges
|
||||
expand = 0.04 # or 4%, same as R plots
|
||||
widest_part = max([xmax - xmin, ymax - ymin])
|
||||
expand_amount = widest_part * expand
|
||||
xmin -= expand_amount
|
||||
ymin -= expand_amount
|
||||
xmax += expand_amount
|
||||
ymax += expand_amount
|
||||
dx = xmax - xmin
|
||||
dy = ymax - ymin
|
||||
width = min([max([100.0, dx]), 300])
|
||||
height = min([max([100.0, dy]), 300])
|
||||
try:
|
||||
scale_factor = max([dx, dy]) / max([width, height])
|
||||
except ZeroDivisionError:
|
||||
scale_factor = 1.0
|
||||
view_box = f"{xmin} {ymin} {dx} {dy}"
|
||||
transform = f"matrix(1,0,0,-1,0,{ymax + ymin})"
|
||||
return svg_top + (
|
||||
'width="{1}" height="{2}" viewBox="{0}" '
|
||||
'preserveAspectRatio="xMinYMin meet">'
|
||||
'<g transform="{3}">{4}</g></svg>'
|
||||
).format(view_box, width, height, transform, self.svg(scale_factor))
|
||||
|
||||
@property
|
||||
def geom_type(self):
|
||||
"""Name of the geometry's type, such as 'Point'"""
|
||||
return GEOMETRY_TYPES[shapely.get_type_id(self)]
|
||||
|
||||
# Real-valued properties and methods
|
||||
# ----------------------------------
|
||||
|
||||
@property
|
||||
def area(self):
|
||||
"""Unitless area of the geometry (float)"""
|
||||
return float(shapely.area(self))
|
||||
|
||||
def distance(self, other):
|
||||
"""Unitless distance to other geometry (float)"""
|
||||
return _maybe_unpack(shapely.distance(self, other))
|
||||
|
||||
def hausdorff_distance(self, other):
|
||||
"""Unitless hausdorff distance to other geometry (float)"""
|
||||
return _maybe_unpack(shapely.hausdorff_distance(self, other))
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""Unitless length of the geometry (float)"""
|
||||
return float(shapely.length(self))
|
||||
|
||||
@property
|
||||
def minimum_clearance(self):
|
||||
"""Unitless distance by which a node could be moved to produce an invalid geometry (float)"""
|
||||
return float(shapely.minimum_clearance(self))
|
||||
|
||||
# Topological properties
|
||||
# ----------------------
|
||||
|
||||
@property
|
||||
def boundary(self):
|
||||
"""Returns a lower dimension geometry that bounds the object
|
||||
|
||||
The boundary of a polygon is a line, the boundary of a line is a
|
||||
collection of points. The boundary of a point is an empty (null)
|
||||
collection.
|
||||
"""
|
||||
return shapely.boundary(self)
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
"""Returns minimum bounding region (minx, miny, maxx, maxy)"""
|
||||
return tuple(shapely.bounds(self).tolist())
|
||||
|
||||
@property
|
||||
def centroid(self):
|
||||
"""Returns the geometric center of the object"""
|
||||
return shapely.centroid(self)
|
||||
|
||||
def point_on_surface(self):
|
||||
"""Returns a point guaranteed to be within the object, cheaply.
|
||||
|
||||
Alias of `representative_point`.
|
||||
"""
|
||||
return shapely.point_on_surface(self)
|
||||
|
||||
def representative_point(self):
|
||||
"""Returns a point guaranteed to be within the object, cheaply.
|
||||
|
||||
Alias of `point_on_surface`.
|
||||
"""
|
||||
return shapely.point_on_surface(self)
|
||||
|
||||
@property
|
||||
def convex_hull(self):
|
||||
"""Imagine an elastic band stretched around the geometry: that's a
|
||||
convex hull, more or less
|
||||
|
||||
The convex hull of a three member multipoint, for example, is a
|
||||
triangular polygon.
|
||||
"""
|
||||
return shapely.convex_hull(self)
|
||||
|
||||
@property
|
||||
def envelope(self):
|
||||
"""A figure that envelopes the geometry"""
|
||||
return shapely.envelope(self)
|
||||
|
||||
@property
|
||||
def oriented_envelope(self):
|
||||
"""
|
||||
Returns the oriented envelope (minimum rotated rectangle) that
|
||||
encloses the geometry.
|
||||
|
||||
Unlike envelope this rectangle is not constrained to be parallel to the
|
||||
coordinate axes. If the convex hull of the object is a degenerate (line
|
||||
or point) this degenerate is returned.
|
||||
|
||||
Alias of `minimum_rotated_rectangle`.
|
||||
"""
|
||||
return shapely.oriented_envelope(self)
|
||||
|
||||
@property
|
||||
def minimum_rotated_rectangle(self):
|
||||
"""
|
||||
Returns the oriented envelope (minimum rotated rectangle) that
|
||||
encloses the geometry.
|
||||
|
||||
Unlike `envelope` this rectangle is not constrained to be parallel to the
|
||||
coordinate axes. If the convex hull of the object is a degenerate (line
|
||||
or point) this degenerate is returned.
|
||||
|
||||
Alias of `oriented_envelope`.
|
||||
"""
|
||||
return shapely.oriented_envelope(self)
|
||||
|
||||
def buffer(
|
||||
self,
|
||||
distance,
|
||||
quad_segs=16,
|
||||
cap_style="round",
|
||||
join_style="round",
|
||||
mitre_limit=5.0,
|
||||
single_sided=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Get a geometry that represents all points within a distance
|
||||
of this geometry.
|
||||
|
||||
A positive distance produces a dilation, a negative distance an
|
||||
erosion. A very small or zero distance may sometimes be used to
|
||||
"tidy" a polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
distance : float
|
||||
The distance to buffer around the object.
|
||||
resolution : int, optional
|
||||
The resolution of the buffer around each vertex of the
|
||||
object.
|
||||
quad_segs : int, optional
|
||||
Sets the number of line segments used to approximate an
|
||||
angle fillet.
|
||||
cap_style : shapely.BufferCapStyle or {'round', 'square', 'flat'}, default 'round'
|
||||
Specifies the shape of buffered line endings. BufferCapStyle.round ('round')
|
||||
results in circular line endings (see ``quad_segs``). Both BufferCapStyle.square
|
||||
('square') and BufferCapStyle.flat ('flat') result in rectangular line endings,
|
||||
only BufferCapStyle.flat ('flat') will end at the original vertex,
|
||||
while BufferCapStyle.square ('square') involves adding the buffer width.
|
||||
join_style : shapely.BufferJoinStyle or {'round', 'mitre', 'bevel'}, default 'round'
|
||||
Specifies the shape of buffered line midpoints. BufferJoinStyle.ROUND ('round')
|
||||
results in rounded shapes. BufferJoinStyle.bevel ('bevel') results in a beveled
|
||||
edge that touches the original vertex. BufferJoinStyle.mitre ('mitre') results
|
||||
in a single vertex that is beveled depending on the ``mitre_limit`` parameter.
|
||||
mitre_limit : float, optional
|
||||
The mitre limit ratio is used for very sharp corners. The
|
||||
mitre ratio is the ratio of the distance from the corner to
|
||||
the end of the mitred offset corner. When two line segments
|
||||
meet at a sharp angle, a miter join will extend the original
|
||||
geometry. To prevent unreasonable geometry, the mitre limit
|
||||
allows controlling the maximum length of the join corner.
|
||||
Corners with a ratio which exceed the limit will be beveled.
|
||||
single_side : bool, optional
|
||||
The side used is determined by the sign of the buffer
|
||||
distance:
|
||||
|
||||
a positive distance indicates the left-hand side
|
||||
a negative distance indicates the right-hand side
|
||||
|
||||
The single-sided buffer of point geometries is the same as
|
||||
the regular buffer. The End Cap Style for single-sided
|
||||
buffers is always ignored, and forced to the equivalent of
|
||||
CAP_FLAT.
|
||||
quadsegs : int, optional
|
||||
Deprecated alias for `quad_segs`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Geometry
|
||||
|
||||
Notes
|
||||
-----
|
||||
The return value is a strictly two-dimensional geometry. All
|
||||
Z coordinates of the original geometry will be ignored.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from shapely.wkt import loads
|
||||
>>> g = loads('POINT (0.0 0.0)')
|
||||
|
||||
16-gon approx of a unit radius circle:
|
||||
|
||||
>>> g.buffer(1.0).area # doctest: +ELLIPSIS
|
||||
3.1365484905459...
|
||||
|
||||
128-gon approximation:
|
||||
|
||||
>>> g.buffer(1.0, 128).area # doctest: +ELLIPSIS
|
||||
3.141513801144...
|
||||
|
||||
triangle approximation:
|
||||
|
||||
>>> g.buffer(1.0, 3).area
|
||||
3.0
|
||||
>>> list(g.buffer(1.0, cap_style=BufferCapStyle.square).exterior.coords)
|
||||
[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)]
|
||||
>>> g.buffer(1.0, cap_style=BufferCapStyle.square).area
|
||||
4.0
|
||||
|
||||
"""
|
||||
quadsegs = kwargs.pop("quadsegs", None)
|
||||
if quadsegs is not None:
|
||||
warn(
|
||||
"The `quadsegs` argument is deprecated. Use `quad_segs` instead.",
|
||||
FutureWarning,
|
||||
)
|
||||
quad_segs = quadsegs
|
||||
|
||||
# TODO deprecate `resolution` keyword for shapely 2.1
|
||||
resolution = kwargs.pop("resolution", None)
|
||||
if resolution is not None:
|
||||
quad_segs = resolution
|
||||
if kwargs:
|
||||
kwarg = list(kwargs.keys())[0] # noqa
|
||||
raise TypeError(f"buffer() got an unexpected keyword argument '{kwarg}'")
|
||||
|
||||
if mitre_limit == 0.0:
|
||||
raise ValueError("Cannot compute offset from zero-length line segment")
|
||||
elif not np.isfinite(distance).all():
|
||||
raise ValueError("buffer distance must be finite")
|
||||
|
||||
return shapely.buffer(
|
||||
self,
|
||||
distance,
|
||||
quad_segs=quad_segs,
|
||||
cap_style=cap_style,
|
||||
join_style=join_style,
|
||||
mitre_limit=mitre_limit,
|
||||
single_sided=single_sided,
|
||||
)
|
||||
|
||||
def simplify(self, tolerance, preserve_topology=True):
|
||||
"""Returns a simplified geometry produced by the Douglas-Peucker
|
||||
algorithm
|
||||
|
||||
Coordinates of the simplified geometry will be no more than the
|
||||
tolerance distance from the original. Unless the topology preserving
|
||||
option is used, the algorithm may produce self-intersecting or
|
||||
otherwise invalid geometries.
|
||||
"""
|
||||
return shapely.simplify(self, tolerance, preserve_topology=preserve_topology)
|
||||
|
||||
def normalize(self):
|
||||
"""Converts geometry to normal form (or canonical form).
|
||||
|
||||
This method orders the coordinates, rings of a polygon and parts of
|
||||
multi geometries consistently. Typically useful for testing purposes
|
||||
(for example in combination with `equals_exact`).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from shapely import MultiLineString
|
||||
>>> line = MultiLineString([[(0, 0), (1, 1)], [(3, 3), (2, 2)]])
|
||||
>>> line.normalize()
|
||||
<MULTILINESTRING ((2 2, 3 3), (0 0, 1 1))>
|
||||
"""
|
||||
return shapely.normalize(self)
|
||||
|
||||
# Overlay operations
|
||||
# ---------------------------
|
||||
|
||||
def difference(self, other, grid_size=None):
|
||||
"""
|
||||
Returns the difference of the geometries.
|
||||
|
||||
Refer to `shapely.difference` for full documentation.
|
||||
"""
|
||||
return shapely.difference(self, other, grid_size=grid_size)
|
||||
|
||||
def intersection(self, other, grid_size=None):
|
||||
"""
|
||||
Returns the intersection of the geometries.
|
||||
|
||||
Refer to `shapely.intersection` for full documentation.
|
||||
"""
|
||||
return shapely.intersection(self, other, grid_size=grid_size)
|
||||
|
||||
def symmetric_difference(self, other, grid_size=None):
|
||||
"""
|
||||
Returns the symmetric difference of the geometries.
|
||||
|
||||
Refer to `shapely.symmetric_difference` for full documentation.
|
||||
"""
|
||||
return shapely.symmetric_difference(self, other, grid_size=grid_size)
|
||||
|
||||
def union(self, other, grid_size=None):
|
||||
"""
|
||||
Returns the union of the geometries.
|
||||
|
||||
Refer to `shapely.union` for full documentation.
|
||||
"""
|
||||
return shapely.union(self, other, grid_size=grid_size)
|
||||
|
||||
# Unary predicates
|
||||
# ----------------
|
||||
|
||||
@property
|
||||
def has_z(self):
|
||||
"""True if the geometry's coordinate sequence(s) have z values (are
|
||||
3-dimensional)"""
|
||||
return bool(shapely.has_z(self))
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
"""True if the set of points in this geometry is empty, else False"""
|
||||
return bool(shapely.is_empty(self))
|
||||
|
||||
@property
|
||||
def is_ring(self):
|
||||
"""True if the geometry is a closed ring, else False"""
|
||||
return bool(shapely.is_ring(self))
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""True if the geometry is closed, else False
|
||||
|
||||
Applicable only to 1-D geometries."""
|
||||
if self.geom_type == "LinearRing":
|
||||
return True
|
||||
return bool(shapely.is_closed(self))
|
||||
|
||||
@property
|
||||
def is_simple(self):
|
||||
"""True if the geometry is simple, meaning that any self-intersections
|
||||
are only at boundary points, else False"""
|
||||
return bool(shapely.is_simple(self))
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
"""True if the geometry is valid (definition depends on sub-class),
|
||||
else False"""
|
||||
return bool(shapely.is_valid(self))
|
||||
|
||||
# Binary predicates
|
||||
# -----------------
|
||||
|
||||
def relate(self, other):
|
||||
"""Returns the DE-9IM intersection matrix for the two geometries
|
||||
(string)"""
|
||||
return shapely.relate(self, other)
|
||||
|
||||
def covers(self, other):
|
||||
"""Returns True if the geometry covers the other, else False"""
|
||||
return _maybe_unpack(shapely.covers(self, other))
|
||||
|
||||
def covered_by(self, other):
|
||||
"""Returns True if the geometry is covered by the other, else False"""
|
||||
return _maybe_unpack(shapely.covered_by(self, other))
|
||||
|
||||
def contains(self, other):
|
||||
"""Returns True if the geometry contains the other, else False"""
|
||||
return _maybe_unpack(shapely.contains(self, other))
|
||||
|
||||
def contains_properly(self, other):
|
||||
"""
|
||||
Returns True if the geometry completely contains the other, with no
|
||||
common boundary points, else False
|
||||
|
||||
Refer to `shapely.contains_properly` for full documentation.
|
||||
"""
|
||||
return _maybe_unpack(shapely.contains_properly(self, other))
|
||||
|
||||
def crosses(self, other):
|
||||
"""Returns True if the geometries cross, else False"""
|
||||
return _maybe_unpack(shapely.crosses(self, other))
|
||||
|
||||
def disjoint(self, other):
|
||||
"""Returns True if geometries are disjoint, else False"""
|
||||
return _maybe_unpack(shapely.disjoint(self, other))
|
||||
|
||||
def equals(self, other):
|
||||
"""Returns True if geometries are equal, else False.
|
||||
|
||||
This method considers point-set equality (or topological
|
||||
equality), and is equivalent to (self.within(other) &
|
||||
self.contains(other)).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> LineString(
|
||||
... [(0, 0), (2, 2)]
|
||||
... ).equals(
|
||||
... LineString([(0, 0), (1, 1), (2, 2)])
|
||||
... )
|
||||
True
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
|
||||
"""
|
||||
return _maybe_unpack(shapely.equals(self, other))
|
||||
|
||||
def intersects(self, other):
|
||||
"""Returns True if geometries intersect, else False"""
|
||||
return _maybe_unpack(shapely.intersects(self, other))
|
||||
|
||||
def overlaps(self, other):
|
||||
"""Returns True if geometries overlap, else False"""
|
||||
return _maybe_unpack(shapely.overlaps(self, other))
|
||||
|
||||
def touches(self, other):
|
||||
"""Returns True if geometries touch, else False"""
|
||||
return _maybe_unpack(shapely.touches(self, other))
|
||||
|
||||
def within(self, other):
|
||||
"""Returns True if geometry is within the other, else False"""
|
||||
return _maybe_unpack(shapely.within(self, other))
|
||||
|
||||
def dwithin(self, other, distance):
|
||||
"""
|
||||
Returns True if geometry is within a given distance from the other, else False.
|
||||
|
||||
Refer to `shapely.dwithin` for full documentation.
|
||||
"""
|
||||
return _maybe_unpack(shapely.dwithin(self, other, distance))
|
||||
|
||||
def equals_exact(self, other, tolerance):
|
||||
"""True if geometries are equal to within a specified
|
||||
tolerance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : BaseGeometry
|
||||
The other geometry object in this comparison.
|
||||
tolerance : float
|
||||
Absolute tolerance in the same units as coordinates.
|
||||
|
||||
This method considers coordinate equality, which requires
|
||||
coordinates to be equal and in the same order for all components
|
||||
of a geometry.
|
||||
|
||||
Because of this it is possible for "equals()" to be True for two
|
||||
geometries and "equals_exact()" to be False.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> LineString(
|
||||
... [(0, 0), (2, 2)]
|
||||
... ).equals_exact(
|
||||
... LineString([(0, 0), (1, 1), (2, 2)]),
|
||||
... 1e-6
|
||||
... )
|
||||
False
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
|
||||
"""
|
||||
return _maybe_unpack(shapely.equals_exact(self, other, tolerance))
|
||||
|
||||
def almost_equals(self, other, decimal=6):
|
||||
"""True if geometries are equal at all coordinates to a
|
||||
specified decimal place.
|
||||
|
||||
.. deprecated:: 1.8.0
|
||||
The 'almost_equals()' method is deprecated
|
||||
and will be removed in Shapely 2.1 because the name is
|
||||
confusing. The 'equals_exact()' method should be used
|
||||
instead.
|
||||
|
||||
Refers to approximate coordinate equality, which requires
|
||||
coordinates to be approximately equal and in the same order for
|
||||
all components of a geometry.
|
||||
|
||||
Because of this it is possible for "equals()" to be True for two
|
||||
geometries and "almost_equals()" to be False.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> LineString(
|
||||
... [(0, 0), (2, 2)]
|
||||
... ).equals_exact(
|
||||
... LineString([(0, 0), (1, 1), (2, 2)]),
|
||||
... 1e-6
|
||||
... )
|
||||
False
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
|
||||
"""
|
||||
warn(
|
||||
"The 'almost_equals()' method is deprecated and will be "
|
||||
"removed in Shapely 2.1; use 'equals_exact()' instead",
|
||||
ShapelyDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.equals_exact(other, 0.5 * 10 ** (-decimal))
|
||||
|
||||
def relate_pattern(self, other, pattern):
|
||||
"""Returns True if the DE-9IM string code for the relationship between
|
||||
the geometries satisfies the pattern, else False"""
|
||||
return _maybe_unpack(shapely.relate_pattern(self, other, pattern))
|
||||
|
||||
# Linear referencing
|
||||
# ------------------
|
||||
|
||||
def line_locate_point(self, other, normalized=False):
|
||||
"""Returns the distance along this geometry to a point nearest the
|
||||
specified point
|
||||
|
||||
If the normalized arg is True, return the distance normalized to the
|
||||
length of the linear geometry.
|
||||
|
||||
Alias of `project`.
|
||||
"""
|
||||
return shapely.line_locate_point(self, other, normalized=normalized)
|
||||
|
||||
def project(self, other, normalized=False):
|
||||
"""Returns the distance along this geometry to a point nearest the
|
||||
specified point
|
||||
|
||||
If the normalized arg is True, return the distance normalized to the
|
||||
length of the linear geometry.
|
||||
|
||||
Alias of `line_locate_point`.
|
||||
"""
|
||||
return shapely.line_locate_point(self, other, normalized=normalized)
|
||||
|
||||
def line_interpolate_point(self, distance, normalized=False):
|
||||
"""Return a point at the specified distance along a linear geometry
|
||||
|
||||
Negative length values are taken as measured in the reverse
|
||||
direction from the end of the geometry. Out-of-range index
|
||||
values are handled by clamping them to the valid range of values.
|
||||
If the normalized arg is True, the distance will be interpreted as a
|
||||
fraction of the geometry's length.
|
||||
|
||||
Alias of `interpolate`.
|
||||
"""
|
||||
return shapely.line_interpolate_point(self, distance, normalized=normalized)
|
||||
|
||||
def interpolate(self, distance, normalized=False):
|
||||
"""Return a point at the specified distance along a linear geometry
|
||||
|
||||
Negative length values are taken as measured in the reverse
|
||||
direction from the end of the geometry. Out-of-range index
|
||||
values are handled by clamping them to the valid range of values.
|
||||
If the normalized arg is True, the distance will be interpreted as a
|
||||
fraction of the geometry's length.
|
||||
|
||||
Alias of `line_interpolate_point`.
|
||||
"""
|
||||
return shapely.line_interpolate_point(self, distance, normalized=normalized)
|
||||
|
||||
def segmentize(self, max_segment_length):
|
||||
"""Adds vertices to line segments based on maximum segment length.
|
||||
|
||||
Additional vertices will be added to every line segment in an input geometry
|
||||
so that segments are no longer than the provided maximum segment length. New
|
||||
vertices will evenly subdivide each segment.
|
||||
|
||||
Only linear components of input geometries are densified; other geometries
|
||||
are returned unmodified.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_segment_length : float or array_like
|
||||
Additional vertices will be added so that all line segments are no
|
||||
longer this value. Must be greater than 0.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from shapely import LineString, Polygon
|
||||
>>> LineString([(0, 0), (0, 10)]).segmentize(max_segment_length=5)
|
||||
<LINESTRING (0 0, 0 5, 0 10)>
|
||||
>>> Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]).segmentize(max_segment_length=5)
|
||||
<POLYGON ((0 0, 5 0, 10 0, 10 5, 10 10, 5 10, 0 10, 0 5, 0 0))>
|
||||
"""
|
||||
return shapely.segmentize(self, max_segment_length)
|
||||
|
||||
def reverse(self):
|
||||
"""Returns a copy of this geometry with the order of coordinates reversed.
|
||||
|
||||
If the geometry is a polygon with interior rings, the interior rings are also
|
||||
reversed.
|
||||
|
||||
Points are unchanged.
|
||||
|
||||
See also
|
||||
--------
|
||||
is_ccw : Checks if a geometry is clockwise.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from shapely import LineString, Polygon
|
||||
>>> LineString([(0, 0), (1, 2)]).reverse()
|
||||
<LINESTRING (1 2, 0 0)>
|
||||
>>> Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]).reverse()
|
||||
<POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>
|
||||
"""
|
||||
return shapely.reverse(self)
|
||||
|
||||
|
||||
class BaseMultipartGeometry(BaseGeometry):
|
||||
|
||||
__slots__ = []
|
||||
|
||||
@property
|
||||
def coords(self):
|
||||
raise NotImplementedError(
|
||||
"Sub-geometries may have coordinate sequences, "
|
||||
"but multi-part geometries do not"
|
||||
)
|
||||
|
||||
@property
|
||||
def geoms(self):
|
||||
return GeometrySequence(self)
|
||||
|
||||
def __bool__(self):
|
||||
return self.is_empty is False
|
||||
|
||||
def svg(self, scale_factor=1.0, color=None):
|
||||
"""Returns a group of SVG elements for the multipart geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
color : str, optional
|
||||
Hex string for stroke or fill color. Default is to use "#66cc99"
|
||||
if geometry is valid, and "#ff3333" if invalid.
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if color is None:
|
||||
color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
return "<g>" + "".join(p.svg(scale_factor, color) for p in self.geoms) + "</g>"
|
||||
|
||||
|
||||
class GeometrySequence:
|
||||
"""
|
||||
Iterative access to members of a homogeneous multipart geometry.
|
||||
"""
|
||||
|
||||
# Attributes
|
||||
# ----------
|
||||
# _parent : object
|
||||
# Parent (Shapely) geometry
|
||||
_parent = None
|
||||
|
||||
def __init__(self, parent):
|
||||
self._parent = parent
|
||||
|
||||
def _get_geom_item(self, i):
|
||||
return shapely.get_geometry(self._parent, i)
|
||||
|
||||
def __iter__(self):
|
||||
for i in range(self.__len__()):
|
||||
yield self._get_geom_item(i)
|
||||
|
||||
def __len__(self):
|
||||
return shapely.get_num_geometries(self._parent)
|
||||
|
||||
def __getitem__(self, key):
|
||||
m = self.__len__()
|
||||
if isinstance(key, (int, np.integer)):
|
||||
if key + m < 0 or key >= m:
|
||||
raise IndexError("index out of range")
|
||||
if key < 0:
|
||||
i = m + key
|
||||
else:
|
||||
i = key
|
||||
return self._get_geom_item(i)
|
||||
elif isinstance(key, slice):
|
||||
res = []
|
||||
start, stop, stride = key.indices(m)
|
||||
for i in range(start, stop, stride):
|
||||
res.append(self._get_geom_item(i))
|
||||
return type(self._parent)(res or None)
|
||||
else:
|
||||
raise TypeError("key must be an index or slice")
|
||||
|
||||
|
||||
class EmptyGeometry(BaseGeometry):
|
||||
def __new__(self):
|
||||
"""Create an empty geometry."""
|
||||
warn(
|
||||
"The 'EmptyGeometry()' constructor to create an empty geometry is "
|
||||
"deprecated, and will raise an error in the future. Use one of the "
|
||||
"geometry subclasses instead, for example 'GeometryCollection()'.",
|
||||
ShapelyDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")
|
||||
58
billinglayer/python/shapely/geometry/collection.py
Normal file
58
billinglayer/python/shapely/geometry/collection.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Multi-part collections of geometries
|
||||
"""
|
||||
|
||||
import shapely
|
||||
from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry
|
||||
|
||||
|
||||
class GeometryCollection(BaseMultipartGeometry):
|
||||
"""
|
||||
A collection of one or more geometries that may contain more than one type
|
||||
of geometry.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geoms : list
|
||||
A list of shapely geometry instances, which may be of varying
|
||||
geometry types.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
geoms : sequence
|
||||
A sequence of Shapely geometry instances
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a GeometryCollection with a Point and a LineString
|
||||
|
||||
>>> from shapely import LineString, Point
|
||||
>>> p = Point(51, -1)
|
||||
>>> l = LineString([(52, -1), (49, 2)])
|
||||
>>> gc = GeometryCollection([p, l])
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, geoms=None):
|
||||
if not geoms:
|
||||
# TODO better empty constructor
|
||||
return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")
|
||||
if isinstance(geoms, BaseGeometry):
|
||||
# TODO(shapely-2.0) do we actually want to split Multi-part geometries?
|
||||
# this is needed for the split() tests
|
||||
if hasattr(geoms, "geoms"):
|
||||
geoms = geoms.geoms
|
||||
else:
|
||||
geoms = [geoms]
|
||||
|
||||
return shapely.geometrycollections(geoms)
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
geometries = []
|
||||
for geom in self.geoms:
|
||||
geometries.append(geom.__geo_interface__)
|
||||
return dict(type="GeometryCollection", geometries=geometries)
|
||||
|
||||
|
||||
shapely.lib.registry[7] = GeometryCollection
|
||||
10
billinglayer/python/shapely/geometry/conftest.py
Normal file
10
billinglayer/python/shapely/geometry/conftest.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Autouse fixtures for doctests."""
|
||||
|
||||
import pytest
|
||||
|
||||
from .linestring import LineString
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def add_linestring(doctest_namespace):
|
||||
doctest_namespace["LineString"] = LineString
|
||||
136
billinglayer/python/shapely/geometry/geo.py
Normal file
136
billinglayer/python/shapely/geometry/geo.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Geometry factories based on the geo interface
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from shapely.errors import GeometryTypeError
|
||||
|
||||
from .collection import GeometryCollection
|
||||
from .linestring import LineString
|
||||
from .multilinestring import MultiLineString
|
||||
from .multipoint import MultiPoint
|
||||
from .multipolygon import MultiPolygon
|
||||
from .point import Point
|
||||
from .polygon import LinearRing, Polygon
|
||||
|
||||
|
||||
def _is_coordinates_empty(coordinates):
|
||||
"""Helper to identify if coordinates or subset of coordinates are empty"""
|
||||
|
||||
if coordinates is None:
|
||||
return True
|
||||
|
||||
if isinstance(coordinates, (list, tuple, np.ndarray)):
|
||||
if len(coordinates) == 0:
|
||||
return True
|
||||
return all(map(_is_coordinates_empty, coordinates))
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def _empty_shape_for_no_coordinates(geom_type):
|
||||
"""Return empty counterpart for geom_type"""
|
||||
if geom_type == "point":
|
||||
return Point()
|
||||
elif geom_type == "multipoint":
|
||||
return MultiPoint()
|
||||
elif geom_type == "linestring":
|
||||
return LineString()
|
||||
elif geom_type == "multilinestring":
|
||||
return MultiLineString()
|
||||
elif geom_type == "polygon":
|
||||
return Polygon()
|
||||
elif geom_type == "multipolygon":
|
||||
return MultiPolygon()
|
||||
else:
|
||||
raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")
|
||||
|
||||
|
||||
def box(minx, miny, maxx, maxy, ccw=True):
|
||||
"""Returns a rectangular polygon with configurable normal vector"""
|
||||
coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)]
|
||||
if not ccw:
|
||||
coords = coords[::-1]
|
||||
return Polygon(coords)
|
||||
|
||||
|
||||
def shape(context):
|
||||
"""
|
||||
Returns a new, independent geometry with coordinates *copied* from the
|
||||
context. Changes to the original context will not be reflected in the
|
||||
geometry object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context :
|
||||
a GeoJSON-like dict, which provides a "type" member describing the type
|
||||
of the geometry and "coordinates" member providing a list of coordinates,
|
||||
or an object which implements __geo_interface__.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Geometry object
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a Point from GeoJSON, and then create a copy using __geo_interface__.
|
||||
|
||||
>>> context = {'type': 'Point', 'coordinates': [0, 1]}
|
||||
>>> geom = shape(context)
|
||||
>>> geom.geom_type == 'Point'
|
||||
True
|
||||
>>> geom.wkt
|
||||
'POINT (0 1)'
|
||||
>>> geom2 = shape(geom)
|
||||
>>> geom == geom2
|
||||
True
|
||||
"""
|
||||
if hasattr(context, "__geo_interface__"):
|
||||
ob = context.__geo_interface__
|
||||
else:
|
||||
ob = context
|
||||
geom_type = ob.get("type").lower()
|
||||
if "coordinates" in ob and _is_coordinates_empty(ob["coordinates"]):
|
||||
return _empty_shape_for_no_coordinates(geom_type)
|
||||
elif geom_type == "point":
|
||||
return Point(ob["coordinates"])
|
||||
elif geom_type == "linestring":
|
||||
return LineString(ob["coordinates"])
|
||||
elif geom_type == "linearring":
|
||||
return LinearRing(ob["coordinates"])
|
||||
elif geom_type == "polygon":
|
||||
return Polygon(ob["coordinates"][0], ob["coordinates"][1:])
|
||||
elif geom_type == "multipoint":
|
||||
return MultiPoint(ob["coordinates"])
|
||||
elif geom_type == "multilinestring":
|
||||
return MultiLineString(ob["coordinates"])
|
||||
elif geom_type == "multipolygon":
|
||||
return MultiPolygon([[c[0], c[1:]] for c in ob["coordinates"]])
|
||||
elif geom_type == "geometrycollection":
|
||||
geoms = [shape(g) for g in ob.get("geometries", [])]
|
||||
return GeometryCollection(geoms)
|
||||
else:
|
||||
raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")
|
||||
|
||||
|
||||
def mapping(ob):
|
||||
"""
|
||||
Returns a GeoJSON-like mapping from a Geometry or any
|
||||
object which implements __geo_interface__
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ob :
|
||||
An object which implements __geo_interface__.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> pt = Point(0, 0)
|
||||
>>> mapping(pt)
|
||||
{'type': 'Point', 'coordinates': (0.0, 0.0)}
|
||||
"""
|
||||
return ob.__geo_interface__
|
||||
188
billinglayer/python/shapely/geometry/linestring.py
Normal file
188
billinglayer/python/shapely/geometry/linestring.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""Line strings and related utilities
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
import shapely
|
||||
from shapely.geometry.base import BaseGeometry, JOIN_STYLE
|
||||
from shapely.geometry.point import Point
|
||||
|
||||
__all__ = ["LineString"]
|
||||
|
||||
|
||||
class LineString(BaseGeometry):
|
||||
"""
|
||||
A geometry type composed of one or more line segments.
|
||||
|
||||
A LineString is a one-dimensional feature and has a non-zero length but
|
||||
zero area. It may approximate a curve and need not be straight. Unlike a
|
||||
LinearRing, a LineString is not closed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coordinates : sequence
|
||||
A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or
|
||||
an array-like with shape (N, 2) or (N, 3).
|
||||
Also can be a sequence of Point objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a LineString with two segments
|
||||
|
||||
>>> a = LineString([[0, 0], [1, 0], [1, 1]])
|
||||
>>> a.length
|
||||
2.0
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, coordinates=None):
|
||||
if coordinates is None:
|
||||
# empty geometry
|
||||
# TODO better constructor
|
||||
return shapely.from_wkt("LINESTRING EMPTY")
|
||||
elif isinstance(coordinates, LineString):
|
||||
if type(coordinates) == LineString:
|
||||
# return original objects since geometries are immutable
|
||||
return coordinates
|
||||
else:
|
||||
# LinearRing
|
||||
# TODO convert LinearRing to LineString more directly
|
||||
coordinates = coordinates.coords
|
||||
else:
|
||||
if hasattr(coordinates, "__array__"):
|
||||
coordinates = np.asarray(coordinates)
|
||||
if isinstance(coordinates, np.ndarray) and np.issubdtype(
|
||||
coordinates.dtype, np.number
|
||||
):
|
||||
pass
|
||||
else:
|
||||
# check coordinates on points
|
||||
def _coords(o):
|
||||
if isinstance(o, Point):
|
||||
return o.coords[0]
|
||||
else:
|
||||
return [float(c) for c in o]
|
||||
|
||||
coordinates = [_coords(o) for o in coordinates]
|
||||
|
||||
if len(coordinates) == 0:
|
||||
# empty geometry
|
||||
# TODO better constructor + should shapely.linestrings handle this?
|
||||
return shapely.from_wkt("LINESTRING EMPTY")
|
||||
|
||||
geom = shapely.linestrings(coordinates)
|
||||
if not isinstance(geom, LineString):
|
||||
raise ValueError("Invalid values passed to LineString constructor")
|
||||
return geom
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {"type": "LineString", "coordinates": tuple(self.coords)}
|
||||
|
||||
def svg(self, scale_factor=1.0, stroke_color=None, opacity=None):
|
||||
"""Returns SVG polyline element for the LineString geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
stroke_color : str, optional
|
||||
Hex string for stroke color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.8
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if stroke_color is None:
|
||||
stroke_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
if opacity is None:
|
||||
opacity = 0.8
|
||||
pnt_format = " ".join(["{},{}".format(*c) for c in self.coords])
|
||||
return (
|
||||
'<polyline fill="none" stroke="{2}" stroke-width="{1}" '
|
||||
'points="{0}" opacity="{3}" />'
|
||||
).format(pnt_format, 2.0 * scale_factor, stroke_color, opacity)
|
||||
|
||||
@property
|
||||
def xy(self):
|
||||
"""Separate arrays of X and Y coordinate values
|
||||
|
||||
Example:
|
||||
|
||||
>>> x, y = LineString([(0, 0), (1, 1)]).xy
|
||||
>>> list(x)
|
||||
[0.0, 1.0]
|
||||
>>> list(y)
|
||||
[0.0, 1.0]
|
||||
"""
|
||||
return self.coords.xy
|
||||
|
||||
def offset_curve(
|
||||
self,
|
||||
distance,
|
||||
quad_segs=16,
|
||||
join_style=JOIN_STYLE.round,
|
||||
mitre_limit=5.0,
|
||||
):
|
||||
"""Returns a LineString or MultiLineString geometry at a distance from
|
||||
the object on its right or its left side.
|
||||
|
||||
The side is determined by the sign of the `distance` parameter
|
||||
(negative for right side offset, positive for left side offset). The
|
||||
resolution of the buffer around each vertex of the object increases
|
||||
by increasing the `quad_segs` keyword parameter.
|
||||
|
||||
The join style is for outside corners between line segments. Accepted
|
||||
values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and
|
||||
JOIN_STYLE.bevel (3).
|
||||
|
||||
The mitre ratio limit is used for very sharp corners. It is the ratio
|
||||
of the distance from the corner to the end of the mitred offset corner.
|
||||
When two line segments meet at a sharp angle, a miter join will extend
|
||||
far beyond the original geometry. To prevent unreasonable geometry, the
|
||||
mitre limit allows controlling the maximum length of the join corner.
|
||||
Corners with a ratio which exceed the limit will be beveled.
|
||||
|
||||
Note: the behaviour regarding orientation of the resulting line
|
||||
depends on the GEOS version. With GEOS < 3.11, the line retains the
|
||||
same direction for a left offset (positive distance) or has reverse
|
||||
direction for a right offset (negative distance), and this behaviour
|
||||
was documented as such in previous Shapely versions. Starting with
|
||||
GEOS 3.11, the function tries to preserve the orientation of the
|
||||
original line.
|
||||
"""
|
||||
if mitre_limit == 0.0:
|
||||
raise ValueError("Cannot compute offset from zero-length line segment")
|
||||
elif not np.isfinite(distance):
|
||||
raise ValueError("offset_curve distance must be finite")
|
||||
return shapely.offset_curve(self, distance, quad_segs, join_style, mitre_limit)
|
||||
|
||||
def parallel_offset(
|
||||
self,
|
||||
distance,
|
||||
side="right",
|
||||
resolution=16,
|
||||
join_style=JOIN_STYLE.round,
|
||||
mitre_limit=5.0,
|
||||
):
|
||||
"""
|
||||
Alternative method to :meth:`offset_curve` method.
|
||||
|
||||
Older alternative method to the :meth:`offset_curve` method, but uses
|
||||
``resolution`` instead of ``quad_segs`` and a ``side`` keyword
|
||||
('left' or 'right') instead of sign of the distance. This method is
|
||||
kept for backwards compatibility for now, but is is recommended to
|
||||
use :meth:`offset_curve` instead.
|
||||
"""
|
||||
if side == "right":
|
||||
distance *= -1
|
||||
return self.offset_curve(
|
||||
distance,
|
||||
quad_segs=resolution,
|
||||
join_style=join_style,
|
||||
mitre_limit=mitre_limit,
|
||||
)
|
||||
|
||||
|
||||
shapely.lib.registry[1] = LineString
|
||||
93
billinglayer/python/shapely/geometry/multilinestring.py
Normal file
93
billinglayer/python/shapely/geometry/multilinestring.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Collections of linestrings and related utilities
|
||||
"""
|
||||
|
||||
import shapely
|
||||
from shapely.errors import EmptyPartError
|
||||
from shapely.geometry import linestring
|
||||
from shapely.geometry.base import BaseMultipartGeometry
|
||||
|
||||
__all__ = ["MultiLineString"]
|
||||
|
||||
|
||||
class MultiLineString(BaseMultipartGeometry):
|
||||
"""
|
||||
A collection of one or more LineStrings.
|
||||
|
||||
A MultiLineString has non-zero length and zero area.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : sequence
|
||||
A sequence LineStrings, or a sequence of line-like coordinate
|
||||
sequences or array-likes (see accepted input for LineString).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
geoms : sequence
|
||||
A sequence of LineStrings
|
||||
|
||||
Examples
|
||||
--------
|
||||
Construct a MultiLineString containing two LineStrings.
|
||||
|
||||
>>> lines = MultiLineString([[[0, 0], [1, 2]], [[4, 4], [5, 6]]])
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, lines=None):
|
||||
if not lines:
|
||||
# allow creation of empty multilinestrings, to support unpickling
|
||||
# TODO better empty constructor
|
||||
return shapely.from_wkt("MULTILINESTRING EMPTY")
|
||||
elif isinstance(lines, MultiLineString):
|
||||
return lines
|
||||
|
||||
lines = getattr(lines, "geoms", lines)
|
||||
m = len(lines)
|
||||
subs = []
|
||||
for i in range(m):
|
||||
line = linestring.LineString(lines[i])
|
||||
if line.is_empty:
|
||||
raise EmptyPartError(
|
||||
"Can't create MultiLineString with empty component"
|
||||
)
|
||||
subs.append(line)
|
||||
|
||||
if len(lines) == 0:
|
||||
return shapely.from_wkt("MULTILINESTRING EMPTY")
|
||||
|
||||
return shapely.multilinestrings(subs)
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {
|
||||
"type": "MultiLineString",
|
||||
"coordinates": tuple(tuple(c for c in g.coords) for g in self.geoms),
|
||||
}
|
||||
|
||||
def svg(self, scale_factor=1.0, stroke_color=None, opacity=None):
|
||||
"""Returns a group of SVG polyline elements for the LineString geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
stroke_color : str, optional
|
||||
Hex string for stroke color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.8
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if stroke_color is None:
|
||||
stroke_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
return (
|
||||
"<g>"
|
||||
+ "".join(p.svg(scale_factor, stroke_color, opacity) for p in self.geoms)
|
||||
+ "</g>"
|
||||
)
|
||||
|
||||
|
||||
shapely.lib.registry[5] = MultiLineString
|
||||
95
billinglayer/python/shapely/geometry/multipoint.py
Normal file
95
billinglayer/python/shapely/geometry/multipoint.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Collections of points and related utilities
|
||||
"""
|
||||
|
||||
import shapely
|
||||
from shapely.errors import EmptyPartError
|
||||
from shapely.geometry import point
|
||||
from shapely.geometry.base import BaseMultipartGeometry
|
||||
|
||||
__all__ = ["MultiPoint"]
|
||||
|
||||
|
||||
class MultiPoint(BaseMultipartGeometry):
|
||||
"""
|
||||
A collection of one or more Points.
|
||||
|
||||
A MultiPoint has zero area and zero length.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : sequence
|
||||
A sequence of Points, or a sequence of (x, y [,z]) numeric coordinate
|
||||
pairs or triples, or an array-like of shape (N, 2) or (N, 3).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
geoms : sequence
|
||||
A sequence of Points
|
||||
|
||||
Examples
|
||||
--------
|
||||
Construct a MultiPoint containing two Points
|
||||
|
||||
>>> from shapely import Point
|
||||
>>> ob = MultiPoint([[0.0, 0.0], [1.0, 2.0]])
|
||||
>>> len(ob.geoms)
|
||||
2
|
||||
>>> type(ob.geoms[0]) == Point
|
||||
True
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, points=None):
|
||||
if points is None:
|
||||
# allow creation of empty multipoints, to support unpickling
|
||||
# TODO better empty constructor
|
||||
return shapely.from_wkt("MULTIPOINT EMPTY")
|
||||
elif isinstance(points, MultiPoint):
|
||||
return points
|
||||
|
||||
m = len(points)
|
||||
subs = []
|
||||
for i in range(m):
|
||||
p = point.Point(points[i])
|
||||
if p.is_empty:
|
||||
raise EmptyPartError("Can't create MultiPoint with empty component")
|
||||
subs.append(p)
|
||||
|
||||
if len(points) == 0:
|
||||
return shapely.from_wkt("MULTIPOINT EMPTY")
|
||||
|
||||
return shapely.multipoints(subs)
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {
|
||||
"type": "MultiPoint",
|
||||
"coordinates": tuple(g.coords[0] for g in self.geoms),
|
||||
}
|
||||
|
||||
def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
|
||||
"""Returns a group of SVG circle elements for the MultiPoint geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG circle diameters. Default is 1.
|
||||
fill_color : str, optional
|
||||
Hex string for fill color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.6
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if fill_color is None:
|
||||
fill_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
return (
|
||||
"<g>"
|
||||
+ "".join(p.svg(scale_factor, fill_color, opacity) for p in self.geoms)
|
||||
+ "</g>"
|
||||
)
|
||||
|
||||
|
||||
shapely.lib.registry[4] = MultiPoint
|
||||
123
billinglayer/python/shapely/geometry/multipolygon.py
Normal file
123
billinglayer/python/shapely/geometry/multipolygon.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Collections of polygons and related utilities
|
||||
"""
|
||||
|
||||
import shapely
|
||||
from shapely.geometry import polygon
|
||||
from shapely.geometry.base import BaseMultipartGeometry
|
||||
|
||||
__all__ = ["MultiPolygon"]
|
||||
|
||||
|
||||
class MultiPolygon(BaseMultipartGeometry):
|
||||
"""
|
||||
A collection of one or more Polygons.
|
||||
|
||||
If component polygons overlap the collection is invalid and some
|
||||
operations on it may fail.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
polygons : sequence
|
||||
A sequence of Polygons, or a sequence of (shell, holes) tuples
|
||||
where shell is the sequence representation of a linear ring
|
||||
(see LinearRing) and holes is a sequence of such linear rings.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
geoms : sequence
|
||||
A sequence of `Polygon` instances
|
||||
|
||||
Examples
|
||||
--------
|
||||
Construct a MultiPolygon from a sequence of coordinate tuples
|
||||
|
||||
>>> from shapely import Polygon
|
||||
>>> ob = MultiPolygon([
|
||||
... (
|
||||
... ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
|
||||
... [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]
|
||||
... )
|
||||
... ])
|
||||
>>> len(ob.geoms)
|
||||
1
|
||||
>>> type(ob.geoms[0]) == Polygon
|
||||
True
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, polygons=None):
|
||||
if not polygons:
|
||||
# allow creation of empty multipolygons, to support unpickling
|
||||
# TODO better empty constructor
|
||||
return shapely.from_wkt("MULTIPOLYGON EMPTY")
|
||||
elif isinstance(polygons, MultiPolygon):
|
||||
return polygons
|
||||
|
||||
polygons = getattr(polygons, "geoms", polygons)
|
||||
polygons = [
|
||||
p
|
||||
for p in polygons
|
||||
if p and not (isinstance(p, polygon.Polygon) and p.is_empty)
|
||||
]
|
||||
|
||||
L = len(polygons)
|
||||
|
||||
# Bail immediately if we have no input points.
|
||||
if L == 0:
|
||||
return shapely.from_wkt("MULTIPOLYGON EMPTY")
|
||||
|
||||
# This function does not accept sequences of MultiPolygons: there is
|
||||
# no implicit flattening.
|
||||
if isinstance(polygons[0], MultiPolygon):
|
||||
raise ValueError("Sequences of multi-polygons are not valid arguments")
|
||||
|
||||
subs = []
|
||||
for i in range(L):
|
||||
ob = polygons[i]
|
||||
if not isinstance(ob, polygon.Polygon):
|
||||
shell = ob[0]
|
||||
holes = ob[1]
|
||||
p = polygon.Polygon(shell, holes)
|
||||
else:
|
||||
p = polygon.Polygon(ob)
|
||||
subs.append(p)
|
||||
|
||||
return shapely.multipolygons(subs)
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
allcoords = []
|
||||
for geom in self.geoms:
|
||||
coords = []
|
||||
coords.append(tuple(geom.exterior.coords))
|
||||
for hole in geom.interiors:
|
||||
coords.append(tuple(hole.coords))
|
||||
allcoords.append(tuple(coords))
|
||||
return {"type": "MultiPolygon", "coordinates": allcoords}
|
||||
|
||||
def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
|
||||
"""Returns group of SVG path elements for the MultiPolygon geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
fill_color : str, optional
|
||||
Hex string for fill color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.6
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if fill_color is None:
|
||||
fill_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
return (
|
||||
"<g>"
|
||||
+ "".join(p.svg(scale_factor, fill_color, opacity) for p in self.geoms)
|
||||
+ "</g>"
|
||||
)
|
||||
|
||||
|
||||
shapely.lib.registry[6] = MultiPolygon
|
||||
145
billinglayer/python/shapely/geometry/point.py
Normal file
145
billinglayer/python/shapely/geometry/point.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""Points and related utilities
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
import shapely
|
||||
from shapely.errors import DimensionError
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
|
||||
__all__ = ["Point"]
|
||||
|
||||
|
||||
class Point(BaseGeometry):
|
||||
"""
|
||||
A geometry type that represents a single coordinate with
|
||||
x,y and possibly z values.
|
||||
|
||||
A point is a zero-dimensional feature and has zero length and zero area.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : float, or sequence of floats
|
||||
The coordinates can either be passed as a single parameter, or as
|
||||
individual float values using multiple parameters:
|
||||
|
||||
1) 1 parameter: a sequence or array-like of with 2 or 3 values.
|
||||
2) 2 or 3 parameters (float): x, y, and possibly z.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x, y, z : float
|
||||
Coordinate values
|
||||
|
||||
Examples
|
||||
--------
|
||||
Constructing the Point using separate parameters for x and y:
|
||||
|
||||
>>> p = Point(1.0, -1.0)
|
||||
|
||||
Constructing the Point using a list of x, y coordinates:
|
||||
|
||||
>>> p = Point([1.0, -1.0])
|
||||
>>> print(p)
|
||||
POINT (1 -1)
|
||||
>>> p.y
|
||||
-1.0
|
||||
>>> p.x
|
||||
1.0
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, *args):
|
||||
if len(args) == 0:
|
||||
# empty geometry
|
||||
# TODO better constructor
|
||||
return shapely.from_wkt("POINT EMPTY")
|
||||
elif len(args) > 3:
|
||||
raise TypeError(f"Point() takes at most 3 arguments ({len(args)} given)")
|
||||
elif len(args) == 1:
|
||||
coords = args[0]
|
||||
if isinstance(coords, Point):
|
||||
return coords
|
||||
|
||||
# Accept either (x, y) or [(x, y)]
|
||||
if not hasattr(coords, "__getitem__"): # generators
|
||||
coords = list(coords)
|
||||
coords = np.asarray(coords).squeeze()
|
||||
else:
|
||||
# 2 or 3 args
|
||||
coords = np.array(args).squeeze()
|
||||
|
||||
if coords.ndim > 1:
|
||||
raise ValueError(
|
||||
f"Point() takes only scalar or 1-size vector arguments, got {args}"
|
||||
)
|
||||
if not np.issubdtype(coords.dtype, np.number):
|
||||
coords = [float(c) for c in coords]
|
||||
geom = shapely.points(coords)
|
||||
if not isinstance(geom, Point):
|
||||
raise ValueError("Invalid values passed to Point constructor")
|
||||
return geom
|
||||
|
||||
# Coordinate getters and setters
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"""Return x coordinate."""
|
||||
return shapely.get_x(self)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"""Return y coordinate."""
|
||||
return shapely.get_y(self)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"""Return z coordinate."""
|
||||
if not shapely.has_z(self):
|
||||
raise DimensionError("This point has no z coordinate.")
|
||||
# return shapely.get_z(self) -> get_z only supported for GEOS 3.7+
|
||||
return self.coords[0][2]
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {"type": "Point", "coordinates": self.coords[0]}
|
||||
|
||||
def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
|
||||
"""Returns SVG circle element for the Point geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG circle diameter. Default is 1.
|
||||
fill_color : str, optional
|
||||
Hex string for fill color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.6
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if fill_color is None:
|
||||
fill_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
if opacity is None:
|
||||
opacity = 0.6
|
||||
return (
|
||||
'<circle cx="{0.x}" cy="{0.y}" r="{1}" '
|
||||
'stroke="#555555" stroke-width="{2}" fill="{3}" opacity="{4}" />'
|
||||
).format(self, 3.0 * scale_factor, 1.0 * scale_factor, fill_color, opacity)
|
||||
|
||||
@property
|
||||
def xy(self):
|
||||
"""Separate arrays of X and Y coordinate values
|
||||
|
||||
Example:
|
||||
>>> x, y = Point(0, 0).xy
|
||||
>>> list(x)
|
||||
[0.0]
|
||||
>>> list(y)
|
||||
[0.0]
|
||||
"""
|
||||
return self.coords.xy
|
||||
|
||||
|
||||
shapely.lib.registry[0] = Point
|
||||
326
billinglayer/python/shapely/geometry/polygon.py
Normal file
326
billinglayer/python/shapely/geometry/polygon.py
Normal file
@@ -0,0 +1,326 @@
|
||||
"""Polygons and their linear ring components
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shapely
|
||||
from shapely.algorithms.cga import is_ccw_impl, signed_area
|
||||
from shapely.errors import TopologicalError
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
from shapely.geometry.linestring import LineString
|
||||
from shapely.geometry.point import Point
|
||||
|
||||
__all__ = ["Polygon", "LinearRing"]
|
||||
|
||||
|
||||
def _unpickle_linearring(wkb):
|
||||
linestring = shapely.from_wkb(wkb)
|
||||
srid = shapely.get_srid(linestring)
|
||||
linearring = shapely.linearrings(shapely.get_coordinates(linestring))
|
||||
if srid:
|
||||
linearring = shapely.set_srid(linearring, srid)
|
||||
return linearring
|
||||
|
||||
|
||||
class LinearRing(LineString):
|
||||
"""
|
||||
A geometry type composed of one or more line segments
|
||||
that forms a closed loop.
|
||||
|
||||
A LinearRing is a closed, one-dimensional feature.
|
||||
A LinearRing that crosses itself or touches itself at a single point is
|
||||
invalid and operations on it may fail.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coordinates : sequence
|
||||
A sequence of (x, y [,z]) numeric coordinate pairs or triples, or
|
||||
an array-like with shape (N, 2) or (N, 3).
|
||||
Also can be a sequence of Point objects.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Rings are automatically closed. There is no need to specify a final
|
||||
coordinate pair identical to the first.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Construct a square ring.
|
||||
|
||||
>>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) )
|
||||
>>> ring.is_closed
|
||||
True
|
||||
>>> list(ring.coords)
|
||||
[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
|
||||
>>> ring.length
|
||||
4.0
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, coordinates=None):
|
||||
if coordinates is None:
|
||||
# empty geometry
|
||||
# TODO better way?
|
||||
return shapely.from_wkt("LINEARRING EMPTY")
|
||||
elif isinstance(coordinates, LineString):
|
||||
if type(coordinates) == LinearRing:
|
||||
# return original objects since geometries are immutable
|
||||
return coordinates
|
||||
elif not coordinates.is_valid:
|
||||
raise TopologicalError("An input LineString must be valid.")
|
||||
else:
|
||||
# LineString
|
||||
# TODO convert LineString to LinearRing more directly?
|
||||
coordinates = coordinates.coords
|
||||
|
||||
else:
|
||||
if hasattr(coordinates, "__array__"):
|
||||
coordinates = np.asarray(coordinates)
|
||||
if isinstance(coordinates, np.ndarray) and np.issubdtype(
|
||||
coordinates.dtype, np.number
|
||||
):
|
||||
pass
|
||||
else:
|
||||
# check coordinates on points
|
||||
def _coords(o):
|
||||
if isinstance(o, Point):
|
||||
return o.coords[0]
|
||||
else:
|
||||
return [float(c) for c in o]
|
||||
|
||||
coordinates = np.array([_coords(o) for o in coordinates])
|
||||
if not np.issubdtype(coordinates.dtype, np.number):
|
||||
# conversion of coords to 2D array failed, this might be due
|
||||
# to inconsistent coordinate dimensionality
|
||||
raise ValueError("Inconsistent coordinate dimensionality")
|
||||
|
||||
if len(coordinates) == 0:
|
||||
# empty geometry
|
||||
# TODO better constructor + should shapely.linearrings handle this?
|
||||
return shapely.from_wkt("LINEARRING EMPTY")
|
||||
|
||||
geom = shapely.linearrings(coordinates)
|
||||
if not isinstance(geom, LinearRing):
|
||||
raise ValueError("Invalid values passed to LinearRing constructor")
|
||||
return geom
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {"type": "LinearRing", "coordinates": tuple(self.coords)}
|
||||
|
||||
def __reduce__(self):
|
||||
"""WKB doesn't differentiate between LineString and LinearRing so we
|
||||
need to move the coordinate sequence into the correct geometry type"""
|
||||
return (_unpickle_linearring, (shapely.to_wkb(self, include_srid=True),))
|
||||
|
||||
@property
|
||||
def is_ccw(self):
|
||||
"""True is the ring is oriented counter clock-wise"""
|
||||
return bool(is_ccw_impl()(self))
|
||||
|
||||
@property
|
||||
def is_simple(self):
|
||||
"""True if the geometry is simple, meaning that any self-intersections
|
||||
are only at boundary points, else False"""
|
||||
return bool(shapely.is_simple(self))
|
||||
|
||||
|
||||
shapely.lib.registry[2] = LinearRing
|
||||
|
||||
|
||||
class InteriorRingSequence:
|
||||
|
||||
_parent = None
|
||||
_ndim = None
|
||||
_index = 0
|
||||
_length = 0
|
||||
|
||||
def __init__(self, parent):
|
||||
self._parent = parent
|
||||
self._ndim = parent._ndim
|
||||
|
||||
def __iter__(self):
|
||||
self._index = 0
|
||||
self._length = self.__len__()
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self._index < self._length:
|
||||
ring = self._get_ring(self._index)
|
||||
self._index += 1
|
||||
return ring
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def __len__(self):
|
||||
return shapely.get_num_interior_rings(self._parent)
|
||||
|
||||
def __getitem__(self, key):
|
||||
m = self.__len__()
|
||||
if isinstance(key, int):
|
||||
if key + m < 0 or key >= m:
|
||||
raise IndexError("index out of range")
|
||||
if key < 0:
|
||||
i = m + key
|
||||
else:
|
||||
i = key
|
||||
return self._get_ring(i)
|
||||
elif isinstance(key, slice):
|
||||
res = []
|
||||
start, stop, stride = key.indices(m)
|
||||
for i in range(start, stop, stride):
|
||||
res.append(self._get_ring(i))
|
||||
return res
|
||||
else:
|
||||
raise TypeError("key must be an index or slice")
|
||||
|
||||
def _get_ring(self, i):
|
||||
return shapely.get_interior_ring(self._parent, i)
|
||||
|
||||
|
||||
class Polygon(BaseGeometry):
|
||||
"""
|
||||
A geometry type representing an area that is enclosed by a linear ring.
|
||||
|
||||
A polygon is a two-dimensional feature and has a non-zero area. It may
|
||||
have one or more negative-space "holes" which are also bounded by linear
|
||||
rings. If any rings cross each other, the feature is invalid and
|
||||
operations on it may fail.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shell : sequence
|
||||
A sequence of (x, y [,z]) numeric coordinate pairs or triples, or
|
||||
an array-like with shape (N, 2) or (N, 3).
|
||||
Also can be a sequence of Point objects.
|
||||
holes : sequence
|
||||
A sequence of objects which satisfy the same requirements as the
|
||||
shell parameters above
|
||||
|
||||
Attributes
|
||||
----------
|
||||
exterior : LinearRing
|
||||
The ring which bounds the positive space of the polygon.
|
||||
interiors : sequence
|
||||
A sequence of rings which bound all existing holes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a square polygon with no holes
|
||||
|
||||
>>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))
|
||||
>>> polygon = Polygon(coords)
|
||||
>>> polygon.area
|
||||
1.0
|
||||
"""
|
||||
|
||||
__slots__ = []
|
||||
|
||||
def __new__(self, shell=None, holes=None):
|
||||
if shell is None:
|
||||
# empty geometry
|
||||
# TODO better way?
|
||||
return shapely.from_wkt("POLYGON EMPTY")
|
||||
elif isinstance(shell, Polygon):
|
||||
# return original objects since geometries are immutable
|
||||
return shell
|
||||
else:
|
||||
shell = LinearRing(shell)
|
||||
|
||||
if holes is not None:
|
||||
if len(holes) == 0:
|
||||
# shapely constructor cannot handle holes=[]
|
||||
holes = None
|
||||
else:
|
||||
holes = [LinearRing(ring) for ring in holes]
|
||||
|
||||
geom = shapely.polygons(shell, holes=holes)
|
||||
if not isinstance(geom, Polygon):
|
||||
raise ValueError("Invalid values passed to Polygon constructor")
|
||||
return geom
|
||||
|
||||
@property
|
||||
def exterior(self):
|
||||
return shapely.get_exterior_ring(self)
|
||||
|
||||
@property
|
||||
def interiors(self):
|
||||
if self.is_empty:
|
||||
return []
|
||||
return InteriorRingSequence(self)
|
||||
|
||||
@property
|
||||
def coords(self):
|
||||
raise NotImplementedError(
|
||||
"Component rings have coordinate sequences, but the polygon does not"
|
||||
)
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
if self.exterior == LinearRing():
|
||||
coords = []
|
||||
else:
|
||||
coords = [tuple(self.exterior.coords)]
|
||||
for hole in self.interiors:
|
||||
coords.append(tuple(hole.coords))
|
||||
return {"type": "Polygon", "coordinates": tuple(coords)}
|
||||
|
||||
def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
|
||||
"""Returns SVG path element for the Polygon geometry.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
fill_color : str, optional
|
||||
Hex string for fill color. Default is to use "#66cc99" if
|
||||
geometry is valid, and "#ff3333" if invalid.
|
||||
opacity : float
|
||||
Float number between 0 and 1 for color opacity. Default value is 0.6
|
||||
"""
|
||||
if self.is_empty:
|
||||
return "<g />"
|
||||
if fill_color is None:
|
||||
fill_color = "#66cc99" if self.is_valid else "#ff3333"
|
||||
if opacity is None:
|
||||
opacity = 0.6
|
||||
exterior_coords = [["{},{}".format(*c) for c in self.exterior.coords]]
|
||||
interior_coords = [
|
||||
["{},{}".format(*c) for c in interior.coords] for interior in self.interiors
|
||||
]
|
||||
path = " ".join(
|
||||
[
|
||||
"M {} L {} z".format(coords[0], " L ".join(coords[1:]))
|
||||
for coords in exterior_coords + interior_coords
|
||||
]
|
||||
)
|
||||
return (
|
||||
'<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
|
||||
'stroke-width="{0}" opacity="{3}" d="{1}" />'
|
||||
).format(2.0 * scale_factor, path, fill_color, opacity)
|
||||
|
||||
@classmethod
|
||||
def from_bounds(cls, xmin, ymin, xmax, ymax):
|
||||
"""Construct a `Polygon()` from spatial bounds."""
|
||||
return cls([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
|
||||
|
||||
|
||||
shapely.lib.registry[3] = Polygon
|
||||
|
||||
|
||||
def orient(polygon, sign=1.0):
|
||||
s = float(sign)
|
||||
rings = []
|
||||
ring = polygon.exterior
|
||||
if signed_area(ring) / s >= 0.0:
|
||||
rings.append(ring)
|
||||
else:
|
||||
rings.append(list(ring.coords)[::-1])
|
||||
for ring in polygon.interiors:
|
||||
if signed_area(ring) / s <= 0.0:
|
||||
rings.append(ring)
|
||||
else:
|
||||
rings.append(list(ring.coords)[::-1])
|
||||
return Polygon(rings[0], rings[1:])
|
||||
Reference in New Issue
Block a user