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.
|
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 creation import create_spatial_db
|
||||||
from field import PostGISField
|
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):
|
def get_query_set(self):
|
||||||
return GeoQuerySet(model=self.model)
|
return GeoQuerySet(model=self.model)
|
||||||
|
|
||||||
def kml(self, field_name):
|
def kml(self, field_name, **kwargs):
|
||||||
return self.get_query_set().kml(field_name)
|
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).
|
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
|
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||||
|
|
||||||
# TODO: docstrings & comments
|
# TODO: docstrings
|
||||||
class GeometryProxy(object):
|
class GeometryProxy(object):
|
||||||
def __init__(self, field):
|
def __init__(self, field):
|
||||||
|
"Proxy initializes on the given GeometryField."
|
||||||
self._field = field
|
self._field = field
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
def __get__(self, obj, type=None):
|
||||||
|
# Getting the value of the field.
|
||||||
geom_value = obj.__dict__[self._field.attname]
|
geom_value = obj.__dict__[self._field.attname]
|
||||||
|
|
||||||
if (geom_value is None) or (isinstance(geom_value, GEOSGeometry)):
|
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
|
geom = geom_value
|
||||||
else:
|
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)
|
setattr(obj, self._field.attname, geom)
|
||||||
return geom
|
return geom
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
def __set__(self, obj, value):
|
||||||
if isinstance(value, GEOSGeometry):
|
if isinstance(value, GEOSGeometry) and (value.geom_type.upper() == self._field._geom):
|
||||||
if value and ((value.srid is None) and (self._field._srid is not None)):
|
# Getting set with GEOS Geometry; geom_type must match that of the field.
|
||||||
value.srid = self._field._srid
|
|
||||||
|
# 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
|
obj.__dict__[self._field.attname] = value
|
||||||
return 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 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.models.fields import GeometryField
|
||||||
from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend.
|
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):
|
class GeoQ(Q):
|
||||||
"Geographical query encapsulation object."
|
"Geographical query encapsulation object."
|
||||||
@ -37,10 +38,21 @@ class GeoQuerySet(QuerySet):
|
|||||||
clone._filters = clone._filters & reduce(operator.and_, map(mapper, args))
|
clone._filters = clone._filters & reduce(operator.and_, map(mapper, args))
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
def kml(self, field_name):
|
def kml(self, field_name, precision=8):
|
||||||
field = self.model._meta.get_field(field_name)
|
"""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),
|
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
|
import unittest
|
||||||
from models import Country, City, State
|
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):
|
class GeoModelTest(unittest.TestCase):
|
||||||
|
|
||||||
def test001_initial_sql(self):
|
def test01_initial_sql(self):
|
||||||
"Testing geographic initial SQL."
|
"Testing geographic initial SQL."
|
||||||
|
|
||||||
# Ensuring that data was loaded from 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(8, City.objects.count())
|
||||||
self.assertEqual(3, State.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."
|
"Testing the 'contained' and 'contains' lookup types."
|
||||||
|
|
||||||
# Getting Texas, yes we were a country -- once ;)
|
# 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=pueblo.point))) # Query w/GEOSGeometry object
|
||||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
|
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."
|
"Testing automatic transform for lookups and inserts."
|
||||||
|
|
||||||
# San Antonio in 'WGS84' (SRID 4326) and 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084)
|
# 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.x, sa.point.x, 6)
|
||||||
self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
|
self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
|
||||||
|
|
||||||
def test004_null_geometries(self):
|
def test12_null_geometries(self):
|
||||||
"Testing NULL geometry support."
|
"Testing NULL geometry support."
|
||||||
|
|
||||||
# Querying for both NULL and Non-NULL values.
|
# 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 = State(name='Northern Mariana Islands', poly=None)
|
||||||
nmi.save()
|
nmi.save()
|
||||||
|
|
||||||
def test005_left_right(self):
|
def test13_left_right(self):
|
||||||
"Testing the 'left' and 'right' lookup types."
|
"Testing the 'left' and 'right' lookup types."
|
||||||
|
|
||||||
# Left: A << B => true if xmax(A) < xmin(B)
|
# Left: A << B => true if xmax(A) < xmin(B)
|
||||||
|
@ -268,7 +268,7 @@ class LayerMapping:
|
|||||||
# Creating the CoordTransform object
|
# Creating the CoordTransform object
|
||||||
ct = CoordTransform(self.source_srs, target_srs)
|
ct = CoordTransform(self.source_srs, target_srs)
|
||||||
except Exception, msg:
|
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:
|
for feat in self.layer:
|
||||||
# The keyword arguments for model construction
|
# The keyword arguments for model construction
|
||||||
|
Loading…
x
Reference in New Issue
Block a user