web/lib/django/contrib/gis/db/models/fields.py
changeset 29 cc9b7e14412b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/contrib/gis/db/models/fields.py	Tue May 25 02:43:45 2010 +0200
@@ -0,0 +1,294 @@
+from django.db.models.fields import Field
+from django.db.models.sql.expressions import SQLEvaluator
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.gis import forms
+from django.contrib.gis.db.models.proxy import GeometryProxy
+from django.contrib.gis.geometry.backend import Geometry, GeometryException
+
+# Local cache of the spatial_ref_sys table, which holds SRID data for each
+# spatial database alias. This cache exists so that the database isn't queried
+# for SRID info each time a distance query is constructed.
+_srid_cache = {}
+
+def get_srid_info(srid, connection):
+    """
+    Returns the units, unit name, and spheroid WKT associated with the
+    given SRID from the `spatial_ref_sys` (or equivalent) spatial database
+    table for the given database connection.  These results are cached.
+    """
+    global _srid_cache
+
+    try:
+        # The SpatialRefSys model for the spatial backend.
+        SpatialRefSys = connection.ops.spatial_ref_sys()
+    except NotImplementedError:
+        # No `spatial_ref_sys` table in spatial backend (e.g., MySQL).
+        return None, None, None
+
+    if not connection.alias in _srid_cache:
+        # Initialize SRID dictionary for database if it doesn't exist.
+        _srid_cache[connection.alias] = {}
+
+    if not srid in _srid_cache[connection.alias]:
+        # Use `SpatialRefSys` model to query for spatial reference info.
+        sr = SpatialRefSys.objects.using(connection.alias).get(srid=srid)
+        units, units_name = sr.units
+        spheroid = SpatialRefSys.get_spheroid(sr.wkt)
+        _srid_cache[connection.alias][srid] = (units, units_name, spheroid)
+
+    return _srid_cache[connection.alias][srid]
+
+class GeometryField(Field):
+    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
+
+    # The OpenGIS Geometry name.
+    geom_type = 'GEOMETRY'
+
+    # Geodetic units.
+    geodetic_units = ('Decimal Degree', 'degree')
+
+    description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
+
+    def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
+                 geography=False, **kwargs):
+        """
+        The initialization function for geometry fields.  Takes the following
+        as keyword arguments:
+
+        srid:
+         The spatial reference system identifier, an OGC standard.
+         Defaults to 4326 (WGS84).
+
+        spatial_index:
+         Indicates whether to create a spatial index.  Defaults to True.
+         Set this instead of 'db_index' for geographic fields since index
+         creation is different for geometry columns.
+
+        dim:
+         The number of dimensions for this geometry.  Defaults to 2.
+
+        extent:
+         Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
+         geometry field entry in the `USER_SDO_GEOM_METADATA` table.  Defaults
+         to (-180.0, -90.0, 180.0, 90.0).
+
+        tolerance:
+         Define the tolerance, in meters, to use for the geometry field
+         entry in the `USER_SDO_GEOM_METADATA` table.  Defaults to 0.05.
+        """
+
+        # Setting the index flag with the value of the `spatial_index` keyword.
+        self.spatial_index = spatial_index
+
+        # Setting the SRID and getting the units.  Unit information must be
+        # easily available in the field instance for distance queries.
+        self.srid = srid
+
+        # Setting the dimension of the geometry field.
+        self.dim = dim
+
+        # Setting the verbose_name keyword argument with the positional
+        # first parameter, so this works like normal fields.
+        kwargs['verbose_name'] = verbose_name
+
+        # Is this a geography rather than a geometry column?
+        self.geography = geography
+
+        # Oracle-specific private attributes for creating the entrie in
+        # `USER_SDO_GEOM_METADATA`
+        self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
+        self._tolerance = kwargs.pop('tolerance', 0.05)
+
+        super(GeometryField, self).__init__(**kwargs)
+
+    # The following functions are used to get the units, their name, and
+    # the spheroid corresponding to the SRID of the GeometryField.
+    def _get_srid_info(self, connection):
+        # Get attributes from `get_srid_info`.
+        self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection)
+
+    def spheroid(self, connection):
+        if not hasattr(self, '_spheroid'):
+            self._get_srid_info(connection)
+        return self._spheroid
+
+    def units(self, connection):
+        if not hasattr(self, '_units'):
+            self._get_srid_info(connection)
+        return self._units
+
+    def units_name(self, connection):
+        if not hasattr(self, '_units_name'):
+            self._get_srid_info(connection)
+        return self._units_name
+
+    ### Routines specific to GeometryField ###
+    def geodetic(self, connection):
+        """
+        Returns true if this field's SRID corresponds with a coordinate
+        system that uses non-projected units (e.g., latitude/longitude).
+        """
+        return self.units_name(connection) in self.geodetic_units
+
+    def get_distance(self, value, lookup_type, connection):
+        """
+        Returns a distance number in units of the field.  For example, if
+        `D(km=1)` was passed in and the units of the field were in meters,
+        then 1000 would be returned.
+        """
+        return connection.ops.get_distance(self, value, lookup_type)
+
+    def get_prep_value(self, value):
+        """
+        Spatial lookup values are either a parameter that is (or may be
+        converted to) a geometry, or a sequence of lookup values that
+        begins with a geometry.  This routine will setup the geometry
+        value properly, and preserve any other lookup parameters before
+        returning to the caller.
+        """
+        if isinstance(value, SQLEvaluator):
+            return value
+        elif isinstance(value, (tuple, list)):
+            geom = value[0]
+            seq_value = True
+        else:
+            geom = value
+            seq_value = False
+
+        # When the input is not a GEOS geometry, attempt to construct one
+        # from the given string input.
+        if isinstance(geom, Geometry):
+            pass
+        elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
+            try:
+                geom = Geometry(geom)
+            except GeometryException:
+                raise ValueError('Could not create geometry from lookup value.')
+        else:
+            raise ValueError('Cannot use object with type %s for a geometry lookup parameter.' % type(geom).__name__)
+
+        # Assigning the SRID value.
+        geom.srid = self.get_srid(geom)
+
+        if seq_value:
+            lookup_val = [geom]
+            lookup_val.extend(value[1:])
+            return tuple(lookup_val)
+        else:
+            return geom
+
+    def get_srid(self, geom):
+        """
+        Returns the default SRID for the given geometry, taking into account
+        the SRID set for the field.  For example, if the input geometry
+        has no SRID, then that of the field will be returned.
+        """
+        gsrid = geom.srid # SRID of given geometry.
+        if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
+            return self.srid
+        else:
+            return gsrid
+
+    ### Routines overloaded from Field ###
+    def contribute_to_class(self, cls, name):
+        super(GeometryField, self).contribute_to_class(cls, name)
+
+        # Setup for lazy-instantiated Geometry object.
+        setattr(cls, self.attname, GeometryProxy(Geometry, self))
+
+    def db_type(self, connection):
+        return connection.ops.geo_db_type(self)
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class' : forms.GeometryField,
+                    'null' : self.null,
+                    'geom_type' : self.geom_type,
+                    'srid' : self.srid,
+                    }
+        defaults.update(kwargs)
+        return super(GeometryField, self).formfield(**defaults)
+
+    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
+        """
+        Prepare for the database lookup, and return any spatial parameters
+        necessary for the query.  This includes wrapping any geometry
+        parameters with a backend-specific adapter and formatting any distance
+        parameters into the correct units for the coordinate system of the
+        field.
+        """
+        if lookup_type in connection.ops.gis_terms:
+            # special case for isnull lookup
+            if lookup_type == 'isnull':
+                return []
+
+            # Populating the parameters list, and wrapping the Geometry
+            # with the Adapter of the spatial backend.
+            if isinstance(value, (tuple, list)):
+                params = [connection.ops.Adapter(value[0])]
+                if lookup_type in connection.ops.distance_functions:
+                    # Getting the distance parameter in the units of the field.
+                    params += self.get_distance(value[1:], lookup_type, connection)
+                elif lookup_type in connection.ops.truncate_params:
+                    # Lookup is one where SQL parameters aren't needed from the
+                    # given lookup value.
+                    pass
+                else:
+                    params += value[1:]
+            elif isinstance(value, SQLEvaluator):
+                params = []
+            else:
+                params = [connection.ops.Adapter(value)]
+
+            return params
+        else:
+            raise ValueError('%s is not a valid spatial lookup for %s.' %
+                             (lookup_type, self.__class__.__name__))
+
+    def get_prep_lookup(self, lookup_type, value):
+        if lookup_type == 'isnull':
+            return bool(value)
+        else:
+            return self.get_prep_value(value)
+
+    def get_db_prep_save(self, value, connection):
+        "Prepares the value for saving in the database."
+        if value is None:
+            return None
+        else:
+            return connection.ops.Adapter(self.get_prep_value(value))
+
+    def get_placeholder(self, value, connection):
+        """
+        Returns the placeholder for the geometry column for the
+        given value.
+        """
+        return connection.ops.get_geom_placeholder(self, value)
+
+# The OpenGIS Geometry Type Fields
+class PointField(GeometryField):
+    geom_type = 'POINT'
+    description = _("Point")
+
+class LineStringField(GeometryField):
+    geom_type = 'LINESTRING'
+    description = _("Line string")
+
+class PolygonField(GeometryField):
+    geom_type = 'POLYGON'
+    description = _("Polygon")
+
+class MultiPointField(GeometryField):
+    geom_type = 'MULTIPOINT'
+    description = _("Multi-point")
+
+class MultiLineStringField(GeometryField):
+    geom_type = 'MULTILINESTRING'
+    description = _("Multi-line string")
+
+class MultiPolygonField(GeometryField):
+    geom_type = 'MULTIPOLYGON'
+    description = _("Multi polygon")
+
+class GeometryCollectionField(GeometryField):
+    geom_type = 'GEOMETRYCOLLECTION'
+    description = _("Geometry collection")