Fixed #27602 -- Added Oracle support for BoundingCircle GIS function.

This commit is contained in:
Sergey Fedoseev 2016-12-14 15:44:23 +05:00 committed by Tim Graham
parent 5a23cc00f5
commit 38a6df555f
6 changed files with 73 additions and 50 deletions

View File

@ -18,6 +18,7 @@ from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Distance from django.contrib.gis.measure import Distance
from django.db.backends.oracle.operations import DatabaseOperations from django.db.backends.oracle.operations import DatabaseOperations
from django.utils import six from django.utils import six
from django.utils.functional import cached_property
DEFAULT_TOLERANCE = '0.05' DEFAULT_TOLERANCE = '0.05'
@ -85,6 +86,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
function_names = { function_names = {
'Area': 'SDO_GEOM.SDO_AREA', 'Area': 'SDO_GEOM.SDO_AREA',
'BoundingCircle': 'SDO_GEOM.SDO_MBC',
'Centroid': 'SDO_GEOM.SDO_CENTROID', 'Centroid': 'SDO_GEOM.SDO_CENTROID',
'Difference': 'SDO_GEOM.SDO_DIFFERENCE', 'Difference': 'SDO_GEOM.SDO_DIFFERENCE',
'Distance': 'SDO_GEOM.SDO_DISTANCE', 'Distance': 'SDO_GEOM.SDO_DISTANCE',
@ -131,11 +133,15 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
truncate_params = {'relate': None} truncate_params = {'relate': None}
unsupported_functions = { @cached_property
'AsGeoJSON', 'AsKML', 'AsSVG', 'BoundingCircle', 'Envelope', def unsupported_functions(self):
'ForceRHR', 'GeoHash', 'MakeValid', 'MemSize', 'Scale', unsupported = {
'SnapToGrid', 'Translate', 'AsGeoJSON', 'AsKML', 'AsSVG', 'Envelope', 'ForceRHR', 'GeoHash',
'MakeValid', 'MemSize', 'Scale', 'SnapToGrid', 'Translate',
} }
if self.connection.oracle_full_version < '12.1.0.2':
unsupported.add('BoundingCircle')
return unsupported
def geo_quote_name(self, name): def geo_quote_name(self, name):
return super(OracleOperations, self).geo_quote_name(name).upper() return super(OracleOperations, self).geo_quote_name(name).upper()

View File

@ -201,10 +201,15 @@ class AsSVG(GeoFunc):
super(AsSVG, self).__init__(*expressions, **extra) super(AsSVG, self).__init__(*expressions, **extra)
class BoundingCircle(GeoFunc): class BoundingCircle(OracleToleranceMixin, GeoFunc):
def __init__(self, expression, num_seg=48, **extra): def __init__(self, expression, num_seg=48, **extra):
super(BoundingCircle, self).__init__(*[expression, num_seg], **extra) super(BoundingCircle, self).__init__(*[expression, num_seg], **extra)
def as_oracle(self, compiler, connection):
clone = self.copy()
clone.set_source_expressions([self.get_source_expressions()[0]])
return super(BoundingCircle, clone).as_oracle(compiler, connection)
class Centroid(OracleToleranceMixin, GeoFunc): class Centroid(OracleToleranceMixin, GeoFunc):
arity = 1 arity = 1

View File

@ -374,15 +374,15 @@ Database functions
The following table provides a summary of what geography-specific database The following table provides a summary of what geography-specific database
functions are available on each spatial backend. functions are available on each spatial backend.
==================================== ======= ====== =========== ========== ==================================== ======= ============== =========== ==========
Function PostGIS Oracle MySQL SpatiaLite Function PostGIS Oracle MySQL SpatiaLite
==================================== ======= ====== =========== ========== ==================================== ======= ============== =========== ==========
:class:`Area` X X X X :class:`Area` X X X X
:class:`AsGeoJSON` X X :class:`AsGeoJSON` X X
:class:`AsGML` X X X :class:`AsGML` X X X
:class:`AsKML` X X :class:`AsKML` X X
:class:`AsSVG` X X :class:`AsSVG` X X
:class:`BoundingCircle` X :class:`BoundingCircle` X X (≥ 12.1.0.2)
:class:`Centroid` X X X X :class:`Centroid` X X X X
:class:`Difference` X X X (≥ 5.6.1) X :class:`Difference` X X X (≥ 5.6.1) X
:class:`Distance` X X X (≥ 5.6.1) X :class:`Distance` X X X (≥ 5.6.1) X
@ -405,7 +405,7 @@ Function PostGIS Oracle MySQL SpatiaLite
:class:`Transform` X X X :class:`Transform` X X X
:class:`Translate` X X :class:`Translate` X X
:class:`Union` X X X (≥ 5.6.1) X :class:`Union` X X X (≥ 5.6.1) X
==================================== ======= ====== =========== ========== ==================================== ======= ============== =========== ==========
Aggregate Functions Aggregate Functions
------------------- -------------------

View File

@ -165,11 +165,18 @@ __ http://www.w3.org/Graphics/SVG/
.. class:: BoundingCircle(expression, num_seg=48, **extra) .. class:: BoundingCircle(expression, num_seg=48, **extra)
*Availability*: `PostGIS <http://postgis.net/docs/ST_MinimumBoundingCircle.html>`__ *Availability*: `PostGIS <http://postgis.net/docs/ST_MinimumBoundingCircle.html>`__,
`Oracle (≥ 12.1.0.2) <https://docs.oracle.com/database/121/SPATL/GUID-82A61626-BB64-4793-B53D-A0DBEC91831A.htm#SPATL1554>`_
Accepts a single geographic field or expression and returns the smallest circle Accepts a single geographic field or expression and returns the smallest circle
polygon that can fully contain the geometry. polygon that can fully contain the geometry.
The ``num_seg`` parameter is used only on PostGIS.
.. versionchanged:: 1.11
Oracle support was added.
``Centroid`` ``Centroid``
============ ============

View File

@ -165,6 +165,7 @@ Minor features
* Added Oracle support for the * Added Oracle support for the
:class:`~django.contrib.gis.db.models.functions.AsGML` function, :class:`~django.contrib.gis.db.models.functions.AsGML` function,
:class:`~django.contrib.gis.db.models.functions.BoundingCircle` function,
:class:`~django.contrib.gis.db.models.functions.IsValid` function, and :class:`~django.contrib.gis.db.models.functions.IsValid` function, and
:lookup:`isvalid` lookup. :lookup:`isvalid` lookup.

View File

@ -11,7 +11,7 @@ from django.db.models import Sum
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.utils import six from django.utils import six
from ..utils import mysql, oracle, spatialite from ..utils import mysql, oracle, postgis, spatialite
from .models import City, Country, CountryWebMercator, State, Track from .models import City, Country, CountryWebMercator, State, Track
@ -148,21 +148,25 @@ class GISFunctionsTests(TestCase):
# num_seg is the number of segments per quarter circle. # num_seg is the number of segments per quarter circle.
return (4 * num_seg) + 1 return (4 * num_seg) + 1
# The weak precision in the assertions is because the BoundingCircle expected_areas = (169, 136) if postgis else (171, 126)
# calculation changed on PostGIS 2.3.
qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name') qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name')
self.assertAlmostEqual(qs[0].circle.area, 169, 0) self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
self.assertAlmostEqual(qs[1].circle.area, 136, 0) self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
if postgis:
# By default num_seg=48. # By default num_seg=48.
self.assertEqual(qs[0].circle.num_points, circle_num_points(48)) self.assertEqual(qs[0].circle.num_points, circle_num_points(48))
self.assertEqual(qs[1].circle.num_points, circle_num_points(48)) self.assertEqual(qs[1].circle.num_points, circle_num_points(48))
qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly', num_seg=12)).order_by('name') qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly', num_seg=12)).order_by('name')
if postgis:
self.assertGreater(qs[0].circle.area, 168.4, 0) self.assertGreater(qs[0].circle.area, 168.4, 0)
self.assertLess(qs[0].circle.area, 169.5, 0) self.assertLess(qs[0].circle.area, 169.5, 0)
self.assertAlmostEqual(qs[1].circle.area, 136, 0) self.assertAlmostEqual(qs[1].circle.area, 136, 0)
self.assertEqual(qs[0].circle.num_points, circle_num_points(12)) self.assertEqual(qs[0].circle.num_points, circle_num_points(12))
self.assertEqual(qs[1].circle.num_points, circle_num_points(12)) self.assertEqual(qs[1].circle.num_points, circle_num_points(12))
else:
self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
@skipUnlessDBFeature("has_Centroid_function") @skipUnlessDBFeature("has_Centroid_function")
def test_centroid(self): def test_centroid(self):