geometry.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. """
  2. This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
  3. inherit from this object.
  4. """
  5. import re
  6. from ctypes import addressof, byref, c_double
  7. from django.contrib.gis import gdal
  8. from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex
  9. from django.contrib.gis.geos import prototypes as capi
  10. from django.contrib.gis.geos.base import GEOSBase
  11. from django.contrib.gis.geos.coordseq import GEOSCoordSeq
  12. from django.contrib.gis.geos.error import GEOSException
  13. from django.contrib.gis.geos.libgeos import GEOM_PTR, geos_version_tuple
  14. from django.contrib.gis.geos.mutable_list import ListMixin
  15. from django.contrib.gis.geos.prepared import PreparedGeometry
  16. from django.contrib.gis.geos.prototypes.io import ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w
  17. from django.utils.deconstruct import deconstructible
  18. from django.utils.encoding import force_bytes, force_str
  19. class GEOSGeometryBase(GEOSBase):
  20. _GEOS_CLASSES = None
  21. ptr_type = GEOM_PTR
  22. destructor = capi.destroy_geom
  23. has_cs = False # Only Point, LineString, LinearRing have coordinate sequences
  24. def __init__(self, ptr, cls):
  25. self._ptr = ptr
  26. # Setting the class type (e.g., Point, Polygon, etc.)
  27. if type(self) in (GEOSGeometryBase, GEOSGeometry):
  28. if cls is None:
  29. if GEOSGeometryBase._GEOS_CLASSES is None:
  30. # Inner imports avoid import conflicts with GEOSGeometry.
  31. from .collections import (
  32. GeometryCollection,
  33. MultiLineString,
  34. MultiPoint,
  35. MultiPolygon,
  36. )
  37. from .linestring import LinearRing, LineString
  38. from .point import Point
  39. from .polygon import Polygon
  40. GEOSGeometryBase._GEOS_CLASSES = {
  41. 0: Point,
  42. 1: LineString,
  43. 2: LinearRing,
  44. 3: Polygon,
  45. 4: MultiPoint,
  46. 5: MultiLineString,
  47. 6: MultiPolygon,
  48. 7: GeometryCollection,
  49. }
  50. cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
  51. self.__class__ = cls
  52. self._post_init()
  53. def _post_init(self):
  54. "Perform post-initialization setup."
  55. # Setting the coordinate sequence for the geometry (will be None on
  56. # geometries that do not have coordinate sequences)
  57. self._cs = (
  58. GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None
  59. )
  60. def __copy__(self):
  61. """
  62. Return a clone because the copy of a GEOSGeometry may contain an
  63. invalid pointer location if the original is garbage collected.
  64. """
  65. return self.clone()
  66. def __deepcopy__(self, memodict):
  67. """
  68. The `deepcopy` routine is used by the `Node` class of django.utils.tree;
  69. thus, the protocol routine needs to be implemented to return correct
  70. copies (clones) of these GEOS objects, which use C pointers.
  71. """
  72. return self.clone()
  73. def __str__(self):
  74. "EWKT is used for the string representation."
  75. return self.ewkt
  76. def __repr__(self):
  77. "Short-hand representation because WKT may be very large."
  78. return "<%s object at %s>" % (self.geom_type, hex(addressof(self.ptr)))
  79. # Pickling support
  80. def _to_pickle_wkb(self):
  81. return bytes(self.wkb)
  82. def _from_pickle_wkb(self, wkb):
  83. return wkb_r().read(memoryview(wkb))
  84. def __getstate__(self):
  85. # The pickled state is simply a tuple of the WKB (in string form)
  86. # and the SRID.
  87. return self._to_pickle_wkb(), self.srid
  88. def __setstate__(self, state):
  89. # Instantiating from the tuple state that was pickled.
  90. wkb, srid = state
  91. ptr = self._from_pickle_wkb(wkb)
  92. if not ptr:
  93. raise GEOSException("Invalid Geometry loaded from pickled state.")
  94. self.ptr = ptr
  95. self._post_init()
  96. self.srid = srid
  97. @classmethod
  98. def _from_wkb(cls, wkb):
  99. return wkb_r().read(wkb)
  100. @staticmethod
  101. def from_ewkt(ewkt):
  102. ewkt = force_bytes(ewkt)
  103. srid = None
  104. parts = ewkt.split(b";", 1)
  105. if len(parts) == 2:
  106. srid_part, wkt = parts
  107. match = re.match(rb"SRID=(?P<srid>\-?\d+)", srid_part)
  108. if not match:
  109. raise ValueError("EWKT has invalid SRID part.")
  110. srid = int(match["srid"])
  111. else:
  112. wkt = ewkt
  113. if not wkt:
  114. raise ValueError("Expected WKT but got an empty string.")
  115. return GEOSGeometry(GEOSGeometry._from_wkt(wkt), srid=srid)
  116. @staticmethod
  117. def _from_wkt(wkt):
  118. return wkt_r().read(wkt)
  119. @classmethod
  120. def from_gml(cls, gml_string):
  121. return gdal.OGRGeometry.from_gml(gml_string).geos
  122. # Comparison operators
  123. def __eq__(self, other):
  124. """
  125. Equivalence testing, a Geometry may be compared with another Geometry
  126. or an EWKT representation.
  127. """
  128. if isinstance(other, str):
  129. try:
  130. other = GEOSGeometry.from_ewkt(other)
  131. except (ValueError, GEOSException):
  132. return False
  133. return (
  134. isinstance(other, GEOSGeometry)
  135. and self.srid == other.srid
  136. and self.equals_exact(other)
  137. )
  138. def __hash__(self):
  139. return hash((self.srid, self.wkt))
  140. # ### Geometry set-like operations ###
  141. # Thanks to Sean Gillies for inspiration:
  142. # http://lists.gispython.org/pipermail/community/2007-July/001034.html
  143. # g = g1 | g2
  144. def __or__(self, other):
  145. "Return the union of this Geometry and the other."
  146. return self.union(other)
  147. # g = g1 & g2
  148. def __and__(self, other):
  149. "Return the intersection of this Geometry and the other."
  150. return self.intersection(other)
  151. # g = g1 - g2
  152. def __sub__(self, other):
  153. "Return the difference this Geometry and the other."
  154. return self.difference(other)
  155. # g = g1 ^ g2
  156. def __xor__(self, other):
  157. "Return the symmetric difference of this Geometry and the other."
  158. return self.sym_difference(other)
  159. # #### Coordinate Sequence Routines ####
  160. @property
  161. def coord_seq(self):
  162. "Return a clone of the coordinate sequence for this Geometry."
  163. if self.has_cs:
  164. return self._cs.clone()
  165. # #### Geometry Info ####
  166. @property
  167. def geom_type(self):
  168. "Return a string representing the Geometry type, e.g. 'Polygon'"
  169. return capi.geos_type(self.ptr).decode()
  170. @property
  171. def geom_typeid(self):
  172. "Return an integer representing the Geometry type."
  173. return capi.geos_typeid(self.ptr)
  174. @property
  175. def num_geom(self):
  176. "Return the number of geometries in the Geometry."
  177. return capi.get_num_geoms(self.ptr)
  178. @property
  179. def num_coords(self):
  180. "Return the number of coordinates in the Geometry."
  181. return capi.get_num_coords(self.ptr)
  182. @property
  183. def num_points(self):
  184. "Return the number points, or coordinates, in the Geometry."
  185. return self.num_coords
  186. @property
  187. def dims(self):
  188. "Return the dimension of this Geometry (0=point, 1=line, 2=surface)."
  189. return capi.get_dims(self.ptr)
  190. def normalize(self, clone=False):
  191. """
  192. Convert this Geometry to normal form (or canonical form).
  193. If the `clone` keyword is set, then the geometry is not modified and a
  194. normalized clone of the geometry is returned instead.
  195. """
  196. if clone:
  197. clone = self.clone()
  198. capi.geos_normalize(clone.ptr)
  199. return clone
  200. capi.geos_normalize(self.ptr)
  201. def make_valid(self):
  202. """
  203. Attempt to create a valid representation of a given invalid geometry
  204. without losing any of the input vertices.
  205. """
  206. return GEOSGeometry(capi.geos_makevalid(self.ptr), srid=self.srid)
  207. # #### Unary predicates ####
  208. @property
  209. def empty(self):
  210. """
  211. Return a boolean indicating whether the set of points in this Geometry
  212. are empty.
  213. """
  214. return capi.geos_isempty(self.ptr)
  215. @property
  216. def hasz(self):
  217. "Return whether the geometry has a 3D dimension."
  218. return capi.geos_hasz(self.ptr)
  219. @property
  220. def ring(self):
  221. "Return whether or not the geometry is a ring."
  222. return capi.geos_isring(self.ptr)
  223. @property
  224. def simple(self):
  225. "Return false if the Geometry isn't simple."
  226. return capi.geos_issimple(self.ptr)
  227. @property
  228. def valid(self):
  229. "Test the validity of this Geometry."
  230. return capi.geos_isvalid(self.ptr)
  231. @property
  232. def valid_reason(self):
  233. """
  234. Return a string containing the reason for any invalidity.
  235. """
  236. return capi.geos_isvalidreason(self.ptr).decode()
  237. # #### Binary predicates. ####
  238. def contains(self, other):
  239. "Return true if other.within(this) returns true."
  240. return capi.geos_contains(self.ptr, other.ptr)
  241. def covers(self, other):
  242. """
  243. Return True if the DE-9IM Intersection Matrix for the two geometries is
  244. T*****FF*, *T****FF*, ***T**FF*, or ****T*FF*. If either geometry is
  245. empty, return False.
  246. """
  247. return capi.geos_covers(self.ptr, other.ptr)
  248. def crosses(self, other):
  249. """
  250. Return true if the DE-9IM intersection matrix for the two Geometries
  251. is T*T****** (for a point and a curve,a point and an area or a line and
  252. an area) 0******** (for two curves).
  253. """
  254. return capi.geos_crosses(self.ptr, other.ptr)
  255. def disjoint(self, other):
  256. """
  257. Return true if the DE-9IM intersection matrix for the two Geometries
  258. is FF*FF****.
  259. """
  260. return capi.geos_disjoint(self.ptr, other.ptr)
  261. def equals(self, other):
  262. """
  263. Return true if the DE-9IM intersection matrix for the two Geometries
  264. is T*F**FFF*.
  265. """
  266. return capi.geos_equals(self.ptr, other.ptr)
  267. def equals_exact(self, other, tolerance=0):
  268. """
  269. Return true if the two Geometries are exactly equal, up to a
  270. specified tolerance.
  271. """
  272. return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
  273. def equals_identical(self, other):
  274. """
  275. Return true if the two Geometries are point-wise equivalent.
  276. """
  277. if geos_version_tuple() < (3, 12):
  278. raise GEOSException(
  279. "GEOSGeometry.equals_identical() requires GEOS >= 3.12.0."
  280. )
  281. return capi.geos_equalsidentical(self.ptr, other.ptr)
  282. def intersects(self, other):
  283. "Return true if disjoint return false."
  284. return capi.geos_intersects(self.ptr, other.ptr)
  285. def overlaps(self, other):
  286. """
  287. Return true if the DE-9IM intersection matrix for the two Geometries
  288. is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
  289. """
  290. return capi.geos_overlaps(self.ptr, other.ptr)
  291. def relate_pattern(self, other, pattern):
  292. """
  293. Return true if the elements in the DE-9IM intersection matrix for the
  294. two Geometries match the elements in pattern.
  295. """
  296. if not isinstance(pattern, str) or len(pattern) > 9:
  297. raise GEOSException("invalid intersection matrix pattern")
  298. return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern))
  299. def touches(self, other):
  300. """
  301. Return true if the DE-9IM intersection matrix for the two Geometries
  302. is FT*******, F**T***** or F***T****.
  303. """
  304. return capi.geos_touches(self.ptr, other.ptr)
  305. def within(self, other):
  306. """
  307. Return true if the DE-9IM intersection matrix for the two Geometries
  308. is T*F**F***.
  309. """
  310. return capi.geos_within(self.ptr, other.ptr)
  311. # #### SRID Routines ####
  312. @property
  313. def srid(self):
  314. "Get the SRID for the geometry. Return None if no SRID is set."
  315. s = capi.geos_get_srid(self.ptr)
  316. if s == 0:
  317. return None
  318. else:
  319. return s
  320. @srid.setter
  321. def srid(self, srid):
  322. "Set the SRID for the geometry."
  323. capi.geos_set_srid(self.ptr, 0 if srid is None else srid)
  324. # #### Output Routines ####
  325. @property
  326. def ewkt(self):
  327. """
  328. Return the EWKT (SRID + WKT) of the Geometry.
  329. """
  330. srid = self.srid
  331. return "SRID=%s;%s" % (srid, self.wkt) if srid else self.wkt
  332. @property
  333. def wkt(self):
  334. "Return the WKT (Well-Known Text) representation of this Geometry."
  335. return wkt_w(dim=3 if self.hasz else 2, trim=True).write(self).decode()
  336. @property
  337. def hex(self):
  338. """
  339. Return the WKB of this Geometry in hexadecimal form. Please note
  340. that the SRID is not included in this representation because it is not
  341. a part of the OGC specification (use the `hexewkb` property instead).
  342. """
  343. # A possible faster, all-python, implementation:
  344. # str(self.wkb).encode('hex')
  345. return wkb_w(dim=3 if self.hasz else 2).write_hex(self)
  346. @property
  347. def hexewkb(self):
  348. """
  349. Return the EWKB of this Geometry in hexadecimal form. This is an
  350. extension of the WKB specification that includes SRID value that are
  351. a part of this geometry.
  352. """
  353. return ewkb_w(dim=3 if self.hasz else 2).write_hex(self)
  354. @property
  355. def json(self):
  356. """
  357. Return GeoJSON representation of this Geometry.
  358. """
  359. return self.ogr.json
  360. geojson = json
  361. @property
  362. def wkb(self):
  363. """
  364. Return the WKB (Well-Known Binary) representation of this Geometry
  365. as a Python memoryview. SRID and Z values are not included, use the
  366. `ewkb` property instead.
  367. """
  368. return wkb_w(3 if self.hasz else 2).write(self)
  369. @property
  370. def ewkb(self):
  371. """
  372. Return the EWKB representation of this Geometry as a Python memoryview.
  373. This is an extension of the WKB specification that includes any SRID
  374. value that are a part of this geometry.
  375. """
  376. return ewkb_w(3 if self.hasz else 2).write(self)
  377. @property
  378. def kml(self):
  379. "Return the KML representation of this Geometry."
  380. gtype = self.geom_type
  381. return "<%s>%s</%s>" % (gtype, self.coord_seq.kml, gtype)
  382. @property
  383. def prepared(self):
  384. """
  385. Return a PreparedGeometry corresponding to this geometry -- it is
  386. optimized for the contains, intersects, and covers operations.
  387. """
  388. return PreparedGeometry(self)
  389. # #### GDAL-specific output routines ####
  390. def _ogr_ptr(self):
  391. return gdal.OGRGeometry._from_wkb(self.wkb)
  392. @property
  393. def ogr(self):
  394. "Return the OGR Geometry for this Geometry."
  395. return gdal.OGRGeometry(self._ogr_ptr(), self.srs)
  396. @property
  397. def srs(self):
  398. "Return the OSR SpatialReference for SRID of this Geometry."
  399. if self.srid:
  400. try:
  401. return gdal.SpatialReference(self.srid)
  402. except (gdal.GDALException, gdal.SRSException):
  403. pass
  404. return None
  405. @property
  406. def crs(self):
  407. "Alias for `srs` property."
  408. return self.srs
  409. def transform(self, ct, clone=False):
  410. """
  411. Requires GDAL. Transform the geometry according to the given
  412. transformation object, which may be an integer SRID, and WKT or
  413. PROJ string. By default, transform the geometry in-place and return
  414. nothing. However if the `clone` keyword is set, don't modify the
  415. geometry and return a transformed clone instead.
  416. """
  417. srid = self.srid
  418. if ct == srid:
  419. # short-circuit where source & dest SRIDs match
  420. if clone:
  421. return self.clone()
  422. else:
  423. return
  424. if isinstance(ct, gdal.CoordTransform):
  425. # We don't care about SRID because CoordTransform presupposes
  426. # source SRS.
  427. srid = None
  428. elif srid is None or srid < 0:
  429. raise GEOSException("Calling transform() with no SRID set is not supported")
  430. # Creating an OGR Geometry, which is then transformed.
  431. g = gdal.OGRGeometry(self._ogr_ptr(), srid)
  432. g.transform(ct)
  433. # Getting a new GEOS pointer
  434. ptr = g._geos_ptr()
  435. if clone:
  436. # User wants a cloned transformed geometry returned.
  437. return GEOSGeometry(ptr, srid=g.srid)
  438. if ptr:
  439. # Reassigning pointer, and performing post-initialization setup
  440. # again due to the reassignment.
  441. capi.destroy_geom(self.ptr)
  442. self.ptr = ptr
  443. self._post_init()
  444. self.srid = g.srid
  445. else:
  446. raise GEOSException("Transformed WKB was invalid.")
  447. # #### Topology Routines ####
  448. def _topology(self, gptr):
  449. "Return Geometry from the given pointer."
  450. return GEOSGeometry(gptr, srid=self.srid)
  451. @property
  452. def boundary(self):
  453. "Return the boundary as a newly allocated Geometry object."
  454. return self._topology(capi.geos_boundary(self.ptr))
  455. def buffer(self, width, quadsegs=8):
  456. """
  457. Return a geometry that represents all points whose distance from this
  458. Geometry is less than or equal to distance. Calculations are in the
  459. Spatial Reference System of this Geometry. The optional third parameter sets
  460. the number of segment used to approximate a quarter circle (defaults to 8).
  461. (Text from PostGIS documentation at ch. 6.1.3)
  462. """
  463. return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
  464. def buffer_with_style(
  465. self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0
  466. ):
  467. """
  468. Same as buffer() but allows customizing the style of the memoryview.
  469. End cap style can be round (1), flat (2), or square (3).
  470. Join style can be round (1), mitre (2), or bevel (3).
  471. Mitre ratio limit only affects mitered join style.
  472. """
  473. return self._topology(
  474. capi.geos_bufferwithstyle(
  475. self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit
  476. ),
  477. )
  478. @property
  479. def centroid(self):
  480. """
  481. The centroid is equal to the centroid of the set of component Geometries
  482. of highest dimension (since the lower-dimension geometries contribute zero
  483. "weight" to the centroid).
  484. """
  485. return self._topology(capi.geos_centroid(self.ptr))
  486. @property
  487. def convex_hull(self):
  488. """
  489. Return the smallest convex Polygon that contains all the points
  490. in the Geometry.
  491. """
  492. return self._topology(capi.geos_convexhull(self.ptr))
  493. def difference(self, other):
  494. """
  495. Return a Geometry representing the points making up this Geometry
  496. that do not make up other.
  497. """
  498. return self._topology(capi.geos_difference(self.ptr, other.ptr))
  499. @property
  500. def envelope(self):
  501. "Return the envelope for this geometry (a polygon)."
  502. return self._topology(capi.geos_envelope(self.ptr))
  503. def intersection(self, other):
  504. "Return a Geometry representing the points shared by this Geometry and other."
  505. return self._topology(capi.geos_intersection(self.ptr, other.ptr))
  506. @property
  507. def point_on_surface(self):
  508. "Compute an interior point of this Geometry."
  509. return self._topology(capi.geos_pointonsurface(self.ptr))
  510. def relate(self, other):
  511. "Return the DE-9IM intersection matrix for this Geometry and the other."
  512. return capi.geos_relate(self.ptr, other.ptr).decode()
  513. def simplify(self, tolerance=0.0, preserve_topology=False):
  514. """
  515. Return the Geometry, simplified using the Douglas-Peucker algorithm
  516. to the specified tolerance (higher tolerance => less points). If no
  517. tolerance provided, defaults to 0.
  518. By default, don't preserve topology - e.g. polygons can be split,
  519. collapse to lines or disappear holes can be created or disappear, and
  520. lines can cross. By specifying preserve_topology=True, the result will
  521. have the same dimension and number of components as the input. This is
  522. significantly slower.
  523. """
  524. if preserve_topology:
  525. return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
  526. else:
  527. return self._topology(capi.geos_simplify(self.ptr, tolerance))
  528. def sym_difference(self, other):
  529. """
  530. Return a set combining the points in this Geometry not in other,
  531. and the points in other not in this Geometry.
  532. """
  533. return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
  534. @property
  535. def unary_union(self):
  536. "Return the union of all the elements of this geometry."
  537. return self._topology(capi.geos_unary_union(self.ptr))
  538. def union(self, other):
  539. "Return a Geometry representing all the points in this Geometry and other."
  540. return self._topology(capi.geos_union(self.ptr, other.ptr))
  541. # #### Other Routines ####
  542. @property
  543. def area(self):
  544. "Return the area of the Geometry."
  545. return capi.geos_area(self.ptr, byref(c_double()))
  546. def distance(self, other):
  547. """
  548. Return the distance between the closest points on this Geometry
  549. and the other. Units will be in those of the coordinate system of
  550. the Geometry.
  551. """
  552. if not isinstance(other, GEOSGeometry):
  553. raise TypeError("distance() works only on other GEOS Geometries.")
  554. return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
  555. @property
  556. def extent(self):
  557. """
  558. Return the extent of this geometry as a 4-tuple, consisting of
  559. (xmin, ymin, xmax, ymax).
  560. """
  561. from .point import Point
  562. env = self.envelope
  563. if isinstance(env, Point):
  564. xmin, ymin = env.tuple
  565. xmax, ymax = xmin, ymin
  566. else:
  567. xmin, ymin = env[0][0]
  568. xmax, ymax = env[0][2]
  569. return (xmin, ymin, xmax, ymax)
  570. @property
  571. def length(self):
  572. """
  573. Return the length of this Geometry (e.g., 0 for point, or the
  574. circumference of a Polygon).
  575. """
  576. return capi.geos_length(self.ptr, byref(c_double()))
  577. def clone(self):
  578. "Clone this Geometry."
  579. return GEOSGeometry(capi.geom_clone(self.ptr))
  580. class LinearGeometryMixin:
  581. """
  582. Used for LineString and MultiLineString.
  583. """
  584. def interpolate(self, distance):
  585. return self._topology(capi.geos_interpolate(self.ptr, distance))
  586. def interpolate_normalized(self, distance):
  587. return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
  588. def project(self, point):
  589. from .point import Point
  590. if not isinstance(point, Point):
  591. raise TypeError("locate_point argument must be a Point")
  592. return capi.geos_project(self.ptr, point.ptr)
  593. def project_normalized(self, point):
  594. from .point import Point
  595. if not isinstance(point, Point):
  596. raise TypeError("locate_point argument must be a Point")
  597. return capi.geos_project_normalized(self.ptr, point.ptr)
  598. @property
  599. def merged(self):
  600. """
  601. Return the line merge of this Geometry.
  602. """
  603. return self._topology(capi.geos_linemerge(self.ptr))
  604. @property
  605. def closed(self):
  606. """
  607. Return whether or not this Geometry is closed.
  608. """
  609. return capi.geos_isclosed(self.ptr)
  610. @deconstructible
  611. class GEOSGeometry(GEOSGeometryBase, ListMixin):
  612. "A class that, generally, encapsulates a GEOS geometry."
  613. def __init__(self, geo_input, srid=None):
  614. """
  615. The base constructor for GEOS geometry objects. It may take the
  616. following inputs:
  617. * strings:
  618. - WKT
  619. - HEXEWKB (a PostGIS-specific canonical form)
  620. - GeoJSON (requires GDAL)
  621. * memoryview:
  622. - WKB
  623. The `srid` keyword specifies the Source Reference Identifier (SRID)
  624. number for this Geometry. If not provided, it defaults to None.
  625. """
  626. input_srid = None
  627. if isinstance(geo_input, bytes):
  628. geo_input = force_str(geo_input)
  629. if isinstance(geo_input, str):
  630. wkt_m = wkt_regex.match(geo_input)
  631. if wkt_m:
  632. # Handle WKT input.
  633. if wkt_m["srid"]:
  634. input_srid = int(wkt_m["srid"])
  635. g = self._from_wkt(force_bytes(wkt_m["wkt"]))
  636. elif hex_regex.match(geo_input):
  637. # Handle HEXEWKB input.
  638. g = wkb_r().read(force_bytes(geo_input))
  639. elif json_regex.match(geo_input):
  640. # Handle GeoJSON input.
  641. ogr = gdal.OGRGeometry.from_json(geo_input)
  642. g = ogr._geos_ptr()
  643. input_srid = ogr.srid
  644. else:
  645. raise ValueError("String input unrecognized as WKT EWKT, and HEXEWKB.")
  646. elif isinstance(geo_input, GEOM_PTR):
  647. # When the input is a pointer to a geometry (GEOM_PTR).
  648. g = geo_input
  649. elif isinstance(geo_input, memoryview):
  650. # When the input is a memoryview (WKB).
  651. g = wkb_r().read(geo_input)
  652. elif isinstance(geo_input, GEOSGeometry):
  653. g = capi.geom_clone(geo_input.ptr)
  654. else:
  655. raise TypeError("Improper geometry input type: %s" % type(geo_input))
  656. if not g:
  657. raise GEOSException("Could not initialize GEOS Geometry with given input.")
  658. input_srid = input_srid or capi.geos_get_srid(g) or None
  659. if input_srid and srid and input_srid != srid:
  660. raise ValueError("Input geometry already has SRID: %d." % input_srid)
  661. super().__init__(g, None)
  662. # Set the SRID, if given.
  663. srid = input_srid or srid
  664. if srid and isinstance(srid, int):
  665. self.srid = srid