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:
parent
feebe39429
commit
398eca3fb2
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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."
|
||||
@ -23,7 +22,7 @@ class GeoQ(Q):
|
||||
|
||||
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 @@ 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.')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user