| 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") |