Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

gis: Applied DRY to spatial SQL generation in anticipation of queryse…

…t-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
  • Loading branch information...
commit 5799c2e048ff829300af88ae839de20e1763ee1d 1 parent 9270d47
Justin Bronn jbronn authored
8 django/contrib/gis/db/backend/__init__.py
View
@@ -26,7 +26,7 @@
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 @@
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.
19 django/contrib/gis/db/backend/mysql/query.py
View
@@ -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 @@
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)'
2  django/contrib/gis/db/backend/oracle/field.py
View
@@ -4,7 +4,7 @@
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
10 django/contrib/gis/db/backend/oracle/models.py
View
@@ -12,8 +12,8 @@
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 @@ def __unicode__(self):
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:
104 django/contrib/gis/db/backend/oracle/query.py
View
@@ -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 @@
TRANSFORM = 'SDO_CS.TRANSFORM'
UNION = 'SDO_AGGR_UNION'
-class SDOOperation(object):
- "Base class for SDO* Oracle operations."
-
- 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)
+# 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)'
- def params(self, table, field):
- return (self.lookup, table, field)
+#### 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')
-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
-
- def params(self, table, field):
- return (self.lookup, table, field, self.tolerance)
+ super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
+ operator=op, result='%%s')
-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
-
- def params(self, table, field):
- return (self.lookup, table, field, self.mask, self.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)
-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 @@ def params(self, table, field):
'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 @@ def params(self, table, field):
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)'
2  django/contrib/gis/db/backend/postgis/creation.py
View
@@ -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):
6 django/contrib/gis/db/backend/postgis/field.py
View
@@ -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
16 django/contrib/gis/db/backend/postgis/models.py
View
@@ -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'
231 django/contrib/gis/db/backend/postgis/query.py
View
@@ -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,121 +19,151 @@
if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
+# Versions of PostGIS >= 1.2.2 changed their naming convention to be
+# '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
+ (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')
+ DISTANCE = get_func('Distance')
+ GEOM_FROM_TEXT = get_func('GeomFromText')
+ GEOM_FROM_WKB = get_func('GeomFromWKB')
+ TRANSFORM = get_func('Transform')
+
+ # Special cases for union and KML methods.
+ if MINOR_VERSION1 < 3:
+ UNION = 'GeomUnion'
+ else:
+ UNION = 'ST_Union'
+
+ if MINOR_VERSION1 == 1:
+ ASKML = False
+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' : '&<',
+ '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' : '&>',
+ 'overlaps_right' : PostGISOperator('&>'),
# The "<<" operator returns true if A's bounding box is strictly
# to the left of B's bounding box.
- 'left' : '<<',
+ 'left' : PostGISOperator('<<'),
# The ">>" operator returns true if A's bounding box is strictly
# to the right of B's bounding box.
- 'right' : '>>',
+ 'right' : PostGISOperator('>>'),
# The "&<|" operator returns true if A's bounding box overlaps or
# is below B's bounding box.
- 'overlaps_below' : '&<|',
+ 'overlaps_below' : PostGISOperator('&<|'),
# The "|&>" operator returns true if A's bounding box overlaps or
# is above B's bounding box.
- 'overlaps_above' : '|&>',
+ 'overlaps_above' : PostGISOperator('|&>'),
# The "<<|" operator returns true if A's bounding box is strictly
# below B's bounding box.
- 'strictly_below' : '<<|',
+ 'strictly_below' : PostGISOperator('<<|'),
# The "|>>" operator returns true if A's bounding box is strictly
# above B's bounding box.
- 'strictly_above' : '|>>',
+ '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' : '~=',
- 'exact' : '~=',
+ 'same_as' : PostGISOperator('~='),
+ 'exact' : PostGISOperator('~='),
# The "@" operator returns true if A's bounding box is completely contained
# by B's bounding box.
- 'contained' : '@',
+ 'contained' : PostGISOperator('@'),
# The "~" operator returns true if A's bounding box completely contains
# by B's bounding box.
- 'bbcontains' : '~',
+ 'bbcontains' : PostGISOperator('~'),
# The "&&" operator returns true if A's bounding box overlaps
# B's bounding box.
- 'bboverlaps' : '&&',
+ 'bboverlaps' : PostGISOperator('&&'),
}
-# Versions of PostGIS >= 1.2.2 changed their naming convention to be
-# '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
- (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)):
- GEOM_FUNC_PREFIX = 'ST_'
-
- def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
-
- # Functions used by the GeoManager & GeoQuerySet
- ASKML = get_func('AsKML')
- ASGML = get_func('AsGML')
- DISTANCE = get_func('Distance')
- GEOM_FROM_TEXT = get_func('GeomFromText')
- GEOM_FROM_WKB = get_func('GeomFromWKB')
- TRANSFORM = get_func('Transform')
-
- # Special cases for union and KML methods.
- if MINOR_VERSION1 < 3:
- UNION = 'GeomUnion'
- else:
- UNION = 'ST_Union'
-
- if MINOR_VERSION1 == 1:
- ASKML = False
-else:
- raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
-
# 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 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, 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
54 django/contrib/gis/db/backend/util.py
View
@@ -8,14 +8,52 @@ def __init__(self, where=[], params=[]):
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):
+ """
+ Base class for generating spatial SQL.
+ """
+ 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):
"""
- 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 related to a function.
"""
- if geom.srid is None or (geom.srid == -1 and field._srid != -1):
- return field._srid
- else:
- return geom.srid
+ 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)
15 django/contrib/gis/db/models/query.py
View
@@ -7,11 +7,12 @@
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 @@ def gml(self, field_name=None, precision=8, version=2):
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)
Please sign in to comment.
Something went wrong with that request. Please try again.