Refs #35058 -- Added support for measured geometries to GDAL Point.

Co-authored-by: Nick Pope <nick@nickpope.me.uk>
This commit is contained in:
David Smith 2024-01-23 08:12:03 +00:00 committed by Mariusz Felisiak
parent a702a0773d
commit 3f6d939c62
6 changed files with 157 additions and 7 deletions

View File

@ -287,6 +287,22 @@ class OGRGeometry(GDALBase):
else: else:
raise ValueError(f"Input to 'set_3d' must be a boolean, got '{value!r}'.") raise ValueError(f"Input to 'set_3d' must be a boolean, got '{value!r}'.")
@property
def is_measured(self):
"""Return True if the geometry has M coordinates."""
return capi.is_measured(self.ptr)
def set_measured(self, value):
"""Set if this geometry has M coordinates."""
if value is True:
capi.set_measured(self.ptr, 1)
elif value is False:
capi.set_measured(self.ptr, 0)
else:
raise ValueError(
f"Input to 'set_measured' must be a boolean, got '{value!r}'."
)
# #### SpatialReference-related Properties #### # #### SpatialReference-related Properties ####
# The SRS property # The SRS property
@ -386,14 +402,22 @@ class OGRGeometry(GDALBase):
sz = self.wkb_size sz = self.wkb_size
# Creating the unsigned character buffer, and passing it in by reference. # Creating the unsigned character buffer, and passing it in by reference.
buf = (c_ubyte * sz)() buf = (c_ubyte * sz)()
capi.to_wkb(self.ptr, byteorder, byref(buf)) # For backward compatibility, export old-style 99-402 extended
# dimension types when geometry does not have an M dimension.
# https://gdal.org/api/vector_c_api.html#_CPPv417OGR_G_ExportToWkb12OGRGeometryH15OGRwkbByteOrderPh
to_wkb = capi.to_iso_wkb if self.is_measured else capi.to_wkb
to_wkb(self.ptr, byteorder, byref(buf))
# Returning a buffer of the string at the pointer. # Returning a buffer of the string at the pointer.
return memoryview(string_at(buf, sz)) return memoryview(string_at(buf, sz))
@property @property
def wkt(self): def wkt(self):
"Return the WKT representation of the Geometry." "Return the WKT representation of the Geometry."
return capi.to_wkt(self.ptr, byref(c_char_p())) # For backward compatibility, export old-style 99-402 extended
# dimension types when geometry does not have an M dimension.
# https://gdal.org/api/vector_c_api.html#_CPPv417OGR_G_ExportToWkt12OGRGeometryHPPc
to_wkt = capi.to_iso_wkt if self.is_measured else capi.to_wkt
return to_wkt(self.ptr, byref(c_char_p()))
@property @property
def ewkt(self): def ewkt(self):
@ -568,11 +592,21 @@ class Point(OGRGeometry):
if self.is_3d: if self.is_3d:
return capi.getz(self.ptr, 0) return capi.getz(self.ptr, 0)
@property
def m(self):
"""Return the M coordinate for this Point."""
if self.is_measured:
return capi.getm(self.ptr, 0)
@property @property
def tuple(self): def tuple(self):
"Return the tuple of this point." "Return the tuple of this point."
if self.is_3d and self.is_measured:
return self.x, self.y, self.z, self.m
if self.is_3d: if self.is_3d:
return (self.x, self.y, self.z) return self.x, self.y, self.z
if self.is_measured:
return self.x, self.y, self.m
return self.x, self.y return self.x, self.y
coords = tuple coords = tuple
@ -753,7 +787,9 @@ GEO_CLASSES = {
6: MultiPolygon, 6: MultiPolygon,
7: GeometryCollection, 7: GeometryCollection,
101: LinearRing, 101: LinearRing,
1 + OGRGeomType.wkb25bit: Point, 2001: Point, # POINT M
3001: Point, # POINT ZM
1 + OGRGeomType.wkb25bit: Point, # POINT Z
2 + OGRGeomType.wkb25bit: LineString, 2 + OGRGeomType.wkb25bit: LineString,
3 + OGRGeomType.wkb25bit: Polygon, 3 + OGRGeomType.wkb25bit: Polygon,
4 + OGRGeomType.wkb25bit: MultiPoint, 4 + OGRGeomType.wkb25bit: MultiPoint,

View File

@ -51,6 +51,7 @@ to_kml = string_output(
getx = pnt_func(lgdal.OGR_G_GetX) getx = pnt_func(lgdal.OGR_G_GetX)
gety = pnt_func(lgdal.OGR_G_GetY) gety = pnt_func(lgdal.OGR_G_GetY)
getz = pnt_func(lgdal.OGR_G_GetZ) getz = pnt_func(lgdal.OGR_G_GetZ)
getm = pnt_func(lgdal.OGR_G_GetM)
# Geometry creation routines. # Geometry creation routines.
if GDAL_VERSION >= (3, 3): if GDAL_VERSION >= (3, 3):
@ -82,6 +83,8 @@ geom_sym_diff = geom_output(lgdal.OGR_G_SymmetricDifference, [c_void_p, c_void_p
geom_union = geom_output(lgdal.OGR_G_Union, [c_void_p, c_void_p]) geom_union = geom_output(lgdal.OGR_G_Union, [c_void_p, c_void_p])
is_3d = bool_output(lgdal.OGR_G_Is3D, [c_void_p]) 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) 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)
# Geometry modification routines. # Geometry modification routines.
add_geom = void_output(lgdal.OGR_G_AddGeometry, [c_void_p, c_void_p]) add_geom = void_output(lgdal.OGR_G_AddGeometry, [c_void_p, c_void_p])
@ -94,9 +97,13 @@ destroy_geom = void_output(lgdal.OGR_G_DestroyGeometry, [c_void_p], errcheck=Fal
to_wkb = void_output( to_wkb = void_output(
lgdal.OGR_G_ExportToWkb, None, errcheck=True lgdal.OGR_G_ExportToWkb, None, errcheck=True
) # special handling for WKB. ) # special handling for WKB.
to_iso_wkb = void_output(lgdal.OGR_G_ExportToIsoWkb, None, errcheck=True)
to_wkt = string_output( to_wkt = string_output(
lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)], decoding="ascii" lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)], decoding="ascii"
) )
to_iso_wkt = string_output(
lgdal.OGR_G_ExportToIsoWkt, [c_void_p, POINTER(c_char_p)], decoding="ascii"
)
to_gml = string_output( to_gml = string_output(
lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True, decoding="ascii" lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True, decoding="ascii"
) )
@ -115,7 +122,7 @@ get_geom_srs = srs_output(lgdal.OGR_G_GetSpatialReference, [c_void_p])
get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p]) get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p])
get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p]) get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p])
get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p]) get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p])
get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p]) get_coord_dim = int_output(lgdal.OGR_G_CoordinateDimension, [c_void_p])
set_coord_dim = void_output( set_coord_dim = void_output(
lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False
) )

