1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

gis: Added the extent method to GeoQuerySet; moved various spatial-backend settings into the SpatialBackend container class.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7028 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-01-22 01:22:27 +00:00
parent feebe39429
commit 398eca3fb2
7 changed files with 94 additions and 21 deletions

View File

@ -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.utils.datastructures import SortedDict
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 @@ if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
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 @@ elif settings.DATABASE_ENGINE == 'mysql':
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.

View File

@ -6,4 +6,4 @@ from django.contrib.gis.db.backend.postgis.field import PostGISField, gqn
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

View File

@ -40,6 +40,7 @@ if MAJOR_VERSION >= 1:
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')

View File

@ -10,6 +10,9 @@ class GeoManager(Manager):
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)

View File

@ -6,13 +6,12 @@ from django.db.models.fields import FieldDoesNotExist
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."
@ -37,10 +36,10 @@ class GeoQuerySet(QuerySet):
# 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 @@ class GeoQuerySet(QuerySet):
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 @@ class GeoQuerySet(QuerySet):
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 @@ class GeoQuerySet(QuerySet):
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 @@ class GeoQuerySet(QuerySet):
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 @@ class GeoQuerySet(QuerySet):
# 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 @@ class GeoQuerySet(QuerySet):
Oracle backends only.
"""
# Making sure backend supports the Union stored procedure
UNION = SpatialBackend.union
if not UNION:
raise ImproperlyConfigured('Union stored procedure not available.')

View File

@ -172,6 +172,20 @@ class GeoModelTest(unittest.TestCase):
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

View File

@ -112,7 +112,7 @@ from datetime import date, datetime
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 @@ class LayerMapping(object):
# 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: