summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/gis/db/backend/postgis/__init__.py12
-rw-r--r--django/contrib/gis/db/models/manager.py4
-rw-r--r--django/contrib/gis/db/models/proxy.py31
-rw-r--r--django/contrib/gis/db/models/query.py26
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py85
-rw-r--r--django/contrib/gis/utils/LayerMapping.py2
6 files changed, 135 insertions, 25 deletions
diff --git a/django/contrib/gis/db/backend/postgis/__init__.py b/django/contrib/gis/db/backend/postgis/__init__.py
index 0564ad7348..7a1cab251b 100644
--- a/django/contrib/gis/db/backend/postgis/__init__.py
+++ b/django/contrib/gis/db/backend/postgis/__init__.py
@@ -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'
+
diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py
index 5faa259a12..e01f1aa724 100644
--- a/django/contrib/gis/db/models/manager.py
+++ b/django/contrib/gis/db/models/manager.py
@@ -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)
diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py
index de366d0186..e8fbb87bd4 100644
--- a/django/contrib/gis/db/models/proxy.py
+++ b/django/contrib/gis/db/models/proxy.py
@@ -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
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index dab1f04eb6..6bf51fc231 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -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)})
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index dcc5e681ff..f416b5b355 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -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)
diff --git a/django/contrib/gis/utils/LayerMapping.py b/django/contrib/gis/utils/LayerMapping.py
index 07708b3b52..facb4975b3 100644
--- a/django/contrib/gis/utils/LayerMapping.py
+++ b/django/contrib/gis/utils/LayerMapping.py
@@ -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