Fixed #27576 -- Made get_srid_info() fallback to GDAL if SpatialRefSys is unavailable.

This commit is contained in:
Sergey Fedoseev 2016-12-16 03:36:18 +06:00 committed by Tim Graham
parent 8ab8a8910c
commit 4884472447
3 changed files with 39 additions and 26 deletions

View File

@ -1,8 +1,11 @@
from collections import defaultdict
from django.contrib.gis import forms, gdal from django.contrib.gis import forms, gdal
from django.contrib.gis.db.models.lookups import ( from django.contrib.gis.db.models.lookups import (
RasterBandTransform, gis_lookups, RasterBandTransform, gis_lookups,
) )
from django.contrib.gis.db.models.proxy import SpatialProxy from django.contrib.gis.db.models.proxy import SpatialProxy
from django.contrib.gis.gdal import SpatialReference
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.geometry.backend import Geometry, GeometryException from django.contrib.gis.geometry.backend import Geometry, GeometryException
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -14,7 +17,7 @@ from django.utils.translation import ugettext_lazy as _
# Local cache of the spatial_ref_sys table, which holds SRID data for each # Local cache of the spatial_ref_sys table, which holds SRID data for each
# spatial database alias. This cache exists so that the database isn't queried # spatial database alias. This cache exists so that the database isn't queried
# for SRID info each time a distance query is constructed. # for SRID info each time a distance query is constructed.
_srid_cache = {} _srid_cache = defaultdict(dict)
def get_srid_info(srid, connection): def get_srid_info(srid, connection):
@ -29,21 +32,21 @@ def get_srid_info(srid, connection):
# The SpatialRefSys model for the spatial backend. # The SpatialRefSys model for the spatial backend.
SpatialRefSys = connection.ops.spatial_ref_sys() SpatialRefSys = connection.ops.spatial_ref_sys()
except NotImplementedError: except NotImplementedError:
# No `spatial_ref_sys` table in spatial backend (e.g., MySQL). SpatialRefSys = None
return None, None, None
if connection.alias not in _srid_cache: alias, get_srs = (
# Initialize SRID dictionary for database if it doesn't exist. (connection.alias, lambda srid: SpatialRefSys.objects.using(connection.alias).get(srid=srid).srs)
_srid_cache[connection.alias] = {} if SpatialRefSys else
(None, SpatialReference)
)
if srid not in _srid_cache[alias]:
srs = get_srs(srid)
units, units_name = srs.units
sphere_name = srs['spheroid']
spheroid = 'SPHEROID["%s",%s,%s]' % (sphere_name, srs.semi_major, srs.inverse_flattening)
_srid_cache[alias][srid] = (units, units_name, spheroid)
if srid not in _srid_cache[connection.alias]: return _srid_cache[alias][srid]
# Use `SpatialRefSys` model to query for spatial reference info.
sr = SpatialRefSys.objects.using(connection.alias).get(srid=srid)
units, units_name = sr.units
spheroid = SpatialRefSys.get_spheroid(sr.wkt)
_srid_cache[connection.alias][srid] = (units, units_name, spheroid)
return _srid_cache[connection.alias][srid]
class GeoSelectFormatMixin(object): class GeoSelectFormatMixin(object):
@ -149,8 +152,6 @@ class BaseSpatialField(Field):
system that uses non-projected units (e.g., latitude/longitude). system that uses non-projected units (e.g., latitude/longitude).
""" """
units_name = self.units_name(connection) units_name = self.units_name(connection)
# Some backends like MySQL cannot determine units name. In that case,
# test if srid is 4326 (WGS84), even if this is over-simplification.
return units_name.lower() in self.geodetic_units if units_name else self.srid == 4326 return units_name.lower() in self.geodetic_units if units_name else self.srid == 4326
def get_placeholder(self, value, compiler, connection): def get_placeholder(self, value, compiler, connection):

View File

