From 83f2e3299eaf46e4ae34e6f17217aa9fb8b9f129 Mon Sep 17 00:00:00 2001 From: merydian Date: Thu, 5 Sep 2024 18:47:53 +0200 Subject: [PATCH 1/5] Implement QgsGeometry.as_numpy and .as_shapely --- python/PyQt6/core/__init__.py.in | 72 ++++++++++++++++++++++++++++ python/core/__init__.py.in | 72 ++++++++++++++++++++++++++++ tests/src/python/test_qgsgeometry.py | 54 +++++++++++++++++++++ 3 files changed, 198 insertions(+) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index e8c4b6814779..91cb85cc6b4b 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -540,6 +540,7 @@ QgsException.__doc__ = "Defines a QGIS exception class." try: import numpy as _numpy + import shapely as _shapely def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]: qgis_to_numpy_dtype_dict = { @@ -588,6 +589,60 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy + + def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: + 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() + ] + 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]) + + + QgsGeometry.as_numpy = _qgsgeometry_as_numpy + + def _geometry_as_shapely(self) -> _shapely.Geometry: + numpy_coords = _qgsgeometry_as_numpy(self) + + if self.isMultipart(): + if self.type() == QgsWkbTypes.PointGeometry: + return _shapely.multipoint([point(coord) for coord in numpy_coords]) + elif self.type() == QgsWkbTypes.LineGeometry: + return _shapely.multilinestring([linestring(coords) for coords in numpy_coords]) + elif self.type() == QgsWkbTypes.PolygonGeometry: + exterior_ring = numpy_coords[0] + interior_rings = numpy_coords[1:] + return _shapely.multipolygon([polygon(exterior_ring, interior_rings if interior_rings else None) for rings in numpy_coords]) + else: + if self.type() == QgsWkbTypes.PointGeometry: + return _shapely.points(numpy_coords) + elif self.type() == QgsWkbTypes.LineGeometry: + return _shapely.linestrings(numpy_coords) + elif self.type() == QgsWkbTypes.PolygonGeometry: + exterior_ring = numpy_coords[0] + interior_rings = numpy_coords[1:] + return _shapely.polygons(exterior_ring, interior_rings if interior_rings else None) + + QgsGeometry.as_shapely = _geometry_as_shapely + + except ModuleNotFoundError: def _raster_block_as_numpy(self, use_masking:bool = True): raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') @@ -599,6 +654,20 @@ except ModuleNotFoundError: QgsRasterLayer.as_numpy = _raster_layer_as_numpy + def _geometry_as_numpy(self): + raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') + + QgsGeometry.as_numpy = _geometry_as_numpy + + def _geometry_as_shapely(self): + raise QgsNotSupportedException('QgsGeometry.as_shapely is not available, shapely is not installed on the system') + + QgsGeometry.as_shapely = _geometry_as_shapely + + + + + QgsRasterBlock.as_numpy.__doc__ = """ Returns the block data as a numpy array. @@ -619,3 +688,6 @@ If `bands` is provided, only the specified bands will be included in the returne .. versionadded:: 3.40 """ +QgsGeometry.as_numpy.__doc__ = """Returns the geometry data as a numpy array or list of numpy arrays.""" + +QgsGeometry.as_shapely.__doc__ = """Returns the geometry data as a shapely object.""" diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 09f137feb535..44c507769b11 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -548,6 +548,7 @@ QgsException.__doc__ = "Defines a QGIS exception class." try: import numpy as _numpy + import shapely as _shapely def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]: qgis_to_numpy_dtype_dict = { @@ -596,6 +597,60 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy + + def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: + 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() + ] + 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]) + + + QgsGeometry.as_numpy = _qgsgeometry_as_numpy + + def _geometry_as_shapely(self) -> _shapely.Geometry: + numpy_coords = _qgsgeometry_as_numpy(self) + + if self.isMultipart(): + if self.type() == QgsWkbTypes.PointGeometry: + return _shapely.multipoint([point(coord) for coord in numpy_coords]) + elif self.type() == QgsWkbTypes.LineGeometry: + return _shapely.multilinestring([linestring(coords) for coords in numpy_coords]) + elif self.type() == QgsWkbTypes.PolygonGeometry: + exterior_ring = numpy_coords[0] + interior_rings = numpy_coords[1:] + return _shapely.multipolygon([polygon(exterior_ring, interior_rings if interior_rings else None) for rings in numpy_coords]) + else: + if self.type() == QgsWkbTypes.PointGeometry: + return _shapely.points(numpy_coords) + elif self.type() == QgsWkbTypes.LineGeometry: + return _shapely.linestrings(numpy_coords) + elif self.type() == QgsWkbTypes.PolygonGeometry: + exterior_ring = numpy_coords[0] + interior_rings = numpy_coords[1:] + return _shapely.polygons(exterior_ring, interior_rings if interior_rings else None) + + QgsGeometry.as_shapely = _geometry_as_shapely + + except ModuleNotFoundError: def _raster_block_as_numpy(self, use_masking:bool = True): raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') @@ -607,6 +662,20 @@ except ModuleNotFoundError: QgsRasterLayer.as_numpy = _raster_layer_as_numpy + def _geometry_as_numpy(self): + raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') + + QgsGeometry.as_numpy = _geometry_as_numpy + + def _geometry_as_shapely(self): + raise QgsNotSupportedException('QgsGeometry.as_shapely is not available, shapely is not installed on the system') + + QgsGeometry.as_shapely = _geometry_as_shapely + + + + + QgsRasterBlock.as_numpy.__doc__ = """ Returns the block data as a numpy array. @@ -627,3 +696,6 @@ If `bands` is provided, only the specified bands will be included in the returne .. versionadded:: 3.40 """ +QgsGeometry.as_numpy.__doc__ = """Returns the geometry data as a numpy array or list of numpy arrays.""" + +QgsGeometry.as_shapely.__doc__ = """Returns the geometry data as a shapely object.""" diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index b542db0831b0..bd49794f37a0 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -55,6 +55,7 @@ QgsWkbTypes, ) import unittest +import numpy from qgis.testing import start_app, QgisTestCase from utilities import compareWkt, unitTestDataPath, writeShape @@ -7807,6 +7808,59 @@ def testConstrainedDelaunayTriangulation(self): "MultiPolygon (((41.96 29.61, 41.96 30.39, 42 30, 41.96 29.61)),((41.66 31.11, 41.85 30.77, 41.96 30.39, 41.66 31.11)),((41.66 28.89, 41.96 29.61, 41.85 29.23, 41.66 28.89)),((41.41 31.41, 41.96 30.39, 41.96 29.61, 41.41 31.41)),((41.41 31.41, 41.66 31.11, 41.96 30.39, 41.41 31.41)),((41.41 28.59, 41.96 29.61, 41.66 28.89, 41.41 28.59)),((41.41 28.59, 41.41 31.41, 41.96 29.61, 41.41 28.59)),((40.39 31.96, 41.11 31.66, 41.41 31.41, 40.39 31.96)),((40.39 31.96, 40.77 31.85, 41.11 31.66, 40.39 31.96)),((40.39 28.04, 41.41 28.59, 41.11 28.34, 40.39 28.04)),((40.39 28.04, 41.11 28.34, 40.77 28.15, 40.39 28.04)),((39.61 31.96, 40.39 31.96, 41.41 31.41, 39.61 31.96)),((39.61 31.96, 40 32, 40.39 31.96, 39.61 31.96)),((39.61 28.04, 40.39 28.04, 40 28, 39.61 28.04)),((38.89 31.66, 39.23 31.85, 39.61 31.96, 38.89 31.66)),((38.89 28.34, 39.61 28.04, 39.23 28.15, 38.89 28.34)),((38.59 31.41, 41.41 31.41, 41.41 28.59, 38.59 31.41)),((38.59 31.41, 39.61 31.96, 41.41 31.41, 38.59 31.41)),((38.59 31.41, 38.89 31.66, 39.61 31.96, 38.59 31.41)),((38.59 28.59, 41.41 28.59, 40.39 28.04, 38.59 28.59)),((38.59 28.59, 40.39 28.04, 39.61 28.04, 38.59 28.59)),((38.59 28.59, 39.61 28.04, 38.89 28.34, 38.59 28.59)),((38.59 28.59, 38.59 31.41, 41.41 28.59, 38.59 28.59)),((38.04 30.39, 38.59 31.41, 38.59 28.59, 38.04 30.39)),((38.04 30.39, 38.34 31.11, 38.59 31.41, 38.04 30.39)),((38.04 30.39, 38.15 30.77, 38.34 31.11, 38.04 30.39)),((38.04 29.61, 38.59 28.59, 38.34 28.89, 38.04 29.61)),((38.04 29.61, 38.34 28.89, 38.15 29.23, 38.04 29.61)),((38.04 29.61, 38.04 30.39, 38.59 28.59, 38.04 29.61)),((38 30, 38.04 30.39, 38.04 29.61, 38 30)))" ) + def testAsNumpy(self): + from qgis.core import QgsGeometry + + # Test POINT + geom_point = QgsGeometry.fromWkt("POINT (1 2)") + array_point = geom_point.as_numpy() + self.assertTrue(isinstance(array_point, numpy.ndarray)) + self.assertEqual(array_point.shape, (2,)) + self.assertTrue(numpy.allclose(array_point, [1, 2])) + + # Test LINESTRING + geom_linestring = QgsGeometry.fromWkt("LINESTRING (0 0, 1 1, 1 2)") + array_linestring = geom_linestring.as_numpy() + self.assertTrue(isinstance(array_linestring, numpy.ndarray)) + self.assertEqual(array_linestring.shape, (3, 2)) + self.assertTrue(numpy.allclose(array_linestring, [[0, 0], [1, 1], [1, 2]])) + + # Test POLYGON + geom_polygon = QgsGeometry.fromWkt("POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))") + array_polygon = geom_polygon.as_numpy() + self.assertTrue(isinstance(array_polygon, numpy.ndarray)) + self.assertEqual(len(array_polygon), 1) + self.assertTrue(numpy.allclose(array_polygon[0], [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0]])) + + # Test MULTIPOINT + geom_multipoint = QgsGeometry.fromWkt("MULTIPOINT ((1 2), (3 4))") + array_multipoint = geom_multipoint.as_numpy() + self.assertTrue(isinstance(array_multipoint, list)) + self.assertEqual(len(array_multipoint), 2) + self.assertTrue(isinstance(array_multipoint[0], numpy.ndarray)) + self.assertTrue(numpy.allclose(array_multipoint[0], [1, 2])) + self.assertTrue(numpy.allclose(array_multipoint[1], [3, 4])) + + # Test MULTILINESTRING + geom_multilinestring = QgsGeometry.fromWkt("MULTILINESTRING ((0 0, 1 1, 1 2), (2 2, 3 3))") + array_multilinestring = geom_multilinestring.as_numpy() + self.assertTrue(isinstance(array_multilinestring, list)) + self.assertEqual(len(array_multilinestring), 2) + self.assertTrue(numpy.allclose(array_multilinestring[0], [[0, 0], [1, 1], [1, 2]])) + self.assertTrue(numpy.allclose(array_multilinestring[1], [[2, 2], [3, 3]])) + + # Test MULTIPOLYGON + geom_multipolygon = QgsGeometry.fromWkt( + "MULTIPOLYGON (((0 0, 4 0, 4 4, 0 4, 0 0)), ((10 10, 14 10, 14 14, 10 14, 10 10)))" + ) + array_multipolygon = geom_multipolygon.as_numpy() + self.assertTrue(isinstance(array_multipolygon, list)) + self.assertEqual(len(array_multipolygon), 2) + self.assertEqual(len(array_multipolygon[0]), 1) # First polygon has 1 ring + self.assertTrue(numpy.allclose(array_multipolygon[0][0], [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0]])) + 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]])) + if __name__ == '__main__': unittest.main() From af0a019e82485607d8fcbce77e590a969fbfe9c5 Mon Sep 17 00:00:00 2001 From: merydian Date: Fri, 6 Sep 2024 10:18:17 +0200 Subject: [PATCH 2/5] Place shapely related things in separate try statement --- python/PyQt6/core/__init__.py.in | 39 ++++++++++++++++---------------- python/core/__init__.py.in | 39 ++++++++++++++++---------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index 91cb85cc6b4b..bae07c841518 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -540,8 +540,6 @@ QgsException.__doc__ = "Defines a QGIS exception class." try: import numpy as _numpy - import shapely as _shapely - def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]: qgis_to_numpy_dtype_dict = { Qgis.DataType.UnknownDataType: None, @@ -589,7 +587,6 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy - def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: if self.isMultipart(): if self.type() == QgsWkbTypes.PointGeometry: @@ -617,6 +614,26 @@ try: QgsGeometry.as_numpy = _qgsgeometry_as_numpy + +except ModuleNotFoundError: + def _raster_block_as_numpy(self, use_masking:bool = True): + raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') + + QgsRasterBlock.as_numpy = _raster_block_as_numpy + + def _raster_layer_as_numpy(self, use_masking:bool = True, bands: _typing.Optional[_typing.List[int]] = None): + raise QgsNotSupportedException('QgsRasterLayer.as_numpy is not available, numpy is not installed on the system') + + QgsRasterLayer.as_numpy = _raster_layer_as_numpy + + def _geometry_as_numpy(self): + raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') + + QgsGeometry.as_numpy = _geometry_as_numpy + +try: + import shapely as _shapely + def _geometry_as_shapely(self) -> _shapely.Geometry: numpy_coords = _qgsgeometry_as_numpy(self) @@ -642,23 +659,7 @@ try: QgsGeometry.as_shapely = _geometry_as_shapely - except ModuleNotFoundError: - def _raster_block_as_numpy(self, use_masking:bool = True): - raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') - - QgsRasterBlock.as_numpy = _raster_block_as_numpy - - def _raster_layer_as_numpy(self, use_masking:bool = True, bands: _typing.Optional[_typing.List[int]] = None): - raise QgsNotSupportedException('QgsRasterLayer.as_numpy is not available, numpy is not installed on the system') - - QgsRasterLayer.as_numpy = _raster_layer_as_numpy - - def _geometry_as_numpy(self): - raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') - - QgsGeometry.as_numpy = _geometry_as_numpy - def _geometry_as_shapely(self): raise QgsNotSupportedException('QgsGeometry.as_shapely is not available, shapely is not installed on the system') diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 44c507769b11..e9ea49d5be49 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -548,8 +548,6 @@ QgsException.__doc__ = "Defines a QGIS exception class." try: import numpy as _numpy - import shapely as _shapely - def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]: qgis_to_numpy_dtype_dict = { Qgis.DataType.UnknownDataType: None, @@ -597,7 +595,6 @@ try: QgsRasterLayer.as_numpy = _raster_layer_as_numpy - def _qgsgeometry_as_numpy(self) -> _typing.Union[_numpy.ndarray, _typing.List[_numpy.ndarray]]: if self.isMultipart(): if self.type() == QgsWkbTypes.PointGeometry: @@ -626,6 +623,26 @@ try: QgsGeometry.as_numpy = _qgsgeometry_as_numpy +except ModuleNotFoundError: + def _raster_block_as_numpy(self, use_masking:bool = True): + raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') + + QgsRasterBlock.as_numpy = _raster_block_as_numpy + + def _raster_layer_as_numpy(self, use_masking:bool = True, bands: _typing.Optional[_typing.List[int]] = None): + raise QgsNotSupportedException('QgsRasterLayer.as_numpy is not available, numpy is not installed on the system') + + QgsRasterLayer.as_numpy = _raster_layer_as_numpy + + def _geometry_as_numpy(self): + raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') + + QgsGeometry.as_numpy = _geometry_as_numpy + +try: + import shapely as _shapely + + def _geometry_as_shapely(self) -> _shapely.Geometry: numpy_coords = _qgsgeometry_as_numpy(self) @@ -650,23 +667,7 @@ try: QgsGeometry.as_shapely = _geometry_as_shapely - except ModuleNotFoundError: - def _raster_block_as_numpy(self, use_masking:bool = True): - raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system') - - QgsRasterBlock.as_numpy = _raster_block_as_numpy - - def _raster_layer_as_numpy(self, use_masking:bool = True, bands: _typing.Optional[_typing.List[int]] = None): - raise QgsNotSupportedException('QgsRasterLayer.as_numpy is not available, numpy is not installed on the system') - - QgsRasterLayer.as_numpy = _raster_layer_as_numpy - - def _geometry_as_numpy(self): - raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system') - - QgsGeometry.as_numpy = _geometry_as_numpy - def _geometry_as_shapely(self): raise QgsNotSupportedException('QgsGeometry.as_shapely is not available, shapely is not installed on the system') From db79ea9026c513765d776275e710661eb3dd2262 Mon Sep 17 00:00:00 2001 From: merydian Date: Wed, 11 Sep 2024 09:05:22 +0200 Subject: [PATCH 3/5] Remove unnecessary import --- tests/src/python/test_qgsgeometry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index bd49794f37a0..814fcbeb0ce6 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -7809,8 +7809,6 @@ def testConstrainedDelaunayTriangulation(self): ) def testAsNumpy(self): - from qgis.core import QgsGeometry - # Test POINT geom_point = QgsGeometry.fromWkt("POINT (1 2)") array_point = geom_point.as_numpy() From d98d5405b01aa82b598a1aa1d14f03dcf109340f Mon Sep 17 00:00:00 2001 From: merydian Date: Wed, 11 Sep 2024 13:52:12 +0200 Subject: [PATCH 4/5] Improve performance of as_shapely By utilizing QgsGeometry.asWkb() this is ~8.45 times faster than the previous implementation. --- python/PyQt6/core/__init__.py.in | 26 +++++--------------------- python/core/__init__.py.in | 26 +++++--------------------- 2 files changed, 10 insertions(+), 42 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index bae07c841518..a867b2fc3de6 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -634,28 +634,12 @@ except ModuleNotFoundError: try: import shapely as _shapely + def _geometry_as_shapely(self) -> _shapely.geometry.base.BaseGeometry: + wkb_qbytearray = self.asWkb() # Get the geometry in WKB format (QByteArray) + wkb_bytes = bytes(wkb_qbytearray) + shapely_geom = _shapely.from_wkb(wkb_bytes) - def _geometry_as_shapely(self) -> _shapely.Geometry: - numpy_coords = _qgsgeometry_as_numpy(self) - - if self.isMultipart(): - if self.type() == QgsWkbTypes.PointGeometry: - return _shapely.multipoint([point(coord) for coord in numpy_coords]) - elif self.type() == QgsWkbTypes.LineGeometry: - return _shapely.multilinestring([linestring(coords) for coords in numpy_coords]) - elif self.type() == QgsWkbTypes.PolygonGeometry: - exterior_ring = numpy_coords[0] - interior_rings = numpy_coords[1:] - return _shapely.multipolygon([polygon(exterior_ring, interior_rings if interior_rings else None) for rings in numpy_coords]) - else: - if self.type() == QgsWkbTypes.PointGeometry: - return _shapely.points(numpy_coords) - elif self.type() == QgsWkbTypes.LineGeometry: - return _shapely.linestrings(numpy_coords) - elif self.type() == QgsWkbTypes.PolygonGeometry: - exterior_ring = numpy_coords[0] - interior_rings = numpy_coords[1:] - return _shapely.polygons(exterior_ring, interior_rings if interior_rings else None) + return shapely_geom QgsGeometry.as_shapely = _geometry_as_shapely diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index e9ea49d5be49..024963c226c6 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -642,28 +642,12 @@ except ModuleNotFoundError: try: import shapely as _shapely + def _geometry_as_shapely(self) -> _shapely.geometry.base.BaseGeometry: + wkb_qbytearray = self.asWkb() # Get the geometry in WKB format (QByteArray) + wkb_bytes = bytes(wkb_qbytearray) + shapely_geom = _shapely.from_wkb(wkb_bytes) - def _geometry_as_shapely(self) -> _shapely.Geometry: - numpy_coords = _qgsgeometry_as_numpy(self) - - if self.isMultipart(): - if self.type() == QgsWkbTypes.PointGeometry: - return _shapely.multipoint([point(coord) for coord in numpy_coords]) - elif self.type() == QgsWkbTypes.LineGeometry: - return _shapely.multilinestring([linestring(coords) for coords in numpy_coords]) - elif self.type() == QgsWkbTypes.PolygonGeometry: - exterior_ring = numpy_coords[0] - interior_rings = numpy_coords[1:] - return _shapely.multipolygon([polygon(exterior_ring, interior_rings if interior_rings else None) for rings in numpy_coords]) - else: - if self.type() == QgsWkbTypes.PointGeometry: - return _shapely.points(numpy_coords) - elif self.type() == QgsWkbTypes.LineGeometry: - return _shapely.linestrings(numpy_coords) - elif self.type() == QgsWkbTypes.PolygonGeometry: - exterior_ring = numpy_coords[0] - interior_rings = numpy_coords[1:] - return _shapely.polygons(exterior_ring, interior_rings if interior_rings else None) + return shapely_geom QgsGeometry.as_shapely = _geometry_as_shapely From f60bf2942505f1192457a2e9b9dd18bd57ad82b6 Mon Sep 17 00:00:00 2001 From: merydian Date: Thu, 12 Sep 2024 13:04:57 +0200 Subject: [PATCH 5/5] Use QByteArray.data() for as_shapely --- python/PyQt6/core/__init__.py.in | 3 +-- python/core/__init__.py.in | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index a867b2fc3de6..7adb81759c9e 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -636,8 +636,7 @@ try: def _geometry_as_shapely(self) -> _shapely.geometry.base.BaseGeometry: wkb_qbytearray = self.asWkb() # Get the geometry in WKB format (QByteArray) - wkb_bytes = bytes(wkb_qbytearray) - shapely_geom = _shapely.from_wkb(wkb_bytes) + shapely_geom = _shapely.from_wkb(wkb_qbytearray.data()) return shapely_geom diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 024963c226c6..916fad6a0366 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -644,8 +644,7 @@ try: def _geometry_as_shapely(self) -> _shapely.geometry.base.BaseGeometry: wkb_qbytearray = self.asWkb() # Get the geometry in WKB format (QByteArray) - wkb_bytes = bytes(wkb_qbytearray) - shapely_geom = _shapely.from_wkb(wkb_bytes) + shapely_geom = _shapely.from_wkb(wkb_qbytearray.data()) return shapely_geom