View File

@ -488,6 +488,10 @@ class LayerMapping:
if necessary (for example if the model field is MultiPolygonField while if necessary (for example if the model field is MultiPolygonField while
the mapped shapefile only contains Polygons). the mapped shapefile only contains Polygons).
""" """
# Measured geometries are not yet supported by GeoDjango models.
if geom.is_measured:
geom.set_measured(False)
# Downgrade a 3D geom to a 2D one, if necessary. # Downgrade a 3D geom to a 2D one, if necessary.
if self.coord_dim == 2 and geom.is_3d: if self.coord_dim == 2 and geom.is_3d:
geom.set_3d(False) geom.set_3d(False)

View File

@ -581,6 +581,27 @@ coordinate transformation:
>>> p.wkt >>> p.wkt
"POINT (1 2)" "POINT (1 2)"
.. attribute:: is_measured
.. versionadded:: 5.1
A boolean indicating if this geometry has M coordinates.
.. method:: set_measured(value)
.. versionadded:: 5.1
A method to add or remove the M coordinate dimension.
.. code-block:: pycon
>>> p = OGRGeometry("POINT (1 2)")
>>> p.is_measured
False
>>> p.set_measured(True)
>>> p.wkt
"POINT M (1 2 0)"
.. attribute:: geom_count .. attribute:: geom_count
Returns the number of elements in this geometry: Returns the number of elements in this geometry:
@ -864,6 +885,18 @@ coordinate transformation:
>>> OGRGeometry("POINT (1 2 3)").z >>> OGRGeometry("POINT (1 2 3)").z
3.0 3.0
.. attribute:: m
.. versionadded:: 5.1
Returns the M coordinate of this point, or ``None`` if the Point does not
have an M coordinate:
.. code-block:: pycon
>>> OGRGeometry("POINT ZM (1 2 3 4)").m
4.0
.. class:: LineString .. class:: LineString
.. attribute:: x .. attribute:: x

View File

