Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #9745 -- Added the `GeoQuerySet` methods `snap_to_grid` and `ge…

…ojson`.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10369 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 8d42902f1908c2fd5a894e082d3a8aead75d1c28 1 parent 9828557
Justin Bronn authored April 02, 2009
2  django/contrib/gis/db/backend/postgis/__init__.py
@@ -19,6 +19,7 @@
19 19
                                     envelope=ENVELOPE,
20 20
                                     extent=EXTENT,
21 21
                                     gis_terms=POSTGIS_TERMS,
  22
+                                    geojson=ASGEOJSON,
22 23
                                     gml=ASGML,
23 24
                                     intersection=INTERSECTION,
24 25
                                     kml=ASKML,
@@ -32,6 +33,7 @@
32 33
                                     point_on_surface=POINT_ON_SURFACE,
33 34
                                     scale=SCALE,
34 35
                                     select=GEOM_SELECT,
  36
+                                    snap_to_grid=SNAP_TO_GRID,
35 37
                                     svg=ASSVG,
36 38
                                     sym_difference=SYM_DIFFERENCE,
37 39
                                     transform=TRANSFORM,
9  django/contrib/gis/db/backend/postgis/query.py
@@ -38,6 +38,7 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
38 38
 
39 39
     # Functions used by the GeoManager & GeoQuerySet
40 40
     AREA = get_func('Area')
  41
+    ASGEOJSON = get_func('AsGeoJson')
41 42
     ASKML = get_func('AsKML')
42 43
     ASGML = get_func('AsGML')
43 44
     ASSVG = get_func('AsSVG')
@@ -61,11 +62,12 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
61 62
     PERIMETER = get_func('Perimeter')
62 63
     POINT_ON_SURFACE = get_func('PointOnSurface')
63 64
     SCALE = get_func('Scale')
  65
+    SNAP_TO_GRID = get_func('SnapToGrid')
64 66
     SYM_DIFFERENCE = get_func('SymDifference')
65 67
     TRANSFORM = get_func('Transform')
66 68
     TRANSLATE = get_func('Translate')
67 69
 
68  
-    # Special cases for union and KML methods.
  70
+    # Special cases for union, KML, and GeoJSON methods.
69 71
     if MINOR_VERSION1 < 3:
70 72
         UNIONAGG = 'GeomUnion'
71 73
         UNION = 'Union'
@@ -75,6 +77,11 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
75 77
 
76 78
     if MINOR_VERSION1 == 1:
77 79
         ASKML = False
  80
+
  81
+    # Only 1.3.4+ have AsGeoJson.
  82
+    if (MINOR_VERSION1 < 3 or 
  83
+        (MINOR_VERSION1 == 3 and MINOR_VERSION2 < 4)):
  84
+        ASGEOJSON = False
78 85
 else:
79 86
     raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
80 87
 
6  django/contrib/gis/db/models/manager.py
@@ -30,6 +30,9 @@ def envelope(self, *args, **kwargs):
30 30
     def extent(self, *args, **kwargs):
31 31
         return self.get_query_set().extent(*args, **kwargs)
32 32
 
  33
+    def geojson(self, *args, **kwargs):
  34
+        return self.get_query_set().geojson(*args, **kwargs)
  35
+
33 36
     def gml(self, *args, **kwargs):
34 37
         return self.get_query_set().gml(*args, **kwargs)
35 38
 
@@ -63,6 +66,9 @@ def point_on_surface(self, *args, **kwargs):
63 66
     def scale(self, *args, **kwargs):
64 67
         return self.get_query_set().scale(*args, **kwargs)
65 68
 
  69
+    def snap_to_grid(self, *args, **kwargs):
  70
+        return self.get_query_set().snap_to_grid(*args, **kwargs)
  71
+
66 72
     def svg(self, *args, **kwargs):
67 73
         return self.get_query_set().svg(*args, **kwargs)
68 74
 
62  django/contrib/gis/db/models/query.py
@@ -103,6 +103,32 @@ def extent(self, **kwargs):
103 103
         """
104 104
         return self._spatial_aggregate(aggregates.Extent, **kwargs)
105 105
 
  106
+    def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
  107
+        """
  108
+        Returns a GeoJSON representation of the geomtry field in a `geojson`
  109
+        attribute on each element of the GeoQuerySet.
  110
+
  111
+        The `crs` and `bbox` keywords may be set to True if the users wants
  112
+        the coordinate reference system and the bounding box to be included
  113
+        in the GeoJSON representation of the geometry.
  114
+        """
  115
+        if not SpatialBackend.postgis or not SpatialBackend.geojson:
  116
+            raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
  117
+        
  118
+        if not isinstance(precision, (int, long)):
  119
+            raise TypeError('Precision keyword must be set with an integer.')
  120
+        
  121
+        # Setting the options flag 
  122
+        options = 0
  123
+        if crs and bbox: options = 3
  124
+        elif crs: options = 1
  125
+        elif bbox: options = 2
  126
+        s = {'desc' : 'GeoJSON', 
  127
+             'procedure_args' : {'precision' : precision, 'options' : options},
  128
+             'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
  129
+             }
  130
+        return self._spatial_attribute('geojson', s, **kwargs)
  131
+
106 132
     def gml(self, precision=8, version=2, **kwargs):
107 133
         """
108 134
         Returns GML representation of the given field in a `gml` attribute
@@ -213,6 +239,42 @@ def scale(self, x, y, z=0.0, **kwargs):
213 239
                  }
214 240
         return self._spatial_attribute('scale', s, **kwargs)
215 241
 
  242
+    def snap_to_grid(self, *args, **kwargs):
  243
+        """
  244
+        Snap all points of the input geometry to the grid.  How the
  245
+        geometry is snapped to the grid depends on how many arguments
  246
+        were given:
  247
+          - 1 argument : A single size to snap both the X and Y grids to.
  248
+          - 2 arguments: X and Y sizes to snap the grid to.
  249
+          - 4 arguments: X, Y sizes and the X, Y origins.
  250
+        """
  251
+        if False in [isinstance(arg, (float, int, long)) for arg in args]:
  252
+            raise TypeError('Size argument(s) for the grid must be a float or integer values.')
  253
+
  254
+        nargs = len(args)
  255
+        if nargs == 1:
  256
+            size = args[0]
  257
+            procedure_fmt = '%(geo_col)s,%(size)s'
  258
+            procedure_args = {'size' : size}
  259
+        elif nargs == 2:
  260
+            xsize, ysize = args
  261
+            procedure_fmt = '%(geo_col)s,%(xsize)s,%(ysize)s'
  262
+            procedure_args = {'xsize' : xsize, 'ysize' : ysize}
  263
+        elif nargs == 4:
  264
+            xsize, ysize, xorigin, yorigin = args
  265
+            procedure_fmt = '%(geo_col)s,%(xorigin)s,%(yorigin)s,%(xsize)s,%(ysize)s'
  266
+            procedure_args = {'xsize' : xsize, 'ysize' : ysize,
  267
+                              'xorigin' : xorigin, 'yorigin' : yorigin}
  268
+        else:
  269
+            raise ValueError('Must provide 1, 2, or 4 arguments to `snap_to_grid`.')
  270
+
  271
+        s = {'procedure_fmt' : procedure_fmt,
  272
+             'procedure_args' : procedure_args,
  273
+             'select_field' : GeomField(),
  274
+             }
  275
+
  276
+        return self._spatial_attribute('snap_to_grid', s, **kwargs)
  277
+
216 278
     def svg(self, **kwargs):
217 279
         """
218 280
         Returns SVG representation of the geographic field in a `svg`
76  django/contrib/gis/tests/geoapp/tests.py
@@ -119,7 +119,7 @@ def test02_proxy(self):
119 119
     @no_oracle # Oracle does not support KML.
120 120
     @no_spatialite # SpatiaLite does not support KML.
121 121
     def test03a_kml(self):
122  
-        "Testing KML output from the database using GeoManager.kml()."
  122
+        "Testing KML output from the database using GeoQuerySet.kml()."
123 123
         if DISABLE: return
124 124
         # Should throw a TypeError when trying to obtain KML from a
125 125
         #  non-geometry field.
@@ -145,7 +145,7 @@ def test03a_kml(self):
145 145
 
146 146
     @no_spatialite # SpatiaLite does not support GML.
147 147
     def test03b_gml(self):
148  
-        "Testing GML output from the database using GeoManager.gml()."
  148
+        "Testing GML output from the database using GeoQuerySet.gml()."
149 149
         if DISABLE: return
150 150
         # Should throw a TypeError when tyring to obtain GML from a
151 151
         #  non-geometry field.
@@ -164,6 +164,38 @@ def test03b_gml(self):
164 164
             for ptown in [ptown1, ptown2]:
165 165
                 self.assertEqual('<gml:Point srsName="EPSG:4326"><gml:coordinates>-104.609252,38.255001</gml:coordinates></gml:Point>', ptown.gml)
166 166
 
  167
+    @no_spatialite
  168
+    @no_oracle
  169
+    def test03c_geojson(self):
  170
+        "Testing GeoJSON output from the database using GeoQuerySet.geojson()."
  171
+        if DISABLE: return
  172
+        # PostGIS only supports GeoJSON on 1.3.4+
  173
+        if not SpatialBackend.geojson:
  174
+            return
  175
+
  176
+        # Precision argument should only be an integer
  177
+        self.assertRaises(TypeError, City.objects.geojson, precision='foo')
  178
+        
  179
+        # Reference queries and values.
  180
+        # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
  181
+        json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}'
  182
+        self.assertEqual(City.objects.geojson().get(name='Pueblo').geojson, json)
  183
+
  184
+        # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
  185
+        json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}'
  186
+        # This time we want to include the CRS by using the `crs` keyword.
  187
+        self.assertEqual(City.objects.geojson(crs=True, model_att='json').get(name='Houston').json, json)
  188
+
  189
+        # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria';
  190
+        json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}'
  191
+        # This time we include the bounding box by using the `bbox` keyword.
  192
+        self.assertEqual(City.objects.geojson(bbox=True).get(name='Victoria').geojson, json)
  193
+
  194
+        # SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
  195
+        json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
  196
+        # Finally, we set every available keyword.
  197
+        self.assertEqual(City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson, json)
  198
+        
167 199
     def test04_transform(self):
168 200
         "Testing the transform() GeoManager method."
169 201
         if DISABLE: return
@@ -620,6 +652,46 @@ def test26_inherited_geofields(self):
620 652
 
621 653
         self.assertEqual(1, qs.count())
622 654
         for pc in qs: self.assertEqual(32128, pc.point.srid)
  655
+        
  656
+    @no_spatialite
  657
+    @no_oracle
  658
+    def test27_snap_to_grid(self):
  659
+        "Testing GeoQuerySet.snap_to_grid()."
  660
+        if DISABLE: return
  661
+
  662
+        # Let's try and break snap_to_grid() with bad combinations of arguments.
  663
+        for bad_args in ((), range(3), range(5)):
  664
+            self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
  665
+        for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))):
  666
+            self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args)
  667
+
  668
+        # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org
  669
+        # from the world borders dataset he provides.
  670
+        wkt = ('MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,'
  671
+               '12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,'
  672
+               '12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,'
  673
+               '12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,'
  674
+               '12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,'
  675
+               '12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,'
  676
+               '12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,'
  677
+               '12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))')
  678
+        sm = Country.objects.create(name='San Marino', mpoly=fromstr(wkt))
  679
+
  680
+        # Because floating-point arithmitic isn't exact, we set a tolerance
  681
+        # to pass into GEOS `equals_exact`.
  682
+        tol = 0.000000001
  683
+
  684
+        # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.1)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
  685
+        ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
  686
+        self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.1).get(name='San Marino').snap_to_grid, tol))
  687
+
  688
+        # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
  689
+        ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
  690
+        self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23).get(name='San Marino').snap_to_grid, tol))
  691
+
  692
+        # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.5, 0.17, 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
  693
+        ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))')
  694
+        self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol))
623 695
 
624 696
 from test_feeds import GeoFeedTest
625 697
 from test_regress import GeoRegressionTests

0 notes on commit 8d42902

Please sign in to comment.
Something went wrong with that request. Please try again.