@ -41,8 +41,12 @@ Measurement Relationships Operations Editors
Accepts a single geographic field or expression and returns the area of the Accepts a single geographic field or expression and returns the area of the
field as an :class:`~django.contrib.gis.measure.Area` measure. On MySQL, a raw field as an :class:`~django.contrib.gis.measure.Area` measure. On MySQL, a raw
float value is returned, as it's not possible to automatically determine the float value is returned when the coordinates are geodetic.
unit of the field.
.. versionchanged:: 1.11
In older versions, a raw value was returned on MySQL when used on
projected SRS.
``AsGeoJSON`` ``AsGeoJSON``
============= =============
@ -211,8 +215,7 @@ geometry B.
Accepts two geographic fields or expressions and returns the distance between Accepts two geographic fields or expressions and returns the distance between
them, as a :class:`~django.contrib.gis.measure.Distance` object. On MySQL, a raw them, as a :class:`~django.contrib.gis.measure.Distance` object. On MySQL, a raw
float value is returned, as it's not possible to automatically determine the float value is returned when the coordinates are geodetic.
unit of the field.
On backends that support distance calculation on geodetic coordinates, the On backends that support distance calculation on geodetic coordinates, the
proper backend function is automatically chosen depending on the SRID value of proper backend function is automatically chosen depending on the SRID value of
@ -246,6 +249,11 @@ queryset is calculated::
in kilometers. See :doc:`measure` for usage details and the list of in kilometers. See :doc:`measure` for usage details and the list of
:ref:`supported_units`. :ref:`supported_units`.
.. versionchanged:: 1.11
In older versions, a raw value was returned on MySQL when used on
projected SRS.
``Envelope`` ``Envelope``
============ ============
@ -325,14 +333,19 @@ Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
Accepts a single geographic linestring or multilinestring field or expression Accepts a single geographic linestring or multilinestring field or expression
and returns its length as an :class:`~django.contrib.gis.measure.Distance` and returns its length as an :class:`~django.contrib.gis.measure.Distance`
measure. On MySQL, a raw float value is returned, as it's not possible to measure. On MySQL, a raw float value is returned when the coordinates
automatically determine the unit of the field. are geodetic.
On PostGIS and SpatiaLite, when the coordinates are geodetic (angular), you can On PostGIS and SpatiaLite, when the coordinates are geodetic (angular), you can
specify if the calculation should be based on a simple sphere (less specify if the calculation should be based on a simple sphere (less
accurate, less resource-intensive) or on a spheroid (more accurate, more accurate, less resource-intensive) or on a spheroid (more accurate, more
resource-intensive) with the ``spheroid`` keyword argument. resource-intensive) with the ``spheroid`` keyword argument.
.. versionchanged:: 1.11
In older versions, a raw value was returned on MySQL when used on
projected SRS.
``MakeValid`` ``MakeValid``
============= =============

View File

@ -475,8 +475,7 @@ class DistanceFunctionsTests(TestCase):
# Tolerance has to be lower for Oracle # Tolerance has to be lower for Oracle
tol = 2 tol = 2
for i, z in enumerate(SouthTexasZipcode.objects.annotate(area=Area('poly')).order_by('name')): for i, z in enumerate(SouthTexasZipcode.objects.annotate(area=Area('poly')).order_by('name')):
# MySQL is returning a raw float value self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
self.assertAlmostEqual(area_sq_m[i], z.area.sq_m if hasattr(z.area, 'sq_m') else z.area, tol)
@skipUnlessDBFeature("has_Distance_function") @skipUnlessDBFeature("has_Distance_function")
def test_distance_simple(self): def test_distance_simple(self):
@ -488,7 +487,7 @@ class DistanceFunctionsTests(TestCase):
houston = SouthTexasCity.objects.annotate(dist=Distance('point', lagrange)).order_by('id').first() houston = SouthTexasCity.objects.annotate(dist=Distance('point', lagrange)).order_by('id').first()
tol = 2 if oracle else 5 tol = 2 if oracle else 5
self.assertAlmostEqual( self.assertAlmostEqual(
houston.dist.m if hasattr(houston.dist, 'm') else houston.dist, houston.dist.m,
147075.069813, 147075.069813,
tol tol
) )
@ -656,7 +655,7 @@ class DistanceFunctionsTests(TestCase):
# Now doing length on a projected coordinate system. # Now doing length on a projected coordinate system.
i10 = SouthTexasInterstate.objects.annotate(length=Length('path')).get(name='I-10') i10 = SouthTexasInterstate.objects.annotate(length=Length('path')).get(name='I-10')
self.assertAlmostEqual(len_m2, i10.length.m if isinstance(i10.length, D) else i10.length, 2) self.assertAlmostEqual(len_m2, i10.length.m, 2)
self.assertTrue( self.assertTrue(
SouthTexasInterstate.objects.annotate(length=Length('path')).filter(length__gt=4000).exists() SouthTexasInterstate.objects.annotate(length=Length('path')).filter(length__gt=4000).exists()
) )