Django

Code

Changeset 7114

Show
Ignore:
Timestamp:
02/14/08 10:33:16 (5 months ago)
Author:
jbronn
Message:

gis: geos: Added GeoJSON input/output support for GEOSGeometry (requires GDAL 1.5+); Polygons may now be instantiated with tuples of coordinates instead of only LinearRing instances; added extent property; added coords property alias for tuple (and added tuple for GeometryCollection); made small optimizations to KML and coordinate array generators. Small GDAL protochange that didn't make last commit: get_envelope is now generated w/the previously unused env_func.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/gdal/prototypes/geom.py

    r7113 r7114  
    106106 
    107107# For retrieving the envelope of the geometry. 
    108 get_envelope = lgdal.OGR_G_GetEnvelope 
    109 get_envelope.restype = None 
    110 get_envelope.argtypes = [c_void_p, POINTER(OGREnvelope)] 
    111 get_envelope.errcheck = check_envelope 
     108get_envelope = env_func(lgdal.OGR_G_GetEnvelope, [c_void_p, POINTER(OGREnvelope)]) 
     109 
  • django/branches/gis/django/contrib/gis/geos/base.py

    r7101 r7114  
    66import re 
    77from ctypes import addressof, byref, c_double, c_size_t 
    8 from types import StringType, UnicodeType, IntType, FloatType, BufferType 
     8from types import UnicodeType 
    99 
    1010# GEOS-related dependencies. 
     
    2121# try/except since this package may be used outside GeoDjango. 
    2222try: 
    23     from django.contrib.gis.gdal import OGRGeometry, SpatialReference 
    24     HAS_GDAL=True 
     23    from django.contrib.gis.gdal import OGRGeometry, SpatialReference, GEOJSON 
     24    HAS_GDAL = True 
    2525except: 
    26     HAS_GDAL=False 
     26    HAS_GDAL, GEOJSON = False, False 
    2727 
    2828# Regular expression for recognizing HEXEWKB and WKT.  A prophylactic measure 
     
    3131hex_regex = re.compile(r'^[0-9A-F]+$', re.I) 
    3232wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I) 
     33json_regex = re.compile(r'^\{.+\}$') 
    3334 
    3435class GEOSGeometry(object): 
     
    5152        (SRID) number for this Geometry.  If not set, the SRID will be None. 
    5253        """  
    53         if isinstance(geo_input, UnicodeType): 
    54             # Encoding to ASCII, WKT or HEXEWKB doesn't need any more. 
    55             geo_input = geo_input.encode('ascii') 
    56         if isinstance(geo_input, StringType): 
    57             if hex_regex.match(geo_input): 
    58                 # If the regex matches, the geometry is in HEX form. 
     54        if isinstance(geo_input, basestring): 
     55            if isinstance(geo_input, UnicodeType): 
     56                # Encoding to ASCII, WKT or HEXEWKB doesn't need any more. 
     57                geo_input = geo_input.encode('ascii') 
     58                             
     59            wkt_m = wkt_regex.match(geo_input) 
     60            if wkt_m: 
     61                # Handling WKT input. 
     62                if wkt_m.group('srid'): srid = int(wkt_m.group('srid')) 
     63                g = from_wkt(wkt_m.group('wkt')) 
     64            elif hex_regex.match(geo_input): 
     65                # Handling HEXEWKB input. 
    5966                g = from_hex(geo_input, len(geo_input)) 
     67            elif GEOJSON and json_regex.match(geo_input): 
     68                # Handling GeoJSON input. 
     69                wkb_input = str(OGRGeometry(geo_input).wkb) 
     70                g = from_wkb(wkb_input, len(wkb_input)) 
    6071            else: 
    61                 m = wkt_regex.match(geo_input) 
    62                 if m: 
    63                     if m.group('srid'): srid = int(m.group('srid')) 
    64                     g = from_wkt(m.group('wkt')) 
    65                 else: 
    66                     raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.') 
     72                raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.') 
    6773        elif isinstance(geo_input, GEOM_PTR): 
    6874            # When the input is a pointer to a geomtry (GEOM_PTR). 
    6975            g = geo_input 
    70         elif isinstance(geo_input, BufferType): 
     76        elif isinstance(geo_input, buffer): 
    7177            # When the input is a buffer (WKB). 
    7278            wkb_input = str(geo_input) 
     
    192198    def coord_seq(self): 
    193199        "Returns a clone of the coordinate sequence for this Geometry." 
    194         return self._cs.clone() 
     200        if self.has_cs: 
     201            return self._cs.clone() 
    195202 
    196203    #### Geometry Info #### 
     
    308315        two Geometries match the elements in pattern. 
    309316        """ 
    310         if not isinstance(pattern, StringType) or len(pattern) > 9: 
     317        if not isinstance(pattern, str) or len(pattern) > 9: 
    311318            raise GEOSException('invalid intersection matrix pattern') 
    312319        return geos_relatepattern(self.ptr, other.ptr, pattern) 
     
    360367        #  str(self.wkb).encode('hex') 
    361368        return to_hex(self.ptr, byref(c_size_t())) 
     369 
     370    @property 
     371    def json(self): 
     372        """ 
     373        Returns GeoJSON representation of this Geometry if GDAL 1.5+  
     374        is installed. 
     375        """ 
     376        if GEOJSON: return self.ogr.json 
     377    geojson = json 
    362378 
    363379    @property 
     
    523539 
    524540    @property 
     541    def extent(self): 
     542        """ 
     543        Returns the extent of this geometry as a 4-tuple, consisting of 
     544        (xmin, ymin, xmax, ymax). 
     545        """ 
     546        env = self.envelope 
     547        if isinstance(env, Point): 
     548            xmin, ymin = env.tuple 
     549            xmax, ymax = xmin, ymin 
     550        else: 
     551            xmin, ymin = env[0][0] 
     552            xmax, ymax = env[0][2] 
     553        return (xmin, ymin, xmax, ymax) 
     554 
     555    @property 
    525556    def length(self): 
    526557        """ 
  • django/branches/gis/django/contrib/gis/geos/collections.py

    r6978 r7114  
    8686    def kml(self): 
    8787        "Returns the KML for this Geometry Collection." 
    88         kml = '<MultiGeometry>' 
    89         for g in self: kml += g.kml 
    90         return kml + '</MultiGeometry>' 
     88        return '<MultiGeometry>%s</MultiGeometry>' % ''.join([g.kml for g in self]) 
     89 
     90    @property 
     91    def tuple(self): 
     92        "Returns a tuple of all the coordinates in this Geometry Collection" 
     93        return tuple([g.tuple for g in self]) 
     94    coords = tuple 
    9195 
    9296# MultiPoint, MultiLineString, and MultiPolygon class definitions. 
  • django/branches/gis/django/contrib/gis/geos/coordseq.py

    r6978 r7114  
    154154        if self.hasz: substr = '%s,%s,%s ' 
    155155        else: substr = '%s,%s,0 ' 
    156         kml = '<coordinates>' 
    157         for i in xrange(len(self)): 
    158             kml += substr % self[i] 
    159         return kml.strip() + '</coordinates>' 
     156        return '<coordinates>%s</coordinates>' % \ 
     157            ''.join([substr % self[i] for i in xrange(len(self))]).strip() 
    160158 
    161159    @property 
     
    164162        n = self.size 
    165163        if n == 1: return self[0] 
    166         else: return tuple(self[i] for i in xrange(n)
     164        else: return tuple([self[i] for i in xrange(n)]
  • django/branches/gis/django/contrib/gis/geos/geometries.py

    r6978 r7114  
    55""" 
    66from ctypes import c_uint, byref 
    7 from types import FloatType, IntType, ListType, TupleType 
    87from django.contrib.gis.geos.base import GEOSGeometry 
    98from django.contrib.gis.geos.coordseq import GEOSCoordSeq 
     
    2524        """ 
    2625 
    27         if isinstance(x, (TupleType, ListType)): 
     26        if isinstance(x, (tuple, list)): 
    2827            # Here a tuple or list was passed in under the `x` parameter. 
    2928            ndim = len(x) 
     
    3130                raise TypeError('Invalid sequence parameter: %s' % str(x)) 
    3231            coords = x 
    33         elif isinstance(x, (IntType, FloatType)) and isinstance(y, (IntType, FloatType)): 
     32        elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)): 
    3433            # Here X, Y, and (optionally) Z were passed in individually, as parameters. 
    35             if isinstance(z, (IntType, FloatType)): 
     34            if isinstance(z, (int, float, long)): 
    3635                ndim = 3 
    3736                coords = [x, y, z] 
     
    104103    # The tuple and coords properties 
    105104    tuple = property(get_coords, set_coords) 
    106     coords = property(get_coords, set_coords) 
     105    coords = tuple 
    107106 
    108107class LineString(GEOSGeometry): 
     
    125124        else: coords = args 
    126125 
    127         if isinstance(coords, (TupleType, ListType)): 
     126        if isinstance(coords, (tuple, list)): 
    128127            # Getting the number of coords and the number of dimensions -- which 
    129128            #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)). 
     
    134133            # Incrementing through each of the coordinates and verifying 
    135134            for i in xrange(1, ncoords): 
    136                 if not isinstance(coords[i], (TupleType, ListType, Point)): 
     135                if not isinstance(coords[i], (tuple, list, Point)): 
    137136                    raise TypeError('each coordinate should be a sequence (list or tuple)') 
    138137                if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.') 
     
    194193        "Returns a tuple version of the geometry from the coordinate sequence." 
    195194        return self._cs.tuple 
     195    coords = tuple 
    196196 
    197197    def _listarr(self, func): 
     
    237237        """ 
    238238        Initializes on an exterior ring and a sequence of holes (both 
    239         instances of LinearRings. 
     239        instances may be either LinearRing instances, or a tuple/list 
     240        that may be constructed into a LinearRing). 
    240241         
    241         Below are some examples of initialization, where shell, hole1, and  
    242         hole2 are valid LinearRing geometries: 
     242        Examples of initialization, where shell, hole1, and hole2 are  
     243        valid LinearRing geometries: 
    243244        >>> poly = Polygon(shell, hole1, hole2) 
    244245        >>> poly = Polygon(shell, (hole1, hole2)) 
     246 
     247        Example where a tuple parameters are used: 
     248        >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),  
     249                           ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) 
    245250        """ 
    246251        if not args: 
     
    250255        ext_ring = args[0] 
    251256        init_holes = args[1:] 
    252         if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)):  
     257        n_holes = len(init_holes) 
     258 
     259        # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility] 
     260        if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \ 
     261                (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)):  
    253262            init_holes = init_holes[0] 
    254  
    255         # Ensuring the exterior ring parameter is a LinearRing object 
    256         if not isinstance(ext_ring, LinearRing): 
    257             raise TypeError('First argument for Polygon initialization must be a LinearRing.') 
    258  
    259         # Making sure all of the holes are LinearRing objects 
    260         if False in [isinstance(hole, LinearRing) for hole in init_holes]
    261             raise TypeError('Holes parameter must be a sequence of LinearRings.'
    262  
    263         # Getting the holes array. 
    264         nholes = len(init_holes) 
    265         holes = get_pointer_arr(nholes) 
    266         for i in xrange(nholes): holes[i] = geom_clone(init_holes[i].ptr) 
     263            n_holes = len(init_holes) 
     264 
     265        # Ensuring the exterior ring and holes parameters are LinearRing objects 
     266        # or may be instantiated into LinearRings. 
     267        ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.') 
     268        holes_list = [] # Create new list, cause init_holes is a tuple. 
     269        for i in xrange(n_holes)
     270            holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')
     271 
     272        # Why another loop?  Because if a TypeError is raised, cloned pointers will 
     273        # be around that can't be cleaned up. 
     274        holes = get_pointer_arr(n_holes) 
     275        for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr) 
    267276                       
    268         # Getting the shell pointer address,  
     277        # Getting the shell pointer address. 
    269278        shell = geom_clone(ext_ring.ptr) 
    270279 
    271280        # Calling with the GEOS createPolygon factory. 
    272         super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(nholes)), **kwargs) 
     281        super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs) 
    273282 
    274283    def __getitem__(self, index): 
    275284        """ 
    276         Returns the ring at the specified index.  The first index, 0, will always 
    277         return the exterior ring.  Indices > 0 will return the interior ring. 
     285        Returns the ring at the specified index.  The first index, 0, will  
     286        always return the exterior ring.  Indices > 0 will return the  
     287        interior ring at the given index (e.g., poly[1] and poly[2] would 
     288        return the first and second interior ring, respectively). 
    278289        """ 
    279290        if index == 0: 
     
    331342            raise GEOSIndexError('invalid Polygon ring index: %s' % index) 
    332343 
     344    def _construct_ring(self, param, msg=''): 
     345        "Helper routine for trying to construct a ring from the given parameter." 
     346        if isinstance(param, LinearRing): return param 
     347        try: 
     348            ring = LinearRing(param) 
     349            return ring 
     350        except TypeError: 
     351            raise TypeError(msg) 
     352 
    333353    def get_interior_ring(self, ring_i): 
    334354        """ 
     
    356376    # properties for the exterior ring/shell 
    357377    exterior_ring = property(get_ext_ring, set_ext_ring) 
    358     shell = property(get_ext_ring, set_ext_ring) 
     378    shell = exterior_ring 
    359379     
    360380    @property 
    361381    def tuple(self): 
    362382        "Gets the tuple for each ring in this Polygon." 
    363         return tuple(self[i].tuple for i in xrange(len(self))) 
     383        return tuple([self[i].tuple for i in xrange(len(self))]) 
     384    coords = tuple 
    364385 
    365386    @property 
    366387    def kml(self): 
    367388        "Returns the KML representation of this Polygon." 
    368         inner_kml = '' 
    369         if self.num_interior_rings > 0:  
    370             for i in xrange(self.num_interior_rings): 
    371                 inner_kml += "<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml 
     389        inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml  
     390                             for i in xrange(self.num_interior_rings)]) 
    372391        return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) 
  • django/branches/gis/django/contrib/gis/tests/test_geos.py

    r7101 r7114  
    66     
    77if HAS_NUMPY: from numpy import array 
    8 if HAS_GDAL: from django.contrib.gis.gdal import OGRGeometry, SpatialReference, CoordTransform 
     8if HAS_GDAL: from django.contrib.gis.gdal import OGRGeometry, SpatialReference, CoordTransform, GEOJSON 
    99 
    1010class GEOSTest(unittest.TestCase): 
     
    8686            self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export 
    8787     
    88     def test01i_eq(self): 
     88    def test01i_json(self): 
     89        "Testing GeoJSON input/output (via GDAL)." 
     90        if not HAS_GDAL or not GEOJSON: return 
     91        for g in json_geoms: 
     92            geom = GEOSGeometry(g.wkt) 
     93            self.assertEqual(g.json, geom.json) 
     94            self.assertEqual(g.json, geom.geojson) 
     95            self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) 
     96 
     97    def test01j_eq(self): 
    8998        "Testing equivalence with WKT." 
    9099        p = fromstr('POINT(5 23)') 
     
    269278            # Testing __getitem__ and __setitem__ on invalid indices 
    270279            self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly)) 
    271             #self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False) 
     280            self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False) 
    272281            self.assertRaises(GEOSIndexError, poly.__getitem__, -1) 
    273282 
     
    280289            self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3]) 
    281290            self.assertRaises(TypeError, Polygon.__init__, 'foo') 
     291             
     292            # Polygon(shell, (hole1, ... holeN)) 
    282293            rings = tuple(r for r in poly) 
    283294            self.assertEqual(poly, Polygon(rings[0], rings[1:])) 
     295             
     296            # Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN) 
     297            ring_tuples = tuple(r.tuple for r in poly) 
     298            self.assertEqual(poly, Polygon(*ring_tuples)) 
     299 
     300            # Constructing with tuples of LinearRings. 
    284301            self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) 
    285302            self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt) 
     
    687704        ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774)) 
    688705        t3.transform(ct) 
    689  
     706        prec = 3 
    690707        for p in (t1, t2, t3): 
    691             prec = 3 
    692708            self.assertAlmostEqual(trans.x, p.x, prec) 
    693709            self.assertAlmostEqual(trans.y, p.y, prec) 
     710 
     711    def test24_extent(self): 
     712        "Testing `extent` method." 
     713        # The xmin, ymin, xmax, ymax of the MultiPoint should be returned. 
     714        mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50)) 
     715        self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent) 
     716        pnt = Point(5.23, 17.8) 
     717        # Extent of points is just the point itself repeated. 
     718        self.assertEqual((5.23, 17.8, 5.23, 17.8), pnt.extent) 
     719        # Testing on the 'real world' Polygon. 
     720        poly = fromstr(polygons[3].wkt) 
     721        ring = poly.shell 
     722        x, y = ring.x, ring.y 
     723        xmin, ymin = min(x), min(y) 
     724        xmax, ymax = max(x), max(y) 
     725        self.assertEqual((xmin, ymin, xmax, ymax), poly.extent) 
    694726 
    695727def suite():