Fixed #9745 -- Added the `GeoQuerySet` methods `snap_to_grid` and `geojson`.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10369 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2009-04-02 16:50:44 +00:00
parent 9828557731
commit 8d42902f19
5 changed files with 152 additions and 3 deletions

View File

@ -19,6 +19,7 @@ SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
envelope=ENVELOPE, envelope=ENVELOPE,
extent=EXTENT, extent=EXTENT,
gis_terms=POSTGIS_TERMS, gis_terms=POSTGIS_TERMS,
geojson=ASGEOJSON,
gml=ASGML, gml=ASGML,
intersection=INTERSECTION, intersection=INTERSECTION,
kml=ASKML, kml=ASKML,
@ -32,6 +33,7 @@ SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
point_on_surface=POINT_ON_SURFACE, point_on_surface=POINT_ON_SURFACE,
scale=SCALE, scale=SCALE,
select=GEOM_SELECT, select=GEOM_SELECT,
snap_to_grid=SNAP_TO_GRID,
svg=ASSVG, svg=ASSVG,
sym_difference=SYM_DIFFERENCE, sym_difference=SYM_DIFFERENCE,
transform=TRANSFORM, transform=TRANSFORM,

View File

@ -38,6 +38,7 @@ if MAJOR_VERSION >= 1:
# Functions used by the GeoManager & GeoQuerySet # Functions used by the GeoManager & GeoQuerySet
AREA = get_func('Area') AREA = get_func('Area')
ASGEOJSON = get_func('AsGeoJson')
ASKML = get_func('AsKML') ASKML = get_func('AsKML')
ASGML = get_func('AsGML') ASGML = get_func('AsGML')
ASSVG = get_func('AsSVG') ASSVG = get_func('AsSVG')
@ -61,11 +62,12 @@ if MAJOR_VERSION >= 1:
PERIMETER = get_func('Perimeter') PERIMETER = get_func('Perimeter')
POINT_ON_SURFACE = get_func('PointOnSurface') POINT_ON_SURFACE = get_func('PointOnSurface')
SCALE = get_func('Scale') SCALE = get_func('Scale')
SNAP_TO_GRID = get_func('SnapToGrid')
SYM_DIFFERENCE = get_func('SymDifference') SYM_DIFFERENCE = get_func('SymDifference')
TRANSFORM = get_func('Transform') TRANSFORM = get_func('Transform')
TRANSLATE = get_func('Translate') TRANSLATE = get_func('Translate')
# Special cases for union and KML methods. # Special cases for union, KML, and GeoJSON methods.
if MINOR_VERSION1 < 3: if MINOR_VERSION1 < 3:
UNIONAGG = 'GeomUnion' UNIONAGG = 'GeomUnion'
UNION = 'Union' UNION = 'Union'
@ -75,6 +77,11 @@ if MAJOR_VERSION >= 1:
if MINOR_VERSION1 == 1: if MINOR_VERSION1 == 1:
ASKML = False ASKML = False
# Only 1.3.4+ have AsGeoJson.
if (MINOR_VERSION1 < 3 or
(MINOR_VERSION1 == 3 and MINOR_VERSION2 < 4)):
ASGEOJSON = False
else: else:
raise NotImplementedError('PostGIS versions < 1.0 are not supported.') raise NotImplementedError('PostGIS versions < 1.0 are not supported.')

View File

@ -30,6 +30,9 @@ class GeoManager(Manager):
def extent(self, *args, **kwargs): def extent(self, *args, **kwargs):
return self.get_query_set().extent(*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): def gml(self, *args, **kwargs):
return self.get_query_set().gml(*args, **kwargs) return self.get_query_set().gml(*args, **kwargs)
@ -63,6 +66,9 @@ class GeoManager(Manager):
def scale(self, *args, **kwargs): def scale(self, *args, **kwargs):
return self.get_query_set().scale(*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): def svg(self, *args, **kwargs):
return self.get_query_set().svg(*args, **kwargs) return self.get_query_set().svg(*args, **kwargs)

View File

@ -103,6 +103,32 @@ class GeoQuerySet(QuerySet):
""" """
return self._spatial_aggregate(aggregates.Extent, **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): def gml(self, precision=8, version=2, **kwargs):
""" """
Returns GML representation of the given field in a `gml` attribute Returns GML representation of the given field in a `gml` attribute
@ -213,6 +239,42 @@ class GeoQuerySet(QuerySet):
} }
return self._spatial_attribute('scale', s, **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): def svg(self, **kwargs):
""" """
Returns SVG representation of the geographic field in a `svg` Returns SVG representation of the geographic field in a `svg`

View File

@ -119,7 +119,7 @@ class GeoModelTest(unittest.TestCase):
@no_oracle # Oracle does not support KML. @no_oracle # Oracle does not support KML.
@no_spatialite # SpatiaLite does not support KML. @no_spatialite # SpatiaLite does not support KML.
def test03a_kml(self): 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 if DISABLE: return
# Should throw a TypeError when trying to obtain KML from a # Should throw a TypeError when trying to obtain KML from a
# non-geometry field. # non-geometry field.
@ -145,7 +145,7 @@ class GeoModelTest(unittest.TestCase):
@no_spatialite # SpatiaLite does not support GML. @no_spatialite # SpatiaLite does not support GML.
def test03b_gml(self): 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 if DISABLE: return
# Should throw a TypeError when tyring to obtain GML from a # Should throw a TypeError when tyring to obtain GML from a
# non-geometry field. # non-geometry field.
@ -164,6 +164,38 @@ class GeoModelTest(unittest.TestCase):
for ptown in [ptown1, ptown2]: for ptown in [ptown1, ptown2]:
self.assertEqual('<gml:Point srsName="EPSG:4326"><gml:coordinates>-104.609252,38.255001</gml:coordinates></gml:Point>', ptown.gml) 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): def test04_transform(self):
"Testing the transform() GeoManager method." "Testing the transform() GeoManager method."
if DISABLE: return if DISABLE: return
@ -621,6 +653,46 @@ class GeoModelTest(unittest.TestCase):
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())
for pc in qs: self.assertEqual(32128, pc.point.srid) 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_feeds import GeoFeedTest
from test_regress import GeoRegressionTests from test_regress import GeoRegressionTests
from test_sitemaps import GeoSitemapTest from test_sitemaps import GeoSitemapTest