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:
parent
9828557731
commit
8d42902f19
|
@ -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,
|
||||||
|
|
|
@ -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.')
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue