PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/django/contrib/gis/db/backends/base.py

https://github.com/andnils/django
Python | 352 lines | 232 code | 45 blank | 75 comment | 46 complexity | c935561c942bbd552d68afaf4210e9f5 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. Base/mixin classes for the spatial backend database operations and the
  3. `SpatialRefSys` model the backend.
  4. """
  5. import re
  6. from django.contrib.gis import gdal
  7. from django.utils import six
  8. from django.utils.encoding import python_2_unicode_compatible
  9. class BaseSpatialOperations(object):
  10. """
  11. This module holds the base `BaseSpatialBackend` object, which is
  12. instantiated by each spatial database backend with the features
  13. it has.
  14. """
  15. distance_functions = {}
  16. geometry_functions = {}
  17. geometry_operators = {}
  18. geography_operators = {}
  19. geography_functions = {}
  20. gis_terms = set()
  21. truncate_params = {}
  22. # Quick booleans for the type of this spatial backend, and
  23. # an attribute for the spatial database version tuple (if applicable)
  24. postgis = False
  25. spatialite = False
  26. mysql = False
  27. oracle = False
  28. spatial_version = None
  29. # How the geometry column should be selected.
  30. select = None
  31. # Does the spatial database have a geometry or geography type?
  32. geography = False
  33. geometry = False
  34. area = False
  35. centroid = False
  36. difference = False
  37. distance = False
  38. distance_sphere = False
  39. distance_spheroid = False
  40. envelope = False
  41. force_rhr = False
  42. mem_size = False
  43. bounding_circle = False
  44. num_geom = False
  45. num_points = False
  46. perimeter = False
  47. perimeter3d = False
  48. point_on_surface = False
  49. polygonize = False
  50. reverse = False
  51. scale = False
  52. snap_to_grid = False
  53. sym_difference = False
  54. transform = False
  55. translate = False
  56. union = False
  57. # Aggregates
  58. collect = False
  59. extent = False
  60. extent3d = False
  61. make_line = False
  62. unionagg = False
  63. # Serialization
  64. geohash = False
  65. geojson = False
  66. gml = False
  67. kml = False
  68. svg = False
  69. # Constructors
  70. from_text = False
  71. from_wkb = False
  72. # Default conversion functions for aggregates; will be overridden if implemented
  73. # for the spatial backend.
  74. def convert_extent(self, box):
  75. raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
  76. def convert_extent3d(self, box):
  77. raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
  78. def convert_geom(self, geom_val, geom_field):
  79. raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
  80. # For quoting column values, rather than columns.
  81. def geo_quote_name(self, name):
  82. return "'%s'" % name
  83. # GeometryField operations
  84. def geo_db_type(self, f):
  85. """
  86. Returns the database column type for the geometry field on
  87. the spatial backend.
  88. """
  89. raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_type() method')
  90. def get_distance(self, f, value, lookup_type):
  91. """
  92. Returns the distance parameters for the given geometry field,
  93. lookup value, and lookup type.
  94. """
  95. raise NotImplementedError('Distance operations not available on this spatial backend.')
  96. def get_geom_placeholder(self, f, value):
  97. """
  98. Returns the placeholder for the given geometry field with the given
  99. value. Depending on the spatial backend, the placeholder may contain a
  100. stored procedure call to the transformation function of the spatial
  101. backend.
  102. """
  103. raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_placeholder() method')
  104. def get_expression_column(self, evaluator):
  105. """
  106. Helper method to return the quoted column string from the evaluator
  107. for its expression.
  108. """
  109. for expr, col_tup in evaluator.cols:
  110. if expr is evaluator.expression:
  111. return '%s.%s' % tuple(map(self.quote_name, col_tup))
  112. raise Exception("Could not find the column for the expression.")
  113. # Spatial SQL Construction
  114. def spatial_aggregate_sql(self, agg):
  115. raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
  116. def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
  117. raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_lookup_sql() method')
  118. # Routines for getting the OGC-compliant models.
  119. def geometry_columns(self):
  120. raise NotImplementedError('subclasses of BaseSpatialOperations must a provide geometry_columns() method')
  121. def spatial_ref_sys(self):
  122. raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method')
  123. @python_2_unicode_compatible
  124. class SpatialRefSysMixin(object):
  125. """
  126. The SpatialRefSysMixin is a class used by the database-dependent
  127. SpatialRefSys objects to reduce redundant code.
  128. """
  129. # For pulling out the spheroid from the spatial reference string. This
  130. # regular expression is used only if the user does not have GDAL installed.
  131. # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
  132. # or 'b' parameter.
  133. spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
  134. # For pulling out the units on platforms w/o GDAL installed.
  135. # TODO: Figure out how to pull out angular units of projected coordinate system and
  136. # fix for LOCAL_CS types. GDAL should be highly recommended for performing
  137. # distance queries.
  138. 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+)"\])?\]$')
  139. @property
  140. def srs(self):
  141. """
  142. Returns a GDAL SpatialReference object, if GDAL is installed.
  143. """
  144. if gdal.HAS_GDAL:
  145. # TODO: Is caching really necessary here? Is complexity worth it?
  146. if hasattr(self, '_srs'):
  147. # Returning a clone of the cached SpatialReference object.
  148. return self._srs.clone()
  149. else:
  150. # Attempting to cache a SpatialReference object.
  151. # Trying to get from WKT first.
  152. try:
  153. self._srs = gdal.SpatialReference(self.wkt)
  154. return self.srs
  155. except Exception as msg:
  156. pass
  157. try:
  158. self._srs = gdal.SpatialReference(self.proj4text)
  159. return self.srs
  160. except Exception as msg:
  161. pass
  162. raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
  163. else:
  164. raise Exception('GDAL is not installed.')
  165. @property
  166. def ellipsoid(self):
  167. """
  168. Returns a tuple of the ellipsoid parameters:
  169. (semimajor axis, semiminor axis, and inverse flattening).
  170. """
  171. if gdal.HAS_GDAL:
  172. return self.srs.ellipsoid
  173. else:
  174. m = self.spheroid_regex.match(self.wkt)
  175. if m:
  176. return (float(m.group('major')), float(m.group('flattening')))
  177. else:
  178. return None
  179. @property
  180. def name(self):
  181. "Returns the projection name."
  182. return self.srs.name
  183. @property
  184. def spheroid(self):
  185. "Returns the spheroid name for this spatial reference."
  186. return self.srs['spheroid']
  187. @property
  188. def datum(self):
  189. "Returns the datum for this spatial reference."
  190. return self.srs['datum']
  191. @property
  192. def projected(self):
  193. "Is this Spatial Reference projected?"
  194. if gdal.HAS_GDAL:
  195. return self.srs.projected
  196. else:
  197. return self.wkt.startswith('PROJCS')
  198. @property
  199. def local(self):
  200. "Is this Spatial Reference local?"
  201. if gdal.HAS_GDAL:
  202. return self.srs.local
  203. else:
  204. return self.wkt.startswith('LOCAL_CS')
  205. @property
  206. def geographic(self):
  207. "Is this Spatial Reference geographic?"
  208. if gdal.HAS_GDAL:
  209. return self.srs.geographic
  210. else:
  211. return self.wkt.startswith('GEOGCS')
  212. @property
  213. def linear_name(self):
  214. "Returns the linear units name."
  215. if gdal.HAS_GDAL:
  216. return self.srs.linear_name
  217. elif self.geographic:
  218. return None
  219. else:
  220. m = self.units_regex.match(self.wkt)
  221. return m.group('unit_name')
  222. @property
  223. def linear_units(self):
  224. "Returns the linear units."
  225. if gdal.HAS_GDAL:
  226. return self.srs.linear_units
  227. elif self.geographic:
  228. return None
  229. else:
  230. m = self.units_regex.match(self.wkt)
  231. return m.group('unit')
  232. @property
  233. def angular_name(self):
  234. "Returns the name of the angular units."
  235. if gdal.HAS_GDAL:
  236. return self.srs.angular_name
  237. elif self.projected:
  238. return None
  239. else:
  240. m = self.units_regex.match(self.wkt)
  241. return m.group('unit_name')
  242. @property
  243. def angular_units(self):
  244. "Returns the angular units."
  245. if gdal.HAS_GDAL:
  246. return self.srs.angular_units
  247. elif self.projected:
  248. return None
  249. else:
  250. m = self.units_regex.match(self.wkt)
  251. return m.group('unit')
  252. @property
  253. def units(self):
  254. "Returns a tuple of the units and the name."
  255. if self.projected or self.local:
  256. return (self.linear_units, self.linear_name)
  257. elif self.geographic:
  258. return (self.angular_units, self.angular_name)
  259. else:
  260. return (None, None)
  261. @classmethod
  262. def get_units(cls, wkt):
  263. """
  264. Class method used by GeometryField on initialization to
  265. retrieve the units on the given WKT, without having to use
  266. any of the database fields.
  267. """
  268. if gdal.HAS_GDAL:
  269. return gdal.SpatialReference(wkt).units
  270. else:
  271. m = cls.units_regex.match(wkt)
  272. return m.group('unit'), m.group('unit_name')
  273. @classmethod
  274. def get_spheroid(cls, wkt, string=True):
  275. """
  276. Class method used by GeometryField on initialization to
  277. retrieve the `SPHEROID[..]` parameters from the given WKT.
  278. """
  279. if gdal.HAS_GDAL:
  280. srs = gdal.SpatialReference(wkt)
  281. sphere_params = srs.ellipsoid
  282. sphere_name = srs['spheroid']
  283. else:
  284. m = cls.spheroid_regex.match(wkt)
  285. if m:
  286. sphere_params = (float(m.group('major')), float(m.group('flattening')))
  287. sphere_name = m.group('name')
  288. else:
  289. return None
  290. if not string:
  291. return sphere_name, sphere_params
  292. else:
  293. # `string` parameter used to place in format acceptable by PostGIS
  294. if len(sphere_params) == 3:
  295. radius, flattening = sphere_params[0], sphere_params[2]
  296. else:
  297. radius, flattening = sphere_params
  298. return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
  299. def __str__(self):
  300. """
  301. Returns the string representation. If GDAL is installed,
  302. it will be 'pretty' OGC WKT.
  303. """
  304. try:
  305. return six.text_type(self.srs)
  306. except Exception:
  307. return six.text_type(self.wkt)