structure saas with tools

This commit is contained in:
Davidson Gomes
2025-04-25 15:30:54 -03:00
commit 1aef473937
16434 changed files with 6584257 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
"""Geometry classes and factories."""
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
from shapely.geometry.collection import GeometryCollection
from shapely.geometry.geo import box, mapping, shape
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.point import Point
from shapely.geometry.polygon import LinearRing, Polygon
__all__ = [
"CAP_STYLE",
"JOIN_STYLE",
"GeometryCollection",
"LineString",
"LinearRing",
"MultiLineString",
"MultiPoint",
"MultiPolygon",
"Point",
"Polygon",
"box",
"mapping",
"shape",
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
"""Multi-part collections of geometries."""
import shapely
from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry
class GeometryCollection(BaseMultipartGeometry):
"""Collection of one or more geometries that can be of different types.
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 GeometryCollection, LineString, Point
>>> p = Point(51, -1)
>>> l = LineString([(52, -1), (49, 2)])
>>> gc = GeometryCollection([p, l])
"""
__slots__ = []
def __new__(self, geoms=None):
"""Create a new GeometryCollection."""
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]
elif geoms is None or len(geoms) == 0:
# TODO better empty constructor
return shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")
return shapely.geometrycollections(geoms)
@property
def __geo_interface__(self):
"""Return a GeoJSON-like mapping of the geometry collection."""
geometries = []
for geom in self.geoms:
geometries.append(geom.__geo_interface__)
return dict(type="GeometryCollection", geometries=geometries)
shapely.lib.registry[7] = GeometryCollection

View File

@@ -0,0 +1,143 @@
"""Geometry factories based on the geo interface."""
import numpy as np
from shapely.errors import GeometryTypeError
from shapely.geometry.collection import GeometryCollection
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.point import Point
from shapely.geometry.polygon import LinearRing, Polygon
def _is_coordinates_empty(coordinates):
"""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):
"""Return 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):
"""Return 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__.
>>> from shapely.geometry import shape
>>> 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 geom_type == "feature":
# GeoJSON features must have a 'geometry' field.
ob = ob["geometry"]
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):
"""Return a GeoJSON-like mapping.
Input should be a Geometry or an object which implements __geo_interface__.
Parameters
----------
ob : geometry or object
An object which implements __geo_interface__.
Returns
-------
dict
Examples
--------
>>> from shapely.geometry import mapping, Point
>>> pt = Point(0, 0)
>>> mapping(pt)
{'type': 'Point', 'coordinates': (0.0, 0.0)}
"""
return ob.__geo_interface__

View File

@@ -0,0 +1,211 @@
"""Line strings and related utilities."""
import numpy as np
import shapely
from shapely.decorators import deprecate_positional
from shapely.geometry.base import JOIN_STYLE, BaseGeometry
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. A LineString may
be 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, or combination of both.
Examples
--------
Create a LineString with two segments
>>> from shapely import LineString
>>> a = LineString([[0, 0], [1, 0], [1, 1]])
>>> a.length
2.0
"""
__slots__ = []
def __new__(self, coordinates=None):
"""Create a new LineString geometry."""
if coordinates is None:
# empty geometry
# TODO better constructor
return shapely.from_wkt("LINESTRING EMPTY")
elif isinstance(coordinates, LineString):
if type(coordinates) is 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 a GeoJSON-like mapping of the LineString geometry."""
return {"type": "LineString", "coordinates": tuple(self.coords)}
def svg(self, scale_factor=1.0, stroke_color=None, opacity=None):
"""Return 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 (
f'<polyline fill="none" stroke="{stroke_color}" '
f'stroke-width="{2.0 * scale_factor}" '
f'points="{pnt_format}" opacity="{opacity}" />'
)
@property
def xy(self):
"""Separate arrays of X and Y coordinate values.
Examples
--------
>>> from shapely import LineString
>>> x, y = LineString([(0, 0), (1, 1)]).xy
>>> list(x)
[0.0, 1.0]
>>> list(y)
[0.0, 1.0]
"""
return self.coords.xy
# Note: future plan is to change this signature over a few releases:
# shapely 2.0:
# offset_curve(self, distance, quad_segs=16, ...)
# shapely 2.1: shows deprecation warning about positional 'quad_segs', etc.
# same signature as 2.0
# shapely 2.2(?): enforce keyword-only arguments after 'distance'
# offset_curve(self, distance, *, quad_segs=16, ...)
@deprecate_positional(
["quad_segs", "join_style", "mitre_limit"], category=DeprecationWarning
)
def offset_curve(
self,
distance,
quad_segs=16,
join_style=JOIN_STYLE.round,
mitre_limit=5.0,
):
"""Return a (Multi)LineString at a distance from the object.
The side, left or right, 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=quad_segs,
join_style=join_style,
mitre_limit=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

View File

@@ -0,0 +1,95 @@
"""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.
>>> from shapely import MultiLineString
>>> lines = MultiLineString([[[0, 0], [1, 2]], [[4, 4], [5, 6]]])
"""
__slots__ = []
def __new__(self, lines=None):
"""Create a new MultiLineString geometry."""
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)
subs = []
for item in lines:
line = linestring.LineString(item)
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 a GeoJSON-like mapping interface for this MultiLineString."""
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):
"""Return 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

View File

@@ -0,0 +1,104 @@
"""Collections of points and related utilities."""
import numpy as np
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 MultiPoint, 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):
"""Create a new MultiPoint geometry."""
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
elif len(points) == 0:
return shapely.from_wkt("MULTIPOINT EMPTY")
if isinstance(points, np.ndarray) and np.issubdtype(points.dtype, np.number):
subs = shapely.points(points)
if not subs.ndim == 1:
raise ValueError("Invalid values passed to MultiPoint constructor")
if shapely.is_empty(subs).any():
raise EmptyPartError("Can't create MultiPoint with empty component")
else:
subs = []
for item in points:
p = point.Point(item)
if p.is_empty:
raise EmptyPartError("Can't create MultiPoint with empty component")
subs.append(p)
return shapely.multipoints(subs)
@property
def __geo_interface__(self):
"""Return a GeoJSON-like mapping interface for this MultiPoint."""
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):
"""Return 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

