diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 85ef265ad1..83b7b39fd0 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -625,10 +625,14 @@ class LineString(OGRGeometry): def __getitem__(self, index): "Return the Point at the given index." if 0 <= index < self.point_count: - x, y, z = c_double(), c_double(), c_double() - capi.get_point(self.ptr, index, byref(x), byref(y), byref(z)) + x, y, z, m = c_double(), c_double(), c_double(), c_double() + capi.get_point(self.ptr, index, byref(x), byref(y), byref(z), byref(m)) + if self.is_3d and self.is_measured: + return x.value, y.value, z.value, m.value if self.is_3d: return x.value, y.value, z.value + if self.is_measured: + return x.value, y.value, m.value dim = self.coord_dim if dim == 1: return (x.value,) @@ -673,6 +677,12 @@ class LineString(OGRGeometry): if self.is_3d: return self._listarr(capi.getz) + @property + def m(self): + """Return the M coordinates in a list.""" + if self.is_measured: + return self._listarr(capi.getm) + # LinearRings are used in Polygons. class LinearRing(LineString): @@ -789,9 +799,11 @@ GEO_CLASSES = { 7: GeometryCollection, 101: LinearRing, 2001: Point, # POINT M + 2002: LineString, # LINESTRING M 3001: Point, # POINT ZM + 3002: LineString, # LINESTRING ZM 1 + OGRGeomType.wkb25bit: Point, # POINT Z - 2 + OGRGeomType.wkb25bit: LineString, + 2 + OGRGeomType.wkb25bit: LineString, # LINESTRING Z 3 + OGRGeomType.wkb25bit: Polygon, 4 + OGRGeomType.wkb25bit: MultiPoint, 5 + OGRGeomType.wkb25bit: MultiLineString, diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 6dc6967ecf..25af48570b 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -137,8 +137,15 @@ get_geom_name = const_string_output( get_geom_type = int_output(lgdal.OGR_G_GetGeometryType, [c_void_p]) get_point_count = int_output(lgdal.OGR_G_GetPointCount, [c_void_p]) get_point = void_output( - lgdal.OGR_G_GetPoint, - [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)], + lgdal.OGR_G_GetPointZM, + [ + c_void_p, + c_int, + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + POINTER(c_double), + ], errcheck=False, ) geom_close_rings = void_output(lgdal.OGR_G_CloseRings, [c_void_p], errcheck=False) diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index be6e738160..c2a333f895 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -936,6 +936,17 @@ coordinate transformation: >>> OGRGeometry("LINESTRING (1 2 3,4 5 6)").z [3.0, 6.0] + .. attribute:: m + + .. versionadded:: 5.1 + + Returns a list of M coordinates in this line or ``None`` if the line does + not have M coordinates: + + .. code-block:: pycon + + >>> OGRGeometry("LINESTRING(0 1 2 10, 1 2 3 11, 2 3 4 12)").m + [10.0, 11.0, 12.0] .. class:: Polygon diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 34ef350afd..5d57edde46 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -78,10 +78,11 @@ Minor features * The new :meth:`.OGRGeometry.set_3d` method allows addition and removal of the ``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. +* :class:`~django.contrib.gis.gdal.OGRGeometry`, + :class:`~django.contrib.gis.gdal.Point`, and + :class:`~django.contrib.gis.gdal.LineString` now support measured geometries + via the new :attr:`.OGRGeometry.is_measured` and ``m`` properties, and the + :meth:`.OGRGeometry.set_measured` method. * :attr:`.OGRGeometry.centroid` is now available on all supported geometry types. diff --git a/tests/gis_tests/gdal_tests/test_geom.py b/tests/gis_tests/gdal_tests/test_geom.py index 6bb7aa0724..abcfbc9eab 100644 --- a/tests/gis_tests/gdal_tests/test_geom.py +++ b/tests/gis_tests/gdal_tests/test_geom.py @@ -673,7 +673,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin): ("TIN Z", 1016, False), ("Triangle Z", 1017, False), ("Point M", 2001, True), - ("LineString M", 2002, False), + ("LineString M", 2002, True), ("Polygon M", 2003, False), ("MultiPoint M", 2004, False), ("MultiLineString M", 2005, False), @@ -688,7 +688,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin): ("TIN M", 2016, False), ("Triangle M", 2017, False), ("Point ZM", 3001, True), - ("LineString ZM", 3002, False), + ("LineString ZM", 3002, True), ("Polygon ZM", 3003, False), ("MultiPoint ZM", 3004, False), ("MultiLineString ZM", 3005, False), @@ -900,6 +900,21 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin): ) self.assertEqual(geometrycollection.centroid.wkt, "POINT (110 30)") + def test_linestring_m_dimension(self): + geom = OGRGeometry("LINESTRING(0 1 2 10, 1 2 3 11, 2 3 4 12)") + self.assertIs(geom.is_measured, True) + self.assertEqual(geom.m, [10.0, 11.0, 12.0]) + self.assertEqual(geom[0], (0.0, 1.0, 2.0, 10.0)) + + geom = OGRGeometry("LINESTRING M (0 1 10, 1 2 11)") + self.assertIs(geom.is_measured, True) + self.assertEqual(geom.m, [10.0, 11.0]) + self.assertEqual(geom[0], (0.0, 1.0, 10.0)) + + geom.set_measured(False) + self.assertIs(geom.is_measured, False) + self.assertIs(geom.m, None) + class DeprecationTests(SimpleTestCase): def test_coord_setter_deprecation(self):