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.db.models.lookups import (
RasterBandTransform, gis_lookups,
)
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.geometry.backend import Geometry, GeometryException
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
# spatial database alias. This cache exists so that the database isn't queried
# for SRID info each time a distance query is constructed.
_srid_cache = {}
_srid_cache = defaultdict(dict)
def get_srid_info(srid, connection):
@ -29,21 +32,21 @@ def get_srid_info(srid, connection):
# The SpatialRefSys model for the spatial backend.
SpatialRefSys = connection.ops.spatial_ref_sys()
except NotImplementedError:
# No `spatial_ref_sys` table in spatial backend (e.g., MySQL).
return None, None, None
SpatialRefSys = None
if connection.alias not in _srid_cache:
# Initialize SRID dictionary for database if it doesn't exist.
_srid_cache[connection.alias] = {}
alias, get_srs = (
(connection.alias, lambda srid: SpatialRefSys.objects.using(connection.alias).get(srid=srid).srs)
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]:
# 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]
return _srid_cache[alias][srid]
class GeoSelectFormatMixin(object):
@ -149,8 +152,6 @@ class BaseSpatialField(Field):
system that uses non-projected units (e.g., latitude/longitude).
"""
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
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
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
unit of the field.
float value is returned when the coordinates are geodetic.
.. versionchanged:: 1.11
In older versions, a raw value was returned on MySQL when used on
projected SRS.
``AsGeoJSON``
=============
@ -211,8 +215,7 @@ geometry B.
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
float value is returned, as it's not possible to automatically determine the
unit of the field.
float value is returned when the coordinates are geodetic.
On backends that support distance calculation on geodetic coordinates, the
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
:ref:`supported_units`.
.. versionchanged:: 1.11
In older versions, a raw value was returned on MySQL when used on
projected SRS.
``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
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
automatically determine the unit of the field.
measure. On MySQL, a raw float value is returned when the coordinates
are geodetic.
On PostGIS and SpatiaLite, when the coordinates are geodetic (angular), you can
specify if the calculation should be based on a simple sphere (less
accurate, less resource-intensive) or on a spheroid (more accurate, more
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``
=============

View File

@ -475,8 +475,7 @@ class DistanceFunctionsTests(TestCase):
# Tolerance has to be lower for Oracle
tol = 2
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 if hasattr(z.area, 'sq_m') else z.area, tol)
self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
@skipUnlessDBFeature("has_Distance_function")
def test_distance_simple(self):
@ -488,7 +487,7 @@ class DistanceFunctionsTests(TestCase):
houston = SouthTexasCity.objects.annotate(dist=Distance('point', lagrange)).order_by('id').first()
tol = 2 if oracle else 5
self.assertAlmostEqual(
houston.dist.m if hasattr(houston.dist, 'm') else houston.dist,
houston.dist.m,
147075.069813,
tol
)
@ -656,7 +655,7 @@ class DistanceFunctionsTests(TestCase):
# Now doing length on a projected coordinate system.
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(
SouthTexasInterstate.objects.annotate(length=Length('path')).filter(length__gt=4000).exists()
)