| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- """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
|