| 1 |
""" |
|---|
| 2 |
This module contains the spatial lookup types, and the `get_geo_where_clause` |
|---|
| 3 |
routine for Oracle Spatial. |
|---|
| 4 |
|
|---|
| 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 |
|---|
| 8 |
the WKT constructors. |
|---|
| 9 |
""" |
|---|
| 10 |
import re |
|---|
| 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 |
|---|
| 16 |
|
|---|
| 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' |
|---|
| 33 |
|
|---|
| 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)' |
|---|
| 40 |
|
|---|
| 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) |
|---|
| 49 |
|
|---|
| 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') |
|---|
| 55 |
|
|---|
| 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) |
|---|
| 64 |
|
|---|
| 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): |
|---|
| 70 |
func = 'SDO_RELATE' |
|---|
| 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) |
|---|
| 74 |
|
|---|
| 75 |
#### Lookup type mapping dictionaries of Oracle spatial operations #### |
|---|
| 76 |
|
|---|
| 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), |
|---|
| 86 |
} |
|---|
| 87 |
|
|---|
| 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'), |
|---|
| 101 |
} |
|---|
| 102 |
ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) |
|---|
| 103 |
|
|---|
| 104 |
# This lookup type does not require a mapping. |
|---|
| 105 |
MISC_TERMS = ['isnull'] |
|---|
| 106 |
|
|---|
| 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 |
|---|
| 111 |
|
|---|
| 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)) |
|---|
| 117 |
|
|---|
| 118 |
# See if a Oracle Geometry function matches the lookup type next |
|---|
| 119 |
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False) |
|---|
| 120 |
if lookup_info: |
|---|
| 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 |
|---|
| 127 |
|
|---|
| 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) |
|---|
| 133 |
|
|---|
| 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]))) |
|---|
| 137 |
|
|---|
| 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) |
|---|
| 142 |
else: |
|---|
| 143 |
# Otherwise, just call the `as_sql` method on the SDOOperation instance. |
|---|
| 144 |
return sdo_op.as_sql(geo_col) |
|---|
| 145 |
else: |
|---|
| 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 '')) |
|---|
| 153 |
|
|---|
| 154 |
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) |
|---|