web/lib/django/contrib/gis/db/models/fields/__init__.py
changeset 0 0d40e90630ef
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 from django.contrib.gis import forms
       
     2 # Getting the SpatialBackend container and the geographic quoting method.
       
     3 from django.contrib.gis.db.backend import SpatialBackend, gqn
       
     4 # GeometryProxy, GEOS, and Distance imports.
       
     5 from django.contrib.gis.db.models.proxy import GeometryProxy
       
     6 from django.contrib.gis.measure import Distance
       
     7 
       
     8 # Local cache of the spatial_ref_sys table, which holds static data.
       
     9 # This exists so that we don't have to hit the database each time.
       
    10 _srid_cache = {}
       
    11 
       
    12 def get_srid_info(srid):
       
    13     """
       
    14     Returns the units, unit name, and spheroid WKT associated with the
       
    15     given SRID from the `spatial_ref_sys` (or equivalent) spatial database
       
    16     table.  These results are cached.
       
    17     """
       
    18     global _srid_cache
       
    19 
       
    20     if SpatialBackend.mysql:
       
    21         return None, None, None
       
    22 
       
    23     if not srid in _srid_cache:
       
    24         from django.contrib.gis.models import SpatialRefSys
       
    25         sr = SpatialRefSys.objects.get(srid=srid)
       
    26         units, units_name = sr.units
       
    27         spheroid = SpatialRefSys.get_spheroid(sr.wkt)
       
    28         _srid_cache[srid] = (units, units_name, spheroid)
       
    29 
       
    30     return _srid_cache[srid]
       
    31 
       
    32 class GeometryField(SpatialBackend.Field):
       
    33     "The base GIS field -- maps to the OpenGIS Specification Geometry type."
       
    34 
       
    35     # The OpenGIS Geometry name.
       
    36     geom_type = 'GEOMETRY'
       
    37 
       
    38     # Geodetic units.
       
    39     geodetic_units = ('Decimal Degree', 'degree')
       
    40 
       
    41     def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
       
    42         """
       
    43         The initialization function for geometry fields.  Takes the following
       
    44         as keyword arguments:
       
    45 
       
    46         srid:
       
    47          The spatial reference system identifier, an OGC standard.
       
    48          Defaults to 4326 (WGS84).
       
    49 
       
    50         spatial_index:
       
    51          Indicates whether to create a spatial index.  Defaults to True.
       
    52          Set this instead of 'db_index' for geographic fields since index
       
    53          creation is different for geometry columns.
       
    54 
       
    55         dim:
       
    56          The number of dimensions for this geometry.  Defaults to 2.
       
    57         """
       
    58 
       
    59         # Setting the index flag with the value of the `spatial_index` keyword.
       
    60         self.spatial_index = spatial_index
       
    61 
       
    62         # Setting the SRID and getting the units.  Unit information must be
       
    63         # easily available in the field instance for distance queries.
       
    64         self.srid = srid
       
    65 
       
    66         # Setting the dimension of the geometry field.
       
    67         self.dim = dim
       
    68 
       
    69         # Setting the verbose_name keyword argument with the positional
       
    70         # first parameter, so this works like normal fields.
       
    71         kwargs['verbose_name'] = verbose_name
       
    72 
       
    73         super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
       
    74 
       
    75     # The following properties are used to get the units, their name, and
       
    76     # the spheroid corresponding to the SRID of the GeometryField.
       
    77     def _get_srid_info(self):
       
    78         # Get attributes from `get_srid_info`.
       
    79         self._units, self._units_name, self._spheroid = get_srid_info(self.srid)
       
    80 
       
    81     @property
       
    82     def spheroid(self):
       
    83         if not hasattr(self, '_spheroid'):
       
    84             self._get_srid_info()
       
    85         return self._spheroid
       
    86 
       
    87     @property
       
    88     def units(self):
       
    89         if not hasattr(self, '_units'):
       
    90             self._get_srid_info()
       
    91         return self._units
       
    92 
       
    93     @property
       
    94     def units_name(self):
       
    95         if not hasattr(self, '_units_name'):
       
    96             self._get_srid_info()
       
    97         return self._units_name
       
    98 
       
    99     # The following properties are for formerly private variables that are now
       
   100     # public for GeometryField.  Because of their use by third-party applications,
       
   101     # a deprecation warning is issued to notify them to use new attribute name.
       
   102     def _deprecated_warning(self, old_name, new_name):
       
   103         from warnings import warn
       
   104         warn('The `%s` attribute name is deprecated, please update your code to use `%s` instead.' %
       
   105              (old_name, new_name))
       
   106 
       
   107     @property
       
   108     def _geom(self):
       
   109         self._deprecated_warning('_geom', 'geom_type')
       
   110         return self.geom_type
       
   111 
       
   112     @property
       
   113     def _index(self):
       
   114         self._deprecated_warning('_index', 'spatial_index')
       
   115         return self.spatial_index
       
   116 
       
   117     @property
       
   118     def _srid(self):
       
   119         self._deprecated_warning('_srid', 'srid')
       
   120         return self.srid
       
   121 
       
   122     ### Routines specific to GeometryField ###
       
   123     @property
       
   124     def geodetic(self):
       
   125         """
       
   126         Returns true if this field's SRID corresponds with a coordinate
       
   127         system that uses non-projected units (e.g., latitude/longitude).
       
   128         """
       
   129         return self.units_name in self.geodetic_units
       
   130 
       
   131     def get_distance(self, dist_val, lookup_type):
       
   132         """
       
   133         Returns a distance number in units of the field.  For example, if
       
   134         `D(km=1)` was passed in and the units of the field were in meters,
       
   135         then 1000 would be returned.
       
   136         """
       
   137         # Getting the distance parameter and any options.
       
   138         if len(dist_val) == 1: dist, option = dist_val[0], None
       
   139         else: dist, option = dist_val
       
   140 
       
   141         if isinstance(dist, Distance):
       
   142             if self.geodetic:
       
   143                 # Won't allow Distance objects w/DWithin lookups on PostGIS.
       
   144                 if SpatialBackend.postgis and lookup_type == 'dwithin':
       
   145                     raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
       
   146                 # Spherical distance calculation parameter should be in meters.
       
   147                 dist_param = dist.m
       
   148             else:
       
   149                 dist_param = getattr(dist, Distance.unit_attname(self.units_name))
       
   150         else:
       
   151             # Assuming the distance is in the units of the field.
       
   152             dist_param = dist
       
   153 
       
   154         if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid':
       
   155             # On PostGIS, by default `ST_distance_sphere` is used; but if the
       
   156             # accuracy of `ST_distance_spheroid` is needed than the spheroid
       
   157             # needs to be passed to the SQL stored procedure.
       
   158             return [gqn(self._spheroid), dist_param]
       
   159         else:
       
   160             return [dist_param]
       
   161 
       
   162     def get_geometry(self, value):
       
   163         """
       
   164         Retrieves the geometry, setting the default SRID from the given
       
   165         lookup parameters.
       
   166         """
       
   167         if isinstance(value, (tuple, list)):
       
   168             geom = value[0]
       
   169         else:
       
   170             geom = value
       
   171 
       
   172         # When the input is not a GEOS geometry, attempt to construct one
       
   173         # from the given string input.
       
   174         if isinstance(geom, SpatialBackend.Geometry):
       
   175             pass
       
   176         elif isinstance(geom, basestring):
       
   177             try:
       
   178                 geom = SpatialBackend.Geometry(geom)
       
   179             except SpatialBackend.GeometryException:
       
   180                 raise ValueError('Could not create geometry from lookup value: %s' % str(value))
       
   181         else:
       
   182             raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
       
   183 
       
   184         # Assigning the SRID value.
       
   185         geom.srid = self.get_srid(geom)
       
   186 
       
   187         return geom
       
   188 
       
   189     def get_srid(self, geom):
       
   190         """
       
   191         Returns the default SRID for the given geometry, taking into account
       
   192         the SRID set for the field.  For example, if the input geometry
       
   193         has no SRID, then that of the field will be returned.
       
   194         """
       
   195         gsrid = geom.srid # SRID of given geometry.
       
   196         if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
       
   197             return self.srid
       
   198         else:
       
   199             return gsrid
       
   200 
       
   201     ### Routines overloaded from Field ###
       
   202     def contribute_to_class(self, cls, name):
       
   203         super(GeometryField, self).contribute_to_class(cls, name)
       
   204 
       
   205         # Setup for lazy-instantiated Geometry object.
       
   206         setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self))
       
   207 
       
   208     def formfield(self, **kwargs):
       
   209         defaults = {'form_class' : forms.GeometryField,
       
   210                     'null' : self.null,
       
   211                     'geom_type' : self.geom_type,
       
   212                     'srid' : self.srid,
       
   213                     }
       
   214         defaults.update(kwargs)
       
   215         return super(GeometryField, self).formfield(**defaults)
       
   216 
       
   217     def get_db_prep_lookup(self, lookup_type, value):
       
   218         """
       
   219         Returns the spatial WHERE clause and associated parameters for the
       
   220         given lookup type and value.  The value will be prepared for database
       
   221         lookup (e.g., spatial transformation SQL will be added if necessary).
       
   222         """
       
   223         if lookup_type in SpatialBackend.gis_terms:
       
   224             # special case for isnull lookup
       
   225             if lookup_type == 'isnull': return [], []
       
   226 
       
   227             # Get the geometry with SRID; defaults SRID to that of the field
       
   228             # if it is None.
       
   229             geom = self.get_geometry(value)
       
   230 
       
   231             # Getting the WHERE clause list and the associated params list. The params
       
   232             # list is populated with the Adaptor wrapping the Geometry for the
       
   233             # backend.  The WHERE clause list contains the placeholder for the adaptor
       
   234             # (e.g. any transformation SQL).
       
   235             where = [self.get_placeholder(geom)]
       
   236             params = [SpatialBackend.Adaptor(geom)]
       
   237 
       
   238             if isinstance(value, (tuple, list)):
       
   239                 if lookup_type in SpatialBackend.distance_functions:
       
   240                     # Getting the distance parameter in the units of the field.
       
   241                     where += self.get_distance(value[1:], lookup_type)
       
   242                 elif lookup_type in SpatialBackend.limited_where:
       
   243                     pass
       
   244                 else:
       
   245                     # Otherwise, making sure any other parameters are properly quoted.
       
   246                     where += map(gqn, value[1:])
       
   247             return where, params
       
   248         else:
       
   249             raise TypeError("Field has invalid lookup: %s" % lookup_type)
       
   250 
       
   251     def get_db_prep_save(self, value):
       
   252         "Prepares the value for saving in the database."
       
   253         if value is None:
       
   254             return None
       
   255         else:
       
   256             return SpatialBackend.Adaptor(self.get_geometry(value))
       
   257 
       
   258 # The OpenGIS Geometry Type Fields
       
   259 class PointField(GeometryField):
       
   260     geom_type = 'POINT'
       
   261 
       
   262 class LineStringField(GeometryField):
       
   263     geom_type = 'LINESTRING'
       
   264 
       
   265 class PolygonField(GeometryField):
       
   266     geom_type = 'POLYGON'
       
   267 
       
   268 class MultiPointField(GeometryField):
       
   269     geom_type = 'MULTIPOINT'
       
   270 
       
   271 class MultiLineStringField(GeometryField):
       
   272     geom_type = 'MULTILINESTRING'
       
   273 
       
   274 class MultiPolygonField(GeometryField):
       
   275     geom_type = 'MULTIPOLYGON'
       
   276 
       
   277 class GeometryCollectionField(GeometryField):
       
   278     geom_type = 'GEOMETRYCOLLECTION'