Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

gis: Added the `extent` method to `GeoQuerySet`; moved various spatia…

…l-backend settings into the `SpatialBackend` container class.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7028 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 398eca3fb2ac84304b179fabd2f0960228c59b09 1 parent feebe39
Justin Bronn jbronn authored
29 django/contrib/gis/db/backend/__init__.py
View
@@ -5,17 +5,14 @@
needed for GeoDjango.
(1) GeoBackEndField, a base class needed for GeometryField.
- (2) GeometryProxy, for lazy-instantiated geometries from the
- database output.
- (3) GIS_TERMS, a list of acceptable geographic lookup types for
+ (2) GIS_TERMS, a list of acceptable geographic lookup types for
the backend.
- (4) The `parse_lookup` function, used for spatial SQL construction by
+ (3) The `parse_lookup` function, used for spatial SQL construction by
the GeoQuerySet.
- (5) The `create_spatial_db`, and `get_geo_where_clause`
+ (4) The `create_spatial_db`, and `get_geo_where_clause`
routines (needed by `parse_lookup`).
-
- Currently only PostGIS is supported, but someday backends will be added for
- additional spatial databases (e.g., Oracle, DB2).
+ (5) The `SpatialBackend` object, which contains information specific
+ to the spatial backend.
"""
from types import StringType, UnicodeType
from django.conf import settings
@@ -26,7 +23,7 @@
from django.contrib.gis.geos import GEOSGeometry
# These routines (needed by GeoManager), default to False.
-ASGML, ASKML, DISTANCE, TRANSFORM, UNION, VERSION = (False, False, False, False, False, False)
+ASGML, ASKML, DISTANCE, EXTENT, TRANSFORM, UNION, VERSION = (False, False, False, False, False, False, False)
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
# PostGIS is the spatial database, getting the rquired modules,
@@ -34,7 +31,7 @@
from django.contrib.gis.db.backend.postgis import \
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
create_spatial_db, get_geo_where_clause, \
- ASGML, ASKML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION, \
+ ASGML, ASKML, DISTANCE, EXTENT, GEOM_SELECT, TRANSFORM, UNION, \
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2)
SPATIAL_BACKEND = 'postgis'
@@ -55,6 +52,18 @@
else:
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
+class SpatialBackend(object):
+ "A container for properties of the Spatial Backend."
+ as_kml = ASKML
+ as_gml = ASGML
+ distance = DISTANCE
+ extent = EXTENT
+ name = SPATIAL_BACKEND
+ select = GEOM_SELECT
+ transform = TRANSFORM
+ union = UNION
+ version = VERSION
+
#### query.py overloaded functions ####
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
# counterparts to support constructing SQL for geographic queries.
2  django/contrib/gis/db/backend/postgis/__init__.py
View
@@ -6,4 +6,4 @@
from django.contrib.gis.db.backend.postgis.query import \
get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \
- ASKML, ASGML, DISTANCE, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT
+ ASKML, ASGML, DISTANCE, EXTENT, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT
1  django/contrib/gis/db/backend/postgis/query.py
View
@@ -40,6 +40,7 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
ASKML = get_func('AsKML')
ASGML = get_func('AsGML')
DISTANCE = get_func('Distance')
+ EXTENT = get_func('extent')
GEOM_FROM_TEXT = get_func('GeomFromText')
GEOM_FROM_WKB = get_func('GeomFromWKB')
TRANSFORM = get_func('Transform')
3  django/contrib/gis/db/models/manager.py
View
@@ -10,6 +10,9 @@ def get_query_set(self):
def distance(self, *args, **kwargs):
return self.get_query_set().distance(*args, **kwargs)
+ def extent(self, *args, **kwargs):
+ return self.get_query_set().extent(*args, **kwargs)
+
def gml(self, *args, **kwargs):
return self.get_query_set().gml(*args, **kwargs)
62 django/contrib/gis/db/models/query.py
View
@@ -6,13 +6,12 @@
from django.utils.datastructures import SortedDict
from django.contrib.gis.db.models.fields import GeometryField
# parse_lookup depends on the spatial database backend.
-from django.contrib.gis.db.backend import parse_lookup, \
- ASGML, ASKML, DISTANCE, GEOM_SELECT, SPATIAL_BACKEND, TRANSFORM, UNION, VERSION
+from django.contrib.gis.db.backend import parse_lookup, SpatialBackend
from django.contrib.gis.geos import GEOSGeometry
# Shortcut booleans for determining the backend.
-oracle = SPATIAL_BACKEND == 'oracle'
-postgis = SPATIAL_BACKEND == 'postgis'
+oracle = SpatialBackend.name == 'oracle'
+postgis = SpatialBackend.name == 'postgis'
class GeoQ(Q):
"Geographical query encapsulation object."
@@ -23,7 +22,7 @@ def get_sql(self, opts):
class GeoQuerySet(QuerySet):
"Geographical-enabled QuerySet object."
-
+
#### Overloaded QuerySet Routines ####
def __init__(self, model=None):
super(GeoQuerySet, self).__init__(model=model)
@@ -37,10 +36,10 @@ def __init__(self, model=None):
# If GEOM_SELECT is defined in the backend, then it will be used
# for the selection format of the geometry column.
- if GEOM_SELECT:
+ if SpatialBackend.select:
# Transformed geometries in Oracle use EWKT so that the SRID
# on the transformed lazy geometries is set correctly).
- self._geo_fmt = GEOM_SELECT
+ self._geo_fmt = SpatialBackend.select
else:
self._geo_fmt = '%s'
@@ -259,6 +258,7 @@ def distance(self, *args, **kwargs):
given geometry in a `distance` attribute on each element of the
GeoQuerySet.
"""
+ DISTANCE = SpatialBackend.distance
if not DISTANCE:
raise ImproperlyConfigured('Distance() stored proecedure not available.')
@@ -299,12 +299,55 @@ def distance(self, *args, **kwargs):
dist_select = {'distance' : '%s(%s, %s)' % (DISTANCE, field_col, geom_sql)}
return self.extra(select=dist_select)
+ def extent(self, field_name=None):
+ """
+ Returns the extent (aggregate) of the features in the GeoQuerySet. The
+ extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax).
+ """
+ EXTENT = SpatialBackend.extent
+ if not EXTENT:
+ raise ImproperlyConfigured('Extent stored procedure not available.')
+
+ if not field_name:
+ field_name = self._get_geofield()
+
+ field_col = self._geo_column(field_name)
+ if not field_col:
+ raise TypeError('Extent information only available on GeometryFields.')
+
+ # Getting the SQL for the query.
+ try:
+ select, sql, params = self._get_sql_clause()
+ except EmptyResultSet:
+ return None
+
+ # Constructing the query that will select the extent.
+ extent_sql = ('SELECT %s(%s)' % (EXTENT, field_col)) + sql
+
+ # Getting a cursor, executing the query, and extracting the returned
+ # value from the extent function.
+ cursor = connection.cursor()
+ cursor.execute(extent_sql, params)
+ box = cursor.fetchone()[0]
+
+ if box:
+ # TODO: Parsing of BOX3D, Oracle support (patches welcome!)
+ # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
+ # parsing out and returning as a 4-tuple.
+ ll, ur = box[4:-1].split(',')
+ xmin, ymin = map(float, ll.split())
+ xmax, ymax = map(float, ur.split())
+ return (xmin, ymin, xmax, ymax)
+ else:
+ return None
+
def gml(self, field_name=None, precision=8, version=2):
"""
Returns GML representation of the given field in a `gml` attribute
on each element of the GeoQuerySet.
"""
# Is GML output supported?
+ ASGML = SpatialBackend.as_gml
if not ASGML:
raise ImproperlyConfigured('AsGML() stored procedure not available.')
@@ -322,7 +365,7 @@ def gml(self, field_name=None, precision=8, version=2):
elif postgis:
# PostGIS AsGML() aggregate function parameter order depends on the
# version -- uggh.
- major, minor1, minor2 = VERSION
+ major, minor1, minor2 = SpatialBackend.version
if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, version, field_col, precision)}
else:
@@ -337,6 +380,7 @@ def kml(self, field_name=None, precision=8):
attribute on each element of the GeoQuerySet.
"""
# Is KML output supported?
+ ASKML = SpatialBackend.as_kml
if not ASKML:
raise ImproperlyConfigured('AsKML() stored procedure not available.')
@@ -375,6 +419,7 @@ def transform(self, field_name=None, srid=4326):
# Setting the key for the field's column with the custom SELECT SQL to
# override the geometry column returned from the database.
+ TRANSFORM = SpatialBackend.transform
if oracle:
custom_sel = '%s(%s, %s)' % (TRANSFORM, col, srid)
self._ewkt = srid
@@ -391,6 +436,7 @@ def union(self, field_name=None, tolerance=0.0005):
Oracle backends only.
"""
# Making sure backend supports the Union stored procedure
+ UNION = SpatialBackend.union
if not UNION:
raise ImproperlyConfigured('Union stored procedure not available.')
14 django/contrib/gis/tests/geoapp/tests.py
View
@@ -172,6 +172,20 @@ def test04_transform(self):
self.assertAlmostEqual(ptown.x, p.point.x, prec)
self.assertAlmostEqual(ptown.y, p.point.y, prec)
+ @no_oracle # Most likely can do this in Oracle, however, it is not yet implemented (patches welcome!)
+ def test05_extent(self):
+ "Testing the extent() GeoManager method."
+ # Reference query:
+ # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
+ # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
+ expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
+
+ qs = City.objects.filter(name__in=('Houston', 'Dallas'))
+ extent = qs.extent()
+
+ for val, exp in zip(extent, expected):
+ self.assertAlmostEqual(exp, val, 8)
+
def test09_disjoint(self):
"Testing the `disjoint` lookup type."
if DISABLE: return
4 django/contrib/gis/utils/layermapping.py
View
@@ -112,7 +112,7 @@ def __str__(self):
from decimal import Decimal
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.gis.db.models.fields import GeometryField
-from django.contrib.gis.db.backend import SPATIAL_BACKEND
+from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.gdal import CoordTransform, DataSource, \
OGRException, OGRGeometry, OGRGeomType, SpatialReference
from django.contrib.gis.gdal.field import \
@@ -506,7 +506,7 @@ def geometry_column(self):
# Getting the GeometryColumn object.
try:
db_table = self.model._meta.db_table
- if SPATIAL_BACKEND == 'oracle': db_table = db_table.upper()
+ if SpatialBackend.name == 'oracle': db_table = db_table.upper()
gc_kwargs = {GeometryColumns.table_name_col() : db_table}
return GeometryColumns.objects.get(**gc_kwargs)
except Exception, msg:
Please sign in to comment.
Something went wrong with that request. Please try again.