mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: added tests, added precision keyword, and generally improved GeoQuerySet.kml(); commented, improved, and added tests for GeometryProxy.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5806 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
e982954602
commit
d2ca3b8b8f
@ -1,7 +1,17 @@
|
||||
"""
|
||||
The PostGIS spatial database backend module.
|
||||
"""
|
||||
from query import get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS
|
||||
from query import \
|
||||
get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
|
||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
|
||||
from creation import create_spatial_db
|
||||
from field import PostGISField
|
||||
|
||||
# Whether PostGIS has AsKML() support.
|
||||
if MAJOR_VERSION == 1:
|
||||
# AsKML() only supported in versions 1.2.1+
|
||||
if MINOR_VERSION1 == 3:
|
||||
ASKML = 'ST_AsKML'
|
||||
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
|
||||
ASKML = 'AsKML'
|
||||
|
||||
|
@ -7,5 +7,5 @@ class GeoManager(Manager):
|
||||
def get_query_set(self):
|
||||
return GeoQuerySet(model=self.model)
|
||||
|
||||
def kml(self, field_name):
|
||||
return self.get_query_set().kml(field_name)
|
||||
def kml(self, field_name, **kwargs):
|
||||
return self.get_query_set().kml(field_name, **kwargs)
|
||||
|
@ -1,30 +1,45 @@
|
||||
"""
|
||||
The GeometryProxy object, allows for lazy-geometries.
|
||||
The GeometryProxy object, allows for lazy-geometries. The proxy uses
|
||||
Python descriptors for instantiating and setting GEOS Geometry objects
|
||||
corresponding to geographic model fields.
|
||||
|
||||
Thanks to Robert Coup for providing this functionality (see #4322).
|
||||
"""
|
||||
|
||||
# GEOS Routines
|
||||
from types import NoneType, StringType, UnicodeType
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
|
||||
# TODO: docstrings & comments
|
||||
# TODO: docstrings
|
||||
class GeometryProxy(object):
|
||||
def __init__(self, field):
|
||||
"Proxy initializes on the given GeometryField."
|
||||
self._field = field
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
# Getting the value of the field.
|
||||
geom_value = obj.__dict__[self._field.attname]
|
||||
|
||||
if (geom_value is None) or (isinstance(geom_value, GEOSGeometry)):
|
||||
# If the value of the field is None, or is already a GEOS Geometry
|
||||
# no more work is needed.
|
||||
geom = geom_value
|
||||
else:
|
||||
geom = GEOSGeometry(geom_value)
|
||||
# Otherwise, a GEOSGeometry object is built using the field's contents,
|
||||
# and the model's corresponding attribute is set.
|
||||
geom = GEOSGeometry(geom_value)
|
||||
setattr(obj, self._field.attname, geom)
|
||||
return geom
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if isinstance(value, GEOSGeometry):
|
||||
if value and ((value.srid is None) and (self._field._srid is not None)):
|
||||
value.srid = self._field._srid
|
||||
|
||||
if isinstance(value, GEOSGeometry) and (value.geom_type.upper() == self._field._geom):
|
||||
# Getting set with GEOS Geometry; geom_type must match that of the field.
|
||||
|
||||
# If value's SRID is not set, setting it to the field's SRID.
|
||||
if value.srid is None: value.srid = self._field._srid
|
||||
elif isinstance(value, (NoneType, StringType, UnicodeType)):
|
||||
# Getting set with None, WKT, or HEX
|
||||
pass
|
||||
else:
|
||||
raise TypeError, 'cannot set %s GeometryProxy with value of type: %s' % (self._field._geom, type(value))
|
||||
obj.__dict__[self._field.attname] = value
|
||||
return value
|
||||
|
@ -1,9 +1,10 @@
|
||||
from django.db.models.query import Q, QuerySet
|
||||
import operator
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import backend
|
||||
from django.db.models.query import Q, QuerySet
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend.
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
import operator
|
||||
|
||||
class GeoQ(Q):
|
||||
"Geographical query encapsulation object."
|
||||
@ -37,10 +38,21 @@ class GeoQuerySet(QuerySet):
|
||||
clone._filters = clone._filters & reduce(operator.and_, map(mapper, args))
|
||||
return clone
|
||||
|
||||
def kml(self, field_name):
|
||||
field = self.model._meta.get_field(field_name)
|
||||
def kml(self, field_name, precision=8):
|
||||
"""Returns KML representation of the given field name in a `kml`
|
||||
attribute on each element of the QuerySet."""
|
||||
# Is KML output supported?
|
||||
try:
|
||||
from django.contrib.gis.db.backend.postgis import ASKML
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured, 'AsKML() only available in PostGIS versions 1.2.1 and greater.'
|
||||
|
||||
# Is the given field name a geographic field?
|
||||
field = self.model._meta.get_field(field_name)
|
||||
if not isinstance(field, GeometryField):
|
||||
raise TypeError, 'KML output only available on GeometryField fields.'
|
||||
field_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
|
||||
backend.quote_name(field.column))
|
||||
backend.quote_name(field.column))
|
||||
|
||||
return self.extra(select={'kml':'AsKML(%s,6)' % field_col})
|
||||
# Adding the AsKML function call to the SELECT part of the SQL.
|
||||
return self.extra(select={'kml':'%s(%s,%s)' % (ASKML, field_col, precision)})
|
||||
|
@ -1,10 +1,10 @@
|
||||
import unittest
|
||||
from models import Country, City, State
|
||||
from django.contrib.gis.geos import fromstr
|
||||
from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon
|
||||
|
||||
class GeoModelTest(unittest.TestCase):
|
||||
|
||||
def test001_initial_sql(self):
|
||||
def test01_initial_sql(self):
|
||||
"Testing geographic initial SQL."
|
||||
|
||||
# Ensuring that data was loaded from initial SQL.
|
||||
@ -12,7 +12,80 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(8, City.objects.count())
|
||||
self.assertEqual(3, State.objects.count())
|
||||
|
||||
def test002_contains_contained(self):
|
||||
def test02_proxy(self):
|
||||
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
||||
#### Testing on a Point
|
||||
pnt = Point(0, 0)
|
||||
nullcity = City(name='NullCity', point=pnt)
|
||||
nullcity.save()
|
||||
|
||||
# Making sure TypeError is thrown when trying to set with an
|
||||
# incompatible type.
|
||||
for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
|
||||
try:
|
||||
nullcity.point = bad
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.fail('Should throw a TypeError')
|
||||
|
||||
# Now setting with a compatible GEOS Geometry, saving, and ensuring
|
||||
# the save took, notice no SRID is explicitly set.
|
||||
new = Point(5, 23)
|
||||
nullcity.point = new
|
||||
|
||||
# Ensuring that the SRID is automatically set to that of the
|
||||
# field after assignment, but before saving.
|
||||
self.assertEqual(4326, nullcity.point.srid)
|
||||
nullcity.save()
|
||||
|
||||
# Ensuring the point was saved correctly after saving
|
||||
self.assertEqual(new, City.objects.get(name='NullCity').point)
|
||||
|
||||
# Setting the X and Y of the Point
|
||||
nullcity.point.x = 23
|
||||
nullcity.point.y = 5
|
||||
# Checking assignments pre & post-save.
|
||||
self.assertNotEqual(Point(23, 5), City.objects.get(name='NullCity').point)
|
||||
nullcity.save()
|
||||
self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point)
|
||||
nullcity.delete()
|
||||
|
||||
#### Testing on a Polygon
|
||||
shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0))
|
||||
inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
|
||||
|
||||
# Creating a State object using a built Polygon
|
||||
ply = Polygon(shell.clone(), inner.clone())
|
||||
nullstate = State(name='NullState', poly=ply)
|
||||
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
|
||||
nullstate.save()
|
||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
||||
|
||||
# Changing the interior ring on the poly attribute.
|
||||
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
|
||||
nullstate.poly[1] = new_inner.clone()
|
||||
ply[1] = new_inner
|
||||
self.assertEqual(4326, nullstate.poly.srid)
|
||||
nullstate.save()
|
||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
||||
nullstate.delete()
|
||||
|
||||
def test03_kml(self):
|
||||
"Testing KML output from the database using GeoManager.kml()."
|
||||
# Should throw an error trying to get KML from a non-geometry field.
|
||||
try:
|
||||
qs = City.objects.all().kml('name')
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.fail('Expected a TypeError exception')
|
||||
|
||||
# Ensuring the KML is as expected.
|
||||
ptown = City.objects.kml('point', precision=9).get(name='Pueblo')
|
||||
self.assertEqual('<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>', ptown.kml)
|
||||
|
||||
def test10_contains_contained(self):
|
||||
"Testing the 'contained' and 'contains' lookup types."
|
||||
|
||||
# Getting Texas, yes we were a country -- once ;)
|
||||
@ -47,7 +120,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
|
||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
|
||||
|
||||
def test003_lookup_insert_transform(self):
|
||||
def test11_lookup_insert_transform(self):
|
||||
"Testing automatic transform for lookups and inserts."
|
||||
|
||||
# San Antonio in 'WGS84' (SRID 4326) and 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084)
|
||||
@ -69,7 +142,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6)
|
||||
self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
|
||||
|
||||
def test004_null_geometries(self):
|
||||
def test12_null_geometries(self):
|
||||
"Testing NULL geometry support."
|
||||
|
||||
# Querying for both NULL and Non-NULL values.
|
||||
@ -90,7 +163,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
nmi = State(name='Northern Mariana Islands', poly=None)
|
||||
nmi.save()
|
||||
|
||||
def test005_left_right(self):
|
||||
def test13_left_right(self):
|
||||
"Testing the 'left' and 'right' lookup types."
|
||||
|
||||
# Left: A << B => true if xmax(A) < xmin(B)
|
||||
|
@ -268,7 +268,7 @@ class LayerMapping:
|
||||
# Creating the CoordTransform object
|
||||
ct = CoordTransform(self.source_srs, target_srs)
|
||||
except Exception, msg:
|
||||
raise Exception, 'Could not translate between the data source and model geometry.'
|
||||
raise Exception, 'Could not translate between the data source and model geometry: %s' % msg
|
||||
|
||||
for feat in self.layer:
|
||||
# The keyword arguments for model construction
|
||||
|
Loading…
x
Reference in New Issue
Block a user