web/lib/django/contrib/gis/db/backend/spatialite/query.py
changeset 29 cc9b7e14412b
parent 28 b758351d191f
child 30 239f9bcae806
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     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))