Django

Code

Changeset 6653

Show
Ignore:
Timestamp:
11/05/07 18:39:36 (8 months ago)
Author:
jbronn
Message:

gis: geos: Re-factored GEOS interface. Improvements include:

(1) All interactions with the GEOS library take place through

pre-defined ctypes prototypes, abstracting away return-value
error-checking.

(2) Mutability is now safe. Because GEOS geometry pointers are

marked as const (Thanks Hobu), previous modification techniques
caused unpredictable instability when geometries were constructed
from the pointers of other geometries (e.g., a Polygon with rings
from another Polygon). Geometry components are cloned first before
creation of the modified geometry.

(3) Fixed memory leaks by freeing pointers from strings allocated

in GEOS because ctypes only frees pointers allocated in Python.

Backwards-Incompatibility Notice:

All children geometries (e.g., rings from a Polygon, geometries from a
collection) are now clones -- modifications will not propagate up to
the parent geometry as before.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/geos/base.py

    r6596 r6653  
    11""" 
    2   This module contains the 'base' GEOSGeometry object -- all GEOS geometries 
    3    inherit from this object. 
     2 This module contains the 'base' GEOSGeometry object -- all GEOS Geometries 
     3 inherit from this object. 
    44""" 
    5 # ctypes and types dependencies. 
    6 from ctypes import \ 
    7      byref, string_at, create_string_buffer, pointer, \ 
    8      c_char_p, c_double, c_int, c_size_t 
     5# Python, ctypes and types dependencies. 
     6import re 
     7from ctypes import addressof, byref, c_double, c_size_t 
    98from types import StringType, UnicodeType, IntType, FloatType, BufferType 
    109 
    11 # Python and GEOS-related dependencies. 
    12 import re 
    13 from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs 
     10# GEOS-related dependencies. 
     11from django.contrib.gis.geos.coordseq import GEOSCoordSeq 
    1412from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
    15 from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY 
    16 from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM 
     13from django.contrib.gis.geos.libgeos import GEOM_PTR 
     14 
     15# All other functions in this module come from the ctypes  
     16# prototypes module -- which handles all interaction with 
     17# the underlying GEOS library. 
     18from django.contrib.gis.geos.prototypes import *  
    1719 
    1820# Trying to import GDAL libraries, if available.  Have to place in 
     
    2527 
    2628# Regular expression for recognizing HEXEWKB and WKT.  A prophylactic measure 
    27 # to prevent potentially malicious input from reaching the underlying C 
    28 # library.  Not a substitute for good web security programming practices. 
     29# to prevent potentially malicious input from reaching the underlying C 
     30# library.  Not a substitute for good web security programming practices. 
    2931hex_regex = re.compile(r'^[0-9A-F]+$', re.I) 
    3032wkt_regex = re.compile(r'^(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+$', re.I) 
     
    3234class GEOSGeometry(object): 
    3335    "A class that, generally, encapsulates a GEOS geometry." 
    34      
    35     # Initially, all geometries use a NULL pointer. 
    36     _ptr = NULL_GEOM 
    37      
     36 
     37    # Initially, the geometry pointer is NULL 
     38    _ptr = None 
     39 
    3840    #### Python 'magic' routines #### 
    3941    def __init__(self, geo_input, srid=None): 
    4042        """ 
    4143        The base constructor for GEOS geometry objects, and may take the  
    42         following inputs: 
     44        following inputs: 
    4345          
    4446         * string: WKT 
     
    4749         
    4850        The `srid` keyword is used to specify the Source Reference Identifier 
    49         (SRID) number for this Geometry.  If not set, the SRID will be None. 
     51        (SRID) number for this Geometry.  If not set, the SRID will be None. 
    5052        """  
    5153        if isinstance(geo_input, UnicodeType): 
     
    5557            if hex_regex.match(geo_input): 
    5658                # If the regex matches, the geometry is in HEX form. 
    57                 sz = c_size_t(len(geo_input)) 
    58                 buf = create_string_buffer(geo_input) 
    59                 g = lgeos.GEOSGeomFromHEX_buf(buf, sz) 
     59                g = from_hex(geo_input, len(geo_input)) 
    6060            elif wkt_regex.match(geo_input): 
    6161                # Otherwise, the geometry is in WKT form. 
    62                 g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input)
     62                g = from_wkt(geo_input
    6363            else: 
    64                 raise GEOSException('String or unicode input unrecognized as WKT or HEXEWKB.') 
    65         elif isinstance(geo_input, (IntType, GEOSPointer)): 
    66             # When the input is either a memory address (an integer), or a  
    67             #  GEOSPointer object. 
     64                raise ValueError('String or unicode input unrecognized as WKT or HEXEWKB.') 
     65        elif isinstance(geo_input, GEOM_PTR): 
     66            # When the input is a pointer to a geomtry (GEOM_PTR). 
    6867            g = geo_input 
    6968        elif isinstance(geo_input, BufferType): 
    7069            # When the input is a buffer (WKB). 
    7170            wkb_input = str(geo_input) 
    72             sz = c_size_t(len(wkb_input)) 
    73             g = lgeos.GEOSGeomFromWKB_buf(c_char_p(wkb_input), sz) 
     71            g = from_wkb(wkb_input, len(wkb_input)) 
    7472        else: 
    7573            # Invalid geometry type. 
    76             raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input)) 
     74            raise TypeError('Improper geometry input type: %s' % str(type(geo_input))) 
    7775 
    7876        if bool(g): 
    7977            # Setting the pointer object with a valid pointer. 
    80             self._ptr = GEOSPointer(g) 
    81         else: 
    82             raise GEOSException, 'Could not initialize GEOS Geometry with given input.' 
     78            self._ptr = g 
     79        else: 
     80            raise GEOSException('Could not initialize GEOS Geometry with given input.') 
    8381 
    8482        # Setting the SRID, if given. 
    8583        if srid and isinstance(srid, int): self.srid = srid 
    8684 
    87         # Setting the class type (e.g., 'Point', 'Polygon', etc.) 
    88         self.__class__ = GEOS_CLASSES[self.geom_type
     85        # Setting the class type (e.g., Point, Polygon, etc.) 
     86        self.__class__ = GEOS_CLASSES[self.geom_typeid
    8987 
    9088        # Setting the coordinate sequence for the geometry (will be None on  
     
    9290        self._set_cs() 
    9391 
    94         # _populate() needs to be called for parent Geometries. 
    95         if isinstance(self, (Polygon, GeometryCollection)): self._populate() 
    96  
    9792    def __del__(self): 
    9893        """ 
    9994        Destroys this Geometry; in other words, frees the memory used by the 
    100          GEOS C++ object -- but only if the pointer is not a child Geometry 
    101          (e.g., don't delete the LinearRings spawned from a Polygon). 
    102         """ 
    103         #print 'base: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid) 
    104         if not self._ptr.child: self._ptr.destroy() 
     95        GEOS C++ object. 
     96        """ 
     97        if self._ptr: destroy_geom(self._ptr) 
    10598 
    10699    def __str__(self): 
     
    109102 
    110103    def __repr__(self): 
    111         return '<%s object>' % self.geom_type 
     104        "Short-hand representation because WKT may be very large." 
     105        return '<%s object at %s>' % (self.geom_type, hex(addressof(self._ptr))) 
    112106 
    113107    # Comparison operators 
     
    166160        return self.sym_difference(other) 
    167161 
    168     #### Internal GEOSPointer-related routines. #### 
    169     def _nullify(self): 
    170         """ 
    171         Returns the address of this Geometry, and nullifies any related pointers. 
    172          This function is called if this Geometry is used in the initialization 
    173          of another Geometry. 
    174         """ 
    175         # First getting the memory address of the geometry. 
    176         address = self._ptr() 
    177  
    178         # If the geometry is a child geometry, then the parent geometry pointer is 
    179         #  nullified. 
    180         if self._ptr.child:  
    181             p = self._ptr.parent 
    182             # If we have a grandchild (a LinearRing from a MultiPolygon or 
    183             #  GeometryCollection), then nullify the collection as well. 
    184             if p.child: p.parent.nullify() 
    185             p.nullify() 
    186              
    187         # Nullifying the geometry pointer 
    188         self._ptr.nullify() 
    189  
    190         return address 
    191  
    192     def _reassign(self, new_geom): 
    193         "Reassigns the internal pointer to that of the new Geometry." 
    194         # Only can re-assign when given a pointer or a geometry. 
    195         if not isinstance(new_geom, (GEOSPointer, GEOSGeometry)): 
    196             raise TypeError, 'cannot reassign geometry on given type: %s' % type(new_geom) 
    197         gtype = new_geom.geom_type  
    198  
    199         # Re-assigning the internal GEOSPointer to the new geometry, nullifying 
    200         #  the new Geometry in the process. 
    201         if isinstance(new_geom, GEOSPointer): self._ptr = new_geom 
    202         else: self._ptr = GEOSPointer(new_geom._nullify()) 
    203          
    204         # The new geometry class may be different from the original, so setting 
    205         #  the __class__ and populating the internal geometry or ring dictionary. 
    206         self.__class__ = GEOS_CLASSES[gtype] 
    207         if isinstance(self, (Polygon, GeometryCollection)): self._populate() 
    208  
    209162    #### Coordinate Sequence Routines #### 
    210163    @property 
     
    220173        "Sets the coordinate sequence for this Geometry." 
    221174        if self.has_cs: 
    222             if not self._ptr.coordseq_valid: 
    223                 self._ptr.set_coordseq(lgeos.GEOSGeom_getCoordSeq(self._ptr())) 
    224             self._cs = GEOSCoordSeq(self._ptr, self.hasz) 
     175            self._cs = GEOSCoordSeq(get_cs(self._ptr), self.hasz) 
    225176        else: 
    226177            self._cs = None 
     
    228179    @property 
    229180    def coord_seq(self): 
    230         "Returns the coordinate sequence for this Geometry." 
    231         return self._cs 
     181        "Returns a clone of the coordinate sequence for this Geometry." 
     182        return self._cs.clone() 
    232183 
    233184    #### Geometry Info #### 
     
    235186    def geom_type(self): 
    236187        "Returns a string representing the Geometry type, e.g. 'Polygon'" 
    237         return string_at(lgeos.GEOSGeomType(self._ptr())
     188        return geos_type(self._ptr
    238189 
    239190    @property 
    240191    def geom_typeid(self): 
    241192        "Returns an integer representing the Geometry type." 
    242         return lgeos.GEOSGeomTypeId(self._ptr()
     193        return geos_typeid(self._ptr
    243194 
    244195    @property 
    245196    def num_geom(self): 
    246197        "Returns the number of geometries in the Geometry." 
    247         n = lgeos.GEOSGetNumGeometries(self._ptr()) 
    248         if n == -1: raise GEOSException, 'Error getting number of geometries.' 
    249         else: return n 
     198        return get_num_geoms(self._ptr) 
    250199 
    251200    @property 
    252201    def num_coords(self): 
    253202        "Returns the number of coordinates in the Geometry." 
    254         n = lgeos.GEOSGetNumCoordinates(self._ptr()) 
    255         if n == -1: raise GEOSException, 'Error getting number of coordinates.' 
    256         else: return n 
     203        return get_num_coords(self._ptr) 
    257204 
    258205    @property 
     
    264211    def dims(self): 
    265212        "Returns the dimension of this Geometry (0=point, 1=line, 2=surface)." 
    266         return lgeos.GEOSGeom_getDimensions(self._ptr()
     213        return get_dims(self._ptr
    267214 
    268215    def normalize(self): 
    269216        "Converts this Geometry to normal form (or canonical form)." 
    270         status = lgeos.GEOSNormalize(self._ptr()) 
    271         if status == -1: raise GEOSException, 'failed to normalize geometry' 
    272  
    273     ## Internal for GEOS unary & binary predicate functions ## 
    274     def _unary_predicate(self, func): 
    275         """ 
    276         Returns the result, or raises an exception for the given unary predicate  
    277          function. 
    278         """ 
    279         val = func(self._ptr()) 
    280         if val == 0: return False 
    281         elif val == 1: return True 
    282         else: raise GEOSException, '%s: exception occurred.' % func.__name__ 
    283  
    284     def _binary_predicate(self, func, other, *args): 
    285         """ 
    286         Returns the result, or raises an exception for the given binary  
    287          predicate function. 
    288         """ 
    289         if not isinstance(other, GEOSGeometry): 
    290             raise TypeError, 'Binary predicate operation ("%s") requires another GEOSGeometry instance.' % func.__name__ 
    291         val = func(self._ptr(), other._ptr(), *args) 
    292         if val == 0: return False 
    293         elif val == 1: return True 
    294         else: raise GEOSException, '%s: exception occurred.' % func.__name__ 
     217        return geos_normalize(self._ptr) 
    295218 
    296219    #### Unary predicates #### 
     
    299222        """ 
    300223        Returns a boolean indicating whether the set of points in this Geometry  
    301          are empty. 
    302         """ 
    303         return self._unary_predicate(lgeos.GEOSisEmpty) 
     224        are empty. 
     225        """ 
     226        return geos_isempty(self._ptr) 
     227 
     228    @property 
     229    def hasz(self): 
     230        "Returns whether the geometry has a 3D dimension." 
     231        return geos_hasz(self._ptr) 
     232 
     233    @property 
     234    def ring(self): 
     235        "Returns whether or not the geometry is a ring." 
     236        return geos_isring(self._ptr) 
     237 
     238    @property 
     239    def simple(self): 
     240        "Returns false if the Geometry not simple." 
     241        return geos_issimple(self._ptr) 
    304242 
    305243    @property 
    306244    def valid(self): 
    307245        "This property tests the validity of this Geometry." 
    308         return self._unary_predicate(lgeos.GEOSisValid) 
    309  
    310     @property 
    311     def simple(self): 
    312         "Returns false if the Geometry not simple." 
    313         return self._unary_predicate(lgeos.GEOSisSimple) 
    314  
    315     @property 
    316     def ring(self): 
    317         "Returns whether or not the geometry is a ring." 
    318         return self._unary_predicate(lgeos.GEOSisRing) 
    319  
    320     @property 
    321     def hasz(self): 
    322         "Returns whether the geometry has a 3D dimension." 
    323         return self._unary_predicate(lgeos.GEOSHasZ) 
     246        return geos_isvalid(self._ptr) 
    324247 
    325248    #### Binary predicates. #### 
    326     def relate_pattern(self, other, pattern): 
    327         """ 
    328         Returns true if the elements in the DE-9IM intersection matrix for the  
    329          two Geometries match the elements in pattern. 
    330         """ 
    331         if len(pattern) > 9: 
    332             raise GEOSException, 'invalid intersection matrix pattern' 
    333         return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern)) 
     249    def contains(self, other): 
     250        "Returns true if other.within(this) returns true." 
     251        return geos_contains(self._ptr, other._ptr) 
     252 
     253    def crosses(self, other): 
     254        """ 
     255        Returns true if the DE-9IM intersection matrix for the two Geometries 
     256        is T*T****** (for a point and a curve,a point and an area or a line and 
     257        an area) 0******** (for two curves). 
     258        """ 
     259        return geos_crosses(self._ptr, other._ptr) 
    334260 
    335261    def disjoint(self, other): 
    336262        """ 
     263        Returns true if the DE-9IM intersection matrix for the two Geometries 
     264        is FF*FF****. 
     265        """ 
     266        return geos_disjoint(self._ptr, other._ptr) 
     267 
     268    def equals(self, other): 
     269        """ 
    337270        Returns true if the DE-9IM intersection matrix for the two Geometries  
    338         is FF*FF****. 
    339         """ 
    340         return self._binary_predicate(lgeos.GEOSDisjoint, other) 
    341  
    342     def touches(self, other): 
    343         """ 
    344         Returns true if the DE-9IM intersection matrix for the two Geometries 
    345         is FT*******, F**T***** or F***T****
    346         """ 
    347         return self._binary_predicate(lgeos.GEOSTouches, other
     271        is T*F**FFF*. 
     272        """ 
     273        return geos_equals(self._ptr, other._ptr) 
     274 
     275    def equals_exact(self, other, tolerance=0): 
     276        """ 
     277        Returns true if the two Geometries are exactly equal, up to a 
     278        specified tolerance
     279        """ 
     280        return geos_equalsexact(self._ptr, other._ptr, float(tolerance)
    348281 
    349282    def intersects(self, other): 
    350283        "Returns true if disjoint returns false." 
    351         return self._binary_predicate(lgeos.GEOSIntersects, other) 
    352  
    353     def crosses(self, other): 
     284        return geos_intersects(self._ptr, other._ptr) 
     285 
     286    def overlaps(self, other): 
    354287        """ 
    355288        Returns true if the DE-9IM intersection matrix for the two Geometries 
    356          is T*T****** (for a point and a curve,a point and an area or a line and 
    357          an area) 0******** (for two curves). 
    358         """ 
    359         return self._binary_predicate(lgeos.GEOSCrosses, other) 
     289        is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves). 
     290        """ 
     291        return geos_overlaps(self._ptr, other._ptr) 
     292 
     293    def relate_pattern(self, other, pattern): 
     294        """ 
     295        Returns true if the elements in the DE-9IM intersection matrix for the 
     296        two Geometries match the elements in pattern. 
     297        """ 
     298        if not isinstance(pattern, StringType) or len(pattern) > 9: 
     299            raise GEOSException('invalid intersection matrix pattern') 
     300        return geos_relatepattern(self._ptr, other._ptr, pattern) 
     301 
     302    def touches(self, other): 
     303        """ 
     304        Returns true if the DE-9IM intersection matrix for the two Geometries 
     305        is FT*******, F**T***** or F***T****. 
     306        """ 
     307        return geos_touches(self._ptr, other._ptr) 
    360308 
    361309    def within(self, other): 
    362310        """ 
    363         Returns true if the DE-9IM intersection matrix for the two Geometries  
    364          is T*F**F***. 
    365         """ 
    366         return self._binary_predicate(lgeos.GEOSWithin, other) 
    367  
    368     def contains(self, other): 
    369         "Returns true if other.within(this) returns true." 
    370         return self._binary_predicate(lgeos.GEOSContains, other) 
    371  
    372     def overlaps(self, other): 
    373         """ 
    374         Returns true if the DE-9IM intersection matrix for the two Geometries  
    375          is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves). 
    376         """ 
    377         return self._binary_predicate(lgeos.GEOSOverlaps, other) 
    378  
    379     def equals(self, other): 
    380         """ 
    381         Returns true if the DE-9IM intersection matrix for the two Geometries  
    382          is T*F**FFF*. 
    383         """ 
    384         return self._binary_predicate(lgeos.GEOSEquals, other) 
    385  
    386     def equals_exact(self, other, tolerance=0): 
    387         """ 
    388         Returns true if the two Geometries are exactly equal, up to a 
    389          specified tolerance. 
    390         """ 
    391         return self._binary_predicate(lgeos.GEOSEqualsExact, other,  
    392                                       c_double(tolerance)) 
     311        Returns true if the DE-9IM intersection matrix for the two Geometries 
     312        is T*F**F***. 
     313        """ 
     314        return geos_within(self._ptr, other._ptr) 
    393315 
    394316    #### SRID Routines #### 
    395317    def get_srid(self): 
    396318        "Gets the SRID for the geometry, returns None if no SRID is set." 
    397         s = lgeos.GEOSGetSRID(self._ptr()
     319        s = geos_get_srid(self._ptr
    398320        if s == 0: return None 
    399321        else: return s 
     
    401323    def set_srid(self, srid): 
    402324        "Sets the SRID for the geometry." 
    403         lgeos.GEOSSetSRID(self._ptr(), c_int(srid)
     325        geos_set_srid(self._ptr, srid
    404326    srid = property(get_srid, set_srid) 
    405327 
     
    408330    def wkt(self): 
    409331        "Returns the WKT (Well-Known Text) of the Geometry." 
    410         return string_at(lgeos.GEOSGeomToWKT(self._ptr())
     332        return to_wkt(self._ptr
    411333 
    412334    @property 
     
    419341        # A possible faster, all-python, implementation:  
    420342        #  str(self.wkb).encode('hex') 
    421         sz = c_size_t() 
    422         h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz)) 
    423         return string_at(h, sz.value) 
     343        return to_hex(self._ptr, byref(c_size_t())) 
    424344 
    425345    @property 
    426346    def wkb(self): 
    427347        "Returns the WKB of the Geometry as a buffer." 
    428         sz = c_size_t() 
    429         h = lgeos.GEOSGeomToWKB_buf(self._ptr(), byref(sz)) 
    430         return buffer(string_at(h, sz.value)) 
     348        bin = to_wkb(self._ptr, byref(c_size_t())) 
     349        return buffer(bin) 
    431350 
    432351    @property 
     
    456375            return None 
    457376 
     377    @property 
     378    def crs(self): 
     379        "Alias for `srs` property." 
     380        return self.srs 
     381 
    458382    #### Topology Routines #### 
    459     def _unary_topology(self, func, *args): 
    460         """ 
    461         Returns a GEOSGeometry for the given unary (takes only one Geomtery  
    462          as a paramter) topological operation. 
    463         """ 
    464         return GEOSGeometry(func(self._ptr(), *args), srid=self.srid) 
    465  
    466     def _binary_topology(self, func, other, *args): 
    467         """ 
    468         Returns a GEOSGeometry for the given binary (takes two Geometries  
    469          as parameters) topological operation. 
    470         """ 
    471         return GEOSGeometry(func(self._ptr(), other._ptr(), *args), srid=self.srid) 
     383    def _topology(self, gptr): 
     384        "Helper routine to return Geometry from the given pointer." 
     385        return GEOSGeometry(gptr, srid=self.srid) 
     386 
     387    @property 
     388    def boundary(self): 
     389        "Returns the boundary as a newly allocated Geometry object." 
     390        return self._topology(geos_boundary(self._ptr)) 
    472391 
    473392    def buffer(self, width, quadsegs=8): 
    474393        """ 
    475394        Returns a geometry that represents all points whose distance from this 
    476          Geometry is less than or equal to distance. Calculations are in the 
    477          Spatial Reference System of this Geometry. The optional third parameter sets 
    478          the number of segment used to approximate a quarter circle (defaults to 8). 
    479          (Text from PostGIS documentation at ch. 6.1.3) 
    480         """ 
    481         if not isinstance(width, (FloatType, IntType)): 
    482             raise TypeError, 'width parameter must be a float' 
    483         if not isinstance(quadsegs, IntType): 
    484             raise TypeError, 'quadsegs parameter must be an integer' 
    485         return self._unary_topology(lgeos.GEOSBuffer, c_double(width), c_int(quadsegs)) 
     395        Geometry is less than or equal to distance. Calculations are in the 
     396        Spatial Reference System of this Geometry. The optional third parameter sets 
     397        the number of segment used to approximate a quarter circle (defaults to 8). 
     398        (Text from PostGIS documentation at ch. 6.1.3) 
     399        """ 
     400        return self._topology(geos_buffer(self._ptr, width, quadsegs)) 
     401 
     402    @property 
     403    def centroid(self): 
     404        """ 
     405        The centroid is equal to the centroid of the set of component Geometries 
     406        of highest dimension (since the lower-dimension geometries contribute zero 
     407        "weight" to the centroid). 
     408        """ 
     409        return self._topology(geos_centroid(self._ptr)) 
     410 
     411    @property 
     412    def convex_hull(self): 
     413        """ 
     414        Returns the smallest convex Polygon that contains all the points  
     415        in the Geometry. 
     416        """ 
     417        return self._topology(geos_convexhull(self._ptr)) 
     418 
     419    def difference(self, other): 
     420        """ 
     421        Returns a Geometry representing the points making up this Geometry 
     422        that do not make up other. 
     423        """ 
     424        return self._topology(geos_difference(self._ptr, other._ptr)) 
    486425 
    487426    @property 
    488427    def envelope(self): 
    489428        "Return the envelope for this geometry (a polygon)." 
    490         return self._unary_topology(lgeos.GEOSEnvelope) 
    491  
    492     @property 
    493     def centroid(self): 
    494         """ 
    495         The centroid is equal to the centroid of the set of component Geometries 
    496          of highest dimension (since the lower-dimension geometries contribute zero 
    497          "weight" to the centroid). 
    498         """ 
    499         return self._unary_topology(lgeos.GEOSGetCentroid) 
    500  
    501     @property 
    502     def boundary(self): 
    503         "Returns the boundary as a newly allocated Geometry object." 
    504         return self._unary_topology(lgeos.GEOSBoundary) 
    505  
    506     @property 
    507     def convex_hull(self): 
    508         """ 
    509         Returns the smallest convex Polygon that contains all the points  
    510          in the Geometry. 
    511         """ 
    512         return self._unary_topology(lgeos.GEOSConvexHull) 
     429        return self._topology(geos_envelope(self._ptr)) 
     430 
     431    def intersection(self, other): 
     432        "Returns a Geometry representing the points shared by this Geometry and other." 
     433        return self._topology(geos_intersection(self._ptr, other._ptr)) 
    513434 
    514435    @property 
    515436    def point_on_surface(self): 
    516437        "Computes an interior point of this Geometry." 
    517         return self._unary_topology(lgeos.GEOSPointOnSurface) 
    518  
    519     def simplify(self, tolerance=0.0, preserve_topology=False): 
    520         """ 
    521         Returns the Geometry, simplified using the Douglas-Peucker algorithm 
    522          to the specified tolerance (higher tolerance => less points).  If no 
    523          tolerance provided, defaults to 0. 
    524  
    525         By default, this function does not preserve topology - e.g. polygons can  
    526          be split, collapse to lines or disappear holes can be created or  
    527          disappear, and lines can cross. By specifying preserve_topology=True,  
    528          the result will have the same dimension and number of components as the  
    529          input. This is significantly slower.          
    530         """ 
    531         if preserve_topology: 
    532             return self._unary_topology(lgeos.GEOSTopologyPreserveSimplify, c_double(tolerance)) 
    533         else: 
    534             return self._unary_topology(lgeos.GEOSSimplify, c_double(tolerance))         
     438        return self._topology(geos_pointonsurface(self._ptr)) 
    535439 
    536440    def relate(self, other): 
    537441        "Returns the DE-9IM intersection matrix for this Geometry and the other." 
    538         return string_at(lgeos.GEOSRelate(self._ptr(), other._ptr())) 
    539  
    540     def difference(self, other): 
    541         """Returns a Geometry representing the points making up this Geometry 
    542         that do not make up other.""" 
    543         return self._binary_topology(lgeos.GEOSDifference, other) 
     442        return geos_relate(self._ptr, other._ptr) 
     443 
     444    def simplify(self, tolerance=0.0, preserve_topology=False): 
     445        """ 
     446        Returns the Geometry, simplified using the Douglas-Peucker algorithm 
     447        to the specified tolerance (higher tolerance => less points).  If no 
     448        tolerance provided, defaults to 0. 
     449 
     450        By default, this function does not preserve topology - e.g. polygons can  
     451        be split, collapse to lines or disappear holes can be created or  
     452        disappear, and lines can cross. By specifying preserve_topology=True,  
     453        the result will have the same dimension and number of components as the  
     454        input. This is significantly slower.          
     455        """ 
     456        if preserve_topology: 
     457            return self._topology(geos_preservesimplify(self._ptr, tolerance)) 
     458        else: 
     459            return self._topology(geos_simplify(self._ptr, tolerance)) 
    544460 
    545461    def sym_difference(self, other): 
    546462        """ 
    547463        Returns a set combining the points in this Geometry not in other, 
    548          and the points in other not in this Geometry. 
    549         """ 
    550         return self._binary_topology(lgeos.GEOSSymDifference, other) 
    551  
    552     def intersection(self, other): 
    553         "Returns a Geometry representing the points shared by this Geometry and other." 
    554         return self._binary_topology(lgeos.GEOSIntersection, other) 
     464        and the points in other not in this Geometry. 
     465        """ 
     466        return self._topology(geos_symdifference(self._ptr, other._ptr)) 
    555467 
    556468    def union(self, other): 
    557469        "Returns a Geometry representing all the points in this Geometry and other." 
    558         return self._binary_topology(lgeos.GEOSUnion, other
     470        return self._topology(geos_union(self._ptr, other._ptr)
    559471 
    560472    #### Other Routines #### 
     
    562474    def area(self): 
    563475        "Returns the area of the Geometry." 
    564         a = c_double() 
    565         status = lgeos.GEOSArea(self._ptr(), byref(a)) 
    566         if status != 1: return None 
    567         else: return a.value 
     476        return geos_area(self._ptr, byref(c_double())) 
    568477 
    569478    def distance(self, other): 
    570479        """ 
    571480        Returns the distance between the closest points on this Geometry 
    572         and the other. Units will be in those of the coordinate system of 
    573         the Geometry. 
     481        and the other. Units will be in those of the coordinate system of 
     482        the Geometry. 
    574483        """ 
    575484        if not isinstance(other, GEOSGeometry):  
    576             raise TypeError, 'distance() works only on other GEOS Geometries.' 
    577         dist = c_double() 
    578         status = lgeos.GEOSDistance(self._ptr(), other._ptr(), byref(dist)) 
    579         if status != 1: return None 
    580         else: return dist.value 
     485            raise TypeError('distance() works only on other GEOS Geometries.') 
     486        return geos_distance(self._ptr, other._ptr, byref(c_double())) 
    581487 
    582488    @property 
     
    584490        """ 
    585491        Returns the length of this Geometry (e.g., 0 for point, or the 
    586          circumfrence of a Polygon). 
    587         """ 
    588         l = c_double() 
    589         status = lgeos.GEOSLength(self._ptr(), byref(l)) 
    590         if status != 1: return None 
    591         else: return l.value 
     492        circumfrence of a Polygon). 
     493        """ 
     494        return geos_length(self._ptr, byref(c_double())) 
    592495     
    593496    def clone(self): 
    594497        "Clones this Geometry." 
    595         return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()), srid=self.srid) 
     498        return GEOSGeometry(geom_clone(self._ptr), srid=self.srid) 
    596499 
    597500# Class mapping dictionary 
    598501from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing 
    599502from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon 
    600 GEOS_CLASSES = {'Point' : Point, 
    601                 'Polygon' : Polygon
    602                 'LineString' : LineString, 
    603                 'LinearRing' : LinearRing
    604                 'MultiPoint' : MultiPoint, 
    605                 'MultiLineString' : MultiLineString, 
    606                 'MultiPolygon' : MultiPolygon, 
    607                 'GeometryCollection' : GeometryCollection,   
     503GEOS_CLASSES = {0 : Point, 
     504                1 : LineString
     505                2 : LinearRing, 
     506                3 : Polygon
     507                4 : MultiPoint, 
     508                5 : MultiLineString, 
     509                6 : MultiPolygon, 
     510                7 : GeometryCollection, 
    608511                } 
  • django/branches/gis/django/contrib/gis/geos/collections.py

    r6024 r6653  
    11""" 
    2   This module houses the Geometry Collection objects: 
    3    GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon 
     2 This module houses the Geometry Collection objects: 
     3 GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon 
    44""" 
    5 from ctypes import c_int, c_uint, byref, cast 
     5from ctypes import c_int, c_uint, byref 
    66from types import TupleType, ListType 
    77from django.contrib.gis.geos.base import GEOSGeometry 
    88from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
    99from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon 
    10 from django.contrib.gis.geos.libgeos import lgeos, get_pointer_arr, GEOM_PTR 
    11 from django.contrib.gis.geos.pointer import GEOSPointer 
     10from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR 
     11from django.contrib.gis.geos.prototypes import create_collection, destroy_geom, geom_clone, geos_typeid, get_cs, get_geomn 
    1212 
    1313class GeometryCollection(GEOSGeometry): 
     
    3434        # Ensuring that only the permitted geometries are allowed in this collection 
    3535        if False in [isinstance(geom, self._allowed) for geom in init_geoms]: 
    36             raise TypeError, 'Invalid Geometry type encountered in the arguments.' 
     36            raise TypeError('Invalid Geometry type encountered in the arguments.') 
    3737 
    38         # Creating the geometry pointer array, and populating each element in 
    39         #  the array with the address of the Geometry returned by _nullify(). 
    40         ngeom = len(init_geoms) 
    41         geoms = get_pointer_arr(ngeom) 
    42         for i in xrange(ngeom): 
    43             geoms[i] = cast(init_geoms[i]._nullify(), GEOM_PTR) 
    44          
    45         # Calling the parent class, using the pointer returned from the  
    46         #  GEOS createCollection() factory. 
    47         addr = lgeos.GEOSGeom_createCollection(c_int(self._typeid),  
    48                                                byref(geoms), c_uint(ngeom)) 
    49         super(GeometryCollection, self).__init__(addr, **kwargs) 
     38        # Creating the geometry pointer array. 
     39        ngeoms = len(init_geoms) 
     40        geoms = get_pointer_arr(ngeoms) 
     41        for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i]._ptr) 
     42        super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs) 
    5043 
    51     def __del__(self): 
    52         "Overloaded deletion method for Geometry Collections." 
    53         #print 'collection: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid) 
    54         # If this geometry is still valid, it hasn't been modified by others. 
    55         if self._ptr.valid: 
    56             # Nullifying pointers to internal Geometries, preventing any  
    57             #  attempted future access. 
    58             for g in self._ptr: g.nullify() 
    59         else: 
    60             # Internal memory has become part of other Geometry objects; must  
    61             #  delete the internal objects which are still valid individually,  
    62             #  because calling the destructor on the entire geometry will result  
    63             #  in an attempted deletion of NULL pointers for the missing  
    64             #  components (which may crash Python). 
    65             for g in self._ptr: 
    66                 if len(g) > 0: 
    67                     # The collection geometry is a Polygon, destroy any leftover 
    68                     #  LinearRings. 
    69                     for r in g: r.destroy() 
    70                 g.destroy() 
    71                      
    72         super(GeometryCollection, self).__del__() 
    73              
    7444    def __getitem__(self, index): 
    7545        "Returns the Geometry from this Collection at the given index (0-based)." 
    7646        # Checking the index and returning the corresponding GEOS geometry. 
    7747        self._checkindex(index) 
    78         return GEOSGeometry(self._ptr[index], srid=self.srid) 
     48        return GEOSGeometry(geom_clone(get_geomn(self._ptr, index)), srid=self.srid) 
    7949 
    8050    def __setitem__(self, index, geom): 
     
    8252        self._checkindex(index) 
    8353        if not isinstance(geom, self._allowed): 
    84             raise TypeError, 'Incompatible Geometry for collection.' 
     54            raise TypeError('Incompatible Geometry for collection.') 
     55         
     56        ngeoms = len(self) 
     57        geoms = get_pointer_arr(ngeoms) 
     58        for i in xrange(ngeoms): 
     59            if i == index: 
     60                geoms[i] = geom_clone(geom._ptr) 
     61            else: 
     62                geoms[i] = geom_clone(get_geomn(self._ptr, i)) 
     63         
     64        # Creating a new collection, and destroying the contents of the previous poiner. 
     65        prev_ptr = self._ptr 
     66        srid = self.srid 
     67        self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)) 
     68        if srid: self.srid = srid 
     69        destroy_geom(prev_ptr) 
    8570 
    86         # Constructing the list of geometries that will go in the collection. 
    87         new_geoms = [] 
    88         for i in xrange(len(self)): 
    89             if i == index: new_geoms.append(geom) 
    90             else: new_geoms.append(self[i]) 
    91  
    92         # Creating a new geometry collection from the list, and 
    93         #  re-assigning the pointers. 
    94         new_collection = self.__class__(*new_geoms, **{'srid':self.srid}) 
    95         self._reassign(new_collection) 
    96          
    9771    def __iter__(self): 
    9872        "Iterates over each Geometry in the Collection." 
     
    10781        "Checks the given geometry index." 
    10882        if index < 0 or index >= self.num_geom: 
    109             raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) 
    110  
    111     def _nullify(self): 
    112         "Overloaded from base method to nullify geometry references in this Collection." 
    113         # Nullifying the references to the internal Geometry objects from this Collection. 
    114         for g in self._ptr: g.nullify() 
    115         return super(GeometryCollection, self)._nullify() 
    116  
    117     def _populate(self): 
    118         "Internal routine that populates the internal children geometries list." 
    119         ptr_list = [] 
    120         for i in xrange(len(self)): 
    121             # Getting the geometry pointer for the geometry at the index. 
    122             geom_ptr = lgeos.GEOSGetGeometryN(self._ptr(), c_int(i)) 
    123  
    124             # Adding the coordinate sequence to the list, or using None if the 
    125             #  collection Geometry doesn't support coordinate sequences. 
    126             if lgeos.GEOSGeomTypeId(geom_ptr) in (0, 1, 2): 
    127                 ptr_list.append((geom_ptr, lgeos.GEOSGeom_getCoordSeq(geom_ptr))) 
    128             else: 
    129                 ptr_list.append((geom_ptr, None)) 
    130         self._ptr.set_children(ptr_list) 
     83            raise GEOSGeometryIndexError('invalid GEOS Geometry index: %s' % str(index)) 
    13184 
    13285    @property 
  • django/branches/gis/django/contrib/gis/geos/coordseq.py

    r6024 r6653  
    11""" 
    2   This module houses the GEOSCoordSeq object, and is used internally 
    3    by GEOSGeometry to house the actual coordinates of the Point, 
    4    LineString, and LinearRing geometries. 
     2 This module houses the GEOSCoordSeq object, which is used internally 
     3 by GEOSGeometry to house the actual coordinates of the Point, 
     4 LineString, and LinearRing geometries. 
    55""" 
     6from ctypes import c_double, c_uint, byref 
     7from types import ListType, TupleType 
    68from django.contrib.gis.geos.error import GEOSException, GEOSGeometry