geometry.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. """
  2. This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
  3. inherit from this object.
  4. """
  5. from __future__ import unicode_literals
  6. # Python, ctypes and types dependencies.
  7. from ctypes import addressof, byref, c_double
  8. from django.contrib.gis import memoryview
  9. # super-class for mutable list behavior
  10. from django.contrib.gis.geos.mutable_list import ListMixin
  11. from django.contrib.gis.gdal.error import SRSException
  12. # GEOS-related dependencies.
  13. from django.contrib.gis.geos.base import GEOSBase, gdal
  14. from django.contrib.gis.geos.coordseq import GEOSCoordSeq
  15. from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
  16. from django.contrib.gis.geos.libgeos import GEOM_PTR
  17. # All other functions in this module come from the ctypes
  18. # prototypes module -- which handles all interaction with
  19. # the underlying GEOS library.
  20. from django.contrib.gis.geos import prototypes as capi
  21. # These functions provide access to a thread-local instance
  22. # of their corresponding GEOS I/O class.
  23. from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w
  24. # For recognizing geometry input.
  25. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex
  26. from django.utils import six
  27. from django.utils.encoding import force_bytes, force_text
  28. class GEOSGeometry(GEOSBase, ListMixin):
  29. "A class that, generally, encapsulates a GEOS geometry."
  30. # Raise GEOSIndexError instead of plain IndexError
  31. # (see ticket #4740 and GEOSIndexError docstring)
  32. _IndexError = GEOSIndexError
  33. ptr_type = GEOM_PTR
  34. #### Python 'magic' routines ####
  35. def __init__(self, geo_input, srid=None):
  36. """
  37. The base constructor for GEOS geometry objects, and may take the
  38. following inputs:
  39. * strings:
  40. - WKT
  41. - HEXEWKB (a PostGIS-specific canonical form)
  42. - GeoJSON (requires GDAL)
  43. * buffer:
  44. - WKB
  45. The `srid` keyword is used to specify the Source Reference Identifier
  46. (SRID) number for this Geometry. If not set, the SRID will be None.
  47. """
  48. if isinstance(geo_input, bytes):
  49. geo_input = force_text(geo_input)
  50. if isinstance(geo_input, six.string_types):
  51. wkt_m = wkt_regex.match(geo_input)
  52. if wkt_m:
  53. # Handling WKT input.
  54. if wkt_m.group('srid'):
  55. srid = int(wkt_m.group('srid'))
  56. g = wkt_r().read(force_bytes(wkt_m.group('wkt')))
  57. elif hex_regex.match(geo_input):
  58. # Handling HEXEWKB input.
  59. g = wkb_r().read(force_bytes(geo_input))
  60. elif gdal.HAS_GDAL and json_regex.match(geo_input):
  61. # Handling GeoJSON input.
  62. g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb)
  63. else:
  64. raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
  65. elif isinstance(geo_input, GEOM_PTR):
  66. # When the input is a pointer to a geometry (GEOM_PTR).
  67. g = geo_input
  68. elif isinstance(geo_input, memoryview):
  69. # When the input is a buffer (WKB).
  70. g = wkb_r().read(geo_input)
  71. elif isinstance(geo_input, GEOSGeometry):
  72. g = capi.geom_clone(geo_input.ptr)
  73. else:
  74. # Invalid geometry type.
  75. raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))
  76. if g:
  77. # Setting the pointer object with a valid pointer.
  78. self.ptr = g
  79. else:
  80. raise GEOSException('Could not initialize GEOS Geometry with given input.')
  81. # Post-initialization setup.
  82. self._post_init(srid)
  83. def _post_init(self, srid):
  84. "Helper routine for performing post-initialization setup."
  85. # Setting the SRID, if given.
  86. if srid and isinstance(srid, int):
  87. self.srid = srid
  88. # Setting the class type (e.g., Point, Polygon, etc.)
  89. self.__class__ = GEOS_CLASSES[self.geom_typeid]
  90. # Setting the coordinate sequence for the geometry (will be None on
  91. # geometries that do not have coordinate sequences)
  92. self._set_cs()
  93. def __del__(self):
  94. """
  95. Destroys this Geometry; in other words, frees the memory used by the
  96. GEOS C++ object.
  97. """
  98. if self._ptr:
  99. capi.destroy_geom(self._ptr)
  100. def __copy__(self):
  101. """
  102. Returns a clone because the copy of a GEOSGeometry may contain an
  103. invalid pointer location if the original is garbage collected.
  104. """
  105. return self.clone()
  106. def __deepcopy__(self, memodict):
  107. """
  108. The `deepcopy` routine is used by the `Node` class of django.utils.tree;
  109. thus, the protocol routine needs to be implemented to return correct
  110. copies (clones) of these GEOS objects, which use C pointers.
  111. """
  112. return self.clone()
  113. def __str__(self):
  114. "WKT is used for the string representation."
  115. return self.wkt
  116. def __repr__(self):
  117. "Short-hand representation because WKT may be very large."
  118. return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
  119. # Pickling support
  120. def __getstate__(self):
  121. # The pickled state is simply a tuple of the WKB (in string form)
  122. # and the SRID.
  123. return bytes(self.wkb), self.srid
  124. def __setstate__(self, state):
  125. # Instantiating from the tuple state that was pickled.
  126. wkb, srid = state
  127. ptr = wkb_r().read(memoryview(wkb))
  128. if not ptr:
  129. raise GEOSException('Invalid Geometry loaded from pickled state.')
  130. self.ptr = ptr
  131. self._post_init(srid)
  132. # Comparison operators
  133. def __eq__(self, other):
  134. """
  135. Equivalence testing, a Geometry may be compared with another Geometry
  136. or a WKT representation.
  137. """
  138. if isinstance(other, six.string_types):
  139. return self.wkt == other
  140. elif isinstance(other, GEOSGeometry):
  141. return self.equals_exact(other)
  142. else:
  143. return False
  144. def __ne__(self, other):
  145. "The not equals operator."
  146. return not (self == other)
  147. ### Geometry set-like operations ###
  148. # Thanks to Sean Gillies for inspiration:
  149. # http://lists.gispython.org/pipermail/community/2007-July/001034.html
  150. # g = g1 | g2
  151. def __or__(self, other):
  152. "Returns the union of this Geometry and the other."
  153. return self.union(other)
  154. # g = g1 & g2
  155. def __and__(self, other):
  156. "Returns the intersection of this Geometry and the other."
  157. return self.intersection(other)
  158. # g = g1 - g2
  159. def __sub__(self, other):
  160. "Return the difference this Geometry and the other."
  161. return self.difference(other)
  162. # g = g1 ^ g2
  163. def __xor__(self, other):
  164. "Return the symmetric difference of this Geometry and the other."
  165. return self.sym_difference(other)
  166. #### Coordinate Sequence Routines ####
  167. @property
  168. def has_cs(self):
  169. "Returns True if this Geometry has a coordinate sequence, False if not."
  170. # Only these geometries are allowed to have coordinate sequences.
  171. if isinstance(self, (Point, LineString, LinearRing)):
  172. return True
  173. else:
  174. return False
  175. def _set_cs(self):
  176. "Sets the coordinate sequence for this Geometry."
  177. if self.has_cs:
  178. self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
  179. else:
  180. self._cs = None
  181. @property
  182. def coord_seq(self):
  183. "Returns a clone of the coordinate sequence for this Geometry."
  184. if self.has_cs:
  185. return self._cs.clone()
  186. #### Geometry Info ####
  187. @property
  188. def geom_type(self):
  189. "Returns a string representing the Geometry type, e.g. 'Polygon'"
  190. return capi.geos_type(self.ptr).decode()
  191. @property
  192. def geom_typeid(self):
  193. "Returns an integer representing the Geometry type."
  194. return capi.geos_typeid(self.ptr)
  195. @property
  196. def num_geom(self):
  197. "Returns the number of geometries in the Geometry."
  198. return capi.get_num_geoms(self.ptr)
  199. @property
  200. def num_coords(self):
  201. "Returns the number of coordinates in the Geometry."
  202. return capi.get_num_coords(self.ptr)
  203. @property
  204. def num_points(self):
  205. "Returns the number points, or coordinates, in the Geometry."
  206. return self.num_coords
  207. @property
  208. def dims(self):
  209. "Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
  210. return capi.get_dims(self.ptr)
  211. def normalize(self):
  212. "Converts this Geometry to normal form (or canonical form)."
  213. return capi.geos_normalize(self.ptr)
  214. #### Unary predicates ####
  215. @property
  216. def empty(self):
  217. """
  218. Returns a boolean indicating whether the set of points in this Geometry
  219. are empty.
  220. """
  221. return capi.geos_isempty(self.ptr)
  222. @property
  223. def hasz(self):
  224. "Returns whether the geometry has a 3D dimension."
  225. return capi.geos_hasz(self.ptr)
  226. @property
  227. def ring(self):
  228. "Returns whether or not the geometry is a ring."
  229. return capi.geos_isring(self.ptr)
  230. @property
  231. def simple(self):
  232. "Returns false if the Geometry not simple."
  233. return capi.geos_issimple(self.ptr)
  234. @property
  235. def valid(self):
  236. "This property tests the validity of this Geometry."
  237. return capi.geos_isvalid(self.ptr)
  238. @property
  239. def valid_reason(self):
  240. """
  241. Returns a string containing the reason for any invalidity.
  242. """
  243. return capi.geos_isvalidreason(self.ptr).decode()
  244. #### Binary predicates. ####
  245. def contains(self, other):
  246. "Returns true if other.within(this) returns true."
  247. return capi.geos_contains(self.ptr, other.ptr)
  248. def crosses(self, other):
  249. """
  250. Returns 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. Returns 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. Returns 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. Returns 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 intersects(self, other):
  274. "Returns true if disjoint returns false."
  275. return capi.geos_intersects(self.ptr, other.ptr)
  276. def overlaps(self, other):
  277. """
  278. Returns true if the DE-9IM intersection matrix for the two Geometries
  279. is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
  280. """
  281. return capi.geos_overlaps(self.ptr, other.ptr)
  282. def relate_pattern(self, other, pattern):
  283. """
  284. Returns true if the elements in the DE-9IM intersection matrix for the
  285. two Geometries match the elements in pattern.
  286. """
  287. if not isinstance(pattern, six.string_types) or len(pattern) > 9:
  288. raise GEOSException('invalid intersection matrix pattern')
  289. return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern))
  290. def touches(self, other):
  291. """
  292. Returns true if the DE-9IM intersection matrix for the two Geometries
  293. is FT*******, F**T***** or F***T****.
  294. """
  295. return capi.geos_touches(self.ptr, other.ptr)
  296. def within(self, other):
  297. """
  298. Returns true if the DE-9IM intersection matrix for the two Geometries
  299. is T*F**F***.
  300. """
  301. return capi.geos_within(self.ptr, other.ptr)
  302. #### SRID Routines ####
  303. def get_srid(self):
  304. "Gets the SRID for the geometry, returns None if no SRID is set."
  305. s = capi.geos_get_srid(self.ptr)
  306. if s == 0:
  307. return None
  308. else:
  309. return s
  310. def set_srid(self, srid):
  311. "Sets the SRID for the geometry."
  312. capi.geos_set_srid(self.ptr, srid)
  313. srid = property(get_srid, set_srid)
  314. #### Output Routines ####
  315. @property
  316. def ewkt(self):
  317. """
  318. Returns the EWKT (WKT + SRID) of the Geometry. Note that Z values
  319. are *not* included in this representation because GEOS does not yet
  320. support serializing them.
  321. """
  322. if self.get_srid():
  323. return 'SRID=%s;%s' % (self.srid, self.wkt)
  324. else:
  325. return self.wkt
  326. @property
  327. def wkt(self):
  328. "Returns the WKT (Well-Known Text) representation of this Geometry."
  329. return wkt_w(3 if self.hasz else 2).write(self).decode()
  330. @property
  331. def hex(self):
  332. """
  333. Returns the WKB of this Geometry in hexadecimal form. Please note
  334. that the SRID is not included in this representation because it is not
  335. a part of the OGC specification (use the `hexewkb` property instead).
  336. """
  337. # A possible faster, all-python, implementation:
  338. # str(self.wkb).encode('hex')
  339. return wkb_w(3 if self.hasz else 2).write_hex(self)
  340. @property
  341. def hexewkb(self):
  342. """
  343. Returns the EWKB of this Geometry in hexadecimal form. This is an
  344. extension of the WKB specification that includes SRID value that are
  345. a part of this geometry.
  346. """
  347. return ewkb_w(3 if self.hasz else 2).write_hex(self)
  348. @property
  349. def json(self):
  350. """
  351. Returns GeoJSON representation of this Geometry if GDAL is installed.
  352. """
  353. if gdal.HAS_GDAL:
  354. return self.ogr.json
  355. else:
  356. raise GEOSException('GeoJSON output only supported when GDAL is installed.')
  357. geojson = json
  358. @property
  359. def wkb(self):
  360. """
  361. Returns the WKB (Well-Known Binary) representation of this Geometry
  362. as a Python buffer. SRID and Z values are not included, use the
  363. `ewkb` property instead.
  364. """
  365. return wkb_w(3 if self.hasz else 2).write(self)
  366. @property
  367. def ewkb(self):
  368. """
  369. Return the EWKB representation of this Geometry as a Python buffer.
  370. This is an extension of the WKB specification that includes any SRID
  371. value that are a part of this geometry.
  372. """
  373. return ewkb_w(3 if self.hasz else 2).write(self)
  374. @property
  375. def kml(self):
  376. "Returns the KML representation of this Geometry."
  377. gtype = self.geom_type
  378. return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
  379. @property
  380. def prepared(self):
  381. """
  382. Returns a PreparedGeometry corresponding to this geometry -- it is
  383. optimized for the contains, intersects, and covers operations.
  384. """
  385. return PreparedGeometry(self)
  386. #### GDAL-specific output routines ####
  387. @property
  388. def ogr(self):
  389. "Returns the OGR Geometry for this Geometry."
  390. if not gdal.HAS_GDAL:
  391. raise GEOSException('GDAL required to convert to an OGRGeometry.')
  392. if self.srid:
  393. try:
  394. return gdal.OGRGeometry(self.wkb, self.srid)
  395. except SRSException:
  396. pass
  397. return gdal.OGRGeometry(self.wkb)
  398. @property
  399. def srs(self):
  400. "Returns the OSR SpatialReference for SRID of this Geometry."
  401. if not gdal.HAS_GDAL:
  402. raise GEOSException('GDAL required to return a SpatialReference object.')
  403. if self.srid:
  404. try:
  405. return gdal.SpatialReference(self.srid)
  406. except SRSException:
  407. pass
  408. return None
  409. @property
  410. def crs(self):
  411. "Alias for `srs` property."
  412. return self.srs
  413. def transform(self, ct, clone=False):
  414. """
  415. Requires GDAL. Transforms the geometry according to the given
  416. transformation object, which may be an integer SRID, and WKT or
  417. PROJ.4 string. By default, the geometry is transformed in-place and
  418. nothing is returned. However if the `clone` keyword is set, then this
  419. geometry will not be modified and a transformed clone will be returned
  420. instead.
  421. """
  422. srid = self.srid
  423. if ct == srid:
  424. # short-circuit where source & dest SRIDs match
  425. if clone:
  426. return self.clone()
  427. else:
  428. return
  429. if (srid is None) or (srid < 0):
  430. raise GEOSException("Calling transform() with no SRID set is not supported")
  431. if not gdal.HAS_GDAL:
  432. raise GEOSException("GDAL library is not available to transform() geometry.")
  433. # Creating an OGR Geometry, which is then transformed.
  434. g = self.ogr
  435. g.transform(ct)
  436. # Getting a new GEOS pointer
  437. ptr = wkb_r().read(g.wkb)
  438. if clone:
  439. # User wants a cloned transformed geometry returned.
  440. return GEOSGeometry(ptr, srid=g.srid)
  441. if ptr:
  442. # Reassigning pointer, and performing post-initialization setup
  443. # again due to the reassignment.
  444. capi.destroy_geom(self.ptr)
  445. self.ptr = ptr
  446. self._post_init(g.srid)
  447. else:
  448. raise GEOSException('Transformed WKB was invalid.')
  449. #### Topology Routines ####
  450. def _topology(self, gptr):
  451. "Helper routine to return Geometry from the given pointer."
  452. return GEOSGeometry(gptr, srid=self.srid)
  453. @property
  454. def boundary(self):
  455. "Returns the boundary as a newly allocated Geometry object."
  456. return self._topology(capi.geos_boundary(self.ptr))
  457. def buffer(self, width, quadsegs=8):
  458. """
  459. Returns a geometry that represents all points whose distance from this
  460. Geometry is less than or equal to distance. Calculations are in the
  461. Spatial Reference System of this Geometry. The optional third parameter sets
  462. the number of segment used to approximate a quarter circle (defaults to 8).
  463. (Text from PostGIS documentation at ch. 6.1.3)
  464. """
  465. return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
  466. @property
  467. def centroid(self):
  468. """
  469. The centroid is equal to the centroid of the set of component Geometries
  470. of highest dimension (since the lower-dimension geometries contribute zero
  471. "weight" to the centroid).
  472. """
  473. return self._topology(capi.geos_centroid(self.ptr))
  474. @property
  475. def convex_hull(self):
  476. """
  477. Returns the smallest convex Polygon that contains all the points
  478. in the Geometry.
  479. """
  480. return self._topology(capi.geos_convexhull(self.ptr))
  481. def difference(self, other):
  482. """
  483. Returns a Geometry representing the points making up this Geometry
  484. that do not make up other.
  485. """
  486. return self._topology(capi.geos_difference(self.ptr, other.ptr))
  487. @property
  488. def envelope(self):
  489. "Return the envelope for this geometry (a polygon)."
  490. return self._topology(capi.geos_envelope(self.ptr))
  491. def interpolate(self, distance):
  492. if not isinstance(self, (LineString, MultiLineString)):
  493. raise TypeError('interpolate only works on LineString and MultiLineString geometries')
  494. if not hasattr(capi, 'geos_interpolate'):
  495. raise NotImplementedError('interpolate requires GEOS 3.2+')
  496. return self._topology(capi.geos_interpolate(self.ptr, distance))
  497. def interpolate_normalized(self, distance):
  498. if not isinstance(self, (LineString, MultiLineString)):
  499. raise TypeError('interpolate only works on LineString and MultiLineString geometries')
  500. if not hasattr(capi, 'geos_interpolate_normalized'):
  501. raise NotImplementedError('interpolate_normalized requires GEOS 3.2+')
  502. return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
  503. def intersection(self, other):
  504. "Returns 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. "Computes an interior point of this Geometry."
  509. return self._topology(capi.geos_pointonsurface(self.ptr))
  510. def project(self, point):
  511. if not isinstance(point, Point):
  512. raise TypeError('locate_point argument must be a Point')
  513. if not isinstance(self, (LineString, MultiLineString)):
  514. raise TypeError('locate_point only works on LineString and MultiLineString geometries')
  515. if not hasattr(capi, 'geos_project'):
  516. raise NotImplementedError('geos_project requires GEOS 3.2+')
  517. return capi.geos_project(self.ptr, point.ptr)
  518. def project_normalized(self, point):
  519. if not isinstance(point, Point):
  520. raise TypeError('locate_point argument must be a Point')
  521. if not isinstance(self, (LineString, MultiLineString)):
  522. raise TypeError('locate_point only works on LineString and MultiLineString geometries')
  523. if not hasattr(capi, 'geos_project_normalized'):
  524. raise NotImplementedError('project_normalized requires GEOS 3.2+')
  525. return capi.geos_project_normalized(self.ptr, point.ptr)
  526. def relate(self, other):
  527. "Returns the DE-9IM intersection matrix for this Geometry and the other."
  528. return capi.geos_relate(self.ptr, other.ptr).decode()
  529. def simplify(self, tolerance=0.0, preserve_topology=False):
  530. """
  531. Returns the Geometry, simplified using the Douglas-Peucker algorithm
  532. to the specified tolerance (higher tolerance => less points). If no
  533. tolerance provided, defaults to 0.
  534. By default, this function does not preserve topology - e.g. polygons can
  535. be split, collapse to lines or disappear holes can be created or
  536. disappear, and lines can cross. By specifying preserve_topology=True,
  537. the result will have the same dimension and number of components as the
  538. input. This is significantly slower.
  539. """
  540. if preserve_topology:
  541. return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
  542. else:
  543. return self._topology(capi.geos_simplify(self.ptr, tolerance))
  544. def sym_difference(self, other):
  545. """
  546. Returns a set combining the points in this Geometry not in other,
  547. and the points in other not in this Geometry.
  548. """
  549. return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
  550. def union(self, other):
  551. "Returns a Geometry representing all the points in this Geometry and other."
  552. return self._topology(capi.geos_union(self.ptr, other.ptr))
  553. #### Other Routines ####
  554. @property
  555. def area(self):
  556. "Returns the area of the Geometry."
  557. return capi.geos_area(self.ptr, byref(c_double()))
  558. def distance(self, other):
  559. """
  560. Returns the distance between the closest points on this Geometry
  561. and the other. Units will be in those of the coordinate system of
  562. the Geometry.
  563. """
  564. if not isinstance(other, GEOSGeometry):
  565. raise TypeError('distance() works only on other GEOS Geometries.')
  566. return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
  567. @property
  568. def extent(self):
  569. """
  570. Returns the extent of this geometry as a 4-tuple, consisting of
  571. (xmin, ymin, xmax, ymax).
  572. """
  573. env = self.envelope
  574. if isinstance(env, Point):
  575. xmin, ymin = env.tuple
  576. xmax, ymax = xmin, ymin
  577. else:
  578. xmin, ymin = env[0][0]
  579. xmax, ymax = env[0][2]
  580. return (xmin, ymin, xmax, ymax)
  581. @property
  582. def length(self):
  583. """
  584. Returns the length of this Geometry (e.g., 0 for point, or the
  585. circumference of a Polygon).
  586. """
  587. return capi.geos_length(self.ptr, byref(c_double()))
  588. def clone(self):
  589. "Clones this Geometry."
  590. return GEOSGeometry(capi.geom_clone(self.ptr), srid=self.srid)
  591. # Class mapping dictionary. Has to be at the end to avoid import
  592. # conflicts with GEOSGeometry.
  593. from django.contrib.gis.geos.linestring import LineString, LinearRing
  594. from django.contrib.gis.geos.point import Point
  595. from django.contrib.gis.geos.polygon import Polygon
  596. from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
  597. from django.contrib.gis.geos.prepared import PreparedGeometry
  598. GEOS_CLASSES = {
  599. 0: Point,
  600. 1: LineString,
  601. 2: LinearRing,
  602. 3: Polygon,
  603. 4: MultiPoint,
  604. 5: MultiLineString,
  605. 6: MultiPolygon,
  606. 7: GeometryCollection,
  607. }