|
0
|
1 |
""" |
|
|
2 |
This module contains the spatial lookup types, and the get_geo_where_clause() |
|
|
3 |
routine for SpatiaLite. |
|
|
4 |
""" |
|
|
5 |
import re |
|
|
6 |
from decimal import Decimal |
|
|
7 |
from django.db import connection |
|
|
8 |
from django.contrib.gis.measure import Distance |
|
|
9 |
from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction |
|
|
10 |
qn = connection.ops.quote_name |
|
|
11 |
|
|
|
12 |
GEOM_SELECT = 'AsText(%s)' |
|
|
13 |
|
|
|
14 |
# Dummy func, in case we need it later: |
|
|
15 |
def get_func(str): |
|
|
16 |
return str |
|
|
17 |
|
|
|
18 |
# Functions used by the GeoManager & GeoQuerySet |
|
|
19 |
AREA = get_func('Area') |
|
|
20 |
ASSVG = get_func('AsSVG') |
|
|
21 |
CENTROID = get_func('Centroid') |
|
|
22 |
CONTAINED = get_func('MbrWithin') |
|
|
23 |
DIFFERENCE = get_func('Difference') |
|
|
24 |
DISTANCE = get_func('Distance') |
|
|
25 |
ENVELOPE = get_func('Envelope') |
|
|
26 |
GEOM_FROM_TEXT = get_func('GeomFromText') |
|
|
27 |
GEOM_FROM_WKB = get_func('GeomFromWKB') |
|
|
28 |
INTERSECTION = get_func('Intersection') |
|
|
29 |
LENGTH = get_func('GLength') # OpenGis defines Length, but this conflicts with an SQLite reserved keyword |
|
|
30 |
NUM_GEOM = get_func('NumGeometries') |
|
|
31 |
NUM_POINTS = get_func('NumPoints') |
|
|
32 |
POINT_ON_SURFACE = get_func('PointOnSurface') |
|
|
33 |
SCALE = get_func('ScaleCoords') |
|
|
34 |
SYM_DIFFERENCE = get_func('SymDifference') |
|
|
35 |
TRANSFORM = get_func('Transform') |
|
|
36 |
TRANSLATE = get_func('ShiftCoords') |
|
|
37 |
UNION = 'GUnion'# OpenGis defines Union, but this conflicts with an SQLite reserved keyword |
|
|
38 |
UNIONAGG = 'GUnion' |
|
|
39 |
|
|
|
40 |
#### Classes used in constructing SpatiaLite spatial SQL #### |
|
|
41 |
class SpatiaLiteOperator(SpatialOperation): |
|
|
42 |
"For SpatiaLite operators (e.g. `&&`, `~`)." |
|
|
43 |
def __init__(self, operator): |
|
|
44 |
super(SpatiaLiteOperator, self).__init__(operator=operator, beg_subst='%s %s %%s') |
|
|
45 |
|
|
|
46 |
class SpatiaLiteFunction(SpatialFunction): |
|
|
47 |
"For SpatiaLite function calls." |
|
|
48 |
def __init__(self, function, **kwargs): |
|
|
49 |
super(SpatiaLiteFunction, self).__init__(get_func(function), **kwargs) |
|
|
50 |
|
|
|
51 |
class SpatiaLiteFunctionParam(SpatiaLiteFunction): |
|
|
52 |
"For SpatiaLite functions that take another parameter." |
|
|
53 |
def __init__(self, func): |
|
|
54 |
super(SpatiaLiteFunctionParam, self).__init__(func, end_subst=', %%s)') |
|
|
55 |
|
|
|
56 |
class SpatiaLiteDistance(SpatiaLiteFunction): |
|
|
57 |
"For SpatiaLite distance operations." |
|
|
58 |
dist_func = 'Distance' |
|
|
59 |
def __init__(self, operator): |
|
|
60 |
super(SpatiaLiteDistance, self).__init__(self.dist_func, end_subst=') %s %s', |
|
|
61 |
operator=operator, result='%%s') |
|
|
62 |
|
|
|
63 |
class SpatiaLiteRelate(SpatiaLiteFunctionParam): |
|
|
64 |
"For SpatiaLite Relate(<geom>, <pattern>) calls." |
|
|
65 |
pattern_regex = re.compile(r'^[012TF\*]{9}$') |
|
|
66 |
def __init__(self, pattern): |
|
|
67 |
if not self.pattern_regex.match(pattern): |
|
|
68 |
raise ValueError('Invalid intersection matrix pattern "%s".' % pattern) |
|
|
69 |
super(SpatiaLiteRelate, self).__init__('Relate') |
|
|
70 |
|
|
|
71 |
|
|
|
72 |
SPATIALITE_GEOMETRY_FUNCTIONS = { |
|
|
73 |
'equals' : SpatiaLiteFunction('Equals'), |
|
|
74 |
'disjoint' : SpatiaLiteFunction('Disjoint'), |
|
|
75 |
'touches' : SpatiaLiteFunction('Touches'), |
|
|
76 |
'crosses' : SpatiaLiteFunction('Crosses'), |
|
|
77 |
'within' : SpatiaLiteFunction('Within'), |
|
|
78 |
'overlaps' : SpatiaLiteFunction('Overlaps'), |
|
|
79 |
'contains' : SpatiaLiteFunction('Contains'), |
|
|
80 |
'intersects' : SpatiaLiteFunction('Intersects'), |
|
|
81 |
'relate' : (SpatiaLiteRelate, basestring), |
|
|
82 |
# Retruns true if B's bounding box completely contains A's bounding box. |
|
|
83 |
'contained' : SpatiaLiteFunction('MbrWithin'), |
|
|
84 |
# Returns true if A's bounding box completely contains B's bounding box. |
|
|
85 |
'bbcontains' : SpatiaLiteFunction('MbrContains'), |
|
|
86 |
# Returns true if A's bounding box overlaps B's bounding box. |
|
|
87 |
'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'), |
|
|
88 |
# These are implemented here as synonyms for Equals |
|
|
89 |
'same_as' : SpatiaLiteFunction('Equals'), |
|
|
90 |
'exact' : SpatiaLiteFunction('Equals'), |
|
|
91 |
} |
|
|
92 |
|
|
|
93 |
# Valid distance types and substitutions |
|
|
94 |
dtypes = (Decimal, Distance, float, int, long) |
|
|
95 |
def get_dist_ops(operator): |
|
|
96 |
"Returns operations for regular distances; spherical distances are not currently supported." |
|
|
97 |
return (SpatiaLiteDistance(operator),) |
|
|
98 |
DISTANCE_FUNCTIONS = { |
|
|
99 |
'distance_gt' : (get_dist_ops('>'), dtypes), |
|
|
100 |
'distance_gte' : (get_dist_ops('>='), dtypes), |
|
|
101 |
'distance_lt' : (get_dist_ops('<'), dtypes), |
|
|
102 |
'distance_lte' : (get_dist_ops('<='), dtypes), |
|
|
103 |
} |
|
|
104 |
|
|
|
105 |
# Distance functions are a part of SpatiaLite geometry functions. |
|
|
106 |
SPATIALITE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) |
|
|
107 |
|
|
|
108 |
# Any other lookup types that do not require a mapping. |
|
|
109 |
MISC_TERMS = ['isnull'] |
|
|
110 |
|
|
|
111 |
# These are the SpatiaLite-customized QUERY_TERMS -- a list of the lookup types |
|
|
112 |
# allowed for geographic queries. |
|
|
113 |
SPATIALITE_TERMS = SPATIALITE_GEOMETRY_FUNCTIONS.keys() # Getting the Geometry Functions |
|
|
114 |
SPATIALITE_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull') |
|
|
115 |
SPATIALITE_TERMS = dict((term, None) for term in SPATIALITE_TERMS) # Making a dictionary for fast lookups |
|
|
116 |
|
|
|
117 |
#### The `get_geo_where_clause` function for SpatiaLite. #### |
|
|
118 |
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): |
|
|
119 |
"Returns the SQL WHERE clause for use in SpatiaLite SQL construction." |
|
|
120 |
# Getting the quoted field as `geo_col`. |
|
|
121 |
geo_col = '%s.%s' % (qn(table_alias), qn(name)) |
|
|
122 |
if lookup_type in SPATIALITE_GEOMETRY_FUNCTIONS: |
|
|
123 |
# See if a SpatiaLite geometry function matches the lookup type. |
|
|
124 |
tmp = SPATIALITE_GEOMETRY_FUNCTIONS[lookup_type] |
|
|
125 |
|
|
|
126 |
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and |
|
|
127 |
# distance lookups. |
|
|
128 |
if isinstance(tmp, tuple): |
|
|
129 |
# First element of tuple is the SpatiaLiteOperation instance, and the |
|
|
130 |
# second element is either the type or a tuple of acceptable types |
|
|
131 |
# that may passed in as further parameters for the lookup type. |
|
|
132 |
op, arg_type = tmp |
|
|
133 |
|
|
|
134 |
# Ensuring that a tuple _value_ was passed in from the user |
|
|
135 |
if not isinstance(geo_annot.value, (tuple, list)): |
|
|
136 |
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) |
|
|
137 |
|
|
|
138 |
# Number of valid tuple parameters depends on the lookup type. |
|
|
139 |
if len(geo_annot.value) != 2: |
|
|
140 |
raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type) |
|
|
141 |
|
|
|
142 |
# Ensuring the argument type matches what we expect. |
|
|
143 |
if not isinstance(geo_annot.value[1], arg_type): |
|
|
144 |
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1]))) |
|
|
145 |
|
|
|
146 |
# For lookup type `relate`, the op instance is not yet created (has |
|
|
147 |
# to be instantiated here to check the pattern parameter). |
|
|
148 |
if lookup_type == 'relate': |
|
|
149 |
op = op(geo_annot.value[1]) |
|
|
150 |
elif lookup_type in DISTANCE_FUNCTIONS: |
|
|
151 |
op = op[0] |
|
|
152 |
else: |
|
|
153 |
op = tmp |
|
|
154 |
# Calling the `as_sql` function on the operation instance. |
|
|
155 |
return op.as_sql(geo_col) |
|
|
156 |
elif lookup_type == 'isnull': |
|
|
157 |
# Handling 'isnull' lookup type |
|
|
158 |
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) |
|
|
159 |
|
|
|
160 |
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) |