From d8f19ac82a0505adb559764f623106108a909b39 Mon Sep 17 00:00:00 2001 From: merydian Date: Thu, 7 Nov 2024 12:58:48 +0100 Subject: [PATCH 1/6] Make Z and M values accessible in QgsGeometry.as_numpy() --- python/PyQt6/core/__init__.py.in | 71 ++++++++---- python/core/__init__.py.in | 71 ++++++++---- tests/src/python/test_qgsgeometry.py | 163 +++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 42 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index 2ee442b06f95..a001f693f3bf 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -591,29 +591,58 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: + wkb = self.wkbType() + hasM = QgsWkbTypes.hasM(wkb) + hasZ = QgsWkbTypes.hasZ(wkb) + + def get_xyzm_coordinates(pt): + if hasZ and hasM: + return _numpy.array([pt.x(), pt.y(), pt.z(), pt.m()]) + elif hasZ: + return _numpy.array([pt.x(), pt.y(), pt.z()]) + elif hasM: + return _numpy.array([pt.x(), pt.y(), pt.m()]) + else: + return _numpy.array([pt.x(), pt.y()]) + + def fill_structure_with_elements(lst: _typing.List, elements: _typing.List, idx: int=0): + for i in range(len(lst)): + if isinstance(lst[i], list): + idx = fill_structure_with_elements(lst[i], elements, idx) + else: + lst[i] = _numpy.array(elements[idx]) + idx += 1 + return idx + if self.isMultipart(): - if self.type() == QgsWkbTypes.PointGeometry: - # MultiPoint - return [_numpy.array([pt.x(), pt.y()]) for pt in self.asMultiPoint()] - elif self.type() == QgsWkbTypes.LineGeometry: - # MultiLineString - return [_numpy.array([[pt.x(), pt.y()] for pt in line]) for line in self.asMultiPolyline()] - elif self.type() == QgsWkbTypes.PolygonGeometry: - # MultiPolygon - return [ - [_numpy.array([[pt.x(), pt.y()] for pt in ring]) for ring in polygon] - for polygon in self.asMultiPolygon() - ] + geometry_type = self.type() + elements = [get_xyzm_coordinates(i) for i in self.vertices()] + + if geometry_type == QgsWkbTypes.PointGeometry: + skeleton = self.asMultiPoint() + fill_structure_with_elements(skeleton, elements) + return skeleton + + elif geometry_type == QgsWkbTypes.LineGeometry: + skeleton = self.asMultiPolyline() + fill_structure_with_elements(skeleton, elements) + return skeleton + + elif geometry_type == QgsWkbTypes.PolygonGeometry: + skeleton = self.asMultiPolygon() + fill_structure_with_elements(skeleton, elements) + return skeleton else: - if self.type() == QgsWkbTypes.PointGeometry: - point = self.asPoint() - return _numpy.array([point.x(), point.y()]) - elif self.type() == QgsWkbTypes.LineGeometry: - line = self.asPolyline() - return _numpy.array([[pt.x(), pt.y()] for pt in line]) - elif self.type() == QgsWkbTypes.PolygonGeometry: - polygon = self.asPolygon() - return _numpy.array([_numpy.array([[pt.x(), pt.y()] for pt in ring]) for ring in polygon]) + if self.type() == QgsWkbTypes.PointGeometry: + return _numpy.array([get_xyzm_coordinates(i) for i in self.vertices()][0]) + elif self.type() == QgsWkbTypes.LineGeometry: + line = self.vertices() + return _numpy.array([get_xyzm_coordinates(pt) for pt in line]) + elif self.type() == QgsWkbTypes.PolygonGeometry: + skeleton = self.asPolygon() + elements = [get_xyzm_coordinates(i) for i in self.vertices()] + fill_structure_with_elements(skeleton, elements) + return _numpy.array(skeleton) QgsGeometry.as_numpy = _qgsgeometry_as_numpy diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index fc57a8676496..0240a504191a 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -601,29 +601,58 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: + wkb = self.wkbType() + hasM = QgsWkbTypes.hasM(wkb) + hasZ = QgsWkbTypes.hasZ(wkb) + + def get_xyzm_coordinates(pt): + if hasZ and hasM: + return _numpy.array([pt.x(), pt.y(), pt.z(), pt.m()]) + elif hasZ: + return _numpy.array([pt.x(), pt.y(), pt.z()]) + elif hasM: + return _numpy.array([pt.x(), pt.y(), pt.m()]) + else: + return _numpy.array([pt.x(), pt.y()]) + + def fill_structure_with_elements(lst: _typing.List, elements: _typing.List, idx: int=0): + for i in range(len(lst)): + if isinstance(lst[i], list): + idx = fill_structure_with_elements(lst[i], elements, idx) + else: + lst[i] = _numpy.array(elements[idx]) + idx += 1 + return idx + if self.isMultipart(): - if self.type() == QgsWkbTypes.PointGeometry: - # MultiPoint - return [_numpy.array([pt.x(), pt.y()]) for pt in self.asMultiPoint()] - elif self.type() == QgsWkbTypes.LineGeometry: - # MultiLineString - return [_numpy.array([[pt.x(), pt.y()] for pt in line]) for line in self.asMultiPolyline()] - elif self.type() == QgsWkbTypes.PolygonGeometry: - # MultiPolygon - return [ - [_numpy.array([[pt.x(), pt.y()] for pt in ring]) for ring in polygon] - for polygon in self.asMultiPolygon() - ] + geometry_type = self.type() + elements = [get_xyzm_coordinates(i) for i in self.vertices()] + + if geometry_type == QgsWkbTypes.PointGeometry: + skeleton = self.asMultiPoint() + fill_structure_with_elements(skeleton, elements) + return skeleton + + elif geometry_type == QgsWkbTypes.LineGeometry: + skeleton = self.asMultiPolyline() + fill_structure_with_elements(skeleton, elements) + return skeleton + + elif geometry_type == QgsWkbTypes.PolygonGeometry: + skeleton = self.asMultiPolygon() + fill_structure_with_elements(skeleton, elements) + return skeleton else: - if self.type() == QgsWkbTypes.PointGeometry: - point = self.asPoint() - return _numpy.array([point.x(), point.y()]) - elif self.type() == QgsWkbTypes.LineGeometry: - line = self.asPolyline() - return _numpy.array([[pt.x(), pt.y()] for pt in line]) - elif self.type() == QgsWkbTypes.PolygonGeometry: - polygon = self.asPolygon() - return _numpy.array([_numpy.array([[pt.x(), pt.y()] for pt in ring]) for ring in polygon]) + if self.type() == QgsWkbTypes.PointGeometry: + return _numpy.array([get_xyzm_coordinates(i) for i in self.vertices()][0]) + elif self.type() == QgsWkbTypes.LineGeometry: + line = self.vertices() + return _numpy.array([get_xyzm_coordinates(pt) for pt in line]) + elif self.type() == QgsWkbTypes.PolygonGeometry: + skeleton = self.asPolygon() + elements = [get_xyzm_coordinates(i) for i in self.vertices()] + fill_structure_with_elements(skeleton, elements) + return _numpy.array(skeleton) QgsGeometry.as_numpy = _qgsgeometry_as_numpy diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index fefb6cbcf0a0..d38ceda5b572 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -8037,6 +8037,169 @@ def testAsNumpy(self): self.assertEqual(len(array_multipolygon[1]), 1) # Second polygon has 1 ring self.assertTrue(numpy.allclose(array_multipolygon[1][0], [[10, 10], [14, 10], [14, 14], [10, 14], [10, 10]])) + # Test POINT with Z + geom_point_z = QgsGeometry.fromWkt("POINT Z (1 2 3)") + array_point_z = geom_point_z.as_numpy() + self.assertTrue(isinstance(array_point_z, numpy.ndarray)) + # self.assertEqual(array_point_z.shape, (3,)) + self.assertTrue(numpy.allclose(array_point_z, [1, 2, 3])) + + # Test POINT with M + geom_point_m = QgsGeometry.fromWkt("POINT M (1 2 4)") + array_point_m = geom_point_m.as_numpy() + self.assertTrue(isinstance(array_point_m, numpy.ndarray)) + self.assertEqual(array_point_m.shape, (3,)) + self.assertTrue(numpy.allclose(array_point_m, [1, 2, 4])) + + # Test POINT with Z and M + geom_point_zm = QgsGeometry.fromWkt("POINT ZM (1 2 3 4)") + array_point_zm = geom_point_zm.as_numpy() + self.assertTrue(isinstance(array_point_zm, numpy.ndarray)) + self.assertEqual(array_point_zm.shape, (4,)) + self.assertTrue(numpy.allclose(array_point_zm, [1, 2, 3, 4])) + + # Test LINESTRING with Z + geom_linestring_z = QgsGeometry.fromWkt("LINESTRING Z (0 0 1, 1 1 2, 1 2 3)") + array_linestring_z = geom_linestring_z.as_numpy() + self.assertTrue(isinstance(array_linestring_z, numpy.ndarray)) + self.assertEqual(array_linestring_z.shape, (3, 3)) + self.assertTrue(numpy.allclose(array_linestring_z, [[0, 0, 1], [1, 1, 2], [1, 2, 3]])) + + # Test LINESTRING with M + geom_linestring_m = QgsGeometry.fromWkt("LINESTRING M (0 0 1, 1 1 2, 1 2 3)") + array_linestring_m = geom_linestring_m.as_numpy() + self.assertTrue(isinstance(array_linestring_m, numpy.ndarray)) + self.assertEqual(array_linestring_m.shape, (3, 3)) + self.assertTrue(numpy.allclose(array_linestring_m, [[0, 0, 1], [1, 1, 2], [1, 2, 3]])) + + # Test LINESTRING with Z and M + geom_linestring_zm = QgsGeometry.fromWkt("LINESTRING ZM (0 0 1 2, 1 1 2 3, 1 2 3 4)") + array_linestring_zm = geom_linestring_zm.as_numpy() + self.assertTrue(isinstance(array_linestring_zm, numpy.ndarray)) + self.assertEqual(array_linestring_zm.shape, (3, 4)) + self.assertTrue(numpy.allclose(array_linestring_zm, [[0, 0, 1, 2], [1, 1, 2, 3], [1, 2, 3, 4]])) + + # Test POLYGON with Z + geom_polygon_z = QgsGeometry.fromWkt("POLYGON Z ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))") + array_polygon_z = geom_polygon_z.as_numpy() + self.assertTrue(isinstance(array_polygon_z, numpy.ndarray)) + self.assertTrue(numpy.allclose(array_polygon_z[0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + + # Test POLYGON with M + geom_polygon_m = QgsGeometry.fromWkt("POLYGON M ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))") + array_polygon_m = geom_polygon_m.as_numpy() + self.assertTrue(isinstance(array_polygon_m, numpy.ndarray)) + self.assertTrue(numpy.allclose(array_polygon_m[0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + + # Test POLYGON with Z and M + geom_polygon_zm = QgsGeometry.fromWkt("POLYGON ZM ((0 0 1 5, 4 0 2 6, 4 4 3 7, 0 4 4 8, 0 0 1 5))") + array_polygon_zm = geom_polygon_zm.as_numpy() + self.assertTrue(isinstance(array_polygon_zm, numpy.ndarray)) + self.assertTrue( + numpy.allclose(array_polygon_zm[0], [[0, 0, 1, 5], [4, 0, 2, 6], [4, 4, 3, 7], [0, 4, 4, 8], [0, 0, 1, 5]])) + + # Test MULTIPOINT with Z + geom_multipoint_z = QgsGeometry.fromWkt("MULTIPOINT Z ((1 2 3), (3 4 5))") + array_multipoint_z = geom_multipoint_z.as_numpy() + self.assertTrue(isinstance(array_multipoint_z, list)) + self.assertEqual(len(array_multipoint_z), 2) + self.assertTrue(numpy.allclose(array_multipoint_z[0], [1, 2, 3])) + self.assertTrue(numpy.allclose(array_multipoint_z[1], [3, 4, 5])) + + # Test MULTIPOINT with M + geom_multipoint_m = QgsGeometry.fromWkt("MULTIPOINT M ((1 2 6), (3 4 7))") + array_multipoint_m = geom_multipoint_m.as_numpy() + self.assertTrue(isinstance(array_multipoint_m, list)) + self.assertEqual(len(array_multipoint_m), 2) + self.assertTrue(numpy.allclose(array_multipoint_m[0], [1, 2, 6])) + self.assertTrue(numpy.allclose(array_multipoint_m[1], [3, 4, 7])) + + # Test MULTIPOINT with Z and M + geom_multipoint_zm = QgsGeometry.fromWkt("MULTIPOINT ZM ((1 2 3 8), (3 4 5 9))") + array_multipoint_zm = geom_multipoint_zm.as_numpy() + self.assertTrue(isinstance(array_multipoint_zm, list)) + self.assertEqual(len(array_multipoint_zm), 2) + self.assertTrue(numpy.allclose(array_multipoint_zm[0], [1, 2, 3, 8])) + self.assertTrue(numpy.allclose(array_multipoint_zm[1], [3, 4, 5, 9])) + + # Test MULTILINESTRING with Z + geom_multilinestring_z = QgsGeometry.fromWkt("MULTILINESTRING Z ((0 0 1, 1 1 2), (2 2 3, 3 3 4))") + array_multilinestring_z = geom_multilinestring_z.as_numpy() + self.assertTrue(isinstance(array_multilinestring_z, list)) + self.assertEqual(len(array_multilinestring_z), 2) + self.assertTrue(numpy.allclose(array_multilinestring_z[0], [[0, 0, 1], [1, 1, 2]])) + self.assertTrue(numpy.allclose(array_multilinestring_z[1], [[2, 2, 3], [3, 3, 4]])) + + # Test MULTILINESTRING with M + geom_multilinestring_m = QgsGeometry.fromWkt("MULTILINESTRING M ((0 0 5, 1 1 6), (2 2 7, 3 3 8))") + array_multilinestring_m = geom_multilinestring_m.as_numpy() + self.assertTrue(isinstance(array_multilinestring_m, list)) + self.assertEqual(len(array_multilinestring_m), 2) + self.assertTrue(numpy.allclose(array_multilinestring_m[0], [[0, 0, 5], [1, 1, 6]])) + self.assertTrue(numpy.allclose(array_multilinestring_m[1], [[2, 2, 7], [3, 3, 8]])) + + # Test MULTILINESTRING with Z and M + geom_multilinestring_zm = QgsGeometry.fromWkt("MULTILINESTRING ZM ((0 0 1 5, 1 1 2 6), (2 2 3 7, 3 3 4 8))") + array_multilinestring_zm = geom_multilinestring_zm.as_numpy() + self.assertTrue(isinstance(array_multilinestring_zm, list)) + self.assertEqual(len(array_multilinestring_zm), 2) + self.assertTrue(numpy.allclose(array_multilinestring_zm[0], [[0, 0, 1, 5], [1, 1, 2, 6]])) + self.assertTrue(numpy.allclose(array_multilinestring_zm[1], [[2, 2, 3, 7], [3, 3, 4, 8]])) + + # Test MULTIPOLYGON with Z, and a hole + geom_multipolygon_z = QgsGeometry.fromWkt( + "MULTIPOLYGON Z (((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1)), ((1 1 1, 3 1 1.5, 3 3 2, 1 3 2.5, 1 1 1)))" + ) + array_multipolygon_z = geom_multipolygon_z.as_numpy() + self.assertTrue(isinstance(array_multipolygon_z, list)) + self.assertEqual(len(array_multipolygon_z), 2) + self.assertTrue( + numpy.allclose(array_multipolygon_z[0][0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + self.assertTrue( + numpy.allclose(array_multipolygon_z[1][0], [[1, 1, 1], [3, 1, 1.5], [3, 3, 2], [1, 3, 2.5], [1, 1, 1]])) + + # Test MULTIPOLYGON with M + geom_multipolygon_m = QgsGeometry.fromWkt( + "MULTIPOLYGON M (((0 0 5, 4 0 6, 4 4 7, 0 4 8, 0 0 5)), ((10 10 9, 14 10 10, 14 14 11, 10 14 12, 10 10 9)))" + ) + array_multipolygon_m = geom_multipolygon_m.as_numpy() + self.assertTrue(isinstance(array_multipolygon_m, list)) + self.assertEqual(len(array_multipolygon_m), 2) + self.assertTrue( + numpy.allclose(array_multipolygon_m[0][0], [[0, 0, 5], [4, 0, 6], [4, 4, 7], [0, 4, 8], [0, 0, 5]])) + self.assertTrue(numpy.allclose(array_multipolygon_m[1][0], + [[10, 10, 9], [14, 10, 10], [14, 14, 11], [10, 14, 12], [10, 10, 9]])) + + # Test MULTIPOLYGON with Z and M and holes + geom_multipolygon_zm = QgsGeometry.fromWkt( + "MultiPolygon ZM (((-2.43197278911564618 0.52380952380952384 0 0, -2.0986394557823127 -0.65986394557823114 0 0,-1.10544217687074831 -0.14965986394557818 0 0, -1.07142857142857162 0.29931972789115646 0 0,-2.43197278911564618 0.52380952380952384 0 0)),((-2.12399582948757359 0.08043619457833384 0 0, -1.33488018322907021 0.08043619457833384 0 0,-1.77025433288893463 -0.19167264895908098 0 0, -2.12399582948757359 0.08043619457833384 0 0)),((1.0 1.0 1 1, 2.0 1.0 2 2, 2.0 2.0 3 3, 1.0 2.0 4 4, 1.0 1.0 1 1)))" + ) + array_multipolygon_zm = geom_multipolygon_zm.as_numpy() + self.assertTrue(isinstance(array_multipolygon_zm, list)) + self.assertEqual(len(array_multipolygon_zm), 3) + self.assertTrue( + numpy.allclose(array_multipolygon_zm[0][0], + [[-2.43197278911564618, 0.52380952380952384, 0, 0], + [-2.0986394557823127, -0.65986394557823114, 0, 0], + [-1.10544217687074831, -0.14965986394557818, 0, 0], + [-1.07142857142857162, 0.29931972789115646, 0, 0], + [-2.43197278911564618, 0.52380952380952384, 0, 0]])) + + self.assertTrue( + numpy.allclose(array_multipolygon_zm[1][0], + [[-2.12399582948757359, 0.08043619457833384, 0, 0], + [-1.33488018322907021, 0.08043619457833384, 0, 0], + [-1.77025433288893463, -0.19167264895908098, 0, 0], + [-2.12399582948757359, 0.08043619457833384, 0, 0]])) + + self.assertTrue( + numpy.allclose(array_multipolygon_zm[2][0], + [[1.0, 1.0, 1, 1], + [2.0, 1.0, 2, 2], + [2.0, 2.0, 3, 3], + [1.0, 2.0, 4, 4], + [1.0, 1.0, 1, 1]])) + if __name__ == '__main__': unittest.main() From 2560545a7f18a7f4114ab7b98ddb166c2120429b Mon Sep 17 00:00:00 2001 From: Till Frankenbach Date: Fri, 8 Nov 2024 09:40:33 +0100 Subject: [PATCH 2/6] Replace wbk with wkb_type Co-authored-by: Even Rouault --- python/PyQt6/core/__init__.py.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index a001f693f3bf..5e84569f16e9 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -591,9 +591,9 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: - wkb = self.wkbType() - hasM = QgsWkbTypes.hasM(wkb) - hasZ = QgsWkbTypes.hasZ(wkb) + wkb_type = self.wkbType() + hasM = QgsWkbTypes.hasM(wkb_type) + hasZ = QgsWkbTypes.hasZ(wkb_type) def get_xyzm_coordinates(pt): if hasZ and hasM: From 33772096653fa464aad2aa31f888f946c09d1ed9 Mon Sep 17 00:00:00 2001 From: merydian Date: Fri, 8 Nov 2024 09:56:17 +0100 Subject: [PATCH 3/6] Use geometry_type variable in else block --- python/core/__init__.py.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 0240a504191a..e32b32cf9f4a 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -604,6 +604,7 @@ try: wkb = self.wkbType() hasM = QgsWkbTypes.hasM(wkb) hasZ = QgsWkbTypes.hasZ(wkb) + geometry_type = self.type() def get_xyzm_coordinates(pt): if hasZ and hasM: @@ -625,7 +626,6 @@ try: return idx if self.isMultipart(): - geometry_type = self.type() elements = [get_xyzm_coordinates(i) for i in self.vertices()] if geometry_type == QgsWkbTypes.PointGeometry: @@ -643,12 +643,12 @@ try: fill_structure_with_elements(skeleton, elements) return skeleton else: - if self.type() == QgsWkbTypes.PointGeometry: + if geometry_type == QgsWkbTypes.PointGeometry: return _numpy.array([get_xyzm_coordinates(i) for i in self.vertices()][0]) - elif self.type() == QgsWkbTypes.LineGeometry: + elif geometry_type == QgsWkbTypes.LineGeometry: line = self.vertices() return _numpy.array([get_xyzm_coordinates(pt) for pt in line]) - elif self.type() == QgsWkbTypes.PolygonGeometry: + elif geometry_type == QgsWkbTypes.PolygonGeometry: skeleton = self.asPolygon() elements = [get_xyzm_coordinates(i) for i in self.vertices()] fill_structure_with_elements(skeleton, elements) From 5f2093165a556f9601a05a1266c0f9f7448319ee Mon Sep 17 00:00:00 2001 From: merydian Date: Fri, 8 Nov 2024 09:59:09 +0100 Subject: [PATCH 4/6] Align PyQt5 and PyQt6 files --- python/PyQt6/core/__init__.py.in | 8 ++++---- python/core/__init__.py.in | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index 5e84569f16e9..c67b4da0a7ef 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -594,6 +594,7 @@ try: wkb_type = self.wkbType() hasM = QgsWkbTypes.hasM(wkb_type) hasZ = QgsWkbTypes.hasZ(wkb_type) + geometry_type = self.type() def get_xyzm_coordinates(pt): if hasZ and hasM: @@ -615,7 +616,6 @@ try: return idx if self.isMultipart(): - geometry_type = self.type() elements = [get_xyzm_coordinates(i) for i in self.vertices()] if geometry_type == QgsWkbTypes.PointGeometry: @@ -633,12 +633,12 @@ try: fill_structure_with_elements(skeleton, elements) return skeleton else: - if self.type() == QgsWkbTypes.PointGeometry: + if geometry_type == QgsWkbTypes.PointGeometry: return _numpy.array([get_xyzm_coordinates(i) for i in self.vertices()][0]) - elif self.type() == QgsWkbTypes.LineGeometry: + elif geometry_type == QgsWkbTypes.LineGeometry: line = self.vertices() return _numpy.array([get_xyzm_coordinates(pt) for pt in line]) - elif self.type() == QgsWkbTypes.PolygonGeometry: + elif geometry_type == QgsWkbTypes.PolygonGeometry: skeleton = self.asPolygon() elements = [get_xyzm_coordinates(i) for i in self.vertices()] fill_structure_with_elements(skeleton, elements) diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index e32b32cf9f4a..6e74b646d5b2 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -601,9 +601,9 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: - wkb = self.wkbType() - hasM = QgsWkbTypes.hasM(wkb) - hasZ = QgsWkbTypes.hasZ(wkb) + wkb_type = self.wkbType() + hasM = QgsWkbTypes.hasM(wkb_type) + hasZ = QgsWkbTypes.hasZ(wkb_type) geometry_type = self.type() def get_xyzm_coordinates(pt): From aedff44ee31984f6cbe455355cfad815177e395a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:38:18 +0000 Subject: [PATCH 5/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/src/python/test_qgsgeometry.py | 161 ++++++++++++++++++++------- 1 file changed, 120 insertions(+), 41 deletions(-) diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index d38ceda5b572..51d4f4fec142 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -8063,40 +8063,70 @@ def testAsNumpy(self): array_linestring_z = geom_linestring_z.as_numpy() self.assertTrue(isinstance(array_linestring_z, numpy.ndarray)) self.assertEqual(array_linestring_z.shape, (3, 3)) - self.assertTrue(numpy.allclose(array_linestring_z, [[0, 0, 1], [1, 1, 2], [1, 2, 3]])) + self.assertTrue( + numpy.allclose(array_linestring_z, [[0, 0, 1], [1, 1, 2], [1, 2, 3]]) + ) # Test LINESTRING with M geom_linestring_m = QgsGeometry.fromWkt("LINESTRING M (0 0 1, 1 1 2, 1 2 3)") array_linestring_m = geom_linestring_m.as_numpy() self.assertTrue(isinstance(array_linestring_m, numpy.ndarray)) self.assertEqual(array_linestring_m.shape, (3, 3)) - self.assertTrue(numpy.allclose(array_linestring_m, [[0, 0, 1], [1, 1, 2], [1, 2, 3]])) + self.assertTrue( + numpy.allclose(array_linestring_m, [[0, 0, 1], [1, 1, 2], [1, 2, 3]]) + ) # Test LINESTRING with Z and M - geom_linestring_zm = QgsGeometry.fromWkt("LINESTRING ZM (0 0 1 2, 1 1 2 3, 1 2 3 4)") + geom_linestring_zm = QgsGeometry.fromWkt( + "LINESTRING ZM (0 0 1 2, 1 1 2 3, 1 2 3 4)" + ) array_linestring_zm = geom_linestring_zm.as_numpy() self.assertTrue(isinstance(array_linestring_zm, numpy.ndarray)) self.assertEqual(array_linestring_zm.shape, (3, 4)) - self.assertTrue(numpy.allclose(array_linestring_zm, [[0, 0, 1, 2], [1, 1, 2, 3], [1, 2, 3, 4]])) + self.assertTrue( + numpy.allclose( + array_linestring_zm, [[0, 0, 1, 2], [1, 1, 2, 3], [1, 2, 3, 4]] + ) + ) # Test POLYGON with Z - geom_polygon_z = QgsGeometry.fromWkt("POLYGON Z ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))") + geom_polygon_z = QgsGeometry.fromWkt( + "POLYGON Z ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))" + ) array_polygon_z = geom_polygon_z.as_numpy() self.assertTrue(isinstance(array_polygon_z, numpy.ndarray)) - self.assertTrue(numpy.allclose(array_polygon_z[0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + self.assertTrue( + numpy.allclose( + array_polygon_z[0], + [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]], + ) + ) # Test POLYGON with M - geom_polygon_m = QgsGeometry.fromWkt("POLYGON M ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))") + geom_polygon_m = QgsGeometry.fromWkt( + "POLYGON M ((0 0 1, 4 0 2, 4 4 3, 0 4 4, 0 0 1))" + ) array_polygon_m = geom_polygon_m.as_numpy() self.assertTrue(isinstance(array_polygon_m, numpy.ndarray)) - self.assertTrue(numpy.allclose(array_polygon_m[0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + self.assertTrue( + numpy.allclose( + array_polygon_m[0], + [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]], + ) + ) # Test POLYGON with Z and M - geom_polygon_zm = QgsGeometry.fromWkt("POLYGON ZM ((0 0 1 5, 4 0 2 6, 4 4 3 7, 0 4 4 8, 0 0 1 5))") + geom_polygon_zm = QgsGeometry.fromWkt( + "POLYGON ZM ((0 0 1 5, 4 0 2 6, 4 4 3 7, 0 4 4 8, 0 0 1 5))" + ) array_polygon_zm = geom_polygon_zm.as_numpy() self.assertTrue(isinstance(array_polygon_zm, numpy.ndarray)) self.assertTrue( - numpy.allclose(array_polygon_zm[0], [[0, 0, 1, 5], [4, 0, 2, 6], [4, 4, 3, 7], [0, 4, 4, 8], [0, 0, 1, 5]])) + numpy.allclose( + array_polygon_zm[0], + [[0, 0, 1, 5], [4, 0, 2, 6], [4, 4, 3, 7], [0, 4, 4, 8], [0, 0, 1, 5]], + ) + ) # Test MULTIPOINT with Z geom_multipoint_z = QgsGeometry.fromWkt("MULTIPOINT Z ((1 2 3), (3 4 5))") @@ -8123,28 +8153,46 @@ def testAsNumpy(self): self.assertTrue(numpy.allclose(array_multipoint_zm[1], [3, 4, 5, 9])) # Test MULTILINESTRING with Z - geom_multilinestring_z = QgsGeometry.fromWkt("MULTILINESTRING Z ((0 0 1, 1 1 2), (2 2 3, 3 3 4))") + geom_multilinestring_z = QgsGeometry.fromWkt( + "MULTILINESTRING Z ((0 0 1, 1 1 2), (2 2 3, 3 3 4))" + ) array_multilinestring_z = geom_multilinestring_z.as_numpy() self.assertTrue(isinstance(array_multilinestring_z, list)) self.assertEqual(len(array_multilinestring_z), 2) - self.assertTrue(numpy.allclose(array_multilinestring_z[0], [[0, 0, 1], [1, 1, 2]])) - self.assertTrue(numpy.allclose(array_multilinestring_z[1], [[2, 2, 3], [3, 3, 4]])) + self.assertTrue( + numpy.allclose(array_multilinestring_z[0], [[0, 0, 1], [1, 1, 2]]) + ) + self.assertTrue( + numpy.allclose(array_multilinestring_z[1], [[2, 2, 3], [3, 3, 4]]) + ) # Test MULTILINESTRING with M - geom_multilinestring_m = QgsGeometry.fromWkt("MULTILINESTRING M ((0 0 5, 1 1 6), (2 2 7, 3 3 8))") + geom_multilinestring_m = QgsGeometry.fromWkt( + "MULTILINESTRING M ((0 0 5, 1 1 6), (2 2 7, 3 3 8))" + ) array_multilinestring_m = geom_multilinestring_m.as_numpy() self.assertTrue(isinstance(array_multilinestring_m, list)) self.assertEqual(len(array_multilinestring_m), 2) - self.assertTrue(numpy.allclose(array_multilinestring_m[0], [[0, 0, 5], [1, 1, 6]])) - self.assertTrue(numpy.allclose(array_multilinestring_m[1], [[2, 2, 7], [3, 3, 8]])) + self.assertTrue( + numpy.allclose(array_multilinestring_m[0], [[0, 0, 5], [1, 1, 6]]) + ) + self.assertTrue( + numpy.allclose(array_multilinestring_m[1], [[2, 2, 7], [3, 3, 8]]) + ) # Test MULTILINESTRING with Z and M - geom_multilinestring_zm = QgsGeometry.fromWkt("MULTILINESTRING ZM ((0 0 1 5, 1 1 2 6), (2 2 3 7, 3 3 4 8))") + geom_multilinestring_zm = QgsGeometry.fromWkt( + "MULTILINESTRING ZM ((0 0 1 5, 1 1 2 6), (2 2 3 7, 3 3 4 8))" + ) array_multilinestring_zm = geom_multilinestring_zm.as_numpy() self.assertTrue(isinstance(array_multilinestring_zm, list)) self.assertEqual(len(array_multilinestring_zm), 2) - self.assertTrue(numpy.allclose(array_multilinestring_zm[0], [[0, 0, 1, 5], [1, 1, 2, 6]])) - self.assertTrue(numpy.allclose(array_multilinestring_zm[1], [[2, 2, 3, 7], [3, 3, 4, 8]])) + self.assertTrue( + numpy.allclose(array_multilinestring_zm[0], [[0, 0, 1, 5], [1, 1, 2, 6]]) + ) + self.assertTrue( + numpy.allclose(array_multilinestring_zm[1], [[2, 2, 3, 7], [3, 3, 4, 8]]) + ) # Test MULTIPOLYGON with Z, and a hole geom_multipolygon_z = QgsGeometry.fromWkt( @@ -8154,9 +8202,17 @@ def testAsNumpy(self): self.assertTrue(isinstance(array_multipolygon_z, list)) self.assertEqual(len(array_multipolygon_z), 2) self.assertTrue( - numpy.allclose(array_multipolygon_z[0][0], [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]])) + numpy.allclose( + array_multipolygon_z[0][0], + [[0, 0, 1], [4, 0, 2], [4, 4, 3], [0, 4, 4], [0, 0, 1]], + ) + ) self.assertTrue( - numpy.allclose(array_multipolygon_z[1][0], [[1, 1, 1], [3, 1, 1.5], [3, 3, 2], [1, 3, 2.5], [1, 1, 1]])) + numpy.allclose( + array_multipolygon_z[1][0], + [[1, 1, 1], [3, 1, 1.5], [3, 3, 2], [1, 3, 2.5], [1, 1, 1]], + ) + ) # Test MULTIPOLYGON with M geom_multipolygon_m = QgsGeometry.fromWkt( @@ -8166,9 +8222,17 @@ def testAsNumpy(self): self.assertTrue(isinstance(array_multipolygon_m, list)) self.assertEqual(len(array_multipolygon_m), 2) self.assertTrue( - numpy.allclose(array_multipolygon_m[0][0], [[0, 0, 5], [4, 0, 6], [4, 4, 7], [0, 4, 8], [0, 0, 5]])) - self.assertTrue(numpy.allclose(array_multipolygon_m[1][0], - [[10, 10, 9], [14, 10, 10], [14, 14, 11], [10, 14, 12], [10, 10, 9]])) + numpy.allclose( + array_multipolygon_m[0][0], + [[0, 0, 5], [4, 0, 6], [4, 4, 7], [0, 4, 8], [0, 0, 5]], + ) + ) + self.assertTrue( + numpy.allclose( + array_multipolygon_m[1][0], + [[10, 10, 9], [14, 10, 10], [14, 14, 11], [10, 14, 12], [10, 10, 9]], + ) + ) # Test MULTIPOLYGON with Z and M and holes geom_multipolygon_zm = QgsGeometry.fromWkt( @@ -8178,27 +8242,42 @@ def testAsNumpy(self): self.assertTrue(isinstance(array_multipolygon_zm, list)) self.assertEqual(len(array_multipolygon_zm), 3) self.assertTrue( - numpy.allclose(array_multipolygon_zm[0][0], - [[-2.43197278911564618, 0.52380952380952384, 0, 0], - [-2.0986394557823127, -0.65986394557823114, 0, 0], - [-1.10544217687074831, -0.14965986394557818, 0, 0], - [-1.07142857142857162, 0.29931972789115646, 0, 0], - [-2.43197278911564618, 0.52380952380952384, 0, 0]])) + numpy.allclose( + array_multipolygon_zm[0][0], + [ + [-2.43197278911564618, 0.52380952380952384, 0, 0], + [-2.0986394557823127, -0.65986394557823114, 0, 0], + [-1.10544217687074831, -0.14965986394557818, 0, 0], + [-1.07142857142857162, 0.29931972789115646, 0, 0], + [-2.43197278911564618, 0.52380952380952384, 0, 0], + ], + ) + ) self.assertTrue( - numpy.allclose(array_multipolygon_zm[1][0], - [[-2.12399582948757359, 0.08043619457833384, 0, 0], - [-1.33488018322907021, 0.08043619457833384, 0, 0], - [-1.77025433288893463, -0.19167264895908098, 0, 0], - [-2.12399582948757359, 0.08043619457833384, 0, 0]])) + numpy.allclose( + array_multipolygon_zm[1][0], + [ + [-2.12399582948757359, 0.08043619457833384, 0, 0], + [-1.33488018322907021, 0.08043619457833384, 0, 0], + [-1.77025433288893463, -0.19167264895908098, 0, 0], + [-2.12399582948757359, 0.08043619457833384, 0, 0], + ], + ) + ) self.assertTrue( - numpy.allclose(array_multipolygon_zm[2][0], - [[1.0, 1.0, 1, 1], - [2.0, 1.0, 2, 2], - [2.0, 2.0, 3, 3], - [1.0, 2.0, 4, 4], - [1.0, 1.0, 1, 1]])) + numpy.allclose( + array_multipolygon_zm[2][0], + [ + [1.0, 1.0, 1, 1], + [2.0, 1.0, 2, 2], + [2.0, 2.0, 3, 3], + [1.0, 2.0, 4, 4], + [1.0, 1.0, 1, 1], + ], + ) + ) if __name__ == '__main__': From a37a425fb0145f0edddc3aa4dfd7de447204ec9f Mon Sep 17 00:00:00 2001 From: Till Frankenbach Date: Sun, 26 Jan 2025 10:17:05 +0100 Subject: [PATCH 6/6] Fix QgsRasterBlock and QgsRasterLayer as_numpy methods --- python/PyQt6/core/__init__.py.in | 17 ++++++++++++----- python/core/__init__.py.in | 17 ++++++++++++----- tests/src/python/test_qgsrasterblock.py | 4 +++- tests/src/python/test_qgsrasterlayer.py | 9 +++++---- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index c67b4da0a7ef..8b920e3897d8 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -569,11 +569,15 @@ try: raise ValueError(f"The raster block data type '{str(self.dataType())}' is not compatible with NumPy arrays.") src_array = _numpy.frombuffer(self.data(), dtype=raster_dtype) src_array = src_array.reshape((self.height(), self.width())) - if not self.hasNoDataValue() or not use_masking: - return src_array - else: - no_data_value = self.noDataValue() if isinstance(self.noDataValue(), raster_dtype) else 0 + if use_masking: + if not self.hasNoDataValue(): + # Default to 0 as noDataValue if none is set + no_data_value = 0 + else: + no_data_value = self.noDataValue() return _numpy.ma.masked_equal(src_array, no_data_value) + else: + return src_array QgsRasterBlock.as_numpy = _raster_block_as_numpy @@ -586,7 +590,10 @@ try: src_array = block.as_numpy(use_masking=use_masking) arrays.append(src_array) - return _numpy.array(arrays) # This converts any maskedArrays to numpy.array + if use_masking: + return _numpy.ma.stack(arrays, axis=0) + else: + return _numpy.array(arrays) QgsRasterLayer.as_numpy = _raster_layer_as_numpy diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 6e74b646d5b2..afa6e7dbe04c 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -579,11 +579,15 @@ try: raise ValueError(f"The raster block data type '{str(self.dataType())}' is not compatible with NumPy arrays.") src_array = _numpy.frombuffer(self.data(), dtype=raster_dtype) src_array = src_array.reshape((self.height(), self.width())) - if not self.hasNoDataValue() or not use_masking: - return src_array - else: - no_data_value = self.noDataValue() if isinstance(self.noDataValue(), raster_dtype) else 0 + if use_masking: + if not self.hasNoDataValue(): + # Default to 0 as noDataValue if none is set + no_data_value = 0 + else: + no_data_value = self.noDataValue() return _numpy.ma.masked_equal(src_array, no_data_value) + else: + return src_array QgsRasterBlock.as_numpy = _raster_block_as_numpy @@ -596,7 +600,10 @@ try: src_array = block.as_numpy(use_masking=use_masking) arrays.append(src_array) - return _numpy.array(arrays) # This converts any maskedArrays to numpy.array + if use_masking: + return _numpy.ma.stack(arrays, axis=0) + else: + return _numpy.array(arrays) QgsRasterLayer.as_numpy = _raster_layer_as_numpy diff --git a/tests/src/python/test_qgsrasterblock.py b/tests/src/python/test_qgsrasterblock.py index d76e6f1dca85..42c4e6dc0fc1 100644 --- a/tests/src/python/test_qgsrasterblock.py +++ b/tests/src/python/test_qgsrasterblock.py @@ -47,10 +47,12 @@ def testQgsRasterBlock(self): self.assertTrue((block.as_numpy() == expected_array).all()) # test with noDataValue set - block.setNoDataValue(0) + block.setNoDataValue(-999) data = numpy.array([[numpy.nan, 2], [4, 4]]) expected_masked_array = numpy.ma.masked_array(data, mask=numpy.isnan(data)) self.assertTrue((block.as_numpy() == expected_masked_array).all()) + self.assertTrue(numpy.ma.isMaskedArray(block.as_numpy())) + self.assertTrue(block.as_numpy().fill_value == -999) # test with noDataValue set and use_masking == False self.assertTrue((block.as_numpy(use_masking=False) == expected_array).all()) diff --git a/tests/src/python/test_qgsrasterlayer.py b/tests/src/python/test_qgsrasterlayer.py index 51cdcaf7fbb9..d83b374c785f 100644 --- a/tests/src/python/test_qgsrasterlayer.py +++ b/tests/src/python/test_qgsrasterlayer.py @@ -18,6 +18,7 @@ from shutil import copyfile import numpy as np +import numpy.ma from osgeo import gdal from qgis.PyQt.QtCore import QFileInfo, QSize, QTemporaryDir from qgis.PyQt.QtGui import QColor, QResizeEvent @@ -1267,13 +1268,13 @@ def test_read_xml_crash(self): def test_as_numpy(self): layer = QgsRasterLayer(self.rpath, 'raster') arrays = layer.as_numpy() - self.assertEqual(type(arrays[5]), np.ndarray) + self.assertTrue(numpy.ma.isMaskedArray(arrays[0])) self.assertEqual(arrays.shape, (9, 200, 200)) self.assertEqual(arrays[0].dtype, np.int8) # test with bands parameter arrays = layer.as_numpy(bands=[1, 3]) - self.assertEqual(type(arrays[0]), np.ndarray) + self.assertTrue(numpy.ma.isMaskedArray(arrays[0])) self.assertEqual(arrays.shape, (2, 200, 200)) self.assertEqual(arrays[0].dtype, np.int8) @@ -1281,7 +1282,7 @@ def test_as_numpy(self): 'rgb_with_mask.tif') layer = QgsRasterLayer(path, QFileInfo(path).baseName()) arrays = layer.as_numpy() - self.assertEqual(type(arrays[0]), np.ndarray) + self.assertTrue(numpy.ma.isMaskedArray(arrays[0])) self.assertEqual(arrays.shape, (4, 150, 162)) self.assertEqual(arrays[0].dtype, np.int8) @@ -1289,7 +1290,7 @@ def test_as_numpy(self): 'rnd_percentile_raster5_float64.tif') layer = QgsRasterLayer(path, QFileInfo(path).baseName()) arrays = layer.as_numpy() - self.assertEqual(type(arrays[0]), np.ndarray) # All maskedArrays are converted to numpy.array + self.assertTrue(numpy.ma.isMaskedArray(arrays[0])) self.assertEqual(arrays.shape, (1, 4, 4)) self.assertEqual(arrays[0].dtype, np.float64)