web/lib/django/contrib/gis/db/backends/postgis/operations.py
author ymh <ymh.work@gmail.com>
Wed, 02 Jun 2010 18:57:35 +0200
changeset 38 77b6da96e6f1
parent 29 cc9b7e14412b
permissions -rw-r--r--
update django
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
29
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
import re
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
from decimal import Decimal
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
from django.conf import settings
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
from django.contrib.gis.db.backends.base import BaseSpatialOperations
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
from django.contrib.gis.geometry.backend import Geometry
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
from django.contrib.gis.measure import Distance
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
from django.core.exceptions import ImproperlyConfigured
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
from django.db.utils import DatabaseError
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
#### Classes used in constructing PostGIS spatial SQL ####
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
class PostGISOperator(SpatialOperation):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
    "For PostGIS operators (e.g. `&&`, `~`)."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
    def __init__(self, operator):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
        super(PostGISOperator, self).__init__(operator=operator)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
class PostGISFunction(SpatialFunction):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
    "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
    def __init__(self, prefix, function, **kwargs):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
        super(PostGISFunction, self).__init__(prefix + function, **kwargs)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
class PostGISFunctionParam(PostGISFunction):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
    "For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
    sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
class PostGISDistance(PostGISFunction):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
    "For PostGIS distance operations."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    dist_func = 'Distance'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
    sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
    def __init__(self, prefix, operator):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
        super(PostGISDistance, self).__init__(prefix, self.dist_func,
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
                                              operator=operator)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
class PostGISSpheroidDistance(PostGISFunction):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
    "For PostGIS spherical distance operations (using the spheroid)."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
    dist_func = 'distance_spheroid'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
    sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s) %(operator)s %%s'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
    def __init__(self, prefix, operator):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
        # An extra parameter in `end_subst` is needed for the spheroid string.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
        super(PostGISSpheroidDistance, self).__init__(prefix, self.dist_func,
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
                                                      operator=operator)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
class PostGISSphereDistance(PostGISDistance):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
    "For PostGIS spherical distance operations."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
    dist_func = 'distance_sphere'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