View File

@@ -0,0 +1,125 @@
"""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 MultiPolygon, 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):
"""Create a new MultiPolygon geometry."""
if polygons is None:
# 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)
# remove None and empty polygons from list of Polygons
polygons = [p for p in polygons if p]
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 any(isinstance(p, MultiPolygon) for p in polygons):
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]
if len(ob) > 1:
holes = ob[1]
else:
holes = None
p = polygon.Polygon(shell, holes)
else:
p = polygon.Polygon(ob)
subs.append(p)
return shapely.multipolygons(subs)
@property
def __geo_interface__(self):
"""Return a GeoJSON-like mapping of the MultiPolygon geometry."""
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):
"""Return 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

View File

@@ -0,0 +1,166 @@
"""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.
Each coordinate has x, y and possibly z and/or m 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, m : float
Coordinate values
Examples
--------
Constructing the Point using separate parameters for x and y:
>>> from shapely import Point
>>> 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):
"""Create a new Point geometry."""
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 float(shapely.get_x(self))
@property
def y(self):
"""Return y coordinate."""
return float(shapely.get_y(self))
@property
def z(self):
"""Return z coordinate."""
z = shapely.get_z(self)
if np.isnan(z) and not shapely.has_z(self):
raise DimensionError("This point has no z coordinate.")
return float(z)
@property
def m(self):
"""Return m coordinate.
.. versionadded:: 2.1.0
Also requires GEOS 3.12.0 or later.
"""
if not shapely.has_m(self):
raise DimensionError("This point has no m coordinate.")
return float(shapely.get_m(self))
@property
def __geo_interface__(self):
"""Return a GeoJSON-like mapping of the Point geometry."""
coords = self.coords
return {"type": "Point", "coordinates": coords[0] if len(coords) > 0 else ()}
def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
"""Return 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 (
f'<circle cx="{self.x}" cy="{self.y}" r="{3.0 * scale_factor}" '
f'stroke="#555555" stroke-width="{1.0 * scale_factor}" fill="{fill_color}" '
f'opacity="{opacity}" />'
)
@property
def xy(self):
"""Separate arrays of X and Y coordinate values.
Examples
--------
>>> from shapely import Point
>>> x, y = Point(0, 0).xy
>>> list(x)
[0.0]
>>> list(y)
[0.0]
"""
return self.coords.xy
shapely.lib.registry[0] = Point

View File

@@ -0,0 +1,345 @@
"""Polygons and their linear ring components."""
import numpy as np
import shapely
from shapely import _geometry_helpers
from shapely.algorithms.cga import signed_area # noqa
from shapely.errors import TopologicalError
from shapely.geometry.base import BaseGeometry
from shapely.geometry.linestring import LineString
from shapely.geometry.point import Point
__all__ = ["LinearRing", "Polygon", "orient"]
def _unpickle_linearring(wkb):
linestring = shapely.from_wkb(wkb)
srid = shapely.get_srid(linestring)
linearring = _geometry_helpers.linestring_to_linearring(linestring)
if srid:
linearring = shapely.set_srid(linearring, srid)
return linearring
class LinearRing(LineString):
"""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.
>>> from shapely import LinearRing
>>> 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):
"""Create a new LinearRing geometry."""
if coordinates is None:
# empty geometry
# TODO better way?
return shapely.from_wkt("LINEARRING EMPTY")
elif isinstance(coordinates, LineString):
if type(coordinates) is 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 a GeoJSON-like mapping of the LinearRing geometry."""
return {"type": "LinearRing", "coordinates": tuple(self.coords)}
def __reduce__(self):
"""Pickle support.
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 if the ring is oriented counter clock-wise."""
return bool(shapely.is_ccw(self))
@property
def is_simple(self):
"""True if the geometry is simple.
Simple means that any self-intersections are only at boundary points.
"""
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
>>> from shapely import Polygon
>>> 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):
"""Create a new Polygon geometry."""
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 the exterior ring of the polygon."""
return shapely.get_exterior_ring(self)
@property
def interiors(self):
"""Return the sequence of interior rings of the polygon."""
if self.is_empty:
return []
return InteriorRingSequence(self)
@property
def coords(self):
"""Not implemented for polygons."""
raise NotImplementedError(
"Component rings have coordinate sequences, but the polygon does not"
)
@property
def __geo_interface__(self):
"""Return a GeoJSON-like mapping of the Polygon geometry."""
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):
"""Return 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 (
f'<path fill-rule="evenodd" fill="{fill_color}" stroke="#555555" '
f'stroke-width="{2.0 * scale_factor}" opacity="{opacity}" d="{path}" />'
)
@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):
"""Return an oriented polygon.
It is recommended to use :func:`shapely.orient_polygons` instead.
Parameters
----------
polygon : shapely.Polygon
sign : float, default 1.
The sign of the result's signed area.
A non-negative sign means that the coordinates of the geometry's exterior
rings will be oriented counter-clockwise.
Returns
-------
Geometry or array_like
Refer to :func:`shapely.orient_polygons` for full documentation.
"""
return shapely.orient_polygons(polygon, exterior_cw=sign < 0.0)