app/django/contrib/gis/db/models/fields/__init__.py
changeset 323 ff1a9aa48cfd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/django/contrib/gis/db/models/fields/__init__.py	Tue Oct 14 16:00:59 2008 +0000
@@ -0,0 +1,211 @@
+from django.contrib.gis import forms
+# Getting the SpatialBackend container and the geographic quoting method.
+from django.contrib.gis.db.backend import SpatialBackend, gqn
+# GeometryProxy, GEOS, and Distance imports.
+from django.contrib.gis.db.models.proxy import GeometryProxy
+from django.contrib.gis.measure import Distance
+# The `get_srid_info` function gets SRID information from the spatial
+# reference system table w/o using the ORM.
+from django.contrib.gis.models import get_srid_info
+
+#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
+class GeometryField(SpatialBackend.Field):
+    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
+
+    # The OpenGIS Geometry name.
+    _geom = 'GEOMETRY'
+
+    # Geodetic units.
+    geodetic_units = ('Decimal Degree', 'degree')
+
+    def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **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.
+        """
+
+        # Setting the index flag with the value of the `spatial_index` keyword.
+        self._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
+        self._unit, self._unit_name, self._spheroid = get_srid_info(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
+
+        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
+
+    ### Routines specific to GeometryField ###
+    @property
+    def geodetic(self):
+        """
+        Returns true if this field's SRID corresponds with a coordinate
+        system that uses non-projected units (e.g., latitude/longitude).
+        """
+        return self._unit_name in self.geodetic_units
+
+    def get_distance(self, dist_val, lookup_type):
+        """
+        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.
+        """
+        # Getting the distance parameter and any options.
+        if len(dist_val) == 1: dist, option = dist_val[0], None
+        else: dist, option = dist_val
+
+        if isinstance(dist, Distance):
+            if self.geodetic:
+                # Won't allow Distance objects w/DWithin lookups on PostGIS.
+                if SpatialBackend.postgis and lookup_type == 'dwithin':
+                    raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
+                # Spherical distance calculation parameter should be in meters.
+                dist_param = dist.m
+            else:
+                dist_param = getattr(dist, Distance.unit_attname(self._unit_name))
+        else:
+            # Assuming the distance is in the units of the field.
+            dist_param = dist
+
+        if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid':
+            # On PostGIS, by default `ST_distance_sphere` is used; but if the
+            # accuracy of `ST_distance_spheroid` is needed than the spheroid
+            # needs to be passed to the SQL stored procedure.
+            return [gqn(self._spheroid), dist_param]
+        else:
+            return [dist_param]
+
+    def get_geometry(self, value):
+        """
+        Retrieves the geometry, setting the default SRID from the given
+        lookup parameters.
+        """
+        if isinstance(value, (tuple, list)):
+            geom = value[0]
+        else:
+            geom = value
+
+        # When the input is not a GEOS geometry, attempt to construct one
+        # from the given string input.
+        if isinstance(geom, SpatialBackend.Geometry):
+            pass
+        elif isinstance(geom, basestring):
+            try:
+                geom = SpatialBackend.Geometry(geom)
+            except SpatialBackend.GeometryException:
+                raise ValueError('Could not create geometry from lookup value: %s' % str(value))
+        else:
+            raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
+
+        # Assigning the SRID value.
+        geom.srid = self.get_srid(geom)
+
+        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(SpatialBackend.Geometry, self))
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class' : forms.GeometryField,
+                    'geom_type' : self._geom,
+                    'null' : self.null,
+                    }
+        defaults.update(kwargs)
+        return super(GeometryField, self).formfield(**defaults)
+
+    def get_db_prep_lookup(self, lookup_type, value):
+        """
+        Returns the spatial WHERE clause and associated parameters for the
+        given lookup type and value.  The value will be prepared for database
+        lookup (e.g., spatial transformation SQL will be added if necessary).
+        """
+        if lookup_type in SpatialBackend.gis_terms:
+            # special case for isnull lookup
+            if lookup_type == 'isnull': return [], []
+
+            # Get the geometry with SRID; defaults SRID to that of the field
+            # if it is None.
+            geom = self.get_geometry(value)
+
+            # Getting the WHERE clause list and the associated params list. The params
+            # list is populated with the Adaptor wrapping the Geometry for the
+            # backend.  The WHERE clause list contains the placeholder for the adaptor
+            # (e.g. any transformation SQL).
+            where = [self.get_placeholder(geom)]
+            params = [SpatialBackend.Adaptor(geom)]
+
+            if isinstance(value, (tuple, list)):
+                if lookup_type in SpatialBackend.distance_functions:
+                    # Getting the distance parameter in the units of the field.
+                    where += self.get_distance(value[1:], lookup_type)
+                elif lookup_type in SpatialBackend.limited_where:
+                    pass
+                else:
+                    # Otherwise, making sure any other parameters are properly quoted.
+                    where += map(gqn, value[1:])
+            return where, params
+        else:
+            raise TypeError("Field has invalid lookup: %s" % lookup_type)
+
+    def get_db_prep_save(self, value):
+        "Prepares the value for saving in the database."
+        if value is None:
+            return None
+        else:
+            return SpatialBackend.Adaptor(self.get_geometry(value))
+
+# The OpenGIS Geometry Type Fields
+class PointField(GeometryField):
+    _geom = 'POINT'
+
+class LineStringField(GeometryField):
+    _geom = 'LINESTRING'
+
+class PolygonField(GeometryField):
+    _geom = 'POLYGON'
+
+class MultiPointField(GeometryField):
+    _geom = 'MULTIPOINT'
+
+class MultiLineStringField(GeometryField):
+    _geom = 'MULTILINESTRING'
+
+class MultiPolygonField(GeometryField):
+    _geom = 'MULTIPOLYGON'
+
+class GeometryCollectionField(GeometryField):
+    _geom = 'GEOMETRYCOLLECTION'