class PostGISRelate(PostGISFunctionParam):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
    "For PostGIS Relate(<geom>, <pattern>) calls."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
    pattern_regex = re.compile(r'^[012TF\*]{9}$')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
    def __init__(self, prefix, pattern):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
        if not self.pattern_regex.match(pattern):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
        super(PostGISRelate, self).__init__(prefix, 'Relate')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
    compiler_module = 'django.contrib.gis.db.models.sql.compiler'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
    name = 'postgis'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
    postgis = True
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
    version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
    valid_aggregates = dict([(k, None) for k in
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
                             ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
    Adapter = PostGISAdapter
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
    Adaptor = Adapter # Backwards-compatibility alias.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
    def __init__(self, connection):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
        super(PostGISOperations, self).__init__(connection)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
        # Trying to get the PostGIS version because the function
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
        # signatures will depend on the version used.  The cost
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
        # here is a database query to determine the version, which
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
        # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
        # comprising user-supplied values for the major, minor, and
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
        # subminor revision of PostGIS.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
        try:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
            if hasattr(settings, 'POSTGIS_VERSION'):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
                vtup = settings.POSTGIS_VERSION
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
                if len(vtup) == 3:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
                    # The user-supplied PostGIS version.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
                    version = vtup
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
                else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
                    # This was the old documented way, but it's stupid to
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
                    # include the string.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
                    version = vtup[1:4]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
            else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
                vtup = self.postgis_version_tuple()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
                version = vtup[1:]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
            # Getting the prefix -- even though we don't officially support
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
            # PostGIS 1.2 anymore, keeping it anyway in case a prefix change
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
            # for something else is necessary.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
            if version >= (1, 2, 2):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
                prefix = 'ST_'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
            else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
                prefix = ''
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
            self.geom_func_prefix = prefix
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
            self.spatial_version = version
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
        except DatabaseError:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
            raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
                                       'GeoDjango requires at least PostGIS version 1.3. '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
                                       'Was the database created from a spatial database '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
                                       'template?' % self.connection.settings_dict['NAME']
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
                                       )
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
        except Exception, e:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
            # TODO: Raise helpful exceptions as they become known.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
            raise
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
        # PostGIS-specific operators. The commented descriptions of these
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
        # operators come from Section 7.6 of the PostGIS 1.4 documentation.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
        self.geometry_operators = {
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
            # The "&<" operator returns true if A's bounding box overlaps or
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
            # is to the left of B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
            'overlaps_left' : PostGISOperator('&<'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
            # The "&>" operator returns true if A's bounding box overlaps or
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
            # is to the right of B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
            'overlaps_right' : PostGISOperator('&>'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
            # The "<<" operator returns true if A's bounding box is strictly
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
            # to the left of B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
            'left' : PostGISOperator('<<'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
            # The ">>" operator returns true if A's bounding box is strictly
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
            # to the right of B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
            'right' : PostGISOperator('>>'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
            # The "&<|" operator returns true if A's bounding box overlaps or
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
            # is below B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
            'overlaps_below' : PostGISOperator('&<|'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
            # The "|&>" operator returns true if A's bounding box overlaps or
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
            # is above B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
            'overlaps_above' : PostGISOperator('|&>'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
            # The "<<|" operator returns true if A's bounding box is strictly
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
            # below B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
            'strictly_below' : PostGISOperator('<<|'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   138
            # The "|>>" operator returns true if A's bounding box is strictly
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   139
            # above B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   140
            'strictly_above' : PostGISOperator('|>>'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   141
            # The "~=" operator is the "same as" operator. It tests actual
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   142
            # geometric equality of two features. So if A and B are the same feature,
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   143
            # vertex-by-vertex, the operator returns true.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   144
            'same_as' : PostGISOperator('~='),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   145
            'exact' : PostGISOperator('~='),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   146
            # The "@" operator returns true if A's bounding box is completely contained
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   147
            # by B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   148
            'contained' : PostGISOperator('@'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   149
            # The "~" operator returns true if A's bounding box completely contains
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   150
            #  by B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   151
            'bbcontains' : PostGISOperator('~'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   152
            # The "&&" operator returns true if A's bounding box overlaps
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   153
            # B's bounding box.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   154
            'bboverlaps' : PostGISOperator('&&'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   155
            }
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   156
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   157
        self.geometry_functions = {
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   158
            'equals' : PostGISFunction(prefix, 'Equals'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   159
            'disjoint' : PostGISFunction(prefix, 'Disjoint'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   160
            'touches' : PostGISFunction(prefix, 'Touches'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   161
            'crosses' : PostGISFunction(prefix, 'Crosses'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   162
            'within' : PostGISFunction(prefix, 'Within'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   163
            'overlaps' : PostGISFunction(prefix, 'Overlaps'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   164
            'contains' : PostGISFunction(prefix, 'Contains'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   165
            'intersects' : PostGISFunction(prefix, 'Intersects'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   166
            'relate' : (PostGISRelate, basestring),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   167
            }
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   168
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   169
        # Valid distance types and substitutions
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   170
        dtypes = (Decimal, Distance, float, int, long)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   171
        def get_dist_ops(operator):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   172
            "Returns operations for both regular and spherical distances."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   173
            return {'cartesian' : PostGISDistance(prefix, operator),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   174
                    'sphere' : PostGISSphereDistance(prefix, operator),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   175
                    'spheroid' : PostGISSpheroidDistance(prefix, operator),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   176
                    }
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   177
        self.distance_functions = {
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   178
            'distance_gt' : (get_dist_ops('>'), dtypes),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   179
            'distance_gte' : (get_dist_ops('>='), dtypes),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   180
            'distance_lt' : (get_dist_ops('<'), dtypes),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   181
            'distance_lte' : (get_dist_ops('<='), dtypes),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   182
            }
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   183
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   184
        # Versions 1.2.2+ have KML serialization support.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   185
        if version < (1, 2, 2):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   186
            ASKML = False
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   187
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   188
            ASKML = 'ST_AsKML'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   189
            self.geometry_functions.update(
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   190
                {'coveredby' : PostGISFunction(prefix, 'CoveredBy'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   191
                 'covers' : PostGISFunction(prefix, 'Covers'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   192
                 })
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   193
            self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   194
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   195
        # Adding the distance functions to the geometries lookup.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   196
        self.geometry_functions.update(self.distance_functions)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   197
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   198
        # The union aggregate and topology operation use the same signature
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   199
        # in versions 1.3+.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   200
        if version < (1, 3, 0):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   201
            UNIONAGG = 'GeomUnion'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   202
            UNION = 'Union'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   203
            MAKELINE = False
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   204
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   205
            UNIONAGG = 'ST_Union'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   206
            UNION = 'ST_Union'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   207
            MAKELINE = 'ST_MakeLine'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   208
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   209
        # Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   210
        if version < (1, 3, 4):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   211
            GEOJSON = False
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   212
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   213
            GEOJSON = prefix + 'AsGeoJson'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   214
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   215
        # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   216
        if version >= (1, 4, 0):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   217
            GEOHASH = 'ST_GeoHash'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   218
            BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   219
            self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   220
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   221
            GEOHASH, BOUNDINGCIRCLE = False, False
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   222
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   223
        # Geography type support added in 1.5.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   224
        if version >= (1, 5, 0):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   225
            self.geography = True
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   226
            # Only a subset of the operators and functions are available
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   227
            # for the geography type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   228
            self.geography_functions = self.distance_functions.copy()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   229
            self.geography_functions.update({
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   230
                    'coveredby' : self.geometry_functions['coveredby'],
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   231
                    'covers' : self.geometry_functions['covers'],
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   232
                    'intersects' : self.geometry_functions['intersects'],
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   233
                    })
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   234
            self.geography_operators = {
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   235
                'bboverlaps' : PostGISOperator('&&'),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   236
                'exact' : PostGISOperator('~='),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   237
                'same_as' : PostGISOperator('~='),
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   238
                }
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   239
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   240
        # Creating a dictionary lookup of all GIS terms for PostGIS.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   241
        gis_terms = ['isnull']
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   242
        gis_terms += self.geometry_operators.keys()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   243
        gis_terms += self.geometry_functions.keys()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   244
        self.gis_terms = dict([(term, None) for term in gis_terms])
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   245
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   246
        self.area = prefix + 'Area'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   247
        self.bounding_circle = BOUNDINGCIRCLE
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   248
        self.centroid = prefix + 'Centroid'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   249
        self.collect = prefix + 'Collect'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   250
        self.difference = prefix + 'Difference'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   251
        self.distance = prefix + 'Distance'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   252
        self.distance_sphere = prefix + 'distance_sphere'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   253
        self.distance_spheroid = prefix + 'distance_spheroid'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   254
        self.envelope = prefix + 'Envelope'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   255
        self.extent = prefix + 'Extent'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   256
        self.extent3d = prefix + 'Extent3D'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   257
        self.force_rhr = prefix + 'ForceRHR'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   258
        self.geohash = GEOHASH
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   259
        self.geojson = GEOJSON
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   260
        self.gml = prefix + 'AsGML'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   261
        self.intersection = prefix + 'Intersection'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   262
        self.kml = ASKML
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   263
        self.length = prefix + 'Length'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   264
        self.length3d = prefix + 'Length3D'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   265
        self.length_spheroid = prefix + 'length_spheroid'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   266
        self.makeline = MAKELINE
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   267
        self.mem_size = prefix + 'mem_size'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   268
        self.num_geom = prefix + 'NumGeometries'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   269
        self.num_points =prefix + 'npoints'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   270
        self.perimeter = prefix + 'Perimeter'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   271
        self.perimeter3d = prefix + 'Perimeter3D'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   272
        self.point_on_surface = prefix + 'PointOnSurface'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   273
        self.polygonize = prefix + 'Polygonize'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   274
        self.reverse = prefix + 'Reverse'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   275
        self.scale = prefix + 'Scale'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   276
        self.snap_to_grid = prefix + 'SnapToGrid'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   277
        self.svg = prefix + 'AsSVG'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   278
        self.sym_difference = prefix + 'SymDifference'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   279
        self.transform = prefix + 'Transform'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   280
        self.translate = prefix + 'Translate'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   281
        self.union = UNION
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   282
        self.unionagg = UNIONAGG
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   283
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   284
    def check_aggregate_support(self, aggregate):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   285
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   286
        Checks if the given aggregate name is supported (that is, if it's
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   287
        in `self.valid_aggregates`).
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   288
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   289
        agg_name = aggregate.__class__.__name__
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   290
        return agg_name in self.valid_aggregates
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   291
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   292
    def convert_extent(self, box):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   293
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   294
        Returns a 4-tuple extent for the `Extent` aggregate by converting
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   295
        the bounding box text returned by PostGIS (`box` argument), for
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   296
        example: "BOX(-90.0 30.0, -85.0 40.0)".
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   297
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   298
        ll, ur = box[4:-1].split(',')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   299
        xmin, ymin = map(float, ll.split())
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   300
        xmax, ymax = map(float, ur.split())
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   301
        return (xmin, ymin, xmax, ymax)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   302
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   303
    def convert_extent3d(self, box3d):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   304
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   305
        Returns a 6-tuple extent for the `Extent3D` aggregate by converting
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   306
        the 3d bounding-box text returnded by PostGIS (`box3d` argument), for
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   307
        example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   308
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   309
        ll, ur = box3d[6:-1].split(',')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   310
        xmin, ymin, zmin = map(float, ll.split())
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   311
        xmax, ymax, zmax = map(float, ur.split())
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   312
        return (xmin, ymin, zmin, xmax, ymax, zmax)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   313
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   314
    def convert_geom(self, hex, geo_field):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   315
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   316
        Converts the geometry returned from PostGIS aggretates.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   317
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   318
        if hex:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   319
            return Geometry(hex)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   320
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   321
            return None
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   322
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   323
    def geo_db_type(self, f):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   324
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   325
        Return the database field type for the given geometry field.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   326
        Typically this is `None` because geometry columns are added via
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   327
        the `AddGeometryColumn` stored procedure, unless the field
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   328
        has been specified to be of geography type instead.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   329
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   330
        if f.geography:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   331
            if not self.geography:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   332
                raise NotImplementedError('PostGIS 1.5 required for geography column support.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   333
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   334
            if f.srid != 4326:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   335
                raise NotImplementedError('PostGIS 1.5 supports geography columns '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   336
                                          'only with an SRID of 4326.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   337
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   338
            return 'geography(%s,%d)'% (f.geom_type, f.srid)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   339
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   340
            return None
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   341
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   342
    def get_distance(self, f, dist_val, lookup_type):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   343
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   344
        Retrieve the distance parameters for the given geometry field,
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   345
        distance lookup value, and the distance lookup type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   346
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   347
        This is the most complex implementation of the spatial backends due to
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   348
        what is supported on geodetic geometry columns vs. what's available on
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   349
        projected geometry columns.  In addition, it has to take into account
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   350
        the newly introduced geography column type introudced in PostGIS 1.5.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   351
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   352
        # Getting the distance parameter and any options.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   353
        if len(dist_val) == 1:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   354
            value, option = dist_val[0], None
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   355
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   356
            value, option = dist_val
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   357
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   358
        # Shorthand boolean flags.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   359
        geodetic = f.geodetic(self.connection)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   360
        geography = f.geography and self.geography
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   361
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   362
        if isinstance(value, Distance):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   363
            if geography:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   364
                dist_param = value.m
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   365
            elif geodetic:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   366
                if lookup_type == 'dwithin':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   367
                    raise ValueError('Only numeric values of degree units are '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   368
                                     'allowed on geographic DWithin queries.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   369
                dist_param = value.m
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   370
            else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   371
                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   372
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   373
            # Assuming the distance is in the units of the field.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   374
            dist_param = value
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   375
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   376
        if (not geography and geodetic and lookup_type != 'dwithin'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   377
            and option == 'spheroid'):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   378
            # using distance_spheroid requires the spheroid of the field as
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   379
            # a parameter.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   380
            return [f._spheroid, dist_param]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   381
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   382
            return [dist_param]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   383
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   384
    def get_geom_placeholder(self, f, value):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   385
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   386
        Provides a proper substitution value for Geometries that are not in the
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   387
        SRID of the field.  Specifically, this routine will substitute in the
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   388
        ST_Transform() function call.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   389
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   390
        if value is None or value.srid == f.srid:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   391
            placeholder = '%s'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   392
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   393
            # Adding Transform() to the SQL placeholder.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   394
            placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   395
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   396
        if hasattr(value, 'expression'):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   397
            # If this is an F expression, then we don't really want
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   398
            # a placeholder and instead substitute in the column
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   399
            # of the expression.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   400
            placeholder = placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   401
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   402
        return placeholder
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   403
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   404
    def _get_postgis_func(self, func):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   405
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   406
        Helper routine for calling PostGIS functions and returning their result.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   407
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   408
        cursor = self.connection._cursor()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   409
        try:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   410
            try:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   411
                cursor.execute('SELECT %s()' % func)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   412
                row = cursor.fetchone()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   413
            except:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   414
                # Responsibility of callers to perform error handling.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   415
                raise
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   416
        finally:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   417
            # Close out the connection.  See #9437.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   418
            self.connection.close()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   419
        return row[0]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   420
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   421
    def postgis_geos_version(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   422
        "Returns the version of the GEOS library used with PostGIS."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   423
        return self._get_postgis_func('postgis_geos_version')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   424
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   425
    def postgis_lib_version(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   426
        "Returns the version number of the PostGIS library used with PostgreSQL."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   427
        return self._get_postgis_func('postgis_lib_version')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   428
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   429
    def postgis_proj_version(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   430
        "Returns the version of the PROJ.4 library used with PostGIS."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   431
        return self._get_postgis_func('postgis_proj_version')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   432
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   433
    def postgis_version(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   434
        "Returns PostGIS version number and compile-time options."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   435
        return self._get_postgis_func('postgis_version')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   436
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   437
    def postgis_full_version(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   438
        "Returns PostGIS version number and compile-time options."
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   439
        return self._get_postgis_func('postgis_full_version')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   440
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   441
    def postgis_version_tuple(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   442
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   443
        Returns the PostGIS version as a tuple (version string, major,
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   444
        minor, subminor).
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   445
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   446
        # Getting the PostGIS version
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   447
        version = self.postgis_lib_version()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   448
        m = self.version_regex.match(version)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   449
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   450
        if m:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   451
            major = int(m.group('major'))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   452
            minor1 = int(m.group('minor1'))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   453
            minor2 = int(m.group('minor2'))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   454
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   455
            raise Exception('Could not parse PostGIS version string: %s' % version)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   456
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   457
        return (version, major, minor1, minor2)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   458
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   459
    def proj_version_tuple(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   460
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   461
        Return the version of PROJ.4 used by PostGIS as a tuple of the
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   462
        major, minor, and subminor release numbers.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   463
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   464
        proj_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   465
        proj_ver_str = self.postgis_proj_version()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   466
        m = proj_regex.search(proj_ver_str)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   467
        if m:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   468
            return tuple(map(int, [m.group(1), m.group(2), m.group(3)]))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   469
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   470
            raise Exception('Could not determine PROJ.4 version from PostGIS.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   471
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   472
    def num_params(self, lookup_type, num_param):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   473
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   474
        Helper routine that returns a boolean indicating whether the number of
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   475
        parameters is correct for the lookup type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   476
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   477
        def exactly_two(np): return np == 2
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   478
        def two_to_three(np): return np >= 2 and np <=3
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   479
        if (lookup_type in self.distance_functions and
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   480
            lookup_type != 'dwithin'):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   481
            return two_to_three(num_param)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   482
        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   483
            return exactly_two(num_param)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   484
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   485
    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   486
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   487
        Constructs spatial SQL from the given lookup value tuple a
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   488
        (alias, col, db_type), the lookup type string, lookup value, and
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   489
        the geometry field.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   490
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   491
        alias, col, db_type = lvalue
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   492
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   493
        # Getting the quoted geometry column.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   494
        geo_col = '%s.%s' % (qn(alias), qn(col))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   495
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   496
        if lookup_type in self.geometry_operators:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   497
            if field.geography and not lookup_type in self.geography_operators:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   498
                raise ValueError('PostGIS geography does not support the '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   499
                                 '"%s" lookup.' % lookup_type)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   500
            # Handling a PostGIS operator.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   501
            op = self.geometry_operators[lookup_type]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   502
            return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   503
        elif lookup_type in self.geometry_functions:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   504
            if field.geography and not lookup_type in self.geography_functions:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   505
                raise ValueError('PostGIS geography type does not support the '
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   506
                                 '"%s" lookup.' % lookup_type)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   507
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   508
            # See if a PostGIS geometry function matches the lookup type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   509
            tmp = self.geometry_functions[lookup_type]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   510
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   511
            # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   512
            # distance lookups.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   513
            if isinstance(tmp, tuple):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   514
                # First element of tuple is the PostGISOperation instance, and the
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   515
                # second element is either the type or a tuple of acceptable types
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   516
                # that may passed in as further parameters for the lookup type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   517
                op, arg_type = tmp
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   518
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   519
                # Ensuring that a tuple _value_ was passed in from the user
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   520
                if not isinstance(value, (tuple, list)):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   521
                    raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   522
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   523
                # Geometry is first element of lookup tuple.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   524
                geom = value[0]
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   525
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   526
                # Number of valid tuple parameters depends on the lookup type.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   527
                nparams = len(value)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   528
                if not self.num_params(lookup_type, nparams):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   529
                    raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   530
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   531
                # Ensuring the argument type matches what we expect.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   532
                if not isinstance(value[1], arg_type):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   533
                    raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   534
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   535
                # For lookup type `relate`, the op instance is not yet created (has
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   536
                # to be instantiated here to check the pattern parameter).
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   537
                if lookup_type == 'relate':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   538
                    op = op(self.geom_func_prefix, value[1])
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   539
                elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   540
                    if not field.geography and field.geodetic(self.connection):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   541
                        # Geodetic distances are only availble from Points to
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   542
                        # PointFields on PostGIS 1.4 and below.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   543
                        if not self.connection.ops.geography:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   544
                            if field.geom_type != 'POINT':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   545
                                raise ValueError('PostGIS spherical operations are only valid on PointFields.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   546
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   547
                            if str(geom.geom_type) != 'Point':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   548
                                raise ValueError('PostGIS geometry distance parameter is required to be of type Point.')
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   549
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   550
                        # Setting up the geodetic operation appropriately.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   551
                        if nparams == 3 and value[2] == 'spheroid':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   552
                            op = op['spheroid']
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   553
                        else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   554
                            op = op['sphere']
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   555
                    else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   556
                        op = op['cartesian']
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   557
            else:
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   558
                op = tmp
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   559
                geom = value
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   560
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   561
            # Calling the `as_sql` function on the operation instance.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   562
            return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   563
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   564
        elif lookup_type == 'isnull':
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   565
            # Handling 'isnull' lookup type
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   566
            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   567
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   568
        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   569
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   570
    def spatial_aggregate_sql(self, agg):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   571
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   572
        Returns the spatial aggregate SQL template and function for the
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   573
        given Aggregate instance.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   574
        """
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   575
        agg_name = agg.__class__.__name__
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   576
        if not self.check_aggregate_support(agg):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   577
            raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   578
        agg_name = agg_name.lower()
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   579
        if agg_name == 'union': agg_name += 'agg'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   580
        sql_template = '%(function)s(%(field)s)'
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   581
        sql_function = getattr(self, agg_name)
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   582
        return sql_template, sql_function
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   583
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   584
    # Routines for getting the OGC-compliant models.
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   585
    def geometry_columns(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   586
        from django.contrib.gis.db.backends.postgis.models import GeometryColumns
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   587
        return GeometryColumns
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   588
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   589
    def spatial_ref_sys(self):
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   590
        from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
cc9b7e14412b update django and lucene
ymh <ymh.work@gmail.com>
parents:
diff changeset
   591
        return SpatialRefSys