Django

Code

Changeset 5742

Show
Ignore:
Timestamp:
07/21/07 12:39:09 (1 year ago)
Author:
jbronn
Message:

gis: big changes within in GEOS internals:

(1) All Geometry types and Collections now have their own constructor (e.g., Polygon(LinearRing?(..)), MultiPoint?(Point(..), Point(..)))
(2) Memory management improved, laying the foundation for fully mutable geometries.
(3) Added set-like operations (|, &, -, )
(4) Added & improved tests.
(5) docstring changes

Files:

Legend:

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

    r5655 r5742  
    1 # Trying not to pollute the namespace. 
     1""" 
     2  This module contains the 'base' GEOSGeometry object -- all GEOS geometries 
     3  inherit from this object. 
     4""" 
     5 
     6# ctypes and types dependencies. 
    27from ctypes import \ 
    38     byref, string_at, create_string_buffer, pointer, \ 
    49     c_char_p, c_double, c_int, c_size_t 
    5 from types import StringType, IntType, FloatType, TupleType, ListType 
    6  
    7 # Getting GEOS-related dependencies. 
     10from types import StringType, IntType, FloatType 
     11 
     12# Python and GEOS-related dependencies. 
    813import re 
    914from warnings import warn 
     
    1116from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
    1217from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs 
    13  
    14 if HAS_NUMPY: 
    15     from numpy import ndarray, array 
    16  
    17 # For recognizing HEXEWKB. 
    18 hex_regex = re.compile(r'^[0-9A-Fa-f]+') 
     18if HAS_NUMPY: from numpy import ndarray, array 
     19 
     20# Regular expression for recognizing HEXEWKB. 
     21hex_regex = re.compile(r'^[0-9A-Fa-f]+$') 
    1922 
    2023class GEOSGeometry(object): 
     
    2225     
    2326    #### Python 'magic' routines #### 
    24     def __init__(self, geo_input, input_type=False, child=False): 
     27    def __init__(self, geo_input, input_type=False, parent=False): 
    2528        """The constructor for GEOS geometry objects.  May take the following 
    2629        strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form). 
     
    2831        The `input_type` keyword has been deprecated -- geometry type is now auto-detected. 
    2932 
    30         The `child` keyword is for internal use only, and indicates to the garbage collector 
    31           not to delete this geometry if it was spawned from a parent (e.g., the exterior 
    32           ring from a polygon). 
     33        The `parent` keyword is for internal use only, and indicates to the garbage collector 
     34          not to delete this geometry because it was spawned from a parent (e.g., the exterior 
     35          ring from a polygon).  Its value is the GEOSPointer of the parent geometry. 
    3336        """ 
    3437 
     
    5659 
    5760        if bool(g): 
    58             # If we have a GEOSPointer object, just set the '_ptr' attribute with g 
     61            # If we have a GEOSPointer object, just set the '_ptr' attribute with input 
    5962            if isinstance(g, GEOSPointer): self._ptr = g 
    60             else: self._ptr.set(g) # Otherwise, set the address 
     63            else: self._ptr.set(g) # Otherwise, set with the address 
    6164        else: 
    6265            raise GEOSException, 'Could not initialize GEOS Geometry with given input.' 
    6366 
    64         # Setting the 'child' flag -- when the object is labeled with this flag 
    65         #  it will not be destroyed by __del__().  This is used for child geometries from 
     67        # Setting the 'parent' flag -- when the object is labeled with this flag 
     68        #  it will not be destroyed by __del__().  This is used for child geometries spawned from 
    6669        #  parent geometries (e.g., LinearRings from a Polygon, Points from a MultiPoint, etc.). 
    67         self._child = child 
    68  
     70        if isinstance(parent, GEOSPointer): 
     71            self._parent = parent 
     72        else: 
     73            self._parent = GEOSPointer(0) 
     74         
    6975        # Setting the class type (e.g., 'Point', 'Polygon', etc.) 
    7076        self.__class__ = GEOS_CLASSES[self.geom_type] 
    7177 
     78        # Getting the coordinate sequence for the geometry (will be None on geometries that 
     79        #   do not have coordinate sequences) 
     80        self._get_cs() 
     81 
    7282        # Extra setup needed for Geometries that may be parents. 
    73         if isinstance(self, GeometryCollection): self._geoms = {} 
    74         if isinstance(self, Polygon): self._rings = {} 
     83        if isinstance(self, (Polygon, GeometryCollection)): self._populate() 
    7584 
    7685    def __del__(self): 
    77         "Destroys this geometry -- only if the pointer is valid and this is not a child geometry." 
    78         #print 'Deleting %s (child=%s, valid=%s)' % (self.geom_type, self._child, self._ptr.valid) 
    79         if self._ptr.valid and not self._child: lgeos.GEOSGeom_destroy(self._ptr()) 
     86        "Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent." 
     87        #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid) 
     88        # Only calling destroy on valid pointers not spawned from a parent 
     89        if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr()) 
    8090 
    8191    def __str__(self): 
     
    8393        return self.wkt 
    8494 
     95    def __repr__(self): 
     96        return '<%s object>' % self.geom_type 
     97 
     98    # Comparison operators 
    8599    def __eq__(self, other): 
    86100        "Equivalence testing." 
    87101        return self.equals(other) 
    88102 
     103    def __ne__(self, other): 
     104        "The not equals operator." 
     105        return not self.equals(other) 
     106 
     107    ### Geometry set-like operations ### 
     108    # Thanks to Sean Gillies for inspiration: 
     109    #  http://lists.gispython.org/pipermail/community/2007-July/001034.html 
     110    # g = g1 | g2 
     111    def __or__(self, other): 
     112        "Returns the union of this Geometry and the other." 
     113        return self.union(other) 
     114 
     115    # g = g1 & g2 
     116    def __and__(self, other): 
     117        "Returns the intersection of this Geometry and the other." 
     118        return self.intersection(other) 
     119 
     120    # g = g1 - g2 
     121    def __sub__(self, other): 
     122        "Return the difference this Geometry and the other." 
     123        return self.difference(other) 
     124 
     125    # g = g1 ^ g2 
     126    def __xor__(self, other): 
     127        "Return the symmetric difference of this Geometry and the other." 
     128        return self.sym_difference(other) 
     129     
    89130    #### Coordinate Sequence Routines #### 
    90     def _cache_cs(self): 
    91         "Caches the coordinate sequence for this Geometry." 
    92         if not hasattr(self, '_cs'): 
    93             # Only these geometries are allowed to have coordinate sequences. 
    94             if self.geom_type in ('LineString', 'LinearRing', 'Point'): 
    95                 self._cs = GEOSCoordSeq(GEOSPointer(lgeos.GEOSGeom_getCoordSeq(self._ptr())), self.hasz) 
    96             else: 
    97                 self._cs = None 
     131    @property 
     132    def has_cs(self): 
     133        "Returns True if this Geometry has a coordinate sequence, False if not." 
     134        # Only these geometries are allowed to have coordinate sequences. 
     135        if isinstance(self, (Point, LineString, LinearRing)): 
     136            return True 
     137        else: 
     138            return False 
     139 
     140    def _get_cs(self): 
     141        "Gets the coordinate sequence for this Geometry." 
     142        if self.has_cs: 
     143            self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True) 
     144            self._cs = GEOSCoordSeq(self._ptr, self.hasz) 
     145        else: 
     146            self._cs = None 
    98147 
    99148    @property 
    100149    def coord_seq(self): 
    101         "Returns the coordinate sequence for the geometry." 
    102         # Getting the coordinate sequence for the geometry 
    103         self._cache_cs() 
    104  
    105         # Returning a GEOSCoordSeq wrapped around the pointer. 
     150        "Returns the coordinate sequence for this Geometry." 
    106151        return self._cs 
    107152 
     
    109154    @property 
    110155    def geom_type(self): 
    111         "Returns a string representing the geometry type, e.g. 'Polygon'" 
     156        "Returns a string representing the Geometry type, e.g. 'Polygon'" 
    112157        return string_at(lgeos.GEOSGeomType(self._ptr())) 
    113158 
    114159    @property 
    115160    def geom_typeid(self): 
    116         "Returns an integer representing the geometry type." 
     161        "Returns an integer representing the Geometry type." 
    117162        return lgeos.GEOSGeomTypeId(self._ptr()) 
    118163 
    119164    @property 
    120165    def num_geom(self): 
    121         "Returns the number of geometries in the geometry." 
     166        "Returns the number of geometries in the Geometry." 
    122167        n = lgeos.GEOSGetNumGeometries(self._ptr()) 
    123168        if n == -1: raise GEOSException, 'Error getting number of geometries.' 
     
    126171    @property 
    127172    def num_coords(self): 
    128         "Returns the number of coordinates in the geometry." 
     173        "Returns the number of coordinates in the Geometry." 
    129174        n = lgeos.GEOSGetNumCoordinates(self._ptr()) 
    130175        if n == -1: raise GEOSException, 'Error getting number of coordinates.' 
     
    133178    @property 
    134179    def num_points(self): 
    135         "Returns the number points, or coordinates, in the geometry." 
     180        "Returns the number points, or coordinates, in the Geometry." 
    136181        return self.num_coords 
    137182 
     
    146191        if status == -1: raise GEOSException, 'failed to normalize geometry' 
    147192 
     193    ## Internal for GEOS unary & binary predicate functions ## 
    148194    def _unary_predicate(self, func): 
    149195        "Returns the result, or raises an exception for the given unary predicate function." 
     
    165211    @property 
    166212    def empty(self): 
    167         "Returns a boolean indicating whether the set of points in this geometry are empty." 
     213        "Returns a boolean indicating whether the set of points in this Geometry are empty." 
    168214        return self._unary_predicate(lgeos.GEOSisEmpty) 
    169215 
    170216    @property 
    171217    def valid(self): 
    172         "This property tests the validity of this geometry." 
     218        "This property tests the validity of this Geometry." 
    173219        return self._unary_predicate(lgeos.GEOSisValid) 
    174220 
     
    191237    def relate_pattern(self, other, pattern): 
    192238        """Returns true if the elements in the DE-9IM intersection matrix for 
    193         the two Geometrys match the elements in pattern.""" 
     239        the two Geometries match the elements in pattern.""" 
    194240        if len(pattern) > 9: 
    195241            raise GEOSException, 'invalid intersection matrix pattern' 
     
    197243 
    198244    def disjoint(self, other): 
    199         "Returns true if the DE-9IM intersection matrix for the two Geometrys is FF*FF****." 
     245        "Returns true if the DE-9IM intersection matrix for the two Geometries is FF*FF****." 
    200246        return self._binary_predicate(lgeos.GEOSDisjoint, other) 
    201247 
    202248    def touches(self, other): 
    203         "Returns true if the DE-9IM intersection matrix for the two Geometrys is FT*******, F**T***** or F***T****." 
     249        "Returns true if the DE-9IM intersection matrix for the two Geometries is FT*******, F**T***** or F***T****." 
    204250        return self._binary_predicate(lgeos.GEOSTouches, other) 
    205251 
     
    209255 
    210256    def crosses(self, other): 
    211         """Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T****** (for a point and a curve, 
     257        """Returns true if the DE-9IM intersection matrix for the two Geometries is T*T****** (for a point and a curve, 
    212258        a point and an area or a line and an area) 0******** (for two curves).""" 
    213259        return self._binary_predicate(lgeos.GEOSCrosses, other) 
    214260 
    215261    def within(self, other): 
    216         "Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**F***." 
     262        "Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**F***." 
    217263        return self._binary_predicate(lgeos.GEOSWithin, other) 
    218264 
     
    222268 
    223269    def overlaps(self, other): 
    224         """Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T***T** (for two points 
     270        """Returns true if the DE-9IM intersection matrix for the two Geometries is T*T***T** (for two points 
    225271        or two surfaces) 1*T***T** (for two curves).""" 
    226272        return self._binary_predicate(lgeos.GEOSOverlaps, other) 
    227273 
    228274    def equals(self, other): 
    229         "Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**FFF*." 
     275        "Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**FFF*." 
    230276        return self._binary_predicate(lgeos.GEOSEquals, other) 
    231277 
    232278    def equals_exact(self, other, tolerance=0): 
    233         "Returns true if the two Geometrys are exactly equal, up to a specified tolerance." 
     279        "Returns true if the two Geometries are exactly equal, up to a specified tolerance." 
    234280        tol = c_double(tolerance) 
    235281        return self._binary_predicate(lgeos.GEOSEqualsExact, other, tol) 
     
    291337    @property 
    292338    def centroid(self): 
    293         """The centroid is equal to the centroid of the set of component Geometry
     339        """The centroid is equal to the centroid of the set of component Geometrie
    294340        of highest dimension (since the lower-dimension geometries contribute zero 
    295341        "weight" to the centroid).""" 
     
    345391        "Clones this Geometry." 
    346392        return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr())) 
    347      
     393 
    348394# Class mapping dictionary 
    349395from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing 
     
    353399                'LineString' : LineString, 
    354400                'LinearRing' : LinearRing, 
    355                 'GeometryCollection' : GeometryCollection, 
    356401                'MultiPoint' : MultiPoint, 
    357402                'MultiLineString' : MultiLineString, 
    358403                'MultiPolygon' : MultiPolygon, 
     404                'GeometryCollection' : GeometryCollection,   
    359405                } 
  • django/branches/gis/django/contrib/gis/geos/collections.py

    r5656 r5742  
    33     GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon 
    44""" 
    5 from ctypes import c_int 
    6 from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer 
     5from ctypes import c_int, c_uint, byref, cast 
     6from types import TupleType, ListType 
     7from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, init_from_geom, get_pointer_arr, GEOM_PTR 
    78from django.contrib.gis.geos.base import GEOSGeometry 
    8 from django.contrib.gis.geos.error import GEOSException 
     9from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
     10from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon 
     11 
     12def init_from_poly(poly): 
     13    "Internal routine used for initializing Geometry Collections from Polygons." 
     14    # Constructing a new Polygon to take control of the rings. 
     15    p = Polygon(*tuple(ring for ring in poly)) 
     16     
     17    # If this Polygon came from a GeometryCollection, it is a child 
     18    #  and the parent geometry pointer is nullified. 
     19    if poly._parent: poly._parent.nullify() 
     20     
     21    # Nullifying the polygon pointer 
     22    poly._ptr.nullify() 
     23 
     24    # Returning the address of the new Polygon. 
     25    return p._ptr() 
    926 
    1027class GeometryCollection(GEOSGeometry): 
     28    _allowed = (Point, LineString, LinearRing, Polygon) 
     29    _typeid = 7 
     30 
     31    def __init__(self, *args): 
     32        self._ptr = GEOSPointer(0) # Initially NULL 
     33        self._geoms = {} 
     34        self._parent = False 
     35 
     36        if not args: 
     37            raise TypeError, 'Must provide at least one LinearRing to initialize Polygon.' 
     38 
     39        if len(args) == 1: # If only one geometry provided or a list of geometries is provided 
     40            if isinstance(args[0], (TupleType, ListType)): 
     41                init_geoms = args[0] 
     42            else: 
     43                init_geoms = args 
     44        else: 
     45            init_geoms = args 
     46 
     47        # Ensuring that only the permitted geometries are allowed in this collection 
     48        if False in [isinstance(geom, self._allowed) for geom in init_geoms]: 
     49            raise TypeError, 'Invalid Geometry type encountered in the arguments.' 
     50 
     51        # Creating the geometry pointer array 
     52        ngeom = len(init_geoms) 
     53        geoms = get_pointer_arr(ngeom) 
     54 
     55        # Incrementing through each input geometry. 
     56        for i in xrange(ngeom): 
     57            if isinstance(init_geoms[i], Polygon):  
     58                # Special care is taken when importing from Polygons 
     59                geoms[i] = cast(init_from_poly(init_geoms[i]), GEOM_PTR) 
     60            else:  
     61                geoms[i] = cast(init_from_geom(init_geoms[i]), GEOM_PTR) 
     62 
     63        # Calling the parent class, using the pointer returned from GEOS createCollection() 
     64        super(GeometryCollection, self).__init__(lgeos.GEOSGeom_createCollection(c_int(self._typeid), byref(geoms), c_uint(ngeom))) 
    1165 
    1266    def __del__(self): 
    13         "Override the GEOSGeometry delete routine to safely take care of any spawned geometries." 
    14         # Nullifying the pointers to internal geometries, preventing any attempted future access 
    15         for k in self._geoms: self._geoms[k].nullify() 
    16         super(GeometryCollection, self).__del__() # Calling the parent __del__() method. 
    17  
     67        "Overloaded deletion method for Geometry Collections." 
     68        #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid) 
     69        # If this geometry is still valid, it hasn't been modified by others. 
     70        if self._ptr.valid: 
     71            # Nullifying pointers to internal geometries, preventing any attempted future access. 
     72            for k in self._geoms: self._geoms[k].nullify() 
     73            super(GeometryCollection, self).__del__() 
     74        else: 
     75            # Internal memory has become part of other Geometry objects, must delete the 
     76            #  internal objects which are still valid individually, since calling destructor 
     77            #  on entire geometry will result in an attempted deletion of NULL pointers for 
     78            #  the missing components. 
     79            for k in self._geoms: 
     80                if self._geoms[k].valid: 
     81                    lgeos.GEOSGeom_destroy(self._geoms[k].address) 
     82                    self._geoms[k].nullify() 
     83             
    1884    def __getitem__(self, index): 
    1985        "For indexing on the multiple geometries." 
     86        # Checking the index and returning the corresponding GEOS geometry. 
    2087        self._checkindex(index) 
    21  
    22         # Setting an entry in the _geoms dictionary for the requested geometry. 
    23         if not index in self._geoms: 
    24             self._geoms[index] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(index))) 
    25  
    26         # Cloning the GEOS Geometry first, before returning it. 
    27         return GEOSGeometry(self._geoms[index], child=True) 
     88        return GEOSGeometry(self._geoms[index], parent=self._ptr) 
    2889 
    2990    def __iter__(self): 
    3091        "For iteration on the multiple geometries." 
    31         for i in xrange(self.__len__()): 
     92        for i in xrange(len(self)): 
    3293            yield self.__getitem__(i) 
    3394 
     
    41102            raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) 
    42103 
     104    def _populate(self): 
     105        "Populates the internal child geometry dictionary." 
     106        self._geoms = {} 
     107        for i in xrange(self.num_geom): 
     108            self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i))) 
     109 
     110 
    43111# MultiPoint, MultiLineString, and MultiPolygon class definitions. 
    44 class MultiPoint(GeometryCollection): pass 
    45 class MultiLineString(GeometryCollection): pass 
    46 class MultiPolygon(GeometryCollection): pass 
     112class MultiPoint(GeometryCollection):  
     113    _allowed = Point 
     114    _typeid = 4 
     115class MultiLineString(GeometryCollection):  
     116    _allowed = (LineString, LinearRing) 
     117    _typeid = 5 
     118class MultiPolygon(GeometryCollection):  
     119    _allowed = Polygon 
     120    _typeid = 6 
  • django/branches/gis/django/contrib/gis/geos/coordseq.py

    r5655 r5742  
    1 from django.contrib.gis.geos.libgeos import lgeos 
     1from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY 
    22from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
    33from ctypes import c_double, c_int, c_uint, byref 
     4from types import ListType, TupleType 
     5if HAS_NUMPY: from numpy import ndarray 
    46 
    57""" 
     
    1517    def __init__(self, ptr, z=False): 
    1618        "Initializes from a GEOS pointer." 
     19        if not isinstance(ptr, GEOSPointer): 
     20            raise TypeError, 'Coordinate sequence should initialize with a GEOSPointer.' 
    1721        self._ptr = ptr 
    1822        self._z = z 
     
    4044    def __setitem__(self, index, value): 
    4145        "Can use the index [] operator to set coordinate sequence at an index." 
     46        # Checking the input value 
     47        if isinstance(value, (ListType, TupleType)): 
     48            pass 
     49        elif HAS_NUMPY and isinstance(value, ndarray): 
     50            pass 
     51        else: 
     52            raise TypeError, 'Must set coordinate with a sequence (list, tuple, or numpy array).' 
     53        # Checking the dims of the input 
    4254        if self.dims == 3 and self._z: 
    4355            n_args = 3 
     
    4860        if len(value) != n_args: 
    4961            raise TypeError, 'Dimension of value does not match.' 
     62        # Setting the X, Y, Z 
    5063        self.setX(index, value[0]) 
    5164        self.setY(index, value[1]) 
     
    7487        idx = c_uint(index) 
    7588 
    76         # 'd' is the value of the point, passed in by reference 
     89        # 'd' is the value of the ordinate, passed in by reference 
    7790        d = c_double() 
    78         status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr(), idx, dim, byref(d)) 
     91        status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr.coordseq(), idx, dim, byref(d)) 
    7992        if status == 0: 
    80             raise GEOSException, 'could not retrieve %th ordinate at index: %s' % (str(dimension), str(index)
     93            raise GEOSException, 'could not retrieve %sth ordinate at index: %s' % (dimension, index
    8194        return d.value 
    8295 
     
    91104 
    92105        # Setting the ordinate 
    93         status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr(), idx, dim, c_double(value)) 
     106        status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr.coordseq(), idx, dim, c_double(value)) 
    94107        if status == 0: 
    95108            raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index) 
     
    124137        "Returns the size of this coordinate sequence." 
    125138        n = c_uint(0) 
    126         status = lgeos.GEOSCoordSeq_getSize(self._ptr(), byref(n)) 
     139        status = lgeos.GEOSCoordSeq_getSize(self._ptr.coordseq(), byref(n)) 
    127140        if status == 0: 
    128141            raise GEOSException, 'Could not get CoordSeq size.' 
     
    133146        "Returns the dimensions of this coordinate sequence." 
    134147        n = c_uint(0) 
    135         status = lgeos.GEOSCoordSeq_getDimensions(self._ptr(), byref(n)) 
     148        status = lgeos.GEOSCoordSeq_getDimensions(self._ptr.coordseq(), byref(n)) 
    136149        if status == 0: 
    137150            raise GEOSException, 'Could not get CoordSeq dimensions.' 
     
    147160    def clone(self): 
    148161        "Clones this coordinate sequence." 
    149         pass 
     162        return GEOSCoordSeq(GEOSPointer(0, lgeos.GEOSCoordSeq_clone(self._ptr.coordseq())), self.hasz) 
    150163 
    151164    @property 
  • django/branches/gis/django/contrib/gis/geos/geometries.py

    r5656 r5742  
    1 from ctypes import c_double, c_int, c_uint 
     1""" 
     2  This module houses the Point, LineString, LinearRing, and Polygon OGC 
     3   geometry classes.  All geometry classes in this module inherit from  
     4   GEOSGeometry. 
     5""" 
     6 
     7from ctypes import c_double, c_int, c_uint, byref, cast 
    28from types import FloatType, IntType, ListType, TupleType 
    39from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs 
    4 from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY 
     10from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, init_from_geom, GEOM_PTR, HAS_NUMPY 
    511from django.contrib.gis.geos.base import GEOSGeometry 
    6 from django.contrib.gis.geos.error import GEOSException 
    7  
    8 if HAS_NUMPY: 
    9     from numpy import ndarray, array 
     12from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 
     13if HAS_NUMPY: from numpy import ndarray, array 
    1014 
    1115class Point(GEOSGeometry): 
     
    1721          >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters 
    1822        """ 
     23 
     24        self._ptr = GEOSPointer(0) # Initially NULL 
    1925 
    2026        if isinstance(x, (TupleType, ListType)): 
     
    5359        super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs)) 
    5460 
     61    def __len__(self): 
     62        "Returns the number of dimensions for this Point (either 2 or 3)." 
     63        if self.hasz: return 3 
     64        else: return 2 
     65         
    5566    def _getOrdinate(self, dim, idx): 
    5667        "The coordinate sequence getOrdinate() wrapper." 
    57         self._cache_cs() 
    5868        return self._cs.getOrdinate(dim, idx) 
    5969 
    6070    def _setOrdinate(self, dim, idx, value): 
    6171        "The coordinate sequence setOrdinate() wrapper." 
    62         self._cache_cs() 
    6372        self._cs.setOrdinate(dim, idx, value) 
    6473 
     
    101110    def get_tuple(self): 
    102111        "Returns a tuple of the point." 
    103         self._cache_cs() 
    104112        return self._cs.tuple 
    105113 
    106     def set_tuple(self): 
    107         "Sets the tuple for this point object." 
    108         pass 
    109  
     114    def set_tuple(self, tup): 
     115        "Sets the coordinates of the point with the given tuple." 
     116        self._cs[0] = tup 
     117     
    110118    # The tuple property 
    111119    tuple = property(get_tuple, set_tuple) 
     
    114122 
    115123    #### Python 'magic' routines #### 
    116     def __init__(self, coords, ring=False): 
    117         """Initializes on the given sequence, may take lists, tuples, or NumPy arrays 
    118         of X,Y pairs.""" 
     124    def __init__(self, *args, **kwargs): 
     125        """Initializes on the given sequence -- may take lists, tuples, NumPy arrays 
     126        of X,Y pairs, or Point objects.  If Point objects are used, ownership is 
     127        _not_ transferred to the LineString object. 
     128 
     129        Examples: 
     130          ls = LineString((1, 1), (2, 2)) 
     131          ls = LineString([(1, 1), (2, 2)]) 
     132          ls = LineString(array([(1, 1), (2, 2)])) 
     133          ls = LineString(Point(1, 1), Point(2, 2)) 
     134        """ 
     135        self._ptr = GEOSPointer(0) # Initially NULL 
     136 
     137        # If only one argument was provided, then set the coords array appropriately 
     138        if len(args) == 1: coords = args[0] 
     139        else: coords = args 
    119140 
    120141        if isinstance(coords, (TupleType, ListType)): 
     142            # Getting the number of coords and the number of dimensions -- which 
     143            #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)). 
    121144            ncoords = len(coords) 
    122             first = True 
    123             for coord in coords: 
    124                 if not isinstance(coord, (TupleType, ListType)): 
     145            if coords: ndim = len(coords[0]) 
     146            else: raise TypeError, 'Cannot initialize on empty sequence.' 
     147            self._checkdim(ndim) 
     148            # Incrementing through each of the coordinates and verifying 
     149            for i in xrange(1, ncoords): 
     150                if not isinstance(coords[i], (TupleType, ListType, Point)): 
    125151                    raise TypeError, 'each coordinate should be a sequence (list or tuple)' 
    126                 if first: 
    127                     ndim = len(coord) 
    128                     self._checkdim(ndim) 
    129                     first = False 
    130                 else: 
    131                     if len(coord) != ndim: raise TypeError, 'Dimension mismatch.' 
     152                if len(coords[i]) != ndim: raise TypeError, 'Dimension mismatch.' 
    132153            numpy_coords = False 
    133154        elif HAS_NUMPY and isinstance(coords, ndarray): 
    134             shape = coords.shape 
     155            shape = coords.shape # Using numpy's shape. 
    135156            if len(shape) != 2: raise TypeError, 'Too many dimensions.' 
    136157            self._checkdim(shape[1]) 
     
    142163 
    143164        # Creating the coordinate sequence 
    144         cs = GEOSCoordSeq(GEOSPointer(create_cs(c_uint(ncoords), c_uint(ndim)))) 
     165        cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim)))) 
    145166 
    146167        # Setting each point in the coordinate sequence 
    147168        for i in xrange(ncoords): 
    148169            if numpy_coords: cs[i] = coords[i,:] 
     170            elif isinstance(coords[i], Point): cs[i] = coords[i].tuple 
    149171            else: cs[i] = coords[i]         
    150172 
    151173        # Getting the initialization function 
    152         if ring
     174        if kwargs.get('ring', False)
    153175            func = lgeos.GEOSGeom_createLinearRing 
    154176        else: 
     
    156178        
    157179        # Calling the base geometry initialization with the returned pointer from the function. 
    158         super(LineString, self).__init__(func(cs._ptr())) 
     180        super(LineString, self).__init__(func(cs._ptr.coordseq())) 
    159181 
    160182    def __getitem__(self, index): 
    161183        "Gets the point at the specified index." 
    162         self._cache_cs() 
    163184        return self._cs[index] 
    164185 
    165186    def __setitem__(self, index, value): 
    166187        "Sets the point at the specified index, e.g., line_str[0] = (1, 2)." 
    167         self._cache_cs() 
    168188        self._cs[index] = value 
    169189 
     
    171191        "Allows iteration over this LineString." 
    172192        for i in xrange(self.__len__()): 
    173             yield self.__getitem__(index
     193            yield self.__getitem__(i
    174194 
    175195    def __len__(self): 
    176196        "Returns the number of points in this LineString." 
    177         self._cache_cs() 
    178197        return len(self._cs) 
    179198 
     
    185204    def tuple(self): 
    186205        "Returns a tuple version of the geometry from the coordinate sequence." 
    187         self._cache_cs() 
    188206        return self._cs.tuple 
    189207 
     
    191209        """Internal routine that returns a sequence (list) corresponding with 
    192210        the given function.  Will return a numpy array if possible.""" 
    193         lst = [func(i) for i in xrange(self.__len__())] # constructing the list, using the function 
     211        lst = [func(i) for i in xrange(len(self))] # constructing the list, using the function 
    194212        if HAS_NUMPY: return array(lst) # ARRRR! 
    195213        else: return lst 
     
    198216    def array(self): 
    199217        "Returns a numpy array for the LineString." 
    200         self._cache_cs() 
    201218        return self._listarr(self._cs.__getitem__) 
    202219 
     
    204221    def x(self): 
    205222        "Returns a list or numpy array of the X variable." 
    206         self._cache_cs() 
    207223        return self._listarr(self._cs.getX) 
    208224     
     
    210226    def y(self): 
    211227        "Returns a list or numpy array of the Y variable." 
    212         self._cache_cs() 
    213228        return self._listarr(self._cs.getY) 
    214229 
     
    216231    def z(self): 
    217232        "Returns a list or numpy array of the Z variable." 
    218         self._cache_cs() 
    219233        if not self.hasz: return None 
    220234        else: return self._listarr(self._cs.getZ) 
     
    222236# LinearRings are LineStrings used within Polygons. 
    223237class LinearRing(LineString): 
    224     def __init__(self, coords): 
     238    def __init__(self, *args): 
    225239        "Overriding the initialization function to set the ring keyword." 
    226         super(LinearRing, self).__init__(coords, ring=True) 
     240        kwargs = {'ring' : True} 
     241        super(LinearRing, self).__init__(*args, **kwargs) 
    227242 
    228243class Polygon(GEOSGeometry): 
    229244 
     245    def __init__(self, *args): 
     246        """Initializes on an exterior ring and a sequence of holes (both instances of LinearRings. 
     247        All LinearRing instances used for creation will become owned by this Polygon. 
     248         
     249        Examples, where shell, hole1, and hole2 are valid LinearRing geometries: 
     250          poly = Polygon(shell, hole1, hole2) 
     251          poly = Polygon(shell, (hole1, hole2)) 
     252        """ 
     253        self._ptr = GEOSPointer(0) # Initially NULL 
     254        self._rings = {}  
     255        if not args: 
     256            raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.' 
     257 
     258        # Getting the ext_ring and init_holes parameters from the argument list 
     259        ext_ring = args[0] 
     260        init_holes = args[1:] 
     261        if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)):  
     262            init_holes = init_holes[0] 
     263 
     264        # Ensuring the exterior ring parameter is a LinearRing object 
     265        if not isinstance(ext_ring, LinearRing): 
     266            raise TypeError, 'First argument for Polygon initialization must be a LinearRing.' 
     267 
     268        # Making sure all of the holes are LinearRing objects 
     269        if False in [isinstance(hole, LinearRing) for hole in init_holes]: 
     270            raise TypeError, 'Holes parameter must be a sequence of LinearRings.' 
     271 
     272        # Getting the holes 
     273        nholes = len(init_holes) 
     274        holes = get_pointer_arr(nholes) 
     275        for i in xrange(nholes): 
     276            # Casting to the Geometry Pointer type 
     277            holes[i] = cast(init_from_geom(init_holes[i]), GEOM_PTR) 
     278                       
     279        # Getting the shell pointer address,  
     280        shell = init_from_geom(ext_ring) 
     281 
     282        # Calling with the GEOS createPolygon factory. 
     283        super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes))) 
     284 
    230285    def __del__(self): 
    231         "Override the GEOSGeometry delete routine to safely take care of any spawned rings." 
    232         # Nullifying the pointers to internal rings, preventing any attempted future access 
    233         for k in self._rings: self._rings[k].nullify() 
    234         super(Polygon, self).__del__() # Calling the parent __del__() method. 
    235      
     286        "Overloaded deletion method for Polygons." 
     287        #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid) 
     288        # If this geometry is still valid, it hasn't been modified by others. 
     289        if self._ptr.valid: 
     290            # Nulling the pointers to internal rings, preventing any attempted future access 
     291            for k in self._rings: self._rings[k].nullify() 
     292            super(Polygon, self).__del__() 
     293        else: 
     294            # Internal memory has become part of other objects; must delete the  
     295            #  internal objects which are still valid individually, since calling 
     296            #  destructor on entire geometry will result in an attempted deletion  
     297            #  of NULL pointers for the missing components. 
     298            for k in self._rings: 
     299                if self._rings[k].valid: 
     300                    lgeos.GEOSGeom_destroy(self._rings[k].address) 
     301                    self._rings[k].nullify() 
     302 
    236303    def __getitem__(self, index): 
    237304        """Returns the ring at the specified index.  The first index, 0, will always 
     
    248315    def __iter__(self): 
    249316        "Iterates over each ring in the polygon." 
    250         for i in xrange(self.__len__()): 
     317        for i in xrange(len(self)): 
    251318            yield self.__getitem__(i) 
    252319 
     
    255322        return self.num_interior_rings + 1 
    256323 
     324    def _populate(self): 
     325        "Populates the internal rings dictionary." 
     326        # Getting the exterior ring first for the 0th index. 
     327        self._rings = {0 : GEOSPointer(lgeos.GEOSGetExteriorRing(self._ptr()))} 
     328 
     329        # Getting the interior rings. 
     330        for i in xrange(self.num_interior_rings): 
     331            self._rings[i+1] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i))) 
     332 
    257333    def get_interior_ring(self, ring_i): 
    258334        """Gets the interior ring at the specified index, 
     
    263339            raise IndexError, 'ring index out of range' 
    264340 
    265         # Placing the ring in internal rings dictionary. 
    266         idx = ring_i+1 # the index for the polygon is +1 because of the exterior ring 
    267         if not idx in self._rings: 
    268             self._rings[idx] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(ring_i))) 
    269  
    270         # Returning the ring at the given index. 
    271         return GEOSGeometry(self._rings[idx], child=True) 
     341        # Returning the ring from the internal ring dictionary (have to 
     342        #   add one to the index) 
     343        return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr) 
    272344                                                         
    273345    #### Polygon Properties #### 
     
    283355        else: return n 
    284356 
    285     @property 
    286     def exterior_ring(self): 
     357    def get_ext_ring(self): 
    287358        "Gets the exterior ring of the Polygon." 
    288         # Returns exterior ring  
    289         self._rings[0] = GEOSPointer(lgeos.GEOSGetExteriorRing((self._ptr()))) 
    290         return GEOSGeometry(self._rings[0], child=True) 
    291  
    292     @property 
    293     def shell(self): 
    294         "Gets the shell (exterior ring) of the Polygon." 
    295         return self.exterior_ring 
     359        return GEOSGeometry(self._rings[0], parent=self._ptr) 
     360 
     361    def set_ext_ring(self): 
     362        "Sets the exterior ring of the Polygon." 
     363        # Sets the exterior ring 
     364        raise NotImplementedError 
     365 
     366    # properties for the exterior ring/shell 
     367    exterior_ring = property(get_ext_ring) 
     368    shell = property(get_ext_ring) 
    296369     
    297370    @property 
  • django/branches/gis/django/contrib/gis/geos/__init__.py

    r5655 r5742  
    3131 
    3232from base import GEOSGeometry 
    33 from geometries import Point, LineString, LinearRing, HAS_NUMPY 
    34 from error import GEOSException 
     33from geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY 
     34from collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon 
     35from error import GEOSException, GEOSGeometryIndexError