From 3d10459ea72a52568d23ff238d9994234cb5cbf8 Mon Sep 17 00:00:00 2001 From: jutoth Date: Mon, 6 Jan 2025 16:35:52 +0100 Subject: [PATCH 1/5] add sensorthings support --- controller.py | 3 +- filters.py | 64 ++++++++++++++++++++++++++++++++++------- helpers.py | 80 ++++++++++++++++++++++++++++++++++++++++----------- settings.py | 6 ++++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/controller.py b/controller.py index 252238c..7d6989e 100644 --- a/controller.py +++ b/controller.py @@ -1,7 +1,7 @@ from typing import Iterable, Optional, List from qgis.PyQt.QtCore import pyqtSignal, QObject -from qgis.core import Qgis, QgsProject, QgsMapLayer, QgsMapLayerType, QgsWkbTypes, QgsGeometry +from qgis.core import QgsProject, QgsMapLayer, QgsMapLayerType, QgsWkbTypes, QgsGeometry from qgis.gui import QgsRubberBand from qgis.utils import iface @@ -125,6 +125,7 @@ def stopSketchingTool(self): self.mapTool.deactivate() def onSketchFinished(self, geometry: QgsGeometry): + geometry.convertToSingleType() # SensorThings Filter only supports single geometry type self.stopSketchingTool() if not geometry.isGeosValid(): iface.messageBar().pushWarning(LOCALIZED_PLUGIN_NAME, self.tr("Geometry is not valid")) diff --git a/filters.py b/filters.py index 881340a..a69e426 100644 --- a/filters.py +++ b/filters.py @@ -3,16 +3,45 @@ from typing import List from qgis.PyQt.QtWidgets import QMessageBox -from qgis.core import QgsVectorLayer, QgsGeometry, QgsCoordinateReferenceSystem + +from qgis.core import ( + QgsVectorLayer, + QgsGeometry, + QgsCoordinateReferenceSystem, + Qgis +) + +if Qgis.QGIS_VERSION_INT > 33600: + from qgis.core import QgsSensorThingsUtils + from qgis.utils import iface -from .settings import FILTER_COMMENT_START, FILTER_COMMENT_STOP, LOCALIZED_PLUGIN_NAME -from .helpers import tr, saveSettingsValue, readSettingsValue, allSettingsValues, removeSettingsValue, \ - getLayerGeomName, matchFormatString +from .settings import ( + FILTER_COMMENT_START, + FILTER_COMMENT_STOP, + LOCALIZED_PLUGIN_NAME, + SENSORTHINGS_STORAGE_TYPE +) + +from .helpers import ( + tr, + saveSettingsValue, + readSettingsValue, + allSettingsValues, + removeSettingsValue, + getLayerGeomName, + matchFormatString, + getEntityTypeFromSensorThingsLayer, + reproject_wkt_geometry +) FILTERSTRING_TEMPLATE = "{spatial_predicate}({geom_name}, ST_TRANSFORM(ST_GeomFromText('{wkt}', {srid}), {layer_srid}))" +# sensorthings filter does not support reprojection (st_transform) +# reprojection happens in helpers.py -> addFilterToLayer +FILTERSTRING_TEMPLATE_SENSORTHINGS = "{spatial_predicate}({geom_name}, geography'{wkt}')" + class Predicate(IntEnum): INTERSECTS = 1 @@ -51,25 +80,38 @@ def filterString(self, layer: QgsVectorLayer) -> str: Returns: str: A layer filter string """ - # ST_DISJOINT does not use spatial indexes, but we can use its opposite "NOT ST_INTERSECTS" which does spatial_predicate = f"ST_{Predicate(self.predicate).name}" + wkt = self.wkt if not self.bbox else self.boxGeometry.asWkt() + srid=self.crs.postgisSrid() + layer_srid=layer.crs().postgisSrid() + + if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: + reprojected_wkt = reproject_wkt_geometry(wkt, srid, layer_srid) + spatial_predicate = spatial_predicate.lower() # sensorthings specification uses lower case + entity_str = getEntityTypeFromSensorThingsLayer(layer) + entity_type = QgsSensorThingsUtils.stringToEntity(entity_str) + geom_field = QgsSensorThingsUtils.geometryFieldForEntityType(entity_type) + + return FILTERSTRING_TEMPLATE_SENSORTHINGS.format( + spatial_predicate=spatial_predicate, + geom_name=geom_field, + wkt=reprojected_wkt + ) + if self.predicate == Predicate.DISJOINT: spatial_predicate = "NOT ST_INTERSECTS" - wkt = self.wkt - if self.bbox: - wkt = self.boxGeometry.asWkt() - geom_name = getLayerGeomName(layer) return FILTERSTRING_TEMPLATE.format( spatial_predicate=spatial_predicate, geom_name=geom_name, wkt=wkt, - srid=self.crs.postgisSrid(), - layer_srid=layer.crs().postgisSrid() + srid=srid, + layer_srid=layer_srid ) + @staticmethod def fromFilterString(subsetString: str) -> 'FilterDefinition': start_index = subsetString.find(FILTER_COMMENT_START) + len(FILTER_COMMENT_START) diff --git a/helpers.py b/helpers.py index 8dd0340..6e76778 100644 --- a/helpers.py +++ b/helpers.py @@ -6,12 +6,24 @@ from osgeo import ogr from qgis.PyQt.QtCore import QCoreApplication from qgis.core import Qgis, QgsExpressionContextUtils, QgsSettings, QgsMapLayer, QgsMapLayerType, QgsVectorLayer,\ - QgsWkbTypes + QgsWkbTypes, QgsProviderRegistry + from qgis.utils import iface -from .settings import SUPPORTED_STORAGE_TYPES, GROUP, FILTER_COMMENT_START, FILTER_COMMENT_STOP, \ - LAYER_EXCEPTION_VARIABLE, LOCALIZED_PLUGIN_NAME +from .settings import ( + SUPPORTED_STORAGE_TYPES, + GROUP, + FILTER_COMMENT_START, + FILTER_COMMENT_STOP, + LAYER_EXCEPTION_VARIABLE, + LOCALIZED_PLUGIN_NAME, + SENSORTHINGS_STORAGE_TYPE +) + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .filters import FilterDefinition def tr(message): return QCoreApplication.translate('@default', message) @@ -73,23 +85,54 @@ def isLayerSupported(layer: QgsMapLayer): def removeFilterFromLayer(layer: QgsVectorLayer): - currentFilter = layer.subsetString() - if FILTER_COMMENT_START not in currentFilter: - return - start_index = currentFilter.find(FILTER_COMMENT_START) - stop_index = currentFilter.find(FILTER_COMMENT_STOP) + len(FILTER_COMMENT_STOP) - newFilter = currentFilter[:start_index] + currentFilter[stop_index:] - layer.setSubsetString(newFilter) + if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: + layer.setSubsetString('') + else: + currentFilter = layer.subsetString() + if FILTER_COMMENT_START not in currentFilter: + return + start_index = currentFilter.find(FILTER_COMMENT_START) + stop_index = currentFilter.find(FILTER_COMMENT_STOP) + len(FILTER_COMMENT_STOP) + newFilter = currentFilter[:start_index] + currentFilter[stop_index:] + layer.setSubsetString(newFilter) def addFilterToLayer(layer: QgsVectorLayer, filterDef: 'FilterDefinition'): currentFilter = layer.subsetString() - if FILTER_COMMENT_START in currentFilter: - removeFilterFromLayer(layer) - currentFilter = layer.subsetString() - connect = " AND " if currentFilter else "" - newFilter = f'{currentFilter}{FILTER_COMMENT_START}{connect}{filterDef.filterString(layer)}{FILTER_COMMENT_STOP}' - layer.setSubsetString(newFilter) + if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: + newFilter = filterDef.filterString(layer) + layer.setSubsetString(newFilter) + else: + if FILTER_COMMENT_START in currentFilter: + removeFilterFromLayer(layer) + currentFilter = layer.subsetString() + connect = " AND " if currentFilter else "" + newFilter = f'{currentFilter}{FILTER_COMMENT_START}{connect}{filterDef.filterString(layer)}{FILTER_COMMENT_STOP}' + layer.setSubsetString(newFilter) + + +from qgis.core import QgsGeometry, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject + +def reproject_wkt_geometry(wkt: str, source_crs_epsg: int, target_crs_epsg: int) -> str: + """ + Reproject a WKT geometry from a source CRS to a target CRS. + + Args: + wkt (str): The WKT geometry to reproject. + source_crs_epsg (int): The EPSG code of the source CRS. + target_crs_epsg (int): The EPSG code of the target CRS. + + Returns: + str: The reprojected WKT geometry. + """ + # Create the source and target CRS objects + source_crs = QgsCoordinateReferenceSystem(source_crs_epsg) + target_crs = QgsCoordinateReferenceSystem(target_crs_epsg) + transform = QgsCoordinateTransform(source_crs, target_crs, QgsProject.instance()) + geometry = QgsGeometry.fromWkt(wkt) + geometry.transform(transform) + return geometry.asWkt() + def getLayerGeomName(layer: QgsVectorLayer): @@ -118,6 +161,11 @@ def getLayerGeomNameOgr(layer: QgsVectorLayer): return columnName +def getEntityTypeFromSensorThingsLayer(layer: QgsVectorLayer): + decoded_url = QgsProviderRegistry.instance().decodeUri(layer.providerType(), layer.dataProvider().dataSourceUri()) + return decoded_url.get('entity') + + def hasLayerException(layer: QgsVectorLayer) -> bool: return QgsExpressionContextUtils.layerScope(layer).variable(LAYER_EXCEPTION_VARIABLE) == 'true' diff --git a/settings.py b/settings.py index 5263872..a2fc9f4 100644 --- a/settings.py +++ b/settings.py @@ -1,4 +1,5 @@ from qgis.PyQt.QtCore import QCoreApplication +from qgis.core import Qgis def tr(message): return QCoreApplication.translate('@default', message) @@ -19,3 +20,8 @@ def tr(message): # The QGIS Storage Types that can be filtered by the plugin SUPPORTED_STORAGE_TYPES = ['POSTGRESQL DATABASE WITH POSTGIS EXTENSION', 'GPKG', 'SQLITE'] + +SENSORTHINGS_STORAGE_TYPE = 'OGC SensorThings API' + +if Qgis.QGIS_VERSION_INT > 33600: + SUPPORTED_STORAGE_TYPES.append(SENSORTHINGS_STORAGE_TYPE.upper()) From 7528706972c71efc0fee87dc55c49b65630aa791 Mon Sep 17 00:00:00 2001 From: jutoth <92805267+jutoth@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:26:03 +0100 Subject: [PATCH 2/5] Update helpers.py --- helpers.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 6e76778..7891316 100644 --- a/helpers.py +++ b/helpers.py @@ -5,8 +5,20 @@ from osgeo import ogr from qgis.PyQt.QtCore import QCoreApplication -from qgis.core import Qgis, QgsExpressionContextUtils, QgsSettings, QgsMapLayer, QgsMapLayerType, QgsVectorLayer,\ - QgsWkbTypes, QgsProviderRegistry +from qgis.core import ( + Qgis, + QgsExpressionContextUtils, + QgsSettings, + QgsMapLayer, + QgsMapLayerType, + QgsVectorLayer, + QgsWkbTypes, + QgsProviderRegistry, + QgsGeometry, + QgsCoordinateReferenceSystem, + QgsCoordinateTransform, + QgsProject +) from qgis.utils import iface @@ -111,7 +123,7 @@ def addFilterToLayer(layer: QgsVectorLayer, filterDef: 'FilterDefinition'): layer.setSubsetString(newFilter) -from qgis.core import QgsGeometry, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject + def reproject_wkt_geometry(wkt: str, source_crs_epsg: int, target_crs_epsg: int) -> str: """ @@ -125,7 +137,6 @@ def reproject_wkt_geometry(wkt: str, source_crs_epsg: int, target_crs_epsg: int) Returns: str: The reprojected WKT geometry. """ - # Create the source and target CRS objects source_crs = QgsCoordinateReferenceSystem(source_crs_epsg) target_crs = QgsCoordinateReferenceSystem(target_crs_epsg) transform = QgsCoordinateTransform(source_crs, target_crs, QgsProject.instance()) From dceaaf40b4fb612165e5cbe6e032f574a8a714ae Mon Sep 17 00:00:00 2001 From: jutoth <92805267+jutoth@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:29:39 +0100 Subject: [PATCH 3/5] Update helpers.py --- helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index 7891316..d2db1b7 100644 --- a/helpers.py +++ b/helpers.py @@ -98,7 +98,7 @@ def isLayerSupported(layer: QgsMapLayer): def removeFilterFromLayer(layer: QgsVectorLayer): if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: - layer.setSubsetString('') + layer.setSubsetString('') # sensorthings filter does not support comments (FILTER_COMMENT_START) else: currentFilter = layer.subsetString() if FILTER_COMMENT_START not in currentFilter: From 6838066817304fd1d9a483fe675139e32d86d991 Mon Sep 17 00:00:00 2001 From: jutoth <92805267+jutoth@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:30:37 +0100 Subject: [PATCH 4/5] Update filters.py --- filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filters.py b/filters.py index a69e426..c79c456 100644 --- a/filters.py +++ b/filters.py @@ -97,7 +97,7 @@ def filterString(self, layer: QgsVectorLayer) -> str: geom_name=geom_field, wkt=reprojected_wkt ) - + # ST_DISJOINT does not use spatial indexes, but we can use its opposite "NOT ST_INTERSECTS" which does if self.predicate == Predicate.DISJOINT: spatial_predicate = "NOT ST_INTERSECTS" From 93f57bc42876dac2ed1f51f539459db2acb3d5f1 Mon Sep 17 00:00:00 2001 From: jutoth Date: Sun, 12 Jan 2025 17:11:27 +0100 Subject: [PATCH 5/5] added start/stop flag to sensorthings filter | convert to single geom type for sensorthings filter --- controller.py | 10 +++---- filters.py | 50 +++++++++++++++++++++-------------- helpers.py | 73 +++++++++++++++++++++++++++++++-------------------- settings.py | 3 +++ 4 files changed, 82 insertions(+), 54 deletions(-) diff --git a/controller.py b/controller.py index 7d6989e..2ea8e75 100644 --- a/controller.py +++ b/controller.py @@ -8,8 +8,8 @@ from .maptool import PolygonTool from .filters import FilterDefinition, Predicate from .helpers import getSupportedLayers, removeFilterFromLayer, addFilterToLayer, refreshLayerTree, hasLayerException, \ - warnAboutCurveGeoms -from .settings import FILTER_COMMENT_START, LOCALIZED_PLUGIN_NAME + warnAboutCurveGeoms, getFilterStartStopString +from .settings import LOCALIZED_PLUGIN_NAME class FilterController(QObject): @@ -51,7 +51,8 @@ def onLayersAdded(self, layers: Iterable[QgsMapLayer]): else: # Look for saved filters to use with the plugin (possible when project was loaded) for layer in getSupportedLayers(layers): - if FILTER_COMMENT_START in layer.subsetString(): + FILTER_START_STRING, _ = getFilterStartStopString(layer) + if FILTER_START_STRING in layer.subsetString(): self.setFilterFromLayer(layer) return @@ -63,7 +64,7 @@ def onProjectCleared(self): self.removeFilter() def setFilterFromLayer(self, layer): - filterDefinition = FilterDefinition.fromFilterString(layer.subsetString()) + filterDefinition = FilterDefinition.fromFilterString(layer) self.currentFilter = filterDefinition self.refreshFilter() @@ -125,7 +126,6 @@ def stopSketchingTool(self): self.mapTool.deactivate() def onSketchFinished(self, geometry: QgsGeometry): - geometry.convertToSingleType() # SensorThings Filter only supports single geometry type self.stopSketchingTool() if not geometry.isGeosValid(): iface.messageBar().pushWarning(LOCALIZED_PLUGIN_NAME, self.tr("Geometry is not valid")) diff --git a/filters.py b/filters.py index c79c456..dc0d9f9 100644 --- a/filters.py +++ b/filters.py @@ -17,8 +17,6 @@ from qgis.utils import iface from .settings import ( - FILTER_COMMENT_START, - FILTER_COMMENT_STOP, LOCALIZED_PLUGIN_NAME, SENSORTHINGS_STORAGE_TYPE ) @@ -30,9 +28,9 @@ allSettingsValues, removeSettingsValue, getLayerGeomName, - matchFormatString, - getEntityTypeFromSensorThingsLayer, - reproject_wkt_geometry + matchFormatString, + reproject_geometry, + getFilterStartStopString ) @@ -84,25 +82,28 @@ def filterString(self, layer: QgsVectorLayer) -> str: wkt = self.wkt if not self.bbox else self.boxGeometry.asWkt() srid=self.crs.postgisSrid() layer_srid=layer.crs().postgisSrid() + geom_name = getLayerGeomName(layer) if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: - reprojected_wkt = reproject_wkt_geometry(wkt, srid, layer_srid) + # SensorThings only supports single geometry types + single_geometry = QgsGeometry.fromWkt(wkt) + single_geometry.convertToSingleType() + + # SensorThings filter does not support reprojection (st_transform) + # thats why the reprojection must be executed on client-side. + reprojected_geometry = reproject_geometry(single_geometry, srid, layer_srid) + spatial_predicate = spatial_predicate.lower() # sensorthings specification uses lower case - entity_str = getEntityTypeFromSensorThingsLayer(layer) - entity_type = QgsSensorThingsUtils.stringToEntity(entity_str) - geom_field = QgsSensorThingsUtils.geometryFieldForEntityType(entity_type) return FILTERSTRING_TEMPLATE_SENSORTHINGS.format( spatial_predicate=spatial_predicate, - geom_name=geom_field, - wkt=reprojected_wkt + geom_name=geom_name, + wkt=reprojected_geometry.asWkt() ) # ST_DISJOINT does not use spatial indexes, but we can use its opposite "NOT ST_INTERSECTS" which does if self.predicate == Predicate.DISJOINT: spatial_predicate = "NOT ST_INTERSECTS" - geom_name = getLayerGeomName(layer) - return FILTERSTRING_TEMPLATE.format( spatial_predicate=spatial_predicate, geom_name=geom_name, @@ -113,15 +114,24 @@ def filterString(self, layer: QgsVectorLayer) -> str: @staticmethod - def fromFilterString(subsetString: str) -> 'FilterDefinition': - start_index = subsetString.find(FILTER_COMMENT_START) + len(FILTER_COMMENT_START) - stop_index = subsetString.find(FILTER_COMMENT_STOP) + def fromFilterString(layer: QgsVectorLayer) -> 'FilterDefinition': + subsetString = layer.subsetString() + FILTER_START_STRING, FILTER_STOP_STRING = getFilterStartStopString(layer) + start_index = subsetString.find(FILTER_START_STRING) + len(FILTER_START_STRING) + stop_index = subsetString.find(FILTER_STOP_STRING) filterString = subsetString[start_index: stop_index] filterString = filterString.replace(' AND ', '') - params = matchFormatString(FILTERSTRING_TEMPLATE, filterString) - predicateName = params['spatial_predicate'][len('ST_'):] - if filterString.startswith('NOT ST_INTERSECTS'): - predicateName = 'DISJOINT' + + if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: + params = matchFormatString(FILTERSTRING_TEMPLATE, filterString) + predicateName = params['spatial_predicate'][len('st_'):] + + else: + params = matchFormatString(FILTERSTRING_TEMPLATE, filterString) + predicateName = params['spatial_predicate'][len('ST_'):] + if filterString.startswith('NOT ST_INTERSECTS'): + predicateName = 'DISJOINT'# + predicate = Predicate[predicateName] filterDefinition = FilterDefinition( name=tr('Unknown filter'), diff --git a/helpers.py b/helpers.py index d2db1b7..f55ce18 100644 --- a/helpers.py +++ b/helpers.py @@ -13,13 +13,15 @@ QgsMapLayerType, QgsVectorLayer, QgsWkbTypes, - QgsProviderRegistry, QgsGeometry, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject ) +if Qgis.QGIS_VERSION_INT > 33600: + from qgis.core import QgsSensorThingsUtils + from qgis.utils import iface from .settings import ( @@ -27,6 +29,8 @@ GROUP, FILTER_COMMENT_START, FILTER_COMMENT_STOP, + FILTER_COMMENT_START_SENSORTHINGS, + FILTER_COMMENT_STOP_SENSORTHINGS, LAYER_EXCEPTION_VARIABLE, LOCALIZED_PLUGIN_NAME, SENSORTHINGS_STORAGE_TYPE @@ -96,57 +100,73 @@ def isLayerSupported(layer: QgsMapLayer): return True -def removeFilterFromLayer(layer: QgsVectorLayer): +def getFilterStartStopString(layer: QgsVectorLayer) -> tuple[str, str]: if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: - layer.setSubsetString('') # sensorthings filter does not support comments (FILTER_COMMENT_START) + return FILTER_COMMENT_START_SENSORTHINGS, FILTER_COMMENT_STOP_SENSORTHINGS else: - currentFilter = layer.subsetString() - if FILTER_COMMENT_START not in currentFilter: - return - start_index = currentFilter.find(FILTER_COMMENT_START) - stop_index = currentFilter.find(FILTER_COMMENT_STOP) + len(FILTER_COMMENT_STOP) - newFilter = currentFilter[:start_index] + currentFilter[stop_index:] - layer.setSubsetString(newFilter) + return FILTER_COMMENT_START, FILTER_COMMENT_STOP + +def removeFilterFromLayer(layer: QgsVectorLayer): + # sensorthings filter does not support inline comments (FILTER_COMMENT_START) + # The workaround for this is a string that always evals to true and is only used with this plugin + FILTER_START_STRING, FILTER_STOP_STRING = getFilterStartStopString(layer) + currentFilter = layer.subsetString() + if FILTER_START_STRING not in currentFilter: + return + start_index = currentFilter.find(FILTER_START_STRING) + stop_index = currentFilter.find(FILTER_STOP_STRING) + len(FILTER_STOP_STRING) + newFilter = currentFilter[:start_index] + currentFilter[stop_index:] + newFilter = newFilter.rstrip(' and ') + layer.setSubsetString(newFilter) def addFilterToLayer(layer: QgsVectorLayer, filterDef: 'FilterDefinition'): currentFilter = layer.subsetString() + FILTER_START_STRING, FILTER_STOP_STRING = getFilterStartStopString(layer) + if FILTER_START_STRING in currentFilter: + removeFilterFromLayer(layer) + + currentFilter = layer.subsetString() + + connect = " AND " if currentFilter else "" if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: - newFilter = filterDef.filterString(layer) - layer.setSubsetString(newFilter) + connect = connect.lower() # SensorThings only supports lowercase 'and' + newFilter = f'{currentFilter}{connect}{FILTER_START_STRING}{filterDef.filterString(layer)}{FILTER_STOP_STRING}' else: - if FILTER_COMMENT_START in currentFilter: - removeFilterFromLayer(layer) - currentFilter = layer.subsetString() - connect = " AND " if currentFilter else "" - newFilter = f'{currentFilter}{FILTER_COMMENT_START}{connect}{filterDef.filterString(layer)}{FILTER_COMMENT_STOP}' - layer.setSubsetString(newFilter) + newFilter = f'{currentFilter}{FILTER_START_STRING}{connect}{filterDef.filterString(layer)}{FILTER_STOP_STRING}' + layer.setSubsetString(newFilter) -def reproject_wkt_geometry(wkt: str, source_crs_epsg: int, target_crs_epsg: int) -> str: +def reproject_geometry(geometry: QgsGeometry, source_crs_epsg: int, target_crs_epsg: int) -> QgsGeometry: """ - Reproject a WKT geometry from a source CRS to a target CRS. + Reproject a QgsGeometry from a source CRS to a target CRS. Args: - wkt (str): The WKT geometry to reproject. + geometry (QgsGeometry): The QgsGeometry to reproject. source_crs_epsg (int): The EPSG code of the source CRS. target_crs_epsg (int): The EPSG code of the target CRS. Returns: - str: The reprojected WKT geometry. + str: The reprojected geometry. """ source_crs = QgsCoordinateReferenceSystem(source_crs_epsg) target_crs = QgsCoordinateReferenceSystem(target_crs_epsg) + if source_crs == target_crs: + return geometry transform = QgsCoordinateTransform(source_crs, target_crs, QgsProject.instance()) - geometry = QgsGeometry.fromWkt(wkt) geometry.transform(transform) - return geometry.asWkt() + return geometry def getLayerGeomName(layer: QgsVectorLayer): + if layer.storageType() == SENSORTHINGS_STORAGE_TYPE: + entity_str = layer.dataProvider().uri().param('entity') + entity_type = QgsSensorThingsUtils.stringToEntity(entity_str) + geom_field = QgsSensorThingsUtils.geometryFieldForEntityType(entity_type) + return geom_field return layer.dataProvider().uri().geometryColumn() or getLayerGeomNameOgr(layer) @@ -172,11 +192,6 @@ def getLayerGeomNameOgr(layer: QgsVectorLayer): return columnName -def getEntityTypeFromSensorThingsLayer(layer: QgsVectorLayer): - decoded_url = QgsProviderRegistry.instance().decodeUri(layer.providerType(), layer.dataProvider().dataSourceUri()) - return decoded_url.get('entity') - - def hasLayerException(layer: QgsVectorLayer) -> bool: return QgsExpressionContextUtils.layerScope(layer).variable(LAYER_EXCEPTION_VARIABLE) == 'true' diff --git a/settings.py b/settings.py index a2fc9f4..e849989 100644 --- a/settings.py +++ b/settings.py @@ -18,6 +18,9 @@ def tr(message): FILTER_COMMENT_START = '/* SpatialFilter Plugin Start */' FILTER_COMMENT_STOP = '/* SpatialFilter Plugin Stop */' +FILTER_COMMENT_START_SENSORTHINGS = "'SpatialFilter Plugin Start' eq 'SpatialFilter Plugin Start' and " +FILTER_COMMENT_STOP_SENSORTHINGS = " and 'SpatialFilter Plugin Stop' eq 'SpatialFilter Plugin Stop'" + # The QGIS Storage Types that can be filtered by the plugin SUPPORTED_STORAGE_TYPES = ['POSTGRESQL DATABASE WITH POSTGIS EXTENSION', 'GPKG', 'SQLITE']