| 1 |
""" |
|---|
| 2 |
Imports the SpatialRefSys and GeometryColumns models dependent on the |
|---|
| 3 |
spatial database backend. |
|---|
| 4 |
""" |
|---|
| 5 |
import re |
|---|
| 6 |
from django.conf import settings |
|---|
| 7 |
|
|---|
| 8 |
# Checking for the presence of GDAL (needed for the SpatialReference object) |
|---|
| 9 |
from django.contrib.gis.gdal import HAS_GDAL |
|---|
| 10 |
if HAS_GDAL: |
|---|
| 11 |
from django.contrib.gis.gdal import SpatialReference |
|---|
| 12 |
|
|---|
| 13 |
class SpatialRefSysMixin(object): |
|---|
| 14 |
""" |
|---|
| 15 |
The SpatialRefSysMixin is a class used by the database-dependent |
|---|
| 16 |
SpatialRefSys objects to reduce redundnant code. |
|---|
| 17 |
""" |
|---|
| 18 |
|
|---|
| 19 |
# For pulling out the spheroid from the spatial reference string. This |
|---|
| 20 |
# regular expression is used only if the user does not have GDAL installed. |
|---|
| 21 |
# TODO: Flattening not used in all ellipsoids, could also be a minor axis, or 'b' |
|---|
| 22 |
# parameter. |
|---|
| 23 |
spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),') |
|---|
| 24 |
|
|---|
| 25 |
# For pulling out the units on platforms w/o GDAL installed. |
|---|
| 26 |
# TODO: Figure out how to pull out angular units of projected coordinate system and |
|---|
| 27 |
# fix for LOCAL_CS types. GDAL should be highly recommended for performing |
|---|
| 28 |
# distance queries. |
|---|
| 29 |
units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$') |
|---|
| 30 |
|
|---|
| 31 |
@property |
|---|
| 32 |
def srs(self): |
|---|
| 33 |
""" |
|---|
| 34 |
Returns a GDAL SpatialReference object, if GDAL is installed. |
|---|
| 35 |
""" |
|---|
| 36 |
if HAS_GDAL: |
|---|
| 37 |
if hasattr(self, '_srs'): |
|---|
| 38 |
# Returning a clone of the cached SpatialReference object. |
|---|
| 39 |
return self._srs.clone() |
|---|
| 40 |
else: |
|---|
| 41 |
# Attempting to cache a SpatialReference object. |
|---|
| 42 |
|
|---|
| 43 |
# Trying to get from WKT first. |
|---|
| 44 |
try: |
|---|
| 45 |
self._srs = SpatialReference(self.wkt) |
|---|
| 46 |
return self.srs |
|---|
| 47 |
except Exception, msg: |
|---|
| 48 |
pass |
|---|
| 49 |
|
|---|
| 50 |
raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) |
|---|
| 51 |
else: |
|---|
| 52 |
raise Exception('GDAL is not installed.') |
|---|
| 53 |
|
|---|
| 54 |
@property |
|---|
| 55 |
def ellipsoid(self): |
|---|
| 56 |
""" |
|---|
| 57 |
Returns a tuple of the ellipsoid parameters: |
|---|
| 58 |
(semimajor axis, semiminor axis, and inverse flattening). |
|---|
| 59 |
""" |
|---|
| 60 |
if HAS_GDAL: |
|---|
| 61 |
return self.srs.ellipsoid |
|---|
| 62 |
else: |
|---|
| 63 |
m = self.spheroid_regex.match(self.wkt) |
|---|
| 64 |
if m: return (float(m.group('major')), float(m.group('flattening'))) |
|---|
| 65 |
else: return None |
|---|
| 66 |
|
|---|
| 67 |
@property |
|---|
| 68 |
def name(self): |
|---|
| 69 |
"Returns the projection name." |
|---|
| 70 |
return self.srs.name |
|---|
| 71 |
|
|---|
| 72 |
@property |
|---|
| 73 |
def spheroid(self): |
|---|
| 74 |
"Returns the spheroid name for this spatial reference." |
|---|
| 75 |
return self.srs['spheroid'] |
|---|
| 76 |
|
|---|
| 77 |
@property |
|---|
| 78 |
def datum(self): |
|---|
| 79 |
"Returns the datum for this spatial reference." |
|---|
| 80 |
return self.srs['datum'] |
|---|
| 81 |
|
|---|
| 82 |
@property |
|---|
| 83 |
def projected(self): |
|---|
| 84 |
"Is this Spatial Reference projected?" |
|---|
| 85 |
if HAS_GDAL: |
|---|
| 86 |
return self.srs.projected |
|---|
| 87 |
else: |
|---|
| 88 |
return self.wkt.startswith('PROJCS') |
|---|
| 89 |
|
|---|
| 90 |
@property |
|---|
| 91 |
def local(self): |
|---|
| 92 |
"Is this Spatial Reference local?" |
|---|
| 93 |
if HAS_GDAL: |
|---|
| 94 |
return self.srs.local |
|---|
| 95 |
else: |
|---|
| 96 |
return self.wkt.startswith('LOCAL_CS') |
|---|
| 97 |
|
|---|
| 98 |
@property |
|---|
| 99 |
def geographic(self): |
|---|
| 100 |
"Is this Spatial Reference geographic?" |
|---|
| 101 |
if HAS_GDAL: |
|---|
| 102 |
return self.srs.geographic |
|---|
| 103 |
else: |
|---|
| 104 |
return self.wkt.startswith('GEOGCS') |
|---|
| 105 |
|
|---|
| 106 |
@property |
|---|
| 107 |
def linear_name(self): |
|---|
| 108 |
"Returns the linear units name." |
|---|
| 109 |
if HAS_GDAL: |
|---|
| 110 |
return self.srs.linear_name |
|---|
| 111 |
elif self.geographic: |
|---|
| 112 |
return None |
|---|
| 113 |
else: |
|---|
| 114 |
m = self.units_regex.match(self.wkt) |
|---|
| 115 |
return m.group('unit_name') |
|---|
| 116 |
|
|---|
| 117 |
@property |
|---|
| 118 |
def linear_units(self): |
|---|
| 119 |
"Returns the linear units." |
|---|
| 120 |
if HAS_GDAL: |
|---|
| 121 |
return self.srs.linear_units |
|---|
| 122 |
elif self.geographic: |
|---|
| 123 |
return None |
|---|
| 124 |
else: |
|---|
| 125 |
m = self.units_regex.match(self.wkt) |
|---|
| 126 |
return m.group('unit') |
|---|
| 127 |
|
|---|
| 128 |
@property |
|---|
| 129 |
def angular_name(self): |
|---|
| 130 |
"Returns the name of the angular units." |
|---|
| 131 |
if HAS_GDAL: |
|---|
| 132 |
return self.srs.angular_name |
|---|
| 133 |
elif self.projected: |
|---|
| 134 |
return None |
|---|
| 135 |
else: |
|---|
| 136 |
m = self.units_regex.match(self.wkt) |
|---|
| 137 |
return m.group('unit_name') |
|---|
| 138 |
|
|---|
| 139 |
@property |
|---|
| 140 |
def angular_units(self): |
|---|
| 141 |
"Returns the angular units." |
|---|
| 142 |
if HAS_GDAL: |
|---|
| 143 |
return self.srs.angular_units |
|---|
| 144 |
elif self.projected: |
|---|
| 145 |
return None |
|---|
| 146 |
else: |
|---|
| 147 |
m = self.units_regex.match(self.wkt) |
|---|
| 148 |
return m.group('unit') |
|---|
| 149 |
|
|---|
| 150 |
@property |
|---|
| 151 |
def units(self): |
|---|
| 152 |
"Returns a tuple of the units and the name." |
|---|
| 153 |
if self.projected or self.local: |
|---|
| 154 |
return (self.linear_units, self.linear_name) |
|---|
| 155 |
elif self.geographic: |
|---|
| 156 |
return (self.angular_units, self.angular_name) |
|---|
| 157 |
else: |
|---|
| 158 |
return (None, None) |
|---|
| 159 |
|
|---|
| 160 |
@classmethod |
|---|
| 161 |
def get_units(cls, wkt): |
|---|
| 162 |
""" |
|---|
| 163 |
Class method used by GeometryField on initialization to |
|---|
| 164 |
retrive the units on the given WKT, without having to use |
|---|
| 165 |
any of the database fields. |
|---|
| 166 |
""" |
|---|
| 167 |
if HAS_GDAL: |
|---|
| 168 |
return SpatialReference(wkt).units |
|---|
| 169 |
else: |
|---|
| 170 |
m = cls.units_regex.match(wkt) |
|---|
| 171 |
return m.group('unit'), m.group('unit_name') |
|---|
| 172 |
|
|---|
| 173 |
@classmethod |
|---|
| 174 |
def get_spheroid(cls, wkt, string=True): |
|---|
| 175 |
""" |
|---|
| 176 |
Class method used by GeometryField on initialization to |
|---|
| 177 |
retrieve the `SPHEROID[..]` parameters from the given WKT. |
|---|
| 178 |
""" |
|---|
| 179 |
if HAS_GDAL: |
|---|
| 180 |
srs = SpatialReference(wkt) |
|---|
| 181 |
sphere_params = srs.ellipsoid |
|---|
| 182 |
sphere_name = srs['spheroid'] |
|---|
| 183 |
else: |
|---|
| 184 |
m = cls.spheroid_regex.match(wkt) |
|---|
| 185 |
if m: |
|---|
| 186 |
sphere_params = (float(m.group('major')), float(m.group('flattening'))) |
|---|
| 187 |
sphere_name = m.group('name') |
|---|
| 188 |
else: |
|---|
| 189 |
return None |
|---|
| 190 |
|
|---|
| 191 |
if not string: |
|---|
| 192 |
return sphere_name, sphere_params |
|---|
| 193 |
else: |
|---|
| 194 |
# `string` parameter used to place in format acceptable by PostGIS |
|---|
| 195 |
if len(sphere_params) == 3: |
|---|
| 196 |
radius, flattening = sphere_params[0], sphere_params[2] |
|---|
| 197 |
else: |
|---|
| 198 |
radius, flattening = sphere_params |
|---|
| 199 |
return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening) |
|---|
| 200 |
|
|---|
| 201 |
def __unicode__(self): |
|---|
| 202 |
""" |
|---|
| 203 |
Returns the string representation. If GDAL is installed, |
|---|
| 204 |
it will be 'pretty' OGC WKT. |
|---|
| 205 |
""" |
|---|
| 206 |
try: |
|---|
| 207 |
return unicode(self.srs) |
|---|
| 208 |
except: |
|---|
| 209 |
return unicode(self.wkt) |
|---|
| 210 |
|
|---|
| 211 |
# The SpatialRefSys and GeometryColumns models |
|---|
| 212 |
_srid_info = True |
|---|
| 213 |
if settings.DATABASE_ENGINE == 'postgresql_psycopg2': |
|---|
| 214 |
# Because the PostGIS version is checked when initializing the spatial |
|---|
| 215 |
# backend a `ProgrammingError` will be raised if the PostGIS tables |
|---|
| 216 |
# and functions are not installed. We catch here so it won't be raised when |
|---|
| 217 |
# running the Django test suite. |
|---|
| 218 |
from psycopg2 import ProgrammingError |
|---|
| 219 |
try: |
|---|
| 220 |
from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys |
|---|
| 221 |
except ProgrammingError: |
|---|
| 222 |
_srid_info = False |
|---|
| 223 |
elif settings.DATABASE_ENGINE == 'oracle': |
|---|
| 224 |
# Same thing as above, except the GEOS library is attempted to be loaded for |
|---|
| 225 |
# `BaseSpatialBackend`, and an exception will be raised during the |
|---|
| 226 |
# Django test suite if it doesn't exist. |
|---|
| 227 |
try: |
|---|
| 228 |
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys |
|---|
| 229 |
except: |
|---|
| 230 |
_srid_info = False |
|---|
| 231 |
else: |
|---|
| 232 |
_srid_info = False |
|---|
| 233 |
|
|---|
| 234 |
if _srid_info: |
|---|
| 235 |
def get_srid_info(srid): |
|---|
| 236 |
""" |
|---|
| 237 |
Returns the units, unit name, and spheroid WKT associated with the |
|---|
| 238 |
given SRID from the `spatial_ref_sys` (or equivalent) spatial database |
|---|
| 239 |
table. We use a database cursor to execute the query because this |
|---|
| 240 |
function is used when it is not possible to use the ORM (for example, |
|---|
| 241 |
during field initialization). |
|---|
| 242 |
""" |
|---|
| 243 |
# SRID=-1 is a common convention for indicating the geometry has no |
|---|
| 244 |
# spatial reference information associated with it. Thus, we will |
|---|
| 245 |
# return all None values without raising an exception. |
|---|
| 246 |
if srid == -1: return None, None, None |
|---|
| 247 |
|
|---|
| 248 |
# Getting the spatial reference WKT associated with the SRID from the |
|---|
| 249 |
# `spatial_ref_sys` (or equivalent) spatial database table. This query |
|---|
| 250 |
# cannot be executed using the ORM because this information is needed |
|---|
| 251 |
# when the ORM cannot be used (e.g., during the initialization of |
|---|
| 252 |
# `GeometryField`). |
|---|
| 253 |
from django.db import connection |
|---|
| 254 |
cur = connection.cursor() |
|---|
| 255 |
qn = connection.ops.quote_name |
|---|
| 256 |
stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)' |
|---|
| 257 |
stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table), |
|---|
| 258 |
'wkt_col' : qn(SpatialRefSys.wkt_col()), |
|---|
| 259 |
'srid_col' : qn('srid'), |
|---|
| 260 |
'srid' : srid, |
|---|
| 261 |
} |
|---|
| 262 |
cur.execute(stmt) |
|---|
| 263 |
|
|---|
| 264 |
# Fetching the WKT from the cursor; if the query failed raise an Exception. |
|---|
| 265 |
fetched = cur.fetchone() |
|---|
| 266 |
if not fetched: |
|---|
| 267 |
raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' % |
|---|
| 268 |
(SpatialRefSys._meta.db_table, srid)) |
|---|
| 269 |
srs_wkt = fetched[0] |
|---|
| 270 |
|
|---|
| 271 |
# Getting metadata associated with the spatial reference system identifier. |
|---|
| 272 |
# Specifically, getting the unit information and spheroid information |
|---|
| 273 |
# (both required for distance queries). |
|---|
| 274 |
unit, unit_name = SpatialRefSys.get_units(srs_wkt) |
|---|
| 275 |
spheroid = SpatialRefSys.get_spheroid(srs_wkt) |
|---|
| 276 |
return unit, unit_name, spheroid |
|---|
| 277 |
else: |
|---|
| 278 |
def get_srid_info(srid): |
|---|
| 279 |
""" |
|---|
| 280 |
Dummy routine for the backends that do not have the OGC required |
|---|
| 281 |
spatial metadata tables (like MySQL). |
|---|
| 282 |
""" |
|---|
| 283 |
return None, None, None |
|---|
| 284 |
|
|---|