~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/contrib/gis/db/backend/spatialite/query.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2009-07-29 11:26:28 UTC
  • mfrom: (1.1.8 upstream) (4.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20090729112628-pg09ino8sz0sj21t
Tags: 1.1-1
* New upstream release.
* Merge from experimental:
  - Ship FastCGI initscript and /etc/default file in python-django's examples
    directory (Closes: #538863)
  - Drop "05_10539-sphinx06-compatibility.diff"; it has been applied
    upstream.
  - Bump Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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))