Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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 jbronn authored
2  django/contrib/gis/db/backend/postgis/__init__.py
View
@@ -19,6 +19,7 @@
envelope=ENVELOPE,
extent=EXTENT,
gis_terms=POSTGIS_TERMS,
+ geojson=ASGEOJSON,
gml=ASGML,
intersection=INTERSECTION,
kml=ASKML,
@@ -32,6 +33,7 @@
point_on_surface=POINT_ON_SURFACE,
scale=SCALE,
select=GEOM_SELECT,
+ snap_to_grid=SNAP_TO_GRID,
svg=ASSVG,
sym_difference=SYM_DIFFERENCE,
transform=TRANSFORM,
9 django/contrib/gis/db/backend/postgis/query.py
View
@@ -38,6 +38,7 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
# Functions used by the GeoManager & GeoQuerySet
AREA = get_func('Area')
+ ASGEOJSON = get_func('AsGeoJson')
ASKML = get_func('AsKML')
ASGML = get_func('AsGML')
ASSVG = get_func('AsSVG')
@@ -61,11 +62,12 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
PERIMETER = get_func('Perimeter')
POINT_ON_SURFACE = get_func('PointOnSurface')
SCALE = get_func('Scale')
+ SNAP_TO_GRID = get_func('SnapToGrid')
SYM_DIFFERENCE = get_func('SymDifference')
TRANSFORM = get_func('Transform')
TRANSLATE = get_func('Translate')
- # Special cases for union and KML methods.
+ # Special cases for union, KML, and GeoJSON methods.
if MINOR_VERSION1 < 3:
UNIONAGG = 'GeomUnion'
UNION = 'Union'
@@ -75,6 +77,11 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
if MINOR_VERSION1 == 1:
ASKML = False
+
+ # Only 1.3.4+ have AsGeoJson.
+ if (MINOR_VERSION1 < 3 or
+ (MINOR_VERSION1 == 3 and MINOR_VERSION2 < 4)):
+ ASGEOJSON = False
else:
raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
6 django/contrib/gis/db/models/manager.py
View
@@ -30,6 +30,9 @@ def envelope(self, *args, **kwargs):
def extent(self, *args, **kwargs):
return self.get_query_set().extent(*args, **kwargs)
+ def geojson(self, *args, **kwargs):
+ return self.get_query_set().geojson(*args, **kwargs)
+
def gml(self, *args, **kwargs):
return self.get_query_set().gml(*args, **kwargs)
@@ -63,6 +66,9 @@ def point_on_surface(self, *args, **kwargs):
def scale(self, *args, **kwargs):
return self.get_query_set().scale(*args, **kwargs)
+ def snap_to_grid(self, *args, **kwargs):
+ return self.get_query_set().snap_to_grid(*args, **kwargs)
+
def svg(self, *args, **kwargs):
return self.get_query_set().svg(*args, **kwargs)
62 django/contrib/gis/db/models/query.py
View
@@ -103,6 +103,32 @@ def extent(self, **kwargs):
"""
return self._spatial_aggregate(aggregates.Extent, **kwargs)
+ def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
+ """
+ Returns a GeoJSON representation of the geomtry field in a `geojson`
+ attribute on each element of the GeoQuerySet.
+
+ The `crs` and `bbox` keywords may be set to True if the users wants
+ the coordinate reference system and the bounding box to be included
+ in the GeoJSON representation of the geometry.
+ """
+ if not SpatialBackend.postgis or not SpatialBackend.geojson:
+ raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
+
+ if not isinstance(precision, (int, long)):
+ raise TypeError('Precision keyword must be set with an integer.')
+
+ # Setting the options flag
+ options = 0
+ if crs and bbox: options = 3
+ elif crs: options = 1
+ elif bbox: options = 2
+ s = {'desc' : 'GeoJSON',
+ 'procedure_args' : {'precision' : precision, 'options' : options},
+ 'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
+ }
+ return self._spatial_attribute('geojson', s, **kwargs)
+
def gml(self, precision=8, version=2, **kwargs):
"""
Returns GML representation of the given field in a `gml` attribute
@@ -213,6 +239,42 @@ def scale(self, x, y, z=0.0, **kwargs):
}
return self._spatial_attribute('scale', s, **kwargs)
+ def snap_to_grid(self, *args, **kwargs):
+ """
+ Snap all points of the input geometry to the grid. How the
+ geometry is snapped to the grid depends on how many arguments
+ were given:
+ - 1 argument : A single size to snap both the X and Y grids to.
+ - 2 arguments: X and Y sizes to snap the grid to.
+ - 4 arguments: X, Y sizes and the X, Y origins.
+ """
+ if False in [isinstance(arg, (float, int, long)) for arg in args]:
+ raise TypeError('Size argument(s) for the grid must be a float or integer values.')
+
+ nargs = len(args)
+ if nargs == 1:
+ size = args[0]
+ procedure_fmt = '%(geo_col)s,%(size)s'
+ procedure_args = {'size' : size}
+ elif nargs == 2:
+ xsize, ysize = args
+ procedure_fmt = '%(geo_col)s,%(xsize)s,%(ysize)s'
+ procedure_args = {'xsize' : xsize, 'ysize' : ysize}
+ elif nargs == 4:
+ xsize, ysize, xorigin, yorigin = args
+ procedure_fmt = '%(geo_col)s,%(xorigin)s,%(yorigin)s,%(xsize)s,%(ysize)s'
+ procedure_args = {'xsize' : xsize, 'ysize' : ysize,
+ 'xorigin' : xorigin, 'yorigin' : yorigin}
+ else:
+ raise ValueError('Must provide 1, 2, or 4 arguments to `snap_to_grid`.')
+
+ s = {'procedure_fmt' : procedure_fmt,
+ 'procedure_args' : procedure_args,
+ 'select_field' : GeomField(),
+ }
+
+ return self._spatial_attribute('snap_to_grid', s, **kwargs)
+
def svg(self, **kwargs):
"""
Returns SVG representation of the geographic field in a `svg`
76 django/contrib/gis/tests/geoapp/tests.py
View
@@ -119,7 +119,7 @@ def test02_proxy(self):
@no_oracle # Oracle does not support KML.
@no_spatialite # SpatiaLite does not support KML.
def test03a_kml(self):
- "Testing KML output from the database using GeoManager.kml()."
+ "Testing KML output from the database using GeoQuerySet.kml()."
if DISABLE: return
# Should throw a TypeError when trying to obtain KML from a
# non-geometry field.
@@ -145,7 +145,7 @@ def test03a_kml(self):
@no_spatialite # SpatiaLite does not support GML.
def test03b_gml(self):
- "Testing GML output from the database using GeoManager.gml()."
+ "Testing GML output from the database using GeoQuerySet.gml()."
if DISABLE: return
# Should throw a TypeError when tyring to obtain GML from a
# non-geometry field.
@@ -164,6 +164,38 @@ def test03b_gml(self):
for ptown in [ptown1, ptown2]:
self.assertEqual('<gml:Point srsName="EPSG:4326"><gml:coordinates>-104.609252,38.255001</gml:coordinates></gml:Point>', ptown.gml)
+ @no_spatialite
+ @no_oracle
+ def test03c_geojson(self):
+ "Testing GeoJSON output from the database using GeoQuerySet.geojson()."
+ if DISABLE: return
+ # PostGIS only supports GeoJSON on 1.3.4+
+ if not SpatialBackend.geojson:
+ return
+
+ # Precision argument should only be an integer
+ self.assertRaises(TypeError, City.objects.geojson, precision='foo')
+
+ # Reference queries and values.
+ # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
+ json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}'
+ self.assertEqual(City.objects.geojson().get(name='Pueblo').geojson, json)
+
+ # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
+ json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}'
+ # This time we want to include the CRS by using the `crs` keyword.
+ self.assertEqual(City.objects.geojson(crs=True, model_att='json').get(name='Houston').json, json)
+
+ # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria';
+ json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}'
+ # This time we include the bounding box by using the `bbox` keyword.
+ self.assertEqual(City.objects.geojson(bbox=True).get(name='Victoria').geojson, json)
+
+ # SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
+ json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
+ # Finally, we set every available keyword.
+ self.assertEqual(City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson, json)
+
def test04_transform(self):
"Testing the transform() GeoManager method."
if DISABLE: return
@@ -620,6 +652,46 @@ def test26_inherited_geofields(self):
self.assertEqual(1, qs.count())
for pc in qs: self.assertEqual(32128, pc.point.srid)
+
+ @no_spatialite
+ @no_oracle
+ def test27_snap_to_grid(self):
+ "Testing GeoQuerySet.snap_to_grid()."
+ if DISABLE: return
+
+ # Let's try and break snap_to_grid() with bad combinations of arguments.
+ for bad_args in ((), range(3), range(5)):
+ self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
+ for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))):
+ self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args)
+
+ # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org
+ # from the world borders dataset he provides.
+ wkt = ('MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,'
+ '12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,'
+ '12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,'
+ '12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,'
+ '12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,'
+ '12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,'
+ '12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,'
+ '12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))')
+ sm = Country.objects.create(name='San Marino', mpoly=fromstr(wkt))
+
+ # Because floating-point arithmitic isn't exact, we set a tolerance
+ # to pass into GEOS `equals_exact`.
+ tol = 0.000000001
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.1)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
+ self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.1).get(name='San Marino').snap_to_grid, tol))
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
+ self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23).get(name='San Marino').snap_to_grid, tol))
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.5, 0.17, 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ 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)))')
+ 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))
from test_feeds import GeoFeedTest
from test_regress import GeoRegressionTests
Please sign in to comment.
Something went wrong with that request. Please try again.