mirror of
https://github.com/django/django.git
synced 2025-06-03 10:39:12 +00:00
Converted GIS lookups for Oracle
This commit is contained in:
parent
2bd1bbc424
commit
65c1a37490
@ -8,74 +8,47 @@
|
|||||||
the WKT constructors.
|
the WKT constructors.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.db.backends.oracle.base import DatabaseOperations
|
from django.db.backends.oracle.base import DatabaseOperations
|
||||||
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
||||||
from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
|
from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
|
||||||
from django.contrib.gis.db.backends.utils import SpatialFunction
|
from django.contrib.gis.db.backends.utils import SpatialOperator
|
||||||
from django.contrib.gis.geometry.backend import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
|
||||||
class SDOOperation(SpatialFunction):
|
DEFAULT_TOLERANCE = '0.05'
|
||||||
"Base class for SDO* Oracle operations."
|
|
||||||
sql_template = "%(function)s(%(geo_col)s, %(geometry)s) %(operator)s '%(result)s'"
|
|
||||||
|
|
||||||
def __init__(self, func, **kwargs):
|
|
||||||
kwargs.setdefault('operator', '=')
|
|
||||||
kwargs.setdefault('result', 'TRUE')
|
|
||||||
super(SDOOperation, self).__init__(func, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SDODistance(SpatialFunction):
|
class SDOOperator(SpatialOperator):
|
||||||
"Class for Distance queries."
|
sql_template = "%(func)s(%(lhs)s, %(rhs)s) = 'TRUE'"
|
||||||
sql_template = ('%(function)s(%(geo_col)s, %(geometry)s, %(tolerance)s) '
|
|
||||||
'%(operator)s %(result)s')
|
|
||||||
dist_func = 'SDO_GEOM.SDO_DISTANCE'
|
|
||||||
|
|
||||||
def __init__(self, op, tolerance=0.05):
|
|
||||||
super(SDODistance, self).__init__(self.dist_func,
|
|
||||||
tolerance=tolerance,
|
|
||||||
operator=op, result='%s')
|
|
||||||
|
|
||||||
|
|
||||||
class SDODWithin(SpatialFunction):
|
class SDODistance(SpatialOperator):
|
||||||
dwithin_func = 'SDO_WITHIN_DISTANCE'
|
sql_template = "SDO_GEOM.SDO_DISTANCE(%%(lhs)s, %%(rhs)s, %s) %%(op)s %%%%s" % DEFAULT_TOLERANCE
|
||||||
sql_template = "%(function)s(%(geo_col)s, %(geometry)s, %%s) = 'TRUE'"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(SDODWithin, self).__init__(self.dwithin_func)
|
|
||||||
|
|
||||||
|
|
||||||
class SDOGeomRelate(SpatialFunction):
|
class SDODWithin(SpatialOperator):
|
||||||
"Class for using SDO_GEOM.RELATE."
|
sql_template = "SDO_WITHIN_DISTANCE(%(lhs)s, %(rhs)s, %%s) = 'TRUE'"
|
||||||
relate_func = 'SDO_GEOM.RELATE'
|
|
||||||
sql_template = ("%(function)s(%(geo_col)s, '%(mask)s', %(geometry)s, "
|
|
||||||
"%(tolerance)s) %(operator)s '%(mask)s'")
|
|
||||||
|
|
||||||
def __init__(self, mask, tolerance=0.05):
|
|
||||||
# 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').
|
|
||||||
super(SDOGeomRelate, self).__init__(self.relate_func, operator='=',
|
|
||||||
mask=mask, tolerance=tolerance)
|
|
||||||
|
|
||||||
|
|
||||||
class SDORelate(SpatialFunction):
|
class SDODisjoint(SpatialOperator):
|
||||||
"Class for using SDO_RELATE."
|
sql_template = "SDO_GEOM.RELATE(%%(lhs)s, 'DISJOINT', %%(rhs)s, %s) = 'DISJOINT'" % DEFAULT_TOLERANCE
|
||||||
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
|
|
||||||
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
|
|
||||||
sql_template = "%(function)s(%(geo_col)s, %(geometry)s, 'mask=%(mask)s') = 'TRUE'"
|
|
||||||
relate_func = 'SDO_RELATE'
|
|
||||||
|
|
||||||
def __init__(self, mask):
|
|
||||||
if not self.mask_regex.match(mask):
|
|
||||||
raise ValueError('Invalid %s mask: "%s"' % (self.relate_func, mask))
|
|
||||||
super(SDORelate, self).__init__(self.relate_func, mask=mask)
|
|
||||||
|
|
||||||
# Valid distance types and substitutions
|
class SDORelate(SpatialOperator):
|
||||||
dtypes = (Decimal, Distance, float) + six.integer_types
|
sql_template = "SDO_RELATE(%(lhs)s, %(rhs)s, 'mask=%(mask)s') = 'TRUE'"
|
||||||
|
|
||||||
|
def check_relate_argument(self, arg):
|
||||||
|
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
|
||||||
|
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
|
||||||
|
if not isinstance(arg, six.string_types) or not mask_regex.match(arg):
|
||||||
|
raise ValueError('Invalid SDO_RELATE mask: "%s"' % arg)
|
||||||
|
|
||||||
|
def as_sql(self, connection, lookup, template_params, sql_params):
|
||||||
|
template_params['mask'] = sql_params.pop()
|
||||||
|
return super(SDORelate, self).as_sql(connection, lookup, template_params, sql_params)
|
||||||
|
|
||||||
|
|
||||||
class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||||
@ -113,33 +86,26 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
# SDO_GEOMETRY(...) parser in Python, let me know =)
|
# SDO_GEOMETRY(...) parser in Python, let me know =)
|
||||||
select = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
|
select = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
|
||||||
|
|
||||||
distance_functions = {
|
gis_operators = {
|
||||||
'distance_gt': (SDODistance('>'), dtypes),
|
'contains': SDOOperator(func='SDO_CONTAINS'),
|
||||||
'distance_gte': (SDODistance('>='), dtypes),
|
'coveredby': SDOOperator(func='SDO_COVEREDBY'),
|
||||||
'distance_lt': (SDODistance('<'), dtypes),
|
'covers': SDOOperator(func='SDO_COVERS'),
|
||||||
'distance_lte': (SDODistance('<='), dtypes),
|
'disjoint': SDODisjoint(),
|
||||||
'dwithin': (SDODWithin(), dtypes),
|
'intersects': SDOOperator(func='SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
|
||||||
|
'equals': SDOOperator(func='SDO_EQUAL'),
|
||||||
|
'exact': SDOOperator(func='SDO_EQUAL'),
|
||||||
|
'overlaps': SDOOperator(func='SDO_OVERLAPS'),
|
||||||
|
'same_as': SDOOperator(func='SDO_EQUAL'),
|
||||||
|
'relate': SDORelate(), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
|
||||||
|
'touches': SDOOperator(func='SDO_TOUCH'),
|
||||||
|
'within': SDOOperator(func='SDO_INSIDE'),
|
||||||
|
'distance_gt': SDODistance(op='>'),
|
||||||
|
'distance_gte': SDODistance(op='>='),
|
||||||
|
'distance_lt': SDODistance(op='<'),
|
||||||
|
'distance_lte': SDODistance(op='<='),
|
||||||
|
'dwithin': SDODWithin(),
|
||||||
}
|
}
|
||||||
|
|
||||||
geometry_functions = {
|
|
||||||
'contains': SDOOperation('SDO_CONTAINS'),
|
|
||||||
'coveredby': SDOOperation('SDO_COVEREDBY'),
|
|
||||||
'covers': SDOOperation('SDO_COVERS'),
|
|
||||||
'disjoint': SDOGeomRelate('DISJOINT'),
|
|
||||||
'intersects': SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
|
|
||||||
'equals': SDOOperation('SDO_EQUAL'),
|
|
||||||
'exact': SDOOperation('SDO_EQUAL'),
|
|
||||||
'overlaps': SDOOperation('SDO_OVERLAPS'),
|
|
||||||
'same_as': SDOOperation('SDO_EQUAL'),
|
|
||||||
'relate': (SDORelate, six.string_types), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
|
|
||||||
'touches': SDOOperation('SDO_TOUCH'),
|
|
||||||
'within': SDOOperation('SDO_INSIDE'),
|
|
||||||
}
|
|
||||||
geometry_functions.update(distance_functions)
|
|
||||||
|
|
||||||
gis_terms = {'isnull'}
|
|
||||||
gis_terms.update(geometry_functions)
|
|
||||||
|
|
||||||
truncate_params = {'relate': None}
|
truncate_params = {'relate': None}
|
||||||
|
|
||||||
def geo_quote_name(self, name):
|
def geo_quote_name(self, name):
|
||||||
@ -244,55 +210,6 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
else:
|
else:
|
||||||
return 'SDO_GEOMETRY(%%s, %s)' % f.srid
|
return 'SDO_GEOMETRY(%%s, %s)' % f.srid
|
||||||
|
|
||||||
def check_relate_argument(self, arg):
|
|
||||||
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
|
|
||||||
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
|
|
||||||
if not self.mask_regex.match(arg):
|
|
||||||
raise ValueError('Invalid SDO_RELATE mask: "%s"' % (self.relate_func, arg))
|
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
|
||||||
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
|
||||||
geo_col, db_type = lvalue
|
|
||||||
|
|
||||||
# See if an Oracle Geometry function matches the lookup type next
|
|
||||||
lookup_info = self.geometry_functions.get(lookup_type, False)
|
|
||||||
if lookup_info:
|
|
||||||
# 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)
|
|
||||||
sdo_op, arg_type = lookup_info
|
|
||||||
geom = value[0]
|
|
||||||
|
|
||||||
# Ensuring that a tuple _value_ was passed in from the user
|
|
||||||
if not isinstance(value, tuple):
|
|
||||||
raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
|
|
||||||
if len(value) != 2:
|
|
||||||
raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
|
|
||||||
|
|
||||||
# Ensuring the argument type matches what we expect.
|
|
||||||
if not isinstance(value[1], arg_type):
|
|
||||||
raise ValueError('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(geo_col, self.get_geom_placeholder(field, geom))
|
|
||||||
else:
|
|
||||||
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
|
||||||
return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
|
|
||||||
else:
|
|
||||||
# Lookup info is a SDOOperation instance, whose `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(geo_col, self.get_geom_placeholder(field, value))
|
|
||||||
elif lookup_type == 'isnull':
|
|
||||||
# Handling 'isnull' lookup type
|
|
||||||
return "%s IS %sNULL" % (geo_col, ('' if value else 'NOT ')), []
|
|
||||||
|
|
||||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
|
||||||
|
|
||||||
def spatial_aggregate_sql(self, agg):
|
def spatial_aggregate_sql(self, agg):
|
||||||
"""
|
"""
|
||||||
Returns the spatial aggregate SQL template and function for the
|
Returns the spatial aggregate SQL template and function for the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user