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. needed for GeoDjango.
(1) GeoBackEndField, a base class needed for GeometryField. (1) GeoBackEndField, a base class needed for GeometryField.
(2) GeometryProxy, for lazy-instantiated geometries from the (2) GIS_TERMS, a list of acceptable geographic lookup types for
database output.
(3) GIS_TERMS, a list of acceptable geographic lookup types for
the backend. 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. 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`). routines (needed by `parse_lookup`).
(5) The `SpatialBackend` object, which contains information specific
Currently only PostGIS is supported, but someday backends will be added for to the spatial backend.
additional spatial databases (e.g., Oracle, DB2).
""" """
from types import StringType, UnicodeType from types import StringType, UnicodeType
from django.conf import settings from django.conf import settings
@ -26,7 +23,7 @@ from django.utils.datastructures import SortedDict
from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geos import GEOSGeometry
# These routines (needed by GeoManager), default to False. # 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': if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
# PostGIS is the spatial database, getting the rquired modules, # 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 \ from django.contrib.gis.db.backend.postgis import \
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
create_spatial_db, get_geo_where_clause, \ 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 MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2) VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2)
SPATIAL_BACKEND = 'postgis' SPATIAL_BACKEND = 'postgis'
@ -55,6 +52,18 @@ elif settings.DATABASE_ENGINE == 'mysql':
else: else:
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE) 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 #### #### query.py overloaded functions ####
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py # parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
# counterparts to support constructing SQL for geographic queries. # 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 \ from django.contrib.gis.db.backend.postgis.query import \
get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \ get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \ 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') ASKML = get_func('AsKML')
ASGML = get_func('AsGML') ASGML = get_func('AsGML')
DISTANCE = get_func('Distance') DISTANCE = get_func('Distance')
EXTENT = get_func('extent')
GEOM_FROM_TEXT = get_func('GeomFromText') GEOM_FROM_TEXT = get_func('GeomFromText')
GEOM_FROM_WKB = get_func('GeomFromWKB') GEOM_FROM_WKB = get_func('GeomFromWKB')
TRANSFORM = get_func('Transform') TRANSFORM = get_func('Transform')

View File

@ -10,6 +10,9 @@ class GeoManager(Manager):
def distance(self, *args, **kwargs): def distance(self, *args, **kwargs):
return self.get_query_set().distance(*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): def gml(self, *args, **kwargs):
return self.get_query_set().gml(*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.utils.datastructures import SortedDict
from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.fields import GeometryField
# parse_lookup depends on the spatial database backend. # parse_lookup depends on the spatial database backend.
from django.contrib.gis.db.backend import parse_lookup, \ from django.contrib.gis.db.backend import parse_lookup, SpatialBackend
ASGML, ASKML, DISTANCE, GEOM_SELECT, SPATIAL_BACKEND, TRANSFORM, UNION, VERSION
from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geos import GEOSGeometry
# Shortcut booleans for determining the backend. # Shortcut booleans for determining the backend.
oracle = SPATIAL_BACKEND == 'oracle' oracle = SpatialBackend.name == 'oracle'
postgis = SPATIAL_BACKEND == 'postgis' postgis = SpatialBackend.name == 'postgis'
class GeoQ(Q): class GeoQ(Q):
"Geographical query encapsulation object." "Geographical query encapsulation object."
@ -23,7 +22,7 @@ class GeoQ(Q):
class GeoQuerySet(QuerySet): class GeoQuerySet(QuerySet):
"Geographical-enabled QuerySet object." "Geographical-enabled QuerySet object."
#### Overloaded QuerySet Routines #### #### Overloaded QuerySet Routines ####
def __init__(self, model=None): def __init__(self, model=None):
super(GeoQuerySet, self).__init__(model=model) super(GeoQuerySet, self).__init__(model=model)
@ -37,10 +36,10 @@ class GeoQuerySet(QuerySet):
# If GEOM_SELECT is defined in the backend, then it will be used # If GEOM_SELECT is defined in the backend, then it will be used
# for the selection format of the geometry column. # for the selection format of the geometry column.
if GEOM_SELECT: if SpatialBackend.select:
# Transformed geometries in Oracle use EWKT so that the SRID # Transformed geometries in Oracle use EWKT so that the SRID
# on the transformed lazy geometries is set correctly). # on the transformed lazy geometries is set correctly).
self._geo_fmt = GEOM_SELECT self._geo_fmt = SpatialBackend.select
else: else:
self._geo_fmt = '%s' self._geo_fmt = '%s'
@ -259,6 +258,7 @@ class GeoQuerySet(QuerySet):
given geometry in a `distance` attribute on each element of the given geometry in a `distance` attribute on each element of the
GeoQuerySet. GeoQuerySet.
""" """
DISTANCE = SpatialBackend.distance
if not DISTANCE: if not DISTANCE:
raise ImproperlyConfigured('Distance() stored proecedure not available.') 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)} dist_select = {'distance' : '%s(%s, %s)' % (DISTANCE, field_col, geom_sql)}
return self.extra(select=dist_select) 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): def gml(self, field_name=None, precision=8, version=2):
""" """
Returns GML representation of the given field in a `gml` attribute Returns GML representation of the given field in a `gml` attribute
on each element of the GeoQuerySet. on each element of the GeoQuerySet.
""" """
# Is GML output supported? # Is GML output supported?
ASGML = SpatialBackend.as_gml
if not ASGML: if not ASGML:
raise ImproperlyConfigured('AsGML() stored procedure not available.') raise ImproperlyConfigured('AsGML() stored procedure not available.')
@ -322,7 +365,7 @@ class GeoQuerySet(QuerySet):
elif postgis: elif postgis:
# PostGIS AsGML() aggregate function parameter order depends on the # PostGIS AsGML() aggregate function parameter order depends on the
# version -- uggh. # version -- uggh.
major, minor1, minor2 = VERSION major, minor1, minor2 = SpatialBackend.version
if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)): if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, version, field_col, precision)} gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, version, field_col, precision)}
else: else:
@ -337,6 +380,7 @@ class GeoQuerySet(QuerySet):
attribute on each element of the GeoQuerySet. attribute on each element of the GeoQuerySet.
""" """
# Is KML output supported? # Is KML output supported?
ASKML = SpatialBackend.as_kml
if not ASKML: if not ASKML:
raise ImproperlyConfigured('AsKML() stored procedure not available.') 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 # Setting the key for the field's column with the custom SELECT SQL to
# override the geometry column returned from the database. # override the geometry column returned from the database.
TRANSFORM = SpatialBackend.transform
if oracle: if oracle:
custom_sel = '%s(%s, %s)' % (TRANSFORM, col, srid) custom_sel = '%s(%s, %s)' % (TRANSFORM, col, srid)
self._ewkt = srid self._ewkt = srid
@ -391,6 +436,7 @@ class GeoQuerySet(QuerySet):
Oracle backends only. Oracle backends only.
""" """
# Making sure backend supports the Union stored procedure # Making sure backend supports the Union stored procedure
UNION = SpatialBackend.union
if not UNION: if not UNION:
raise ImproperlyConfigured('Union stored procedure not available.') 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.x, p.point.x, prec)
self.assertAlmostEqual(ptown.y, p.point.y, 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): def test09_disjoint(self):
"Testing the `disjoint` lookup type." "Testing the `disjoint` lookup type."
if DISABLE: return if DISABLE: return

View File

@ -112,7 +112,7 @@ from datetime import date, datetime
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
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 SPATIAL_BACKEND from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.gdal import CoordTransform, DataSource, \ from django.contrib.gis.gdal import CoordTransform, DataSource, \
OGRException, OGRGeometry, OGRGeomType, SpatialReference OGRException, OGRGeometry, OGRGeomType, SpatialReference
from django.contrib.gis.gdal.field import \ from django.contrib.gis.gdal.field import \
@ -506,7 +506,7 @@ class LayerMapping(object):
# Getting the GeometryColumn object. # Getting the GeometryColumn object.
try: try:
db_table = self.model._meta.db_table 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} gc_kwargs = {GeometryColumns.table_name_col() : db_table}
return GeometryColumns.objects.get(**gc_kwargs) return GeometryColumns.objects.get(**gc_kwargs)
except Exception, msg: except Exception, msg: