ops.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. """Support for various GEOS geometry operations."""
  2. import shapely
  3. from shapely.algorithms.polylabel import polylabel # noqa
  4. from shapely.errors import GeometryTypeError
  5. from shapely.geometry import (
  6. GeometryCollection,
  7. LineString,
  8. MultiLineString,
  9. MultiPoint,
  10. Point,
  11. Polygon,
  12. shape,
  13. )
  14. from shapely.geometry.base import BaseGeometry
  15. from shapely.prepared import prep
  16. __all__ = [
  17. "clip_by_rect",
  18. "linemerge",
  19. "nearest_points",
  20. "operator",
  21. "orient",
  22. "polygonize",
  23. "polygonize_full",
  24. "shared_paths",
  25. "snap",
  26. "split",
  27. "substring",
  28. "transform",
  29. "triangulate",
  30. "unary_union",
  31. "validate",
  32. "voronoi_diagram",
  33. ]
  34. class CollectionOperator:
  35. def shapeup(self, ob):
  36. if isinstance(ob, BaseGeometry):
  37. return ob
  38. else:
  39. try:
  40. return shape(ob)
  41. except (ValueError, AttributeError):
  42. return LineString(ob)
  43. def polygonize(self, lines):
  44. """Create polygons from a source of lines.
  45. The source may be a MultiLineString, a sequence of LineString objects,
  46. or a sequence of objects than can be adapted to LineStrings.
  47. """
  48. source = getattr(lines, "geoms", None) or lines
  49. try:
  50. source = iter(source)
  51. except TypeError:
  52. source = [source]
  53. finally:
  54. obs = [self.shapeup(line) for line in source]
  55. collection = shapely.polygonize(obs)
  56. return collection.geoms
  57. def polygonize_full(self, lines):
  58. """Create polygons from a source of lines.
  59. The polygons and leftover geometries are returned as well.
  60. The source may be a MultiLineString, a sequence of LineString objects,
  61. or a sequence of objects than can be adapted to LineStrings.
  62. Returns a tuple of objects: (polygons, cut edges, dangles, invalid ring
  63. lines). Each are a geometry collection.
  64. Dangles are edges which have one or both ends which are not incident on
  65. another edge endpoint. Cut edges are connected at both ends but do not
  66. form part of polygon. Invalid ring lines form rings which are invalid
  67. (bowties, etc).
  68. """
  69. source = getattr(lines, "geoms", None) or lines
  70. try:
  71. source = iter(source)
  72. except TypeError:
  73. source = [source]
  74. finally:
  75. obs = [self.shapeup(line) for line in source]
  76. return shapely.polygonize_full(obs)
  77. def linemerge(self, lines, directed=False):
  78. """Merge all connected lines from a source.
  79. The source may be a MultiLineString, a sequence of LineString objects,
  80. or a sequence of objects than can be adapted to LineStrings. Returns a
  81. LineString or MultiLineString when lines are not contiguous.
  82. """
  83. source = None
  84. if getattr(lines, "geom_type", None) == "MultiLineString":
  85. source = lines
  86. elif hasattr(lines, "geoms"):
  87. # other Multi geometries
  88. source = MultiLineString([ls.coords for ls in lines.geoms])
  89. elif hasattr(lines, "__iter__"):
  90. try:
  91. source = MultiLineString([ls.coords for ls in lines])
  92. except AttributeError:
  93. source = MultiLineString(lines)
  94. if source is None:
  95. raise ValueError(f"Cannot linemerge {lines}")
  96. return shapely.line_merge(source, directed=directed)
  97. def unary_union(self, geoms):
  98. """Return the union of a sequence of geometries.
  99. Usually used to convert a collection into the smallest set of polygons
  100. that cover the same area.
  101. """
  102. return shapely.union_all(geoms, axis=None)
  103. operator = CollectionOperator()
  104. polygonize = operator.polygonize
  105. polygonize_full = operator.polygonize_full
  106. linemerge = operator.linemerge
  107. unary_union = operator.unary_union
  108. def triangulate(geom, tolerance=0.0, edges=False):
  109. """Create the Delaunay triangulation and return a list of geometries.
  110. The source may be any geometry type. All vertices of the geometry will be
  111. used as the points of the triangulation.
  112. From the GEOS documentation:
  113. tolerance is the snapping tolerance used to improve the robustness of
  114. the triangulation computation. A tolerance of 0.0 specifies that no
  115. snapping will take place.
  116. If edges is False, a list of Polygons (triangles) will be returned.
  117. Otherwise the list of LineString edges is returned.
  118. """
  119. collection = shapely.delaunay_triangles(geom, tolerance=tolerance, only_edges=edges)
  120. return list(collection.geoms)
  121. def voronoi_diagram(geom, envelope=None, tolerance=0.0, edges=False):
  122. """Construct a Voronoi Diagram [1] from the given geometry.
  123. Returns a list of geometries.
  124. Parameters
  125. ----------
  126. geom: geometry
  127. the input geometry whose vertices will be used to calculate
  128. the final diagram.
  129. envelope: geometry, None
  130. clipping envelope for the returned diagram, automatically
  131. determined if None. The diagram will be clipped to the larger
  132. of this envelope or an envelope surrounding the sites.
  133. tolerance: float, 0.0
  134. sets the snapping tolerance used to improve the robustness
  135. of the computation. A tolerance of 0.0 specifies that no
  136. snapping will take place.
  137. edges: bool, False
  138. If False, return regions as polygons. Else, return only
  139. edges e.g. LineStrings.
  140. GEOS documentation can be found at [2]
  141. Returns
  142. -------
  143. GeometryCollection
  144. geometries representing the Voronoi regions.
  145. Notes
  146. -----
  147. The tolerance `argument` can be finicky and is known to cause the
  148. algorithm to fail in several cases. If you're using `tolerance`
  149. and getting a failure, try removing it. The test cases in
  150. tests/test_voronoi_diagram.py show more details.
  151. References
  152. ----------
  153. [1] https://en.wikipedia.org/wiki/Voronoi_diagram
  154. [2] https://geos.osgeo.org/doxygen/geos__c_8h_source.html (line 730)
  155. """
  156. try:
  157. result = shapely.voronoi_polygons(
  158. geom, tolerance=tolerance, extend_to=envelope, only_edges=edges
  159. )
  160. except shapely.GEOSException as err:
  161. errstr = "Could not create Voronoi Diagram with the specified inputs "
  162. errstr += f"({err!s})."
  163. if tolerance:
  164. errstr += " Try running again with default tolerance value."
  165. raise ValueError(errstr) from err
  166. if result.geom_type != "GeometryCollection":
  167. return GeometryCollection([result])
  168. return result
  169. def validate(geom):
  170. """Return True if the geometry is valid."""
  171. return shapely.is_valid_reason(geom)
  172. def transform(func, geom):
  173. """Apply `func` to all coordinates of `geom`.
  174. Returns a new geometry of the same type from the transformed coordinates.
  175. `func` maps x, y, and optionally z to output xp, yp, zp. The input
  176. parameters may iterable types like lists or arrays or single values.
  177. The output shall be of the same type. Scalars in, scalars out.
  178. Lists in, lists out.
  179. For example, here is an identity function applicable to both types
  180. of input.
  181. def id_func(x, y, z=None):
  182. return tuple(filter(None, [x, y, z]))
  183. g2 = transform(id_func, g1)
  184. Using pyproj >= 2.1, this example will accurately project Shapely geometries:
  185. import pyproj
  186. wgs84 = pyproj.CRS('EPSG:4326')
  187. utm = pyproj.CRS('EPSG:32618')
  188. project = pyproj.Transformer.from_crs(wgs84, utm, always_xy=True).transform
  189. g2 = transform(project, g1)
  190. Note that the always_xy kwarg is required here as Shapely geometries only support
  191. X,Y coordinate ordering.
  192. Lambda expressions such as the one in
  193. g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)
  194. also satisfy the requirements for `func`.
  195. """
  196. if geom.is_empty:
  197. return geom
  198. if geom.geom_type in ("Point", "LineString", "LinearRing", "Polygon"):
  199. # First we try to apply func to x, y, z sequences. When func is
  200. # optimized for sequences, this is the fastest, though zipping
  201. # the results up to go back into the geometry constructors adds
  202. # extra cost.
  203. try:
  204. if geom.geom_type in ("Point", "LineString", "LinearRing"):
  205. return type(geom)(zip(*func(*zip(*geom.coords))))
  206. elif geom.geom_type == "Polygon":
  207. shell = type(geom.exterior)(zip(*func(*zip(*geom.exterior.coords))))
  208. holes = [
  209. type(ring)(zip(*func(*zip(*ring.coords))))
  210. for ring in geom.interiors
  211. ]
  212. return type(geom)(shell, holes)
  213. # A func that assumes x, y, z are single values will likely raise a
  214. # TypeError, in which case we'll try again.
  215. except TypeError:
  216. if geom.geom_type in ("Point", "LineString", "LinearRing"):
  217. return type(geom)([func(*c) for c in geom.coords])
  218. elif geom.geom_type == "Polygon":
  219. shell = type(geom.exterior)([func(*c) for c in geom.exterior.coords])
  220. holes = [
  221. type(ring)([func(*c) for c in ring.coords])
  222. for ring in geom.interiors
  223. ]
  224. return type(geom)(shell, holes)
  225. elif geom.geom_type.startswith("Multi") or geom.geom_type == "GeometryCollection":
  226. return type(geom)([transform(func, part) for part in geom.geoms])
  227. else:
  228. raise GeometryTypeError(f"Type {geom.geom_type!r} not recognized")
  229. def nearest_points(g1, g2):
  230. """Return the calculated nearest points in the input geometries.
  231. The points are returned in the same order as the input geometries.
  232. """
  233. seq = shapely.shortest_line(g1, g2)
  234. if seq is None:
  235. if g1.is_empty:
  236. raise ValueError("The first input geometry is empty")
  237. else:
  238. raise ValueError("The second input geometry is empty")
  239. p1 = shapely.get_point(seq, 0)
  240. p2 = shapely.get_point(seq, 1)
  241. return (p1, p2)
  242. def snap(g1, g2, tolerance):
  243. """Snaps an input geometry (g1) to reference (g2) geometry's vertices.
  244. Parameters
  245. ----------
  246. g1 : geometry
  247. The first geometry
  248. g2 : geometry
  249. The second geometry
  250. tolerance : float
  251. The snapping tolerance
  252. Refer to :func:`shapely.snap` for full documentation.
  253. """
  254. return shapely.snap(g1, g2, tolerance)
  255. def shared_paths(g1, g2):
  256. """Find paths shared between the two given lineal geometries.
  257. Returns a GeometryCollection with two elements:
  258. - First element is a MultiLineString containing shared paths with the
  259. same direction for both inputs.
  260. - Second element is a MultiLineString containing shared paths with the
  261. opposite direction for the two inputs.
  262. Parameters
  263. ----------
  264. g1 : geometry
  265. The first geometry
  266. g2 : geometry
  267. The second geometry
  268. """
  269. if not isinstance(g1, LineString):
  270. raise GeometryTypeError("First geometry must be a LineString")
  271. if not isinstance(g2, LineString):
  272. raise GeometryTypeError("Second geometry must be a LineString")
  273. return shapely.shared_paths(g1, g2)
  274. class SplitOp:
  275. @staticmethod
  276. def _split_polygon_with_line(poly, splitter):
  277. """Split a Polygon with a LineString."""
  278. if not isinstance(poly, Polygon):
  279. raise GeometryTypeError("First argument must be a Polygon")
  280. if not isinstance(splitter, (LineString, MultiLineString)):
  281. raise GeometryTypeError("Second argument must be a (Multi)LineString")
  282. union = poly.boundary.union(splitter)
  283. # greatly improves split performance for big geometries with many
  284. # holes (the following contains checks) with minimal overhead
  285. # for common cases
  286. poly = prep(poly)
  287. # some polygonized geometries may be holes, we do not want them
  288. # that's why we test if the original polygon (poly) contains
  289. # an inner point of polygonized geometry (pg)
  290. return [
  291. pg for pg in polygonize(union) if poly.contains(pg.representative_point())
  292. ]
  293. @staticmethod
  294. def _split_line_with_line(line, splitter):
  295. """Split a LineString with another (Multi)LineString or (Multi)Polygon."""
  296. # if splitter is a polygon, pick it's boundary
  297. if splitter.geom_type in ("Polygon", "MultiPolygon"):
  298. splitter = splitter.boundary
  299. if not isinstance(line, LineString):
  300. raise GeometryTypeError("First argument must be a LineString")
  301. if not isinstance(splitter, LineString) and not isinstance(
  302. splitter, MultiLineString
  303. ):
  304. raise GeometryTypeError(
  305. "Second argument must be either a LineString or a MultiLineString"
  306. )
  307. # | s\l | Interior | Boundary | Exterior |
  308. # |----------|----------|----------|----------|
  309. # | Interior | 0 or F | * | * | At least one of these two must be 0 # noqa: E501
  310. # | Boundary | 0 or F | * | * | So either '0********' or '[0F]**0*****' # noqa: E501
  311. # | Exterior | * | * | * | No overlapping interiors ('1********') # noqa: E501
  312. relation = splitter.relate(line)
  313. if relation[0] == "1":
  314. # The lines overlap at some segment (linear intersection of interiors)
  315. raise ValueError("Input geometry segment overlaps with the splitter.")
  316. elif relation[0] == "0" or relation[3] == "0":
  317. # The splitter crosses or touches the line's interior
  318. # --> return multilinestring from the split
  319. return line.difference(splitter)
  320. else:
  321. # The splitter does not cross or touch the line's interior
  322. # --> return collection with identity line
  323. return [line]
  324. @staticmethod
  325. def _split_line_with_point(line, splitter):
  326. """Split a LineString with a Point."""
  327. if not isinstance(line, LineString):
  328. raise GeometryTypeError("First argument must be a LineString")
  329. if not isinstance(splitter, Point):
  330. raise GeometryTypeError("Second argument must be a Point")
  331. # check if point is in the interior of the line
  332. if not line.relate_pattern(splitter, "0********"):
  333. # point not on line interior --> return collection with single identity line
  334. # (REASONING: Returning a list with the input line reference and creating a
  335. # GeometryCollection at the general split function prevents unnecessary
  336. # copying of linestrings in multipoint splitting function)
  337. return [line]
  338. elif line.coords[0] == splitter.coords[0]:
  339. # if line is a closed ring the previous test doesn't behave as desired
  340. return [line]
  341. # point is on line, get the distance from the first point on line
  342. distance_on_line = line.project(splitter)
  343. coords = list(line.coords)
  344. # split the line at the point and create two new lines
  345. current_position = 0.0
  346. for i in range(len(coords) - 1):
  347. point1 = coords[i]
  348. point2 = coords[i + 1]
  349. dx = point1[0] - point2[0]
  350. dy = point1[1] - point2[1]
  351. segment_length = (dx**2 + dy**2) ** 0.5
  352. current_position += segment_length
  353. if distance_on_line == current_position:
  354. # splitter is exactly on a vertex
  355. return [LineString(coords[: i + 2]), LineString(coords[i + 1 :])]
  356. elif distance_on_line < current_position:
  357. # splitter is between two vertices
  358. return [
  359. LineString(coords[: i + 1] + [splitter.coords[0]]),
  360. LineString([splitter.coords[0]] + coords[i + 1 :]),
  361. ]
  362. return [line]
  363. @staticmethod
  364. def _split_line_with_multipoint(line, splitter):
  365. """Split a LineString with a MultiPoint."""
  366. if not isinstance(line, LineString):
  367. raise GeometryTypeError("First argument must be a LineString")
  368. if not isinstance(splitter, MultiPoint):
  369. raise GeometryTypeError("Second argument must be a MultiPoint")
  370. chunks = [line]
  371. for pt in splitter.geoms:
  372. new_chunks = []
  373. for chunk in filter(lambda x: not x.is_empty, chunks):
  374. # add the newly split 2 lines or the same line if not split
  375. new_chunks.extend(SplitOp._split_line_with_point(chunk, pt))
  376. chunks = new_chunks
  377. return chunks
  378. @staticmethod
  379. def split(geom, splitter):
  380. """Split a geometry by another geometry and return a collection of geometries.
  381. This function is the theoretical opposite of the union of
  382. the split geometry parts. If the splitter does not split the geometry, a
  383. collection with a single geometry equal to the input geometry is
  384. returned.
  385. The function supports:
  386. - Splitting a (Multi)LineString by a (Multi)Point or (Multi)LineString
  387. or (Multi)Polygon
  388. - Splitting a (Multi)Polygon by a LineString
  389. It may be convenient to snap the splitter with low tolerance to the
  390. geometry. For example in the case of splitting a line by a point, the
  391. point must be exactly on the line, for the line to be correctly split.
  392. When splitting a line by a polygon, the boundary of the polygon is used
  393. for the operation. When splitting a line by another line, a ValueError
  394. is raised if the two overlap at some segment.
  395. Parameters
  396. ----------
  397. geom : geometry
  398. The geometry to be split
  399. splitter : geometry
  400. The geometry that will split the input geom
  401. Examples
  402. --------
  403. >>> import shapely.ops
  404. >>> from shapely import Point, LineString
  405. >>> pt = Point((1, 1))
  406. >>> line = LineString([(0,0), (2,2)])
  407. >>> result = shapely.ops.split(line, pt)
  408. >>> result.wkt
  409. 'GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))'
  410. """
  411. if geom.geom_type in ("MultiLineString", "MultiPolygon"):
  412. return GeometryCollection(
  413. [i for part in geom.geoms for i in SplitOp.split(part, splitter).geoms]
  414. )
  415. elif geom.geom_type == "LineString":
  416. if splitter.geom_type in (
  417. "LineString",
  418. "MultiLineString",
  419. "Polygon",
  420. "MultiPolygon",
  421. ):
  422. split_func = SplitOp._split_line_with_line
  423. elif splitter.geom_type == "Point":
  424. split_func = SplitOp._split_line_with_point
  425. elif splitter.geom_type == "MultiPoint":
  426. split_func = SplitOp._split_line_with_multipoint
  427. else:
  428. raise GeometryTypeError(
  429. f"Splitting a LineString with a {splitter.geom_type} is "
  430. "not supported"
  431. )
  432. elif geom.geom_type == "Polygon":
  433. if splitter.geom_type in ("LineString", "MultiLineString"):
  434. split_func = SplitOp._split_polygon_with_line
  435. else:
  436. raise GeometryTypeError(
  437. f"Splitting a Polygon with a {splitter.geom_type} is not supported"
  438. )
  439. else:
  440. raise GeometryTypeError(
  441. f"Splitting {geom.geom_type} geometry is not supported"
  442. )
  443. return GeometryCollection(split_func(geom, splitter))
  444. split = SplitOp.split
  445. def substring(geom, start_dist, end_dist, normalized=False):
  446. """Return a line segment between specified distances along a LineString.
  447. Negative distance values are taken as measured in the reverse
  448. direction from the end of the geometry. Out-of-range index
  449. values are handled by clamping them to the valid range of values.
  450. If the start distance equals the end distance, a Point is returned.
  451. If the start distance is actually beyond the end distance, then the
  452. reversed substring is returned such that the start distance is
  453. at the first coordinate.
  454. Parameters
  455. ----------
  456. geom : LineString
  457. The geometry to get a substring of.
  458. start_dist : float
  459. The distance along `geom` of the start of the substring.
  460. end_dist : float
  461. The distance along `geom` of the end of the substring.
  462. normalized : bool, False
  463. Whether the distance parameters are interpreted as a
  464. fraction of the geometry's length.
  465. Returns
  466. -------
  467. Union[Point, LineString]
  468. The substring between `start_dist` and `end_dist` or a Point
  469. if they are at the same location.
  470. Raises
  471. ------
  472. TypeError
  473. If `geom` is not a LineString.
  474. Examples
  475. --------
  476. >>> from shapely.geometry import LineString
  477. >>> from shapely.ops import substring
  478. >>> ls = LineString((i, 0) for i in range(6))
  479. >>> ls.wkt
  480. 'LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)'
  481. >>> substring(ls, start_dist=1, end_dist=3).wkt
  482. 'LINESTRING (1 0, 2 0, 3 0)'
  483. >>> substring(ls, start_dist=3, end_dist=1).wkt
  484. 'LINESTRING (3 0, 2 0, 1 0)'
  485. >>> substring(ls, start_dist=1, end_dist=-3).wkt
  486. 'LINESTRING (1 0, 2 0)'
  487. >>> substring(ls, start_dist=0.2, end_dist=-0.6, normalized=True).wkt
  488. 'LINESTRING (1 0, 2 0)'
  489. Returning a `Point` when `start_dist` and `end_dist` are at the
  490. same location.
  491. >>> substring(ls, 2.5, -2.5).wkt
  492. 'POINT (2.5 0)'
  493. """
  494. if not isinstance(geom, LineString):
  495. raise GeometryTypeError(
  496. "Can only calculate a substring of LineString geometries. "
  497. f"A {geom.geom_type} was provided."
  498. )
  499. # Filter out cases in which to return a point
  500. if start_dist == end_dist:
  501. return geom.interpolate(start_dist, normalized=normalized)
  502. elif not normalized and start_dist >= geom.length and end_dist >= geom.length:
  503. return geom.interpolate(geom.length, normalized=normalized)
  504. elif not normalized and -start_dist >= geom.length and -end_dist >= geom.length:
  505. return geom.interpolate(0, normalized=normalized)
  506. elif normalized and start_dist >= 1 and end_dist >= 1:
  507. return geom.interpolate(1, normalized=normalized)
  508. elif normalized and -start_dist >= 1 and -end_dist >= 1:
  509. return geom.interpolate(0, normalized=normalized)
  510. if normalized:
  511. start_dist *= geom.length
  512. end_dist *= geom.length
  513. # Filter out cases where distances meet at a middle point from opposite ends.
  514. if start_dist < 0 < end_dist and abs(start_dist) + end_dist == geom.length:
  515. return geom.interpolate(end_dist)
  516. elif end_dist < 0 < start_dist and abs(end_dist) + start_dist == geom.length:
  517. return geom.interpolate(start_dist)
  518. start_point = geom.interpolate(start_dist)
  519. end_point = geom.interpolate(end_dist)
  520. if start_dist < 0:
  521. start_dist = geom.length + start_dist # Values may still be negative,
  522. if end_dist < 0: # but only in the out-of-range
  523. end_dist = geom.length + end_dist # sense, not the wrap-around sense.
  524. reverse = start_dist > end_dist
  525. if reverse:
  526. start_dist, end_dist = end_dist, start_dist
  527. start_dist = max(start_dist, 0) # to avoid duplicating the first vertex
  528. if reverse:
  529. vertex_list = [tuple(*end_point.coords)]
  530. else:
  531. vertex_list = [tuple(*start_point.coords)]
  532. coords = list(geom.coords)
  533. current_distance = 0
  534. for p1, p2 in zip(coords, coords[1:]): # noqa
  535. if start_dist < current_distance < end_dist:
  536. vertex_list.append(p1)
  537. elif current_distance >= end_dist:
  538. break
  539. current_distance += ((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) ** 0.5
  540. if reverse:
  541. vertex_list.append(tuple(*start_point.coords))
  542. # reverse direction result
  543. vertex_list = reversed(vertex_list)
  544. else:
  545. vertex_list.append(tuple(*end_point.coords))
  546. return LineString(vertex_list)
  547. def clip_by_rect(geom, xmin, ymin, xmax, ymax):
  548. """Return the portion of a geometry within a rectangle.
  549. The geometry is clipped in a fast but possibly dirty way. The output is
  550. not guaranteed to be valid. No exceptions will be raised for topological
  551. errors.
  552. Parameters
  553. ----------
  554. geom : geometry
  555. The geometry to be clipped
  556. xmin : float
  557. Minimum x value of the rectangle
  558. ymin : float
  559. Minimum y value of the rectangle
  560. xmax : float
  561. Maximum x value of the rectangle
  562. ymax : float
  563. Maximum y value of the rectangle
  564. Notes
  565. -----
  566. New in 1.7.
  567. """
  568. if geom.is_empty:
  569. return geom
  570. return shapely.clip_by_rect(geom, xmin, ymin, xmax, ymax)
  571. def orient(geom, sign=1.0):
  572. """Return a properly oriented copy of the given geometry.
  573. The signed area of the result will have the given sign. A sign of
  574. 1.0 means that the coordinates of the product's exterior rings will
  575. be oriented counter-clockwise.
  576. It is recommended to use :func:`shapely.orient_polygons` instead.
  577. Parameters
  578. ----------
  579. geom : Geometry
  580. The original geometry. May be a Polygon, MultiPolygon, or
  581. GeometryCollection.
  582. sign : float, optional.
  583. The sign of the result's signed area.
  584. Returns
  585. -------
  586. Geometry
  587. """
  588. return shapely.orient_polygons(geom, exterior_cw=sign < 0)