| 1 |
from django.contrib.gis import forms |
|---|
| 2 |
from django.db import connection |
|---|
| 3 |
# Getting the SpatialBackend container and the geographic quoting method. |
|---|
| 4 |
from django.contrib.gis.db.backend import SpatialBackend, gqn |
|---|
| 5 |
# GeometryProxy, GEOS, Distance, and oldforms imports. |
|---|
| 6 |
from django.contrib.gis.db.models.proxy import GeometryProxy |
|---|
| 7 |
from django.contrib.gis.measure import Distance |
|---|
| 8 |
from django.contrib.gis.oldforms import WKTField |
|---|
| 9 |
|
|---|
| 10 |
# The `get_srid_info` function gets SRID information from the spatial |
|---|
| 11 |
# reference system table w/o using the ORM. |
|---|
| 12 |
from django.contrib.gis.models import get_srid_info |
|---|
| 13 |
|
|---|
| 14 |
#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. |
|---|
| 15 |
class GeometryField(SpatialBackend.Field): |
|---|
| 16 |
"The base GIS field -- maps to the OpenGIS Specification Geometry type." |
|---|
| 17 |
|
|---|
| 18 |
# The OpenGIS Geometry name. |
|---|
| 19 |
_geom = 'GEOMETRY' |
|---|
| 20 |
|
|---|
| 21 |
# Geodetic units. |
|---|
| 22 |
geodetic_units = ('Decimal Degree', 'degree') |
|---|
| 23 |
|
|---|
| 24 |
def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs): |
|---|
| 25 |
""" |
|---|
| 26 |
The initialization function for geometry fields. Takes the following |
|---|
| 27 |
as keyword arguments: |
|---|
| 28 |
|
|---|
| 29 |
srid: |
|---|
| 30 |
The spatial reference system identifier, an OGC standard. |
|---|
| 31 |
Defaults to 4326 (WGS84). |
|---|
| 32 |
|
|---|
| 33 |
spatial_index: |
|---|
| 34 |
Indicates whether to create a spatial index. Defaults to True. |
|---|
| 35 |
Set this instead of 'db_index' for geographic fields since index |
|---|
| 36 |
creation is different for geometry columns. |
|---|
| 37 |
|
|---|
| 38 |
dim: |
|---|
| 39 |
The number of dimensions for this geometry. Defaults to 2. |
|---|
| 40 |
""" |
|---|
| 41 |
|
|---|
| 42 |
# Setting the index flag with the value of the `spatial_index` keyword. |
|---|
| 43 |
self._index = spatial_index |
|---|
| 44 |
|
|---|
| 45 |
# Setting the SRID and getting the units. Unit information must be |
|---|
| 46 |
# easily available in the field instance for distance queries. |
|---|
| 47 |
self._srid = srid |
|---|
| 48 |
self._unit, self._unit_name, self._spheroid = get_srid_info(srid) |
|---|
| 49 |
|
|---|
| 50 |
# Setting the dimension of the geometry field. |
|---|
| 51 |
self._dim = dim |
|---|
| 52 |
|
|---|
| 53 |
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function |
|---|
| 54 |
|
|---|
| 55 |
### Routines specific to GeometryField ### |
|---|
| 56 |
@property |
|---|
| 57 |
def geodetic(self): |
|---|
| 58 |
""" |
|---|
| 59 |
Returns true if this field's SRID corresponds with a coordinate |
|---|
| 60 |
system that uses non-projected units (e.g., latitude/longitude). |
|---|
| 61 |
""" |
|---|
| 62 |
return self._unit_name in self.geodetic_units |
|---|
| 63 |
|
|---|
| 64 |
def get_distance(self, dist_val, lookup_type): |
|---|
| 65 |
""" |
|---|
| 66 |
Returns a distance number in units of the field. For example, if |
|---|
| 67 |
`D(km=1)` was passed in and the units of the field were in meters, |
|---|
| 68 |
then 1000 would be returned. |
|---|
| 69 |
""" |
|---|
| 70 |
# Getting the distance parameter and any options. |
|---|
| 71 |
if len(dist_val) == 1: dist, option = dist_val[0], None |
|---|
| 72 |
else: dist, option = dist_val |
|---|
| 73 |
|
|---|
| 74 |
if isinstance(dist, Distance): |
|---|
| 75 |
if self.geodetic: |
|---|
| 76 |
# Won't allow Distance objects w/DWithin lookups on PostGIS. |
|---|
| 77 |
if SpatialBackend.postgis and lookup_type == 'dwithin': |
|---|
| 78 |
raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.') |
|---|
| 79 |
# Spherical distance calculation parameter should be in meters. |
|---|
| 80 |
dist_param = dist.m |
|---|
| 81 |
else: |
|---|
| 82 |
dist_param = getattr(dist, Distance.unit_attname(self._unit_name)) |
|---|
| 83 |
else: |
|---|
| 84 |
# Assuming the distance is in the units of the field. |
|---|
| 85 |
dist_param = dist |
|---|
| 86 |
|
|---|
| 87 |
if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid': |
|---|
| 88 |
# On PostGIS, by default `ST_distance_sphere` is used; but if the |
|---|
| 89 |
# accuracy of `ST_distance_spheroid` is needed than the spheroid |
|---|
| 90 |
# needs to be passed to the SQL stored procedure. |
|---|
| 91 |
return [gqn(self._spheroid), dist_param] |
|---|
| 92 |
else: |
|---|
| 93 |
return [dist_param] |
|---|
| 94 |
|
|---|
| 95 |
def get_geometry(self, value): |
|---|
| 96 |
""" |
|---|
| 97 |
Retrieves the geometry, setting the default SRID from the given |
|---|
| 98 |
lookup parameters. |
|---|
| 99 |
""" |
|---|
| 100 |
if isinstance(value, (tuple, list)): |
|---|
| 101 |
geom = value[0] |
|---|
| 102 |
else: |
|---|
| 103 |
geom = value |
|---|
| 104 |
|
|---|
| 105 |
# When the input is not a GEOS geometry, attempt to construct one |
|---|
| 106 |
# from the given string input. |
|---|
| 107 |
if isinstance(geom, SpatialBackend.Geometry): |
|---|
| 108 |
pass |
|---|
| 109 |
elif isinstance(geom, basestring): |
|---|
| 110 |
try: |
|---|
| 111 |
geom = SpatialBackend.Geometry(geom) |
|---|
| 112 |
except SpatialBackend.GeometryException: |
|---|
| 113 |
raise ValueError('Could not create geometry from lookup value: %s' % str(value)) |
|---|
| 114 |
else: |
|---|
| 115 |
raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value)) |
|---|
| 116 |
|
|---|
| 117 |
# Assigning the SRID value. |
|---|
| 118 |
geom.srid = self.get_srid(geom) |
|---|
| 119 |
|
|---|
| 120 |
return geom |
|---|
| 121 |
|
|---|
| 122 |
def get_srid(self, geom): |
|---|
| 123 |
""" |
|---|
| 124 |
Returns the default SRID for the given geometry, taking into account |
|---|
| 125 |
the SRID set for the field. For example, if the input geometry |
|---|
| 126 |
has no SRID, then that of the field will be returned. |
|---|
| 127 |
""" |
|---|
| 128 |
gsrid = geom.srid # SRID of given geometry. |
|---|
| 129 |
if gsrid is None or self._srid == -1 or (gsrid == -1 and self._srid != -1): |
|---|
| 130 |
return self._srid |
|---|
| 131 |
else: |
|---|
| 132 |
return gsrid |
|---|
| 133 |
|
|---|
| 134 |
### Routines overloaded from Field ### |
|---|
| 135 |
def contribute_to_class(self, cls, name): |
|---|
| 136 |
super(GeometryField, self).contribute_to_class(cls, name) |
|---|
| 137 |
|
|---|
| 138 |
# Setup for lazy-instantiated Geometry object. |
|---|
| 139 |
setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self)) |
|---|
| 140 |
|
|---|
| 141 |
def formfield(self, **kwargs): |
|---|
| 142 |
defaults = {'form_class' : forms.GeometryField, |
|---|
| 143 |
'geom_type' : self._geom, |
|---|
| 144 |
'null' : self.null, |
|---|
| 145 |
} |
|---|
| 146 |
defaults.update(kwargs) |
|---|
| 147 |
return super(GeometryField, self).formfield(**defaults) |
|---|
| 148 |
|
|---|
| 149 |
def get_db_prep_lookup(self, lookup_type, value): |
|---|
| 150 |
""" |
|---|
| 151 |
Returns the spatial WHERE clause and associated parameters for the |
|---|
| 152 |
given lookup type and value. The value will be prepared for database |
|---|
| 153 |
lookup (e.g., spatial transformation SQL will be added if necessary). |
|---|
| 154 |
""" |
|---|
| 155 |
if lookup_type in SpatialBackend.gis_terms: |
|---|
| 156 |
# special case for isnull lookup |
|---|
| 157 |
if lookup_type == 'isnull': return [], [] |
|---|
| 158 |
|
|---|
| 159 |
# Get the geometry with SRID; defaults SRID to that of the field |
|---|
| 160 |
# if it is None. |
|---|
| 161 |
geom = self.get_geometry(value) |
|---|
| 162 |
|
|---|
| 163 |
# Getting the WHERE clause list and the associated params list. The params |
|---|
| 164 |
# list is populated with the Adaptor wrapping the Geometry for the |
|---|
| 165 |
# backend. The WHERE clause list contains the placeholder for the adaptor |
|---|
| 166 |
# (e.g. any transformation SQL). |
|---|
| 167 |
where = [self.get_placeholder(geom)] |
|---|
| 168 |
params = [SpatialBackend.Adaptor(geom)] |
|---|
| 169 |
|
|---|
| 170 |
if isinstance(value, (tuple, list)): |
|---|
| 171 |
if lookup_type in SpatialBackend.distance_functions: |
|---|
| 172 |
# Getting the distance parameter in the units of the field. |
|---|
| 173 |
where += self.get_distance(value[1:], lookup_type) |
|---|
| 174 |
elif lookup_type in SpatialBackend.limited_where: |
|---|
| 175 |
pass |
|---|
| 176 |
else: |
|---|
| 177 |
# Otherwise, making sure any other parameters are properly quoted. |
|---|
| 178 |
where += map(gqn, value[1:]) |
|---|
| 179 |
return where, params |
|---|
| 180 |
else: |
|---|
| 181 |
raise TypeError("Field has invalid lookup: %s" % lookup_type) |
|---|
| 182 |
|
|---|
| 183 |
def get_db_prep_save(self, value): |
|---|
| 184 |
"Prepares the value for saving in the database." |
|---|
| 185 |
if value is None: |
|---|
| 186 |
return None |
|---|
| 187 |
else: |
|---|
| 188 |
return SpatialBackend.Adaptor(self.get_geometry(value)) |
|---|
| 189 |
|
|---|
| 190 |
def get_manipulator_field_objs(self): |
|---|
| 191 |
"Using the WKTField (oldforms) to be our manipulator." |
|---|
| 192 |
return [WKTField] |
|---|
| 193 |
|
|---|
| 194 |
# The OpenGIS Geometry Type Fields |
|---|
| 195 |
class PointField(GeometryField): |
|---|
| 196 |
_geom = 'POINT' |
|---|
| 197 |
|
|---|
| 198 |
class LineStringField(GeometryField): |
|---|
| 199 |
_geom = 'LINESTRING' |
|---|
| 200 |
|
|---|
| 201 |
class PolygonField(GeometryField): |
|---|
| 202 |
_geom = 'POLYGON' |
|---|
| 203 |
|
|---|
| 204 |
class MultiPointField(GeometryField): |
|---|
| 205 |
_geom = 'MULTIPOINT' |
|---|
| 206 |
|
|---|
| 207 |
class MultiLineStringField(GeometryField): |
|---|
| 208 |
_geom = 'MULTILINESTRING' |
|---|
| 209 |
|
|---|
| 210 |
class MultiPolygonField(GeometryField): |
|---|
| 211 |
_geom = 'MULTIPOLYGON' |
|---|
| 212 |
|
|---|
| 213 |
class GeometryCollectionField(GeometryField): |
|---|
| 214 |
_geom = 'GEOMETRYCOLLECTION' |
|---|