PageRenderTime 124ms CodeModel.GetById 60ms app.highlight 19ms RepoModel.GetById 41ms app.codeStats 1ms

/django/contrib/gis/db/models/fields.py

https://code.google.com/p/mango-py/
Python | 294 lines | 218 code | 22 blank | 54 comment | 16 complexity | 46e71eb11a743e8a805d7666a93cef55 MD5 | raw file
  1from django.db.models.fields import Field
  2from django.db.models.sql.expressions import SQLEvaluator
  3from django.utils.translation import ugettext_lazy as _
  4from django.contrib.gis import forms
  5from django.contrib.gis.db.models.proxy import GeometryProxy
  6from django.contrib.gis.geometry.backend import Geometry, GeometryException
  7
  8# Local cache of the spatial_ref_sys table, which holds SRID data for each
  9# spatial database alias. This cache exists so that the database isn't queried
 10# for SRID info each time a distance query is constructed.
 11_srid_cache = {}
 12
 13def get_srid_info(srid, connection):
 14    """
 15    Returns the units, unit name, and spheroid WKT associated with the
 16    given SRID from the `spatial_ref_sys` (or equivalent) spatial database
 17    table for the given database connection.  These results are cached.
 18    """
 19    global _srid_cache
 20
 21    try:
 22        # The SpatialRefSys model for the spatial backend.
 23        SpatialRefSys = connection.ops.spatial_ref_sys()
 24    except NotImplementedError:
 25        # No `spatial_ref_sys` table in spatial backend (e.g., MySQL).
 26        return None, None, None
 27
 28    if not connection.alias in _srid_cache:
 29        # Initialize SRID dictionary for database if it doesn't exist.
 30        _srid_cache[connection.alias] = {}
 31
 32    if not srid in _srid_cache[connection.alias]:
 33        # Use `SpatialRefSys` model to query for spatial reference info.
 34        sr = SpatialRefSys.objects.using(connection.alias).get(srid=srid)
 35        units, units_name = sr.units
 36        spheroid = SpatialRefSys.get_spheroid(sr.wkt)
 37        _srid_cache[connection.alias][srid] = (units, units_name, spheroid)
 38
 39    return _srid_cache[connection.alias][srid]
 40
 41class GeometryField(Field):
 42    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
 43
 44    # The OpenGIS Geometry name.
 45    geom_type = 'GEOMETRY'
 46
 47    # Geodetic units.
 48    geodetic_units = ('Decimal Degree', 'degree')
 49
 50    description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
 51
 52    def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
 53                 geography=False, **kwargs):
 54        """
 55        The initialization function for geometry fields.  Takes the following
 56        as keyword arguments:
 57
 58        srid:
 59         The spatial reference system identifier, an OGC standard.
 60         Defaults to 4326 (WGS84).
 61
 62        spatial_index:
 63         Indicates whether to create a spatial index.  Defaults to True.
 64         Set this instead of 'db_index' for geographic fields since index
 65         creation is different for geometry columns.
 66
 67        dim:
 68         The number of dimensions for this geometry.  Defaults to 2.
 69
 70        extent:
 71         Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
 72         geometry field entry in the `USER_SDO_GEOM_METADATA` table.  Defaults
 73         to (-180.0, -90.0, 180.0, 90.0).
 74
 75        tolerance:
 76         Define the tolerance, in meters, to use for the geometry field
 77         entry in the `USER_SDO_GEOM_METADATA` table.  Defaults to 0.05.
 78        """
 79
 80        # Setting the index flag with the value of the `spatial_index` keyword.
 81        self.spatial_index = spatial_index
 82
 83        # Setting the SRID and getting the units.  Unit information must be
 84        # easily available in the field instance for distance queries.
 85        self.srid = srid
 86
 87        # Setting the dimension of the geometry field.
 88        self.dim = dim
 89
 90        # Setting the verbose_name keyword argument with the positional
 91        # first parameter, so this works like normal fields.
 92        kwargs['verbose_name'] = verbose_name
 93
 94        # Is this a geography rather than a geometry column?
 95        self.geography = geography
 96
 97        # Oracle-specific private attributes for creating the entrie in
 98        # `USER_SDO_GEOM_METADATA`
 99        self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
