mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
gis: Applied DRY to spatial SQL generation in anticipation of queryset-refactor; fixed gml
function for PostGIS 1.3.2 parameter ordering.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6919 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
9270d47d80
commit
5799c2e048
@ -26,7 +26,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= (False, False, False, False, False)
|
||||
ASGML, ASKML, DISTANCE, TRANSFORM, UNION, VERSION = (False, False, False, False, False, False)
|
||||
|
||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||
# PostGIS is the spatial database, getting the rquired modules,
|
||||
@ -34,7 +34,9 @@ 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, GEOM_SELECT, TRANSFORM, UNION, \
|
||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
|
||||
VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2)
|
||||
SPATIAL_BACKEND = 'postgis'
|
||||
elif settings.DATABASE_ENGINE == 'oracle':
|
||||
from django.contrib.gis.db.backend.oracle import \
|
||||
@ -283,7 +285,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
|
||||
geo_prep = field.get_db_prep_lookup(lookup_type, value)
|
||||
|
||||
# Getting the adapted geometry from the field.
|
||||
gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
|
||||
gwc = get_geo_where_clause(lookup_type, current_table, column, value)
|
||||
|
||||
# Substituting in the the where parameters into the geographic where
|
||||
# clause, and extending the parameters.
|
||||
|
@ -5,6 +5,11 @@
|
||||
from django.db import connection
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# To ease implementation, WKT is passed to/from MySQL.
|
||||
GEOM_FROM_TEXT = 'GeomFromText'
|
||||
GEOM_FROM_WKB = 'GeomFromWKB'
|
||||
GEOM_SELECT = 'AsText(%s)'
|
||||
|
||||
# WARNING: MySQL is NOT compliant w/the OpenGIS specification and
|
||||
# _every_ one of these lookup types is on the _bounding box_ only.
|
||||
MYSQL_GIS_FUNCTIONS = {
|
||||
@ -31,24 +36,18 @@ MYSQL_GIS_TERMS = tuple(MYSQL_GIS_TERMS) # Making immutable
|
||||
|
||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
"Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
|
||||
if table_prefix.endswith('.'):
|
||||
table_prefix = qn(table_prefix[:-1])+'.'
|
||||
field_name = qn(field_name)
|
||||
# Getting the quoted field as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_prefix), qn(field_name))
|
||||
|
||||
# See if a MySQL Geometry function matches the lookup type next
|
||||
lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
|
||||
if lookup_info:
|
||||
return "%s(%s, %%s)" % (lookup_info, table_prefix + field_name)
|
||||
return "%s(%s, %%s)" % (lookup_info, geo_col)
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
# TODO: Is this needed because MySQL cannot handle NULL
|
||||
# geometries in its spatial indices.
|
||||
if lookup_type == 'isnull':
|
||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
# To ease implementation, WKT is passed to/from MySQL.
|
||||
GEOM_FROM_TEXT = 'GeomFromText'
|
||||
GEOM_FROM_WKB = 'GeomFromWKB'
|
||||
GEOM_SELECT = 'AsText(%s)'
|
||||
|
@ -4,7 +4,7 @@ from django.db import connection
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL
|
||||
from django.contrib.gis.db.backend.util import GeoFieldSQL
|
||||
from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor
|
||||
from django.contrib.gis.db.backend.oracle.query import ORACLE_SPATIAL_TERMS, DISTANCE_FUNCTIONS, TRANSFORM
|
||||
|
||||
|
@ -12,8 +12,8 @@ from django.contrib.gis.models import SpatialRefSysMixin
|
||||
|
||||
class GeometryColumns(models.Model):
|
||||
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
||||
table_name = models.CharField(maxlength=32)
|
||||
column_name = models.CharField(maxlength=1024)
|
||||
table_name = models.CharField(max_length=32)
|
||||
column_name = models.CharField(max_length=1024)
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
# TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY).
|
||||
class Meta:
|
||||
@ -28,11 +28,11 @@ class GeometryColumns(models.Model):
|
||||
|
||||
class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||
"Maps to the Oracle MDSYS.CS_SRS table."
|
||||
cs_name = models.CharField(maxlength=68)
|
||||
cs_name = models.CharField(max_length=68)
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
auth_srid = models.IntegerField()
|
||||
auth_name = models.CharField(maxlength=256)
|
||||
wktext = models.CharField(maxlength=2046)
|
||||
auth_name = models.CharField(max_length=256)
|
||||
wktext = models.CharField(max_length=2046)
|
||||
#cs_bounds = models.GeometryField()
|
||||
|
||||
class Meta:
|
||||
|
@ -5,6 +5,7 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from django.db import connection
|
||||
from django.contrib.gis.db.backend.util import SpatialFunction
|
||||
from django.contrib.gis.measure import Distance
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
@ -14,61 +15,44 @@ DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
|
||||
TRANSFORM = 'SDO_CS.TRANSFORM'
|
||||
UNION = 'SDO_AGGR_UNION'
|
||||
|
||||
class SDOOperation(object):
|
||||
# We want to get SDO Geometries as WKT because it is much easier to
|
||||
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
|
||||
# However, this adversely affects performance (i.e., Java is called
|
||||
# to convert to WKT on every query). If someone wishes to write a
|
||||
# SDO_GEOMETRY(...) parser in Python, let me know =)
|
||||
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
|
||||
|
||||
#### Classes used in constructing Oracle spatial SQL ####
|
||||
class SDOOperation(SpatialFunction):
|
||||
"Base class for SDO* Oracle operations."
|
||||
def __init__(self, func, end_subst=") %s '%s'"):
|
||||
super(SDOOperation, self).__init__(func, end_subst=end_subst, operator='=', result='TRUE')
|
||||
|
||||
def __init__(self, lookup, subst='', operator='=', result="'TRUE'",
|
||||
beg_subst='%s(%s%s, %%s'):
|
||||
self.lookup = lookup
|
||||
self.subst = subst
|
||||
self.operator = operator
|
||||
self.result = result
|
||||
self.beg_subst = beg_subst
|
||||
self.end_subst = ') %s %s' % (self.operator, self.result)
|
||||
|
||||
@property
|
||||
def sql_subst(self):
|
||||
return ''.join([self.beg_subst, self.subst, self.end_subst])
|
||||
|
||||
def as_sql(self, table, field):
|
||||
return self.sql_subst % self.params(table, field)
|
||||
|
||||
def params(self, table, field):
|
||||
return (self.lookup, table, field)
|
||||
|
||||
class SDODistance(SDOOperation):
|
||||
class SDODistance(SpatialFunction):
|
||||
"Class for Distance queries."
|
||||
def __init__(self, op, tolerance=0.05):
|
||||
super(SDODistance, self).__init__(DISTANCE, subst=", %s", operator=op, result='%%s')
|
||||
self.tolerance = tolerance
|
||||
super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
|
||||
operator=op, result='%%s')
|
||||
|
||||
def params(self, table, field):
|
||||
return (self.lookup, table, field, self.tolerance)
|
||||
|
||||
class SDOGeomRelate(SDOOperation):
|
||||
class SDOGeomRelate(SpatialFunction):
|
||||
"Class for using SDO_GEOM.RELATE."
|
||||
def __init__(self, mask, tolerance=0.05):
|
||||
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst="%s(%s%s, '%s'",
|
||||
subst=", %%s, %s", result="'%s'" % mask)
|
||||
self.mask = mask
|
||||
self.tolerance = tolerance
|
||||
# SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
|
||||
# Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
|
||||
end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
|
||||
beg_subst = "%%s(%%s, '%s'" % mask
|
||||
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
|
||||
|
||||
def params(self, table, field):
|
||||
return (self.lookup, table, field, self.mask, self.tolerance)
|
||||
|
||||
class SDORelate(SDOOperation):
|
||||
class SDORelate(SpatialFunction):
|
||||
"Class for using SDO_RELATE."
|
||||
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
|
||||
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
|
||||
|
||||
def __init__(self, mask, **kwargs):
|
||||
super(SDORelate, self).__init__('SDO_RELATE', subst=", 'mask=%s'", **kwargs)
|
||||
def __init__(self, mask):
|
||||
if not self.mask_regex.match(mask):
|
||||
raise ValueError('Invalid %s mask: "%s"' % (self.lookup, mask))
|
||||
self.mask = mask
|
||||
super(SDORelate, self).__init__('SDO_RELATE', end_subst=", 'mask=%s') = 'TRUE'" % mask)
|
||||
|
||||
def params(self, table, field):
|
||||
return (self.lookup, table, field, self.mask)
|
||||
#### Lookup type mapping dictionaries of Oracle spatial operations ####
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int)
|
||||
@ -84,7 +68,7 @@ ORACLE_GEOMETRY_FUNCTIONS = {
|
||||
'coveredby' : SDOOperation('SDO_COVEREDBY'),
|
||||
'covers' : SDOOperation('SDO_COVERS'),
|
||||
'disjoint' : SDOGeomRelate('DISJOINT'),
|
||||
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', "%%s, 'distance=%%s'"), dtypes),
|
||||
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', end_subst=", %%s, 'distance=%%s') %s '%s'"), dtypes),
|
||||
'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
|
||||
'equals' : SDOOperation('SDO_EQUAL'),
|
||||
'exact' : SDOOperation('SDO_EQUAL'),
|
||||
@ -104,20 +88,20 @@ ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys()
|
||||
ORACLE_SPATIAL_TERMS += MISC_TERMS
|
||||
ORACLE_SPATIAL_TERMS = tuple(ORACLE_SPATIAL_TERMS) # Making immutable
|
||||
|
||||
#### The `get_geo_where_clause` function for Oracle ####
|
||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
||||
if table_prefix.endswith('.'):
|
||||
table_prefix = qn(table_prefix[:-1])+'.'
|
||||
field_name = qn(field_name)
|
||||
# Getting the quoted table name as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_prefix), qn(field_name))
|
||||
|
||||
# See if a Oracle Geometry function matches the lookup type next
|
||||
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
|
||||
if lookup_info:
|
||||
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and
|
||||
# 'dwithin' lookup types.
|
||||
# 'dwithin' lookup types.
|
||||
if isinstance(lookup_info, tuple):
|
||||
# First element of tuple is lookup type, second element is the type
|
||||
# of the expected argument (e.g., str, float)
|
||||
# of the expected argument (e.g., str, float)
|
||||
sdo_op, arg_type = lookup_info
|
||||
|
||||
# Ensuring that a tuple _value_ was passed in from the user
|
||||
@ -131,27 +115,19 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
|
||||
|
||||
if lookup_type == 'relate':
|
||||
# The SDORelate class handles construction for these queries, and verifies
|
||||
# the mask argument.
|
||||
return sdo_op(value[1]).as_sql(table_prefix, field_name)
|
||||
elif lookup_type in DISTANCE_FUNCTIONS:
|
||||
op = DISTANCE_FUNCTIONS[lookup_type][0]
|
||||
return op.as_sql(table_prefix, field_name)
|
||||
# return '%s(%s%s, %%s) %s %%s' % (DISTANCE, table_prefix, field_name, op)
|
||||
# The SDORelate class handles construction for these queries,
|
||||
# and verifies the mask argument.
|
||||
return sdo_op(value[1]).as_sql(geo_col)
|
||||
else:
|
||||
return sdo_op.as_sql(table_prefix, field_name)
|
||||
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
||||
return sdo_op.as_sql(geo_col)
|
||||
else:
|
||||
# Lookup info is a SDOOperation instance, whos `as_sql` method returns
|
||||
# the SQL necessary for the geometry function call. For example:
|
||||
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
|
||||
return lookup_info.as_sql(table_prefix, field_name)
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
if lookup_type == 'isnull':
|
||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||
return lookup_info.as_sql(geo_col)
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
# Want to get SDO Geometries as WKT (much easier to instantiate GEOS proxies
|
||||
# from WKT than SDO_GEOMETRY(...) strings ;)
|
||||
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
|
||||
|
@ -45,6 +45,7 @@ def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
||||
try:
|
||||
# Trying to create the database first.
|
||||
cursor.execute(create_sql)
|
||||
#print create_sql
|
||||
except Exception, e:
|
||||
# Drop and recreate, if necessary.
|
||||
if not autoclobber:
|
||||
@ -56,6 +57,7 @@ def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
||||
cursor.execute(create_sql)
|
||||
else:
|
||||
raise Exception('Spatial Database Creation canceled.')
|
||||
foo = _create_with_cursor
|
||||
|
||||
created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists')
|
||||
def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
||||
|
@ -1,11 +1,11 @@
|
||||
from types import UnicodeType
|
||||
from django.db import connection
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.contrib.gis.db.backend.util import GeoFieldSQL
|
||||
from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
|
||||
from django.contrib.gis.db.backend.postgis.query import \
|
||||
DISTANCE, DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM
|
||||
DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM
|
||||
|
||||
# Quotename & geographic quotename, respectively
|
||||
qn = connection.ops.quote_name
|
||||
|
@ -14,13 +14,13 @@ class GeometryColumns(models.Model):
|
||||
The 'geometry_columns' table from the PostGIS. See the PostGIS
|
||||
documentation at Ch. 4.2.2.
|
||||
"""
|
||||
f_table_catalog = models.CharField(maxlength=256)
|
||||
f_table_schema = models.CharField(maxlength=256)
|
||||
f_table_name = models.CharField(maxlength=256)
|
||||
f_geometry_column = models.CharField(maxlength=256)
|
||||
f_table_catalog = models.CharField(max_length=256)
|
||||
f_table_schema = models.CharField(max_length=256)
|
||||
f_table_name = models.CharField(max_length=256)
|
||||
f_geometry_column = models.CharField(max_length=256)
|
||||
coord_dimension = models.IntegerField()
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
type = models.CharField(maxlength=30)
|
||||
type = models.CharField(max_length=30)
|
||||
|
||||
class Meta:
|
||||
db_table = 'geometry_columns'
|
||||
@ -41,10 +41,10 @@ class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||
documentaiton at Ch. 4.2.1.
|
||||
"""
|
||||
srid = models.IntegerField(primary_key=True)
|
||||
auth_name = models.CharField(maxlength=256)
|
||||
auth_name = models.CharField(max_length=256)
|
||||
auth_srid = models.IntegerField()
|
||||
srtext = models.CharField(maxlength=2048)
|
||||
proj4text = models.CharField(maxlength=2048)
|
||||
srtext = models.CharField(max_length=2048)
|
||||
proj4text = models.CharField(max_length=2048)
|
||||
|
||||
class Meta:
|
||||
db_table = 'spatial_ref_sys'
|
||||
|
@ -2,10 +2,12 @@
|
||||
This module contains the spatial lookup types, and the get_geo_where_clause()
|
||||
routine for PostGIS.
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from django.db import connection
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
|
||||
from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# Getting the PostGIS version information
|
||||
@ -17,60 +19,23 @@ POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version
|
||||
if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
|
||||
raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||
POSTGIS_OPERATORS = {
|
||||
# The "&<" operator returns true if A's bounding box overlaps or
|
||||
# is to the left of B's bounding box.
|
||||
'overlaps_left' : '&<',
|
||||
# The "&>" operator returns true if A's bounding box overlaps or
|
||||
# is to the right of B's bounding box.
|
||||
'overlaps_right' : '&>',
|
||||
# The "<<" operator returns true if A's bounding box is strictly
|
||||
# to the left of B's bounding box.
|
||||
'left' : '<<',
|
||||
# The ">>" operator returns true if A's bounding box is strictly
|
||||
# to the right of B's bounding box.
|
||||
'right' : '>>',
|
||||
# The "&<|" operator returns true if A's bounding box overlaps or
|
||||
# is below B's bounding box.
|
||||
'overlaps_below' : '&<|',
|
||||
# The "|&>" operator returns true if A's bounding box overlaps or
|
||||
# is above B's bounding box.
|
||||
'overlaps_above' : '|&>',
|
||||
# The "<<|" operator returns true if A's bounding box is strictly
|
||||
# below B's bounding box.
|
||||
'strictly_below' : '<<|',
|
||||
# The "|>>" operator returns true if A's bounding box is strictly
|
||||
# above B's bounding box.
|
||||
'strictly_above' : '|>>',
|
||||
# The "~=" operator is the "same as" operator. It tests actual
|
||||
# geometric equality of two features. So if A and B are the same feature,
|
||||
# vertex-by-vertex, the operator returns true.
|
||||
'same_as' : '~=',
|
||||
'exact' : '~=',
|
||||
# The "@" operator returns true if A's bounding box is completely contained
|
||||
# by B's bounding box.
|
||||
'contained' : '@',
|
||||
# The "~" operator returns true if A's bounding box completely contains
|
||||
# by B's bounding box.
|
||||
'bbcontains' : '~',
|
||||
# The "&&" operator returns true if A's bounding box overlaps
|
||||
# B's bounding box.
|
||||
'bboverlaps' : '&&',
|
||||
}
|
||||
|
||||
# Versions of PostGIS >= 1.2.2 changed their naming convention to be
|
||||
# 'SQL-MM-centric' to conform with the ISO standard. Practically, this
|
||||
# 'SQL-MM-centric' to conform with the ISO standard. Practically, this
|
||||
# means that 'ST_' is prefixes geometry function names.
|
||||
GEOM_FUNC_PREFIX = ''
|
||||
if MAJOR_VERSION >= 1:
|
||||
if (MINOR_VERSION1 > 2 or
|
||||
if (MINOR_VERSION1 > 2 or
|
||||
(MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)):
|
||||
GEOM_FUNC_PREFIX = 'ST_'
|
||||
|
||||
|
||||
def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
|
||||
|
||||
# Custom selection not needed for PostGIS since GEOS geometries may be
|
||||
# instantiated directly from the HEXEWKB returned by default. If
|
||||
# WKT is needed for some reason in the future, this value may be changed,
|
||||
# 'AsText(%s)'
|
||||
GEOM_SELECT = None
|
||||
|
||||
# Functions used by the GeoManager & GeoQuerySet
|
||||
ASKML = get_func('AsKML')
|
||||
ASGML = get_func('AsGML')
|
||||
@ -90,48 +55,115 @@ if MAJOR_VERSION >= 1:
|
||||
else:
|
||||
raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
|
||||
|
||||
#### Classes used in constructing PostGIS spatial SQL ####
|
||||
class PostGISOperator(SpatialOperation):
|
||||
"For PostGIS operators (e.g. `&&`, `~`)."
|
||||
def __init__(self, operator):
|
||||
super(PostGISOperator, self).__init__(operator=operator, beg_subst='%s %s %%s')
|
||||
|
||||
class PostGISFunction(SpatialFunction):
|
||||
"For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
|
||||
def __init__(self, function, **kwargs):
|
||||
super(PostGISFunction, self).__init__(get_func(function), **kwargs)
|
||||
|
||||
class PostGISFunctionParam(PostGISFunction):
|
||||
"For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
|
||||
def __init__(self, func):
|
||||
super(PostGISFunctionParam, self).__init__(func, end_subst=', %%s)')
|
||||
|
||||
class PostGISDistance(PostGISFunction):
|
||||
"For PostGIS distance operations."
|
||||
def __init__(self, operator):
|
||||
super(PostGISDistance, self).__init__('Distance', end_subst=') %s %s', operator=operator, result='%%s')
|
||||
|
||||
class PostGISRelate(PostGISFunctionParam):
|
||||
"For PostGIS Relate(<geom>, <pattern>) calls."
|
||||
pattern_regex = re.compile(r'^[012TF\*]{9}$')
|
||||
def __init__(self, pattern):
|
||||
if not self.pattern_regex.match(pattern):
|
||||
raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
|
||||
super(PostGISRelate, self).__init__('Relate')
|
||||
|
||||
#### Lookup type mapping dictionaries of PostGIS operations. ####
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||
POSTGIS_OPERATORS = {
|
||||
# The "&<" operator returns true if A's bounding box overlaps or
|
||||
# is to the left of B's bounding box.
|
||||
'overlaps_left' : PostGISOperator('&<'),
|
||||
# The "&>" operator returns true if A's bounding box overlaps or
|
||||
# is to the right of B's bounding box.
|
||||
'overlaps_right' : PostGISOperator('&>'),
|
||||
# The "<<" operator returns true if A's bounding box is strictly
|
||||
# to the left of B's bounding box.
|
||||
'left' : PostGISOperator('<<'),
|
||||
# The ">>" operator returns true if A's bounding box is strictly
|
||||
# to the right of B's bounding box.
|
||||
'right' : PostGISOperator('>>'),
|
||||
# The "&<|" operator returns true if A's bounding box overlaps or
|
||||
# is below B's bounding box.
|
||||
'overlaps_below' : PostGISOperator('&<|'),
|
||||
# The "|&>" operator returns true if A's bounding box overlaps or
|
||||
# is above B's bounding box.
|
||||
'overlaps_above' : PostGISOperator('|&>'),
|
||||
# The "<<|" operator returns true if A's bounding box is strictly
|
||||
# below B's bounding box.
|
||||
'strictly_below' : PostGISOperator('<<|'),
|
||||
# The "|>>" operator returns true if A's bounding box is strictly
|
||||
# above B's bounding box.
|
||||
'strictly_above' : PostGISOperator('|>>'),
|
||||
# The "~=" operator is the "same as" operator. It tests actual
|
||||
# geometric equality of two features. So if A and B are the same feature,
|
||||
# vertex-by-vertex, the operator returns true.
|
||||
'same_as' : PostGISOperator('~='),
|
||||
'exact' : PostGISOperator('~='),
|
||||
# The "@" operator returns true if A's bounding box is completely contained
|
||||
# by B's bounding box.
|
||||
'contained' : PostGISOperator('@'),
|
||||
# The "~" operator returns true if A's bounding box completely contains
|
||||
# by B's bounding box.
|
||||
'bbcontains' : PostGISOperator('~'),
|
||||
# The "&&" operator returns true if A's bounding box overlaps
|
||||
# B's bounding box.
|
||||
'bboverlaps' : PostGISOperator('&&'),
|
||||
}
|
||||
|
||||
# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query
|
||||
# first before calling the more computationally expensive GEOS routines (called
|
||||
# "inline index magic"):
|
||||
# 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and
|
||||
# 'covers'.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS = {
|
||||
'equals' : 'Equals',
|
||||
'disjoint' : 'Disjoint',
|
||||
'touches' : 'Touches',
|
||||
'crosses' : 'Crosses',
|
||||
'within' : 'Within',
|
||||
'overlaps' : 'Overlaps',
|
||||
'contains' : 'Contains',
|
||||
'intersects' : 'Intersects',
|
||||
'relate' : ('Relate', basestring),
|
||||
'equals' : PostGISFunction('Equals'),
|
||||
'disjoint' : PostGISFunction('Disjoint'),
|
||||
'touches' : PostGISFunction('Touches'),
|
||||
'crosses' : PostGISFunction('Crosses'),
|
||||
'within' : PostGISFunction('Within'),
|
||||
'overlaps' : PostGISFunction('Overlaps'),
|
||||
'contains' : PostGISFunction('Contains'),
|
||||
'intersects' : PostGISFunction('Intersects'),
|
||||
'relate' : (PostGISRelate, basestring),
|
||||
}
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int)
|
||||
DISTANCE_FUNCTIONS = {
|
||||
'distance_gt' : ('>', dtypes),
|
||||
'distance_gte' : ('>=', dtypes),
|
||||
'distance_lt' : ('<', dtypes),
|
||||
'distance_lte' : ('<=', dtypes),
|
||||
'distance_gt' : (PostGISDistance('>'), dtypes),
|
||||
'distance_gte' : (PostGISDistance('>='), dtypes),
|
||||
'distance_lt' : (PostGISDistance('<'), dtypes),
|
||||
'distance_lte' : (PostGISDistance('<='), dtypes),
|
||||
}
|
||||
|
||||
if GEOM_FUNC_PREFIX == 'ST_':
|
||||
# Adding the GEOM_FUNC_PREFIX to the lookup functions.
|
||||
for lookup, f in POSTGIS_GEOMETRY_FUNCTIONS.items():
|
||||
if isinstance(f, tuple):
|
||||
POSTGIS_GEOMETRY_FUNCTIONS[lookup] = (get_func(f[0]), f[1])
|
||||
else:
|
||||
POSTGIS_GEOMETRY_FUNCTIONS[lookup] = get_func(f)
|
||||
|
||||
# The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+
|
||||
POSTGIS_GEOMETRY_FUNCTIONS.update(
|
||||
{'dwithin' : ('ST_DWithin', dtypes),
|
||||
'coveredby' : 'ST_CoveredBy',
|
||||
'covers' : 'ST_Covers',
|
||||
}
|
||||
)
|
||||
{'dwithin' : (PostGISFunctionParam('DWithin'), dtypes),
|
||||
'coveredby' : PostGISFunction('CoveredBy'),
|
||||
'covers' : PostGISFunction('Covers'),
|
||||
})
|
||||
|
||||
# Distance functions are a part of PostGIS geometry functions.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
|
||||
|
||||
# Any other lookup types that do not require a mapping.
|
||||
@ -144,33 +176,25 @@ POSTGIS_TERMS += POSTGIS_GEOMETRY_FUNCTIONS.keys() # Adding on the Geometry Func
|
||||
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
|
||||
|
||||
### PostGIS-specific Methods ###
|
||||
def get_geom_func(lookup_type):
|
||||
func_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
|
||||
if isinstance(func_info, tuple):
|
||||
return func_info[0]
|
||||
else:
|
||||
return func_info
|
||||
|
||||
#### The `get_geo_where_clause` function for PostGIS. ####
|
||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
"Returns the SQL WHERE clause for use in PostGIS SQL construction."
|
||||
if table_prefix.endswith('.'):
|
||||
table_prefix = qn(table_prefix[:-1])+'.'
|
||||
field_name = qn(field_name)
|
||||
|
||||
# See if a PostGIS operator matches the lookup type first
|
||||
# Getting the quoted field as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_prefix), qn(field_name))
|
||||
if lookup_type in POSTGIS_OPERATORS:
|
||||
return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type])
|
||||
# See if a PostGIS operator matches the lookup type.
|
||||
return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col)
|
||||
elif lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
|
||||
# See if a PostGIS geometry function matches the lookup type.
|
||||
tmp = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
|
||||
|
||||
# See if a PostGIS Geometry function matches the lookup type next
|
||||
if lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
|
||||
lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
|
||||
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and
|
||||
# 'dwithin' lookup types.
|
||||
if isinstance(lookup_info, tuple):
|
||||
# First element of tuple is lookup type, second element is the type
|
||||
# of the expected argument (e.g., str, float)
|
||||
func, arg_type = lookup_info
|
||||
# distance lookups.
|
||||
if isinstance(tmp, tuple):
|
||||
# First element of tuple is the PostGISOperation instance, and the
|
||||
# second element is either the type or a tuple of acceptable types
|
||||
# that may passed in as further parameters for the lookup type.
|
||||
op, arg_type = tmp
|
||||
|
||||
# Ensuring that a tuple _value_ was passed in from the user
|
||||
if not isinstance(value, tuple):
|
||||
@ -182,24 +206,15 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
if not isinstance(value[1], arg_type):
|
||||
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
|
||||
|
||||
if lookup_type in DISTANCE_FUNCTIONS:
|
||||
op = DISTANCE_FUNCTIONS[lookup_type][0]
|
||||
return '%s(%s%s, %%s) %s %%s' % (DISTANCE, table_prefix, field_name, op)
|
||||
else:
|
||||
return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
|
||||
# For lookup type `relate`, the op instance is not yet created (has
|
||||
# to be instantiated here to check the pattern parameter).
|
||||
if lookup_type == 'relate': op = op(value[1])
|
||||
else:
|
||||
# Returning the SQL necessary for the geometry function call. For example:
|
||||
# ST_Contains("geoapp_country"."poly", ST_GeomFromWKB(..))
|
||||
return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name)
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
if lookup_type == 'isnull':
|
||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||
op = tmp
|
||||
# Calling the `as_sql` function on the operation instance.
|
||||
return op.as_sql(geo_col)
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
# Custom selection not needed for PostGIS since GEOS geometries may be
|
||||
# instantiated directly from the HEXEWKB returned by default. If
|
||||
# WKT is needed for some reason in the future, this value may be changed,
|
||||
# 'AsText(%s)'
|
||||
GEOM_SELECT = None
|
||||
|
@ -8,14 +8,52 @@ class GeoFieldSQL(object):
|
||||
self.params = params
|
||||
|
||||
def __str__(self):
|
||||
return self.where[0] % tuple(self.params)
|
||||
return self.as_sql()
|
||||
|
||||
def get_srid(field, geom):
|
||||
def as_sql(self, quote=False):
|
||||
if not quote:
|
||||
return self.where[0] % tuple(self.params)
|
||||
else:
|
||||
# Used for quoting WKT on certain backends.
|
||||
tmp_params = ["'%s'" % self.params[0]]
|
||||
tmp_params.extend(self.params[1:])
|
||||
return self.where[0] % tuple(tmp_params)
|
||||
|
||||
class SpatialOperation(object):
|
||||
"""
|
||||
Gets the SRID depending on the value of the SRID setting of the field
|
||||
and that of the given geometry.
|
||||
Base class for generating spatial SQL.
|
||||
"""
|
||||
if geom.srid is None or (geom.srid == -1 and field._srid != -1):
|
||||
return field._srid
|
||||
else:
|
||||
return geom.srid
|
||||
def __init__(self, function='', operator='', result='', beg_subst='', end_subst=''):
|
||||
self.function = function
|
||||
self.operator = operator
|
||||
self.result = result
|
||||
self.beg_subst = beg_subst
|
||||
try:
|
||||
# Try and put the operator and result into to the
|
||||
# end substitution.
|
||||
self.end_subst = end_subst % (operator, result)
|
||||
except TypeError:
|
||||
self.end_subst = end_subst
|
||||
|
||||
@property
|
||||
def sql_subst(self):
|
||||
return ''.join([self.beg_subst, self.end_subst])
|
||||
|
||||
def as_sql(self, geo_col):
|
||||
return self.sql_subst % self.params(geo_col)
|
||||
|
||||
def params(self, geo_col):
|
||||
return (geo_col, self.operator)
|
||||
|
||||
class SpatialFunction(SpatialOperation):
|
||||
"""
|
||||
Base class for generating spatial SQL related to a function.
|
||||
"""
|
||||
def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''):
|
||||
# Getting the function prefix.
|
||||
kwargs = {'function' : func, 'operator' : operator, 'result' : result,
|
||||
'beg_subst' : beg_subst, 'end_subst' : end_subst,}
|
||||
super(SpatialFunction, self).__init__(**kwargs)
|
||||
|
||||
def params(self, geo_col):
|
||||
return (self.function, geo_col)
|
||||
|
@ -7,11 +7,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
|
||||
ASGML, ASKML, DISTANCE, GEOM_SELECT, SPATIAL_BACKEND, TRANSFORM, UNION, VERSION
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
|
||||
# Flag indicating whether the backend is Oracle.
|
||||
# Shortcut booleans for determining the backend.
|
||||
oracle = SPATIAL_BACKEND == 'oracle'
|
||||
postgis = SPATIAL_BACKEND == 'postgis'
|
||||
|
||||
class GeoQ(Q):
|
||||
"Geographical query encapsulation object."
|
||||
@ -325,8 +326,14 @@ class GeoQuerySet(QuerySet):
|
||||
|
||||
if oracle:
|
||||
gml_select = {'gml':'%s(%s)' % (ASGML, field_col)}
|
||||
else:
|
||||
gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, field_col, precision, version)}
|
||||
elif postgis:
|
||||
# PostGIS AsGML() aggregate function parameter order depends on the
|
||||
# version -- uggh.
|
||||
major, minor1, minor2 = 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:
|
||||
gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, field_col, precision, version)}
|
||||
|
||||
# Adding GML function call to SELECT part of the SQL.
|
||||
return self.extra(select=gml_select)
|
||||
|
Loading…
x
Reference in New Issue
Block a user