|
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, PYTHON23 |
|
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 def srs(self): |
|
32 """ |
|
33 Returns a GDAL SpatialReference object, if GDAL is installed. |
|
34 """ |
|
35 if HAS_GDAL: |
|
36 if hasattr(self, '_srs'): |
|
37 # Returning a clone of the cached SpatialReference object. |
|
38 return self._srs.clone() |
|
39 else: |
|
40 # Attempting to cache a SpatialReference object. |
|
41 |
|
42 # Trying to get from WKT first. |
|
43 try: |
|
44 self._srs = SpatialReference(self.wkt) |
|
45 return self.srs |
|
46 except Exception, msg: |
|
47 pass |
|
48 |
|
49 raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) |
|
50 else: |
|
51 raise Exception('GDAL is not installed.') |
|
52 srs = property(srs) |
|
53 |
|
54 def ellipsoid(self): |
|
55 """ |
|
56 Returns a tuple of the ellipsoid parameters: |
|
57 (semimajor axis, semiminor axis, and inverse flattening). |
|
58 """ |
|
59 if HAS_GDAL: |
|
60 return self.srs.ellipsoid |
|
61 else: |
|
62 m = self.spheroid_regex.match(self.wkt) |
|
63 if m: return (float(m.group('major')), float(m.group('flattening'))) |
|
64 else: return None |
|
65 ellipsoid = property(ellipsoid) |
|
66 |
|
67 def name(self): |
|
68 "Returns the projection name." |
|
69 return self.srs.name |
|
70 name = property(name) |
|
71 |
|
72 def spheroid(self): |
|
73 "Returns the spheroid name for this spatial reference." |
|
74 return self.srs['spheroid'] |
|
75 spheroid = property(spheroid) |
|
76 |
|
77 def datum(self): |
|
78 "Returns the datum for this spatial reference." |
|
79 return self.srs['datum'] |
|
80 datum = property(datum) |
|
81 |
|
82 def projected(self): |
|
83 "Is this Spatial Reference projected?" |
|
84 if HAS_GDAL: |
|
85 return self.srs.projected |
|
86 else: |
|
87 return self.wkt.startswith('PROJCS') |
|
88 projected = property(projected) |
|
89 |
|
90 def local(self): |
|
91 "Is this Spatial Reference local?" |
|
92 if HAS_GDAL: |
|
93 return self.srs.local |
|
94 else: |
|
95 return self.wkt.startswith('LOCAL_CS') |
|
96 local = property(local) |
|
97 |
|
98 def geographic(self): |
|
99 "Is this Spatial Reference geographic?" |
|
100 if HAS_GDAL: |
|
101 return self.srs.geographic |
|
102 else: |
|
103 return self.wkt.startswith('GEOGCS') |
|
104 geographic = property(geographic) |
|
105 |
|
106 def linear_name(self): |
|
107 "Returns the linear units name." |
|
108 if HAS_GDAL: |
|
109 return self.srs.linear_name |
|
110 elif self.geographic: |
|
111 return None |
|
112 else: |
|
113 m = self.units_regex.match(self.wkt) |
|
114 return m.group('unit_name') |
|
115 linear_name = property(linear_name) |
|
116 |
|
117 def linear_units(self): |
|
118 "Returns the linear units." |
|
119 if HAS_GDAL: |
|
120 return self.srs.linear_units |
|
121 elif self.geographic: |
|
122 return None |
|
123 else: |
|
124 m = self.units_regex.match(self.wkt) |
|
125 return m.group('unit') |
|
126 linear_units = property(linear_units) |
|
127 |
|
128 def angular_name(self): |
|
129 "Returns the name of the angular units." |
|
130 if HAS_GDAL: |
|
131 return self.srs.angular_name |
|
132 elif self.projected: |
|
133 return None |
|
134 else: |
|
135 m = self.units_regex.match(self.wkt) |
|
136 return m.group('unit_name') |
|
137 angular_name = property(angular_name) |
|
138 |
|
139 def angular_units(self): |
|
140 "Returns the angular units." |
|
141 if HAS_GDAL: |
|
142 return self.srs.angular_units |
|
143 elif self.projected: |
|
144 return None |
|
145 else: |
|
146 m = self.units_regex.match(self.wkt) |
|
147 return m.group('unit') |
|
148 angular_units = property(angular_units) |
|
149 |
|
150 def units(self): |
|
151 "Returns a tuple of the units and the name." |
|
152 if self.projected or self.local: |
|
153 return (self.linear_units, self.linear_name) |
|
154 elif self.geographic: |
|
155 return (self.angular_units, self.angular_name) |
|
156 else: |
|
157 return (None, None) |
|
158 units = property(units) |
|
159 |
|
160 def get_units(cls, wkt): |
|
161 """ |
|
162 Class method used by GeometryField on initialization to |
|
163 retrive the units on the given WKT, without having to use |
|
164 any of the database fields. |
|
165 """ |
|
166 if HAS_GDAL: |
|
167 return SpatialReference(wkt).units |
|
168 else: |
|
169 m = cls.units_regex.match(wkt) |
|
170 return m.group('unit'), m.group('unit_name') |
|
171 get_units = classmethod(get_units) |
|
172 |
|
173 def get_spheroid(cls, wkt, string=True): |
|
174 """ |
|
175 Class method used by GeometryField on initialization to |
|
176 retrieve the `SPHEROID[..]` parameters from the given WKT. |
|
177 """ |
|
178 if HAS_GDAL: |
|
179 srs = SpatialReference(wkt) |
|
180 sphere_params = srs.ellipsoid |
|
181 sphere_name = srs['spheroid'] |
|
182 else: |
|
183 m = cls.spheroid_regex.match(wkt) |
|
184 if m: |
|
185 sphere_params = (float(m.group('major')), float(m.group('flattening'))) |
|
186 sphere_name = m.group('name') |
|
187 else: |
|
188 return None |
|
189 |
|
190 if not string: |
|
191 return sphere_name, sphere_params |
|
192 else: |
|
193 # `string` parameter used to place in format acceptable by PostGIS |
|
194 if len(sphere_params) == 3: |
|
195 radius, flattening = sphere_params[0], sphere_params[2] |
|
196 else: |
|
197 radius, flattening = sphere_params |
|
198 return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening) |
|
199 get_spheroid = classmethod(get_spheroid) |
|
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 not PYTHON23 and 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. `ImportError` is also possible if no ctypes. |
|
218 try: |
|
219 from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys |
|
220 except: |
|
221 _srid_info = False |
|
222 elif not PYTHON23 and settings.DATABASE_ENGINE == 'oracle': |
|
223 # Same thing as above, except the GEOS library is attempted to be loaded for |
|
224 # `BaseSpatialBackend`, and an exception will be raised during the |
|
225 # Django test suite if it doesn't exist. |
|
226 try: |
|
227 from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys |
|
228 except: |
|
229 _srid_info = False |
|
230 else: |
|
231 _srid_info = False |
|
232 |
|
233 if _srid_info: |
|
234 def get_srid_info(srid): |
|
235 """ |
|
236 Returns the units, unit name, and spheroid WKT associated with the |
|
237 given SRID from the `spatial_ref_sys` (or equivalent) spatial database |
|
238 table. We use a database cursor to execute the query because this |
|
239 function is used when it is not possible to use the ORM (for example, |
|
240 during field initialization). |
|
241 """ |
|
242 # SRID=-1 is a common convention for indicating the geometry has no |
|
243 # spatial reference information associated with it. Thus, we will |
|
244 # return all None values without raising an exception. |
|
245 if srid == -1: return None, None, None |
|
246 |
|
247 # Getting the spatial reference WKT associated with the SRID from the |
|
248 # `spatial_ref_sys` (or equivalent) spatial database table. This query |
|
249 # cannot be executed using the ORM because this information is needed |
|
250 # when the ORM cannot be used (e.g., during the initialization of |
|
251 # `GeometryField`). |
|
252 from django.db import connection |
|
253 cur = connection.cursor() |
|
254 qn = connection.ops.quote_name |
|
255 stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)' |
|
256 stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table), |
|
257 'wkt_col' : qn(SpatialRefSys.wkt_col()), |
|
258 'srid_col' : qn('srid'), |
|
259 'srid' : srid, |
|
260 } |
|
261 cur.execute(stmt) |
|
262 |
|
263 # Fetching the WKT from the cursor; if the query failed raise an Exception. |
|
264 fetched = cur.fetchone() |
|
265 if not fetched: |
|
266 raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' % |
|
267 (SpatialRefSys._meta.db_table, srid)) |
|
268 srs_wkt = fetched[0] |
|
269 |
|
270 # Getting metadata associated with the spatial reference system identifier. |
|
271 # Specifically, getting the unit information and spheroid information |
|
272 # (both required for distance queries). |
|
273 unit, unit_name = SpatialRefSys.get_units(srs_wkt) |
|
274 spheroid = SpatialRefSys.get_spheroid(srs_wkt) |
|
275 return unit, unit_name, spheroid |
|
276 else: |
|
277 def get_srid_info(srid): |
|
278 """ |
|
279 Dummy routine for the backends that do not have the OGC required |
|
280 spatial metadata tables (like MySQL). |
|
281 """ |
|
282 return None, None, None |
|
283 |