Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement QgsGeometry.as_numpy and .as_shapely #58589

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion python/PyQt6/core/__init__.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,6 @@ QgsException.__doc__ = "Defines a QGIS exception class."

try:
import numpy as _numpy

def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]:
qgis_to_numpy_dtype_dict = {
Qgis.DataType.UnknownDataType: None,
Expand Down Expand Up @@ -588,6 +587,34 @@ 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

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')
Expand All @@ -598,6 +625,32 @@ except ModuleNotFoundError:
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.base.BaseGeometry:
wkb_qbytearray = self.asWkb() # Get the geometry in WKB format (QByteArray)
shapely_geom = _shapely.from_wkb(wkb_qbytearray.data())

return shapely_geom

QgsGeometry.as_shapely = _geometry_as_shapely

except ModuleNotFoundError:
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.
Expand All @@ -619,3 +672,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."""
58 changes: 57 additions & 1 deletion python/core/__init__.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ QgsException.__doc__ = "Defines a QGIS exception class."

try:
import numpy as _numpy

def _qgis_data_type_to_numeric_data_type(dataType: Qgis.DataType) -> _typing.Optional[_numpy.dtype]:
qgis_to_numpy_dtype_dict = {
Qgis.DataType.UnknownDataType: None,
Expand Down Expand Up @@ -596,6 +595,34 @@ 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

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')
Expand All @@ -607,6 +634,32 @@ 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

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)
shapely_geom = _shapely.from_wkb(wkb_qbytearray.data())

return shapely_geom

QgsGeometry.as_shapely = _geometry_as_shapely

except ModuleNotFoundError:
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.

Expand All @@ -627,3 +680,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."""
52 changes: 52 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
QgsWkbTypes,
)
import unittest
import numpy
from qgis.testing import start_app, QgisTestCase

from utilities import compareWkt, unitTestDataPath, writeShape
Expand Down Expand Up @@ -7807,6 +7808,57 @@ 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):
# 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()
Loading