1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Refs #34406 -- Added support for GDAL curved geometries.

Co-authored-by: Fabien Le Frapper <contact@fabienlefrapper.me>
This commit is contained in:
David Smith 2024-10-22 21:24:36 +01:00 committed by GitHub
parent dd0a116b93
commit 04adff9f98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 292 additions and 23 deletions

View File

@ -64,6 +64,7 @@ class OGRGeometry(GDALBase):
"""Encapsulate an OGR geometry."""
destructor = capi.destroy_geom
geos_support = True
def __init__(self, geom_input, srs=None):
"""Initialize Geometry on either WKT or an OGR pointer as input."""
@ -304,6 +305,19 @@ class OGRGeometry(GDALBase):
f"Input to 'set_measured' must be a boolean, got '{value!r}'."
)
@property
def has_curve(self):
"""Return True if the geometry is or has curve geometry."""
return capi.has_curve_geom(self.ptr, 0)
def get_linear_geometry(self):
"""Return a linear version of this geometry."""
return OGRGeometry(capi.get_linear_geom(self.ptr, 0, None))
def get_curve_geometry(self):
"""Return a curve version of this geometry."""
return OGRGeometry(capi.get_curve_geom(self.ptr, None))
# #### SpatialReference-related Properties ####
# The SRS property
@ -360,9 +374,14 @@ class OGRGeometry(GDALBase):
@property
def geos(self):
"Return a GEOSGeometry object from this OGRGeometry."
from django.contrib.gis.geos import GEOSGeometry
if self.geos_support:
from django.contrib.gis.geos import GEOSGeometry
return GEOSGeometry(self._geos_ptr(), self.srid)
return GEOSGeometry(self._geos_ptr(), self.srid)
else:
from django.contrib.gis.geos import GEOSException
raise GEOSException(f"GEOS does not support {self.__class__.__qualname__}.")
@property
def gml(self):
@ -727,6 +746,18 @@ class Polygon(OGRGeometry):
return sum(self[i].point_count for i in range(self.geom_count))
class CircularString(LineString):
geos_support = False
class CurvePolygon(Polygon):
geos_support = False
class CompoundCurve(OGRGeometry):
geos_support = False
# Geometry Collection base class.
class GeometryCollection(OGRGeometry):
"The Geometry Collection class."
@ -788,6 +819,14 @@ class MultiPolygon(GeometryCollection):
pass
class MultiSurface(GeometryCollection):
geos_support = False
class MultiCurve(GeometryCollection):
geos_support = False
# Class mapping dictionary (using the OGRwkbGeometryType as the key)
GEO_CLASSES = {
1: Point,
@ -797,7 +836,17 @@ GEO_CLASSES = {
5: MultiLineString,
6: MultiPolygon,
7: GeometryCollection,
8: CircularString,
9: CompoundCurve,
10: CurvePolygon,
11: MultiCurve,
12: MultiSurface,
101: LinearRing,
1008: CircularString, # CIRCULARSTRING Z
1009: CompoundCurve, # COMPOUNDCURVE Z
1010: CurvePolygon, # CURVEPOLYGON Z
1011: MultiCurve, # MULTICURVE Z
1012: MultiSurface, # MULTICURVE Z
2001: Point, # POINT M
2002: LineString, # LINESTRING M
2003: Polygon, # POLYGON M
@ -805,6 +854,11 @@ GEO_CLASSES = {
2005: MultiLineString, # MULTILINESTRING M
2006: MultiPolygon, # MULTIPOLYGON M
2007: GeometryCollection, # GEOMETRYCOLLECTION M
2008: CircularString, # CIRCULARSTRING M
2009: CompoundCurve, # COMPOUNDCURVE M
2010: CurvePolygon, # CURVEPOLYGON M
2011: MultiCurve, # MULTICURVE M
2012: MultiSurface, # MULTICURVE M
3001: Point, # POINT ZM
3002: LineString, # LINESTRING ZM
3003: Polygon, # POLYGON ZM
@ -812,6 +866,11 @@ GEO_CLASSES = {
3005: MultiLineString, # MULTILINESTRING ZM
3006: MultiPolygon, # MULTIPOLYGON ZM
3007: GeometryCollection, # GEOMETRYCOLLECTION ZM
3008: CircularString, # CIRCULARSTRING ZM
3009: CompoundCurve, # COMPOUNDCURVE ZM
3010: CurvePolygon, # CURVEPOLYGON ZM
3011: MultiCurve, # MULTICURVE ZM
3012: MultiSurface, # MULTISURFACE ZM
1 + OGRGeomType.wkb25bit: Point, # POINT Z
2 + OGRGeomType.wkb25bit: LineString, # LINESTRING Z
3 + OGRGeomType.wkb25bit: Polygon, # POLYGON Z

View File

@ -85,6 +85,13 @@ is_3d = bool_output(lgdal.OGR_G_Is3D, [c_void_p])
set_3d = void_output(lgdal.OGR_G_Set3D, [c_void_p, c_int], errcheck=False)
is_measured = bool_output(lgdal.OGR_G_IsMeasured, [c_void_p])
set_measured = void_output(lgdal.OGR_G_SetMeasured, [c_void_p, c_int], errcheck=False)
has_curve_geom = bool_output(lgdal.OGR_G_HasCurveGeometry, [c_void_p, c_int])
get_linear_geom = geom_output(
lgdal.OGR_G_GetLinearGeometry, [c_void_p, c_double, POINTER(c_char_p)]
)
get_curve_geom = geom_output(
lgdal.OGR_G_GetCurveGeometry, [c_void_p, POINTER(c_char_p)]
)
# Geometry modification routines.
add_geom = void_output(lgdal.OGR_G_AddGeometry, [c_void_p, c_void_p])

View File

@ -611,6 +611,26 @@ coordinate transformation:
>>> polygon.geom_count
1
.. attribute:: has_curve
.. versionadded:: 5.2
A boolean indicating if this geometry is or contains a curve geometry.
.. method:: get_linear_geometry
.. versionadded:: 5.2
Returns a linear version of the geometry. If no conversion can be made, the
original geometry is returned.
.. method:: get_curve_geometry
.. versionadded:: 5.2
Returns a curved version of the geometry. If no conversion can be made, the
original geometry is returned.
.. attribute:: point_count
Returns the number of points used to describe this geometry:

View File

@ -94,7 +94,11 @@ Minor features
:mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~
* ...
* GDAL now supports curved geometries ``CurvePolygon``, ``CompoundCurve``,
``CircularString``, ``MultiSurface``, and ``MultiCurve`` via the new
:attr:`.OGRGeometry.has_curve` property, and the
:meth:`.OGRGeometry.get_linear_geometry` and
:meth:`.OGRGeometry.get_curve_geometry` methods.
:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -137,5 +137,87 @@
"union_geoms": [
{"wkt": "POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))"},
{"wkt": "POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0))"}
],
"curved_geoms": [
{"wkt": "CIRCULARSTRING(1 5, 6 2, 7 3)",
"name": "CircularString",
"num": 8
},
{"wkt": "COMPOUNDCURVE((5 3, 5 13), CIRCULARSTRING(5 13, 7 15, 9 13), (9 13, 9 3), CIRCULARSTRING(9 3, 7 1, 5 3))",
"name": "CompoundCurve",
"num": 9
},
{"wkt": "CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1))",
"name": "CurvePolygon",
"num": 10
},
{"wkt": "MULTICURVE((0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4))",
"name": "MultiCurve",
"num": 11
},
{"wkt": "MULTISURFACE(((0 0, 0 1, 1 1, 1 0, 0 0)), ((1 1, 1 2, 2 2, 2 1, 1 1)))",
"name": "MultiSurface",
"num": 12
},
{"wkt": "CIRCULARSTRING Z (1 5 1, 6 2 2, 7 3 3)",
"name": "CircularStringZ",
"num": 1008
},
{"wkt": "COMPOUNDCURVE Z ((5 3 0, 5 13 0), CIRCULARSTRING Z (5 13 0, 7 15 0, 9 13 0), (9 13 0 , 9 3 0), CIRCULARSTRING(9 3 0, 7 1 0, 5 3 0))",
"name": "CompoundCurveZ",
"num": 1009
},
{"wkt": "CURVEPOLYGON Z(CIRCULARSTRING Z (0 0 0, 4 0 0, 4 4 0, 0 4 0, 0 0 0),(1 1 0, 3 3 0, 3 1 0, 1 1 0))",
"name": "CurvePolygonZ",
"num": 1010
},
{"wkt": "MULTICURVE Z ((0 0 1, 5 5 2), CIRCULARSTRING Z (4 0 0, 4 4 0, 8 4 0))",
"name": "MultiCurveZ",
"num": 1011
},
{"wkt": "MULTISURFACE Z (((0 0 1, 0 1 2, 1 1 3, 1 0 4, 0 0 5)), ((1 1 0, 1 2 0, 2 2 0, 2 1 0, 1 1 0)))",
"name": "MultiSurfaceZ",
"num": 1012
},
{"wkt": "CIRCULARSTRING M (1 5 1, 6 2 2, 7 3 3)",
"name": "CircularStringM",
"num": 2008
},
{"wkt": "COMPOUNDCURVE M ((5 3 0, 5 13 0), CIRCULARSTRING M (5 13 0, 7 15 0, 9 13 0), (9 13 0 , 9 3 0), CIRCULARSTRING M (9 3 0, 7 1 0, 5 3 0))",
"name": "CompoundCurveM",
"num": 2009
},
{"wkt": "CURVEPOLYGON M (CIRCULARSTRING M (0 0 0, 4 0 0, 4 4 0, 0 4 0, 0 0 0),(1 1 0, 3 3 1, 3 1 1, 1 1 2))",
"name": "CurvePolygonM",
"num": 2010
},
{"wkt": "MULTICURVE M ((0 0 1, 5 5 2), CIRCULARSTRING M (4 0 0, 4 4 0, 8 4 0))",
"name": "MultiCurveM",
"num": 2011
},
{"wkt": "MULTISURFACE M (((0 0 1, 0 1 2, 1 1 3, 1 0 4, 0 0 5)), ((1 1 0, 1 2 0, 2 2 0, 2 1 0, 1 1 0)))",
"name": "MultiSurfaceM",
"num": 2012
},
{"wkt": "CIRCULARSTRING ZM (1 5 0 1, 6 2 0 2, 7 3 0 3)",
"name": "CircularStringZM",
"num": 3008
},
{"wkt": "COMPOUNDCURVE ZM ((5 3 0 0, 5 13 0 0), CIRCULARSTRING ZM (5 13 0 0, 7 15 0 0, 9 13 0 0), (9 13 0 0, 9 3 0 0), CIRCULARSTRING ZM (9 3 0 0, 7 1 0 0, 5 3 0 0))",
"name": "CompoundCurveZM",
"num": 3009
},
{"wkt": "CURVEPOLYGON ZM (CIRCULARSTRING ZM (0 0 0 0, 4 0 0 0, 4 4 0 0, 0 4 0 0, 0 0 0 0), (1 1 0 0, 3 3 0 0, 3 1 0 0, 1 1 0 0))",
"name": "CurvePolygonZM",
"num": 3010
},
{"wkt": "MULTICURVE ZM ((0 0 0 1, 5 5 0 2), CIRCULARSTRING ZM (4 0 0 0, 4 4 0 0, 8 4 0 0))",
"name": "MultiCurveZM",
"num": 3011
},
{"wkt": "MULTISURFACE ZM (((0 0 0 1, 0 1 0 2, 1 1 0 3, 1 0 0 4, 0 0 0 5)), ((1 1 0 0, 1 2 0 0, 2 2 0 0, 2 1 0 0, 1 1 0 0)))",
"name": "MultiSurfaceZM",
"num": 3012
}
]
}

View File

@ -8,6 +8,8 @@ from django.contrib.gis.gdal import (
OGRGeomType,
SpatialReference,
)
from django.contrib.gis.gdal.geometries import CircularString, CurvePolygon
from django.contrib.gis.geos import GEOSException
from django.template import Context
from django.template.engine import Engine
from django.test import SimpleTestCase
@ -646,11 +648,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("Multilinestring", 5, True),
("MultiPolygon", 6, True),
("GeometryCollection", 7, True),
("CircularString", 8, False),
("CompoundCurve", 9, False),
("CurvePolygon", 10, False),
("MultiCurve", 11, False),
("MultiSurface", 12, False),
("CircularString", 8, True),
("CompoundCurve", 9, True),
("CurvePolygon", 10, True),
("MultiCurve", 11, True),
("MultiSurface", 12, True),
# 13 (Curve) and 14 (Surface) are abstract types.
("PolyhedralSurface", 15, False),
("TIN", 16, False),
@ -664,11 +666,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("Multilinestring Z", -2147483643, True), # 1005
("MultiPolygon Z", -2147483642, True), # 1006
("GeometryCollection Z", -2147483641, True), # 1007
("CircularString Z", 1008, False),
("CompoundCurve Z", 1009, False),
("CurvePolygon Z", 1010, False),
("MultiCurve Z", 1011, False),
("MultiSurface Z", 1012, False),
("CircularString Z", 1008, True),
("CompoundCurve Z", 1009, True),
("CurvePolygon Z", 1010, True),
("MultiCurve Z", 1011, True),
("MultiSurface Z", 1012, True),
("PolyhedralSurface Z", 1015, False),
("TIN Z", 1016, False),
("Triangle Z", 1017, False),
@ -679,11 +681,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("MultiLineString M", 2005, True),
("MultiPolygon M", 2006, True),
("GeometryCollection M", 2007, True),
("CircularString M", 2008, False),
("CompoundCurve M", 2009, False),
("CurvePolygon M", 2010, False),
("MultiCurve M", 2011, False),
("MultiSurface M", 2012, False),
("CircularString M", 2008, True),
("CompoundCurve M", 2009, True),
("CurvePolygon M", 2010, True),
("MultiCurve M", 2011, True),
("MultiSurface M", 2012, True),
("PolyhedralSurface M", 2015, False),
("TIN M", 2016, False),
("Triangle M", 2017, False),
@ -694,11 +696,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("MultiLineString ZM", 3005, True),
("MultiPolygon ZM", 3006, True),
("GeometryCollection ZM", 3007, True),
("CircularString ZM", 3008, False),
("CompoundCurve ZM", 3009, False),
("CurvePolygon ZM", 3010, False),
("MultiCurve ZM", 3011, False),
("MultiSurface ZM", 3012, False),
("CircularString ZM", 3008, True),
("CompoundCurve ZM", 3009, True),
("CurvePolygon ZM", 3010, True),
("MultiCurve ZM", 3011, True),
("MultiSurface ZM", 3012, True),
("PolyhedralSurface ZM", 3015, False),
("TIN ZM", 3016, False),
("Triangle ZM", 3017, False),
@ -967,6 +969,101 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
geom = OGRGeometry(geom_input)
self.assertIs(geom.is_measured, True)
def test_has_curve(self):
for geom in self.geometries.curved_geoms:
with self.subTest(wkt=geom.wkt):
geom = OGRGeometry(geom.wkt)
self.assertIs(geom.has_curve, True)
msg = f"GEOS does not support {geom.__class__.__qualname__}."
with self.assertRaisesMessage(GEOSException, msg):
geom.geos
geom = OGRGeometry("POINT (0 1)")
self.assertIs(geom.has_curve, False)
def test_get_linear_geometry(self):
geom = OGRGeometry("CIRCULARSTRING (-0.797 0.466,-0.481 0.62,-0.419 0.473)")
linear = geom.get_linear_geometry()
self.assertEqual(linear.geom_name, "LINESTRING")
self.assertIs(linear.has_curve, False)
def test_get_linear_geometry_no_conversion_possible(self):
wkt = "POINT (0 0)"
geom = OGRGeometry(wkt)
geom2 = geom.get_linear_geometry()
self.assertEqual(geom2.wkt, wkt)
def test_get_curve_geometry(self):
linear_string = OGRGeometry(
"LINESTRING (-0.797 0.466,-0.797500910583869 0.479079607685707,"
"-0.797096828208069 0.49216256476959,-0.795789684575482 0.505186328593822,"
"-0.793585728444384 0.518088639471983,-0.79049549575663 0.530807818319715,"
"-0.786533759270668 0.543283061509385,-0.781719457941079 0.555454731539925,"
"-0.776075606381369 0.567264642132187,-0.769629184843353 0.578656336386302,"
"-0.76241101023902 0.589575356672327,-0.754455588821145 0.599969504963013,"
"-0.745800951227352 0.609789092364991,-0.736488470675795 0.618987176654798,"
"-0.726562665181888 0.627519786684672,-0.716070984741265 0.635346132585369,"
"-0.705063584496685 0.642428800760598,-0.693593084972889 0.648733932741749,"
"-0.681714320525941 0.654231387047048,-0.669484077209319 0.658894883272069,"
"-0.656960821309923 0.662702127722269,-0.644204419852031 0.665634919987354,"
"-0.631275854404748 0.667679239947688,-0.618236929561618 0.668825314797118,"
"-0.60514997748578 0.669067665761503,-0.592077559933017 0.66840513428977,"
"-0.579082169177269 0.666840887592428,-0.566225929268313 0.664382403500809,"
"-0.553570299049824 0.661041434719465,-0.541175778357228 0.656833952642756,"
"-0.529101618800212 0.651780071004197,-0.5174055405123 0.645903949723276,"
"-0.506143456221622 0.639233679409784,-0.495369203961872 0.631801147077652,"
"-0.485134289701335 0.623641883709865,-0.475487641120239 0.614794894404014,"
"-0.46647537371355 0.605302471909454,-0.458140570337321 0.595209994448282,"
"-0.450523075252448 0.58456570878613,-0.443659303650563 0.573420499590156,"
"-0.437582067572208 0.561827646176397,-0.432320419050072 0.549842567809747,"
"-0.427899511226613 0.537522558773986,-0.424340478110267 0.524926514478182,"
"-0.421660333544978 0.512114649909193,-0.419871889876113 0.499148211775737,"
"-0.418983696701434 0.486089185720561,-0.419 0.473)"
)
curve = linear_string.get_curve_geometry()
self.assertEqual(curve.geom_name, "CIRCULARSTRING")
self.assertEqual(
curve.wkt,
"CIRCULARSTRING (-0.797 0.466,-0.618236929561618 "
"0.668825314797118,-0.419 0.473)",
)
def test_get_curve_geometry_no_conversion_possible(self):
geom = OGRGeometry("LINESTRING (0 0, 1 0, 2 0)")
geom2 = geom.get_curve_geometry()
self.assertEqual(geom2.wkt, geom.wkt)
def test_curved_geometries(self):
for geom in self.geometries.curved_geoms:
with self.subTest(wkt=geom.wkt, geom_name=geom.name):
g = OGRGeometry(geom.wkt)
self.assertEqual(geom.name, g.geom_type.name)
self.assertEqual(geom.num, g.geom_type.num)
msg = f"GEOS does not support {g.__class__.__qualname__}."
with self.assertRaisesMessage(GEOSException, msg):
g.geos
def test_circularstring_has_linestring_features(self):
geom = OGRGeometry("CIRCULARSTRING ZM (1 5 0 1, 6 2 0 2, 7 3 0 3)")
self.assertIsInstance(geom, CircularString)
self.assertEqual(geom.x, [1, 6, 7])
self.assertEqual(geom.y, [5, 2, 3])
self.assertEqual(geom.z, [0, 0, 0])
self.assertEqual(geom.m, [1, 2, 3])
self.assertEqual(
geom.tuple,
((1.0, 5.0, 0.0, 1.0), (6.0, 2.0, 0.0, 2.0), (7.0, 3.0, 0.0, 3.0)),
)
self.assertEqual(geom[0], (1, 5, 0, 1))
self.assertEqual(len(geom), 3)
def test_curvepolygon_has_polygon_features(self):
geom = OGRGeometry(
"CURVEPOLYGON ZM (CIRCULARSTRING ZM (0 0 0 0, 4 0 0 0, 4 4 0 0, 0 4 0 0, "
"0 0 0 0), (1 1 0 0, 3 3 0 0, 3 1 0 0, 1 1 0 0))"
)
self.assertIsInstance(geom, CurvePolygon)
self.assertIsInstance(geom.shell, CircularString)
class DeprecationTests(SimpleTestCase):
def test_coord_setter_deprecation(self):