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

View File

@ -201,10 +201,15 @@ class AsSVG(GeoFunc):
super(AsSVG, self).__init__(*expressions, **extra)
class BoundingCircle(GeoFunc):
class BoundingCircle(OracleToleranceMixin, GeoFunc):
def __init__(self, expression, num_seg=48, **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):
arity = 1

View File

@ -374,38 +374,38 @@ Database functions
The following table provides a summary of what geography-specific database
functions are available on each spatial backend.
==================================== ======= ====== =========== ==========
Function PostGIS Oracle MySQL SpatiaLite
==================================== ======= ====== =========== ==========
:class:`Area` X X X X
:class:`AsGeoJSON` X X
:class:`AsGML` X X X
:class:`AsKML` X X
:class:`AsSVG` X X
:class:`BoundingCircle` X
:class:`Centroid` X X X X
:class:`Difference` X X X (≥ 5.6.1) X
:class:`Distance` X X X (≥ 5.6.1) X
:class:`Envelope` X X X
==================================== ======= ============== =========== ==========
Function PostGIS Oracle MySQL SpatiaLite
==================================== ======= ============== =========== ==========
:class:`Area` X X X X
:class:`AsGeoJSON` X X
:class:`AsGML` X X X
:class:`AsKML` X X
:class:`AsSVG` X X
:class:`BoundingCircle` X X (≥ 12.1.0.2)
:class:`Centroid` X X X X
:class:`Difference` X X X (≥ 5.6.1) X
:class:`Distance` X X X (≥ 5.6.1) X
:class:`Envelope` X X X
:class:`ForceRHR` X
:class:`GeoHash` X X (LWGEOM)
:class:`Intersection` X X X (≥ 5.6.1) X
:class:`IsValid` X X X (LWGEOM)
:class:`Length` X X X X
:class:`MakeValid` X X (LWGEOM)
:class:`GeoHash` X X (LWGEOM)
:class:`Intersection` X X X (≥ 5.6.1) X
:class:`IsValid` X X X (LWGEOM)
:class:`Length` X X X X
:class:`MakeValid` X X (LWGEOM)
:class:`MemSize` X
:class:`NumGeometries` X X X X
:class:`NumPoints` X X X X
:class:`Perimeter` X X X
:class:`PointOnSurface` X X X
:class:`Reverse` X X X
:class:`Scale` X X
:class:`SnapToGrid` X X
:class:`SymDifference` X X X (≥ 5.6.1) X
:class:`Transform` X X X
:class:`Translate` X X
:class:`Union` X X X (≥ 5.6.1) X
==================================== ======= ====== =========== ==========
:class:`NumGeometries` X X X X
:class:`NumPoints` X X X X
:class:`Perimeter` X X X
:class:`PointOnSurface` X X X
:class:`Reverse` X X X
:class:`Scale` X X
:class:`SnapToGrid` X X
:class:`SymDifference` X X X (≥ 5.6.1) X
:class:`Transform` X X X
:class:`Translate` X X
:class:`Union` X X X (≥ 5.6.1) X
==================================== ======= ============== =========== ==========
Aggregate Functions
-------------------

View File

@ -165,11 +165,18 @@ __ http://www.w3.org/Graphics/SVG/
.. 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
polygon that can fully contain the geometry.
The ``num_seg`` parameter is used only on PostGIS.
.. versionchanged:: 1.11
Oracle support was added.
``Centroid``
============

View File

@ -165,6 +165,7 @@ Minor features
* Added Oracle support for the
: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
:lookup:`isvalid` lookup.

View File

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