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