mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +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.
|
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.
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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."
|
||||||
@ -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.')
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user