2
This module contains the spatial lookup types, and the `get_geo_where_clause`
3
routine for Oracle Spatial.
5
Please note that WKT support is broken on the XE version, and thus
6
this backend will not work on such platforms. Specifically, XE lacks
7
support for an internal JVM, and Java libraries are required to use
11
from decimal import Decimal
12
from django.db import connection
13
from django.contrib.gis.db.backend.util import SpatialFunction
14
from django.contrib.gis.measure import Distance
15
qn = connection.ops.quote_name
17
# The GML, distance, transform, and union procedures.
18
AREA = 'SDO_GEOM.SDO_AREA'
19
ASGML = 'SDO_UTIL.TO_GMLGEOMETRY'
20
CENTROID = 'SDO_GEOM.SDO_CENTROID'
21
DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE'
22
DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
23
EXTENT = 'SDO_AGGR_MBR'
24
INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION'
25
LENGTH = 'SDO_GEOM.SDO_LENGTH'
26
NUM_GEOM = 'SDO_UTIL.GETNUMELEM'
27
NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES'
28
POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
29
SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR'
30
TRANSFORM = 'SDO_CS.TRANSFORM'
31
UNION = 'SDO_GEOM.SDO_UNION'
32
UNIONAGG = 'SDO_AGGR_UNION'
34
# We want to get SDO Geometries as WKT because it is much easier to
35
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
36
# However, this adversely affects performance (i.e., Java is called
37
# to convert to WKT on every query). If someone wishes to write a
38
# SDO_GEOMETRY(...) parser in Python, let me know =)
39
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
41
#### Classes used in constructing Oracle spatial SQL ####
42
class SDOOperation(SpatialFunction):
43
"Base class for SDO* Oracle operations."
44
def __init__(self, func, **kwargs):
45
kwargs.setdefault('operator', '=')
46
kwargs.setdefault('result', 'TRUE')
47
kwargs.setdefault('end_subst', ") %s '%s'")
48
super(SDOOperation, self).__init__(func, **kwargs)
50
class SDODistance(SpatialFunction):
51
"Class for Distance queries."
52
def __init__(self, op, tolerance=0.05):
53
super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
54
operator=op, result='%%s')
56
class SDOGeomRelate(SpatialFunction):
57
"Class for using SDO_GEOM.RELATE."
58
def __init__(self, mask, tolerance=0.05):
59
# SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
60
# Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
61
end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
62
beg_subst = "%%s(%%s, '%s'" % mask
63
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
65
class SDORelate(SpatialFunction):
66
"Class for using SDO_RELATE."
67
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
68
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
69
def __init__(self, mask):
71
if not self.mask_regex.match(mask):
72
raise ValueError('Invalid %s mask: "%s"' % (func, mask))
73
super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
75
#### Lookup type mapping dictionaries of Oracle spatial operations ####
77
# Valid distance types and substitutions
78
dtypes = (Decimal, Distance, float, int, long)
79
DISTANCE_FUNCTIONS = {
80
'distance_gt' : (SDODistance('>'), dtypes),
81
'distance_gte' : (SDODistance('>='), dtypes),
82
'distance_lt' : (SDODistance('<'), dtypes),
83
'distance_lte' : (SDODistance('<='), dtypes),
84
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
85
beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
88
ORACLE_GEOMETRY_FUNCTIONS = {
89
'contains' : SDOOperation('SDO_CONTAINS'),
90
'coveredby' : SDOOperation('SDO_COVEREDBY'),
91
'covers' : SDOOperation('SDO_COVERS'),
92
'disjoint' : SDOGeomRelate('DISJOINT'),
93
'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
94
'equals' : SDOOperation('SDO_EQUAL'),
95
'exact' : SDOOperation('SDO_EQUAL'),
96
'overlaps' : SDOOperation('SDO_OVERLAPS'),
97
'same_as' : SDOOperation('SDO_EQUAL'),
98
'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
99
'touches' : SDOOperation('SDO_TOUCH'),
100
'within' : SDOOperation('SDO_INSIDE'),
102
ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
104
# This lookup type does not require a mapping.
105
MISC_TERMS = ['isnull']
107
# Acceptable lookup types for Oracle spatial.
108
ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys()
109
ORACLE_SPATIAL_TERMS += MISC_TERMS
110
ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
112
#### The `get_geo_where_clause` function for Oracle ####
113
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
114
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
115
# Getting the quoted table name as `geo_col`.
116
geo_col = '%s.%s' % (qn(table_alias), qn(name))
118
# See if a Oracle Geometry function matches the lookup type next
119
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
121
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and
122
# 'dwithin' lookup types.
123
if isinstance(lookup_info, tuple):
124
# First element of tuple is lookup type, second element is the type
125
# of the expected argument (e.g., str, float)
126
sdo_op, arg_type = lookup_info
128
# Ensuring that a tuple _value_ was passed in from the user
129
if not isinstance(geo_annot.value, tuple):
130
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
131
if len(geo_annot.value) != 2:
132
raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
134
# Ensuring the argument type matches what we expect.
135
if not isinstance(geo_annot.value[1], arg_type):
136
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
138
if lookup_type == 'relate':
139
# The SDORelate class handles construction for these queries,
140
# and verifies the mask argument.
141
return sdo_op(geo_annot.value[1]).as_sql(geo_col)
143
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
144
return sdo_op.as_sql(geo_col)
146
# Lookup info is a SDOOperation instance, whose `as_sql` method returns
147
# the SQL necessary for the geometry function call. For example:
148
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
149
return lookup_info.as_sql(geo_col)
150
elif lookup_type == 'isnull':
151
# Handling 'isnull' lookup type
152
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
154
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))