Made OracleSpatialAdapter clone geometries rather than mutate them.

This commit is contained in:
Tim Graham 2020-10-21 20:20:26 -04:00 committed by Mariusz Felisiak
parent 49ece89702
commit 7734337bcb
6 changed files with 44 additions and 13 deletions

View File

@ -17,3 +17,8 @@ class WKTAdapter:
def __str__(self): def __str__(self):
return self.wkt return self.wkt
@classmethod
def _fix_polygon(cls, poly):
# Hook for Oracle.
return poly

View File

@ -16,17 +16,31 @@ class OracleSpatialAdapter(WKTAdapter):
* Inner ring(s) - clockwise * Inner ring(s) - clockwise
""" """
if isinstance(geom, Polygon): if isinstance(geom, Polygon):
self._fix_polygon(geom) if self._polygon_must_be_fixed(geom):
geom = self._fix_polygon(geom)
elif isinstance(geom, GeometryCollection): elif isinstance(geom, GeometryCollection):
self._fix_geometry_collection(geom) if any(isinstance(g, Polygon) and self._polygon_must_be_fixed(g) for g in geom):
geom = self._fix_geometry_collection(geom)
self.wkt = geom.wkt self.wkt = geom.wkt
self.srid = geom.srid self.srid = geom.srid
def _fix_polygon(self, poly): @staticmethod
def _polygon_must_be_fixed(poly):
return (
not poly.empty and
(
not poly.exterior_ring.is_counterclockwise or
any(x.is_counterclockwise for x in poly)
)
)
@classmethod
def _fix_polygon(cls, poly, clone=True):
"""Fix single polygon orientation as described in __init__().""" """Fix single polygon orientation as described in __init__()."""
if poly.empty: if clone:
return poly poly = poly.clone()
if not poly.exterior_ring.is_counterclockwise: if not poly.exterior_ring.is_counterclockwise:
poly.exterior_ring = list(reversed(poly.exterior_ring)) poly.exterior_ring = list(reversed(poly.exterior_ring))
@ -36,11 +50,14 @@ class OracleSpatialAdapter(WKTAdapter):
return poly return poly
def _fix_geometry_collection(self, coll): @classmethod
def _fix_geometry_collection(cls, coll):
""" """
Fix polygon orientations in geometry collections as described in Fix polygon orientations in geometry collections as described in
__init__(). __init__().
""" """
coll = coll.clone()
for i, geom in enumerate(coll): for i, geom in enumerate(coll):
if isinstance(geom, Polygon): if isinstance(geom, Polygon):
coll[i] = self._fix_polygon(geom) coll[i] = cls._fix_polygon(geom, clone=False)
return coll

View File

@ -42,6 +42,10 @@ class PostGISAdapter:
def __str__(self): def __str__(self):
return self.getquoted() return self.getquoted()
@classmethod
def _fix_polygon(cls, poly):
return poly
def prepare(self, conn): def prepare(self, conn):
""" """
This method allows escaping the binary in the style required by the This method allows escaping the binary in the style required by the

View File

@ -488,6 +488,11 @@ backends.
* Support for PostGIS 2.2 is removed. * Support for PostGIS 2.2 is removed.
* The Oracle backend now clones polygons (and geometry collections containing
polygons) before reorienting them and saving them to the database. They are
no longer mutated in place. You might notice this if you use the polygons
after a model is saved.
Dropped support for PostgreSQL 9.5 Dropped support for PostgreSQL 9.5
---------------------------------- ----------------------------------

View File

@ -9,9 +9,7 @@ from django.db import NotSupportedError, connection
from django.db.models import Exists, F, OuterRef, Q from django.db.models import Exists, F, OuterRef, Q
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from ..utils import ( from ..utils import FuncTestMixin, mysql, oracle, postgis, spatialite
FuncTestMixin, mysql, no_oracle, oracle, postgis, spatialite,
)
from .models import ( from .models import (
AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt, AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt,
SouthTexasInterstate, SouthTexasZipcode, SouthTexasInterstate, SouthTexasZipcode,
@ -475,7 +473,6 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
list(qs) list(qs)
@no_oracle # Oracle already handles geographic distance calculation.
@skipUnlessDBFeature("has_Distance_function", 'has_Transform_function') @skipUnlessDBFeature("has_Distance_function", 'has_Transform_function')
def test_distance_transform(self): def test_distance_transform(self):
""" """

View File

@ -79,7 +79,7 @@ class GeoModelTest(TestCase):
nullstate.save() nullstate.save()
ns = State.objects.get(name='NullState') ns = State.objects.get(name='NullState')
self.assertEqual(ply, ns.poly) self.assertEqual(connection.ops.Adapter._fix_polygon(ply), ns.poly)
# Testing the `ogr` and `srs` lazy-geometry properties. # Testing the `ogr` and `srs` lazy-geometry properties.
self.assertIsInstance(ns.poly.ogr, gdal.OGRGeometry) self.assertIsInstance(ns.poly.ogr, gdal.OGRGeometry)
@ -93,7 +93,10 @@ class GeoModelTest(TestCase):
ply[1] = new_inner ply[1] = new_inner
self.assertEqual(4326, ns.poly.srid) self.assertEqual(4326, ns.poly.srid)
ns.save() ns.save()
self.assertEqual(ply, State.objects.get(name='NullState').poly) self.assertEqual(
connection.ops.Adapter._fix_polygon(ply),
State.objects.get(name='NullState').poly
)
ns.delete() ns.delete()
@skipUnlessDBFeature("supports_transform") @skipUnlessDBFeature("supports_transform")