@ -78,6 +78,11 @@ Minor features
* The new :meth:`.OGRGeometry.set_3d` method allows addition and removal of the * The new :meth:`.OGRGeometry.set_3d` method allows addition and removal of the
``Z`` coordinate dimension. ``Z`` coordinate dimension.
* :class:`~django.contrib.gis.gdal.OGRGeometry` and
:class:`~django.contrib.gis.gdal.Point` now support measured geometries
via the new :attr:`.OGRGeometry.is_measured` and :attr:`.Point.m` properties,
and the :meth:`.OGRGeometry.set_measured` method.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -672,7 +672,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("PolyhedralSurface Z", 1015, False), ("PolyhedralSurface Z", 1015, False),
("TIN Z", 1016, False), ("TIN Z", 1016, False),
("Triangle Z", 1017, False), ("Triangle Z", 1017, False),
("Point M", 2001, False), ("Point M", 2001, True),
("LineString M", 2002, False), ("LineString M", 2002, False),
("Polygon M", 2003, False), ("Polygon M", 2003, False),
("MultiPoint M", 2004, False), ("MultiPoint M", 2004, False),
@ -687,7 +687,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
("PolyhedralSurface M", 2015, False), ("PolyhedralSurface M", 2015, False),
("TIN M", 2016, False), ("TIN M", 2016, False),
("Triangle M", 2017, False), ("Triangle M", 2017, False),
("Point ZM", 3001, False), ("Point ZM", 3001, True),
("LineString ZM", 3002, False), ("LineString ZM", 3002, False),
("Polygon ZM", 3003, False), ("Polygon ZM", 3003, False),
("MultiPoint ZM", 3004, False), ("MultiPoint ZM", 3004, False),
@ -812,6 +812,71 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
self.assertEqual(g.wkt, geom) self.assertEqual(g.wkt, geom)
self.assertEqual(g.wkb.hex(), wkb) self.assertEqual(g.wkb.hex(), wkb)
def test_measure_is_measure_and_set_measure(self):
geom = OGRGeometry("POINT (1 2 3)")
self.assertIs(geom.is_measured, False)
geom.set_measured(True)
self.assertIs(geom.is_measured, True)
self.assertEqual(geom.wkt, "POINT ZM (1 2 3 0)")
geom.set_measured(False)
self.assertIs(geom.is_measured, False)
self.assertEqual(geom.wkt, "POINT (1 2 3)")
msg = "Input to 'set_measured' must be a boolean, got 'None'"
with self.assertRaisesMessage(ValueError, msg):
geom.set_measured(None)
def test_point_m_coordinate(self):
geom = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geom.m, 4)
geom = OGRGeometry("POINT (1 2 3 4)")
self.assertEqual(geom.m, 4)
geom = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geom.m, 3)
geom = OGRGeometry("POINT Z (1 2 3)")
self.assertEqual(geom.m, None)
def test_point_m_tuple(self):
geom = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geom.tuple, (geom.x, geom.y, geom.z, geom.m))
geom = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geom.tuple, (geom.x, geom.y, geom.m))
geom = OGRGeometry("POINT Z (1 2 3)")
self.assertEqual(geom.tuple, (geom.x, geom.y, geom.z))
geom = OGRGeometry("POINT (1 2 3)")
self.assertEqual(geom.tuple, (geom.x, geom.y, geom.z))
def test_point_m_wkt_wkb(self):
wkt = "POINT ZM (1 2 3 4)"
geom = OGRGeometry(wkt)
self.assertEqual(geom.wkt, wkt)
self.assertEqual(
geom.wkb.hex(),
"01b90b0000000000000000f03f00000000000000"
"4000000000000008400000000000001040",
)
wkt = "POINT M (1 2 3)"
geom = OGRGeometry(wkt)
self.assertEqual(geom.wkt, wkt)
self.assertEqual(
geom.wkb.hex(),
"01d1070000000000000000f03f00000000000000400000000000000840",
)
def test_point_m_dimension_types(self):
geom = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geom.geom_type.name, "PointZM")
self.assertEqual(geom.geom_type.num, 3001)
geom = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geom.geom_type.name, "PointM")
self.assertEqual(geom.geom_type.num, 2001)
def test_point_m_dimension_geos(self):
"""GEOSGeometry does not yet support the M dimension."""
geom = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geom.geos.wkt, "POINT Z (1 2 3)")
geom = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geom.geos.wkt, "POINT (1 2)")
class DeprecationTests(SimpleTestCase): class DeprecationTests(SimpleTestCase):
def test_coord_setter_deprecation(self): def test_coord_setter_deprecation(self):