100        self._tolerance = kwargs.pop('tolerance', 0.05)
101
102        super(GeometryField, self).__init__(**kwargs)
103
104    # The following functions are used to get the units, their name, and
105    # the spheroid corresponding to the SRID of the GeometryField.
106    def _get_srid_info(self, connection):
107        # Get attributes from `get_srid_info`.
108        self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection)
109
110    def spheroid(self, connection):
111        if not hasattr(self, '_spheroid'):
112            self._get_srid_info(connection)
113        return self._spheroid
114
115    def units(self, connection):
116        if not hasattr(self, '_units'):
117            self._get_srid_info(connection)
118        return self._units
119
120    def units_name(self, connection):
121        if not hasattr(self, '_units_name'):
122            self._get_srid_info(connection)
123        return self._units_name
124
125    ### Routines specific to GeometryField ###
126    def geodetic(self, connection):
127        """
128        Returns true if this field's SRID corresponds with a coordinate
129        system that uses non-projected units (e.g., latitude/longitude).
130        """
131        return self.units_name(connection) in self.geodetic_units
132
133    def get_distance(self, value, lookup_type, connection):
134        """
135        Returns a distance number in units of the field.  For example, if
136        `D(km=1)` was passed in and the units of the field were in meters,
137        then 1000 would be returned.
138        """
139        return connection.ops.get_distance(self, value, lookup_type)
140
141    def get_prep_value(self, value):
142        """
143        Spatial lookup values are either a parameter that is (or may be
144        converted to) a geometry, or a sequence of lookup values that
145        begins with a geometry.  This routine will setup the geometry
146        value properly, and preserve any other lookup parameters before
147        returning to the caller.
148        """
149        if isinstance(value, SQLEvaluator):
150            return value
151        elif isinstance(value, (tuple, list)):
152            geom = value[0]
153            seq_value = True
154        else:
155            geom = value
156            seq_value = False
157
158        # When the input is not a GEOS geometry, attempt to construct one
159        # from the given string input.
160        if isinstance(geom, Geometry):
161            pass
162        elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
163            try:
164                geom = Geometry(geom)
165            except GeometryException:
166                raise ValueError('Could not create geometry from lookup value.')
167        else:
168            raise ValueError('Cannot use object with type %s for a geometry lookup parameter.' % type(geom).__name__)
169
170        # Assigning the SRID value.
171        geom.srid = self.get_srid(geom)
172
173        if seq_value:
174            lookup_val = [geom]
175            lookup_val.extend(value[1:])
176            return tuple(lookup_val)
177        else:
178            return geom
179
180    def get_srid(self, geom):
181        """
182        Returns the default SRID for the given geometry, taking into account
183        the SRID set for the field.  For example, if the input geometry
184        has no SRID, then that of the field will be returned.
185        """
186        gsrid = geom.srid # SRID of given geometry.
187        if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
188            return self.srid
189        else:
190            return gsrid
191
192    ### Routines overloaded from Field ###
193    def contribute_to_class(self, cls, name):
194        super(GeometryField, self).contribute_to_class(cls, name)
195
196        # Setup for lazy-instantiated Geometry object.
197        setattr(cls, self.attname, GeometryProxy(Geometry, self))
198
199    def db_type(self, connection):
200        return connection.ops.geo_db_type(self)
201
202    def formfield(self, **kwargs):
203        defaults = {'form_class' : forms.GeometryField,
204                    'null' : self.null,
205                    'geom_type' : self.geom_type,
206                    'srid' : self.srid,
207                    }
208        defaults.update(kwargs)
209        return super(GeometryField, self).formfield(**defaults)
210
211    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
212        """
213        Prepare for the database lookup, and return any spatial parameters
214        necessary for the query.  This includes wrapping any geometry
215        parameters with a backend-specific adapter and formatting any distance
216        parameters into the correct units for the coordinate system of the
217        field.
218        """
219        if lookup_type in connection.ops.gis_terms:
220            # special case for isnull lookup
221            if lookup_type == 'isnull':
222                return []
223
224            # Populating the parameters list, and wrapping the Geometry
225            # with the Adapter of the spatial backend.
226            if isinstance(value, (tuple, list)):
227                params = [connection.ops.Adapter(value[0])]
228                if lookup_type in connection.ops.distance_functions:
229                    # Getting the distance parameter in the units of the field.
230                    params += self.get_distance(value[1:], lookup_type, connection)
231                elif lookup_type in connection.ops.truncate_params:
232                    # Lookup is one where SQL parameters aren't needed from the
233                    # given lookup value.
234                    pass
235                else:
236                    params += value[1:]
237            elif isinstance(value, SQLEvaluator):
238                params = []
239            else:
240                params = [connection.ops.Adapter(value)]
241
242            return params
243        else:
244            raise ValueError('%s is not a valid spatial lookup for %s.' %
245                             (lookup_type, self.__class__.__name__))
246
247    def get_prep_lookup(self, lookup_type, value):
248        if lookup_type == 'isnull':
249            return bool(value)
250        else:
251            return self.get_prep_value(value)
252
253    def get_db_prep_save(self, value, connection):
254        "Prepares the value for saving in the database."
255        if value is None:
256            return None
257        else:
258            return connection.ops.Adapter(self.get_prep_value(value))
259
260    def get_placeholder(self, value, connection):
261        """
262        Returns the placeholder for the geometry column for the
263        given value.
264        """
265        return connection.ops.get_geom_placeholder(self, value)
266
267# The OpenGIS Geometry Type Fields
268class PointField(GeometryField):
269    geom_type = 'POINT'
270    description = _("Point")
271
272class LineStringField(GeometryField):
273    geom_type = 'LINESTRING'
274    description = _("Line string")
275
276class PolygonField(GeometryField):
277    geom_type = 'POLYGON'
278    description = _("Polygon")
279
280class MultiPointField(GeometryField):
281    geom_type = 'MULTIPOINT'
282    description = _("Multi-point")
283
284class MultiLineStringField(GeometryField):
285    geom_type = 'MULTILINESTRING'
286    description = _("Multi-line string")
287
288class MultiPolygonField(GeometryField):
289    geom_type = 'MULTIPOLYGON'
290    description = _("Multi polygon")
291
292class GeometryCollectionField(GeometryField):
293    geom_type = 'GEOMETRYCOLLECTION'
294    description = _("Geometry collection")