From 76da280dab6ac242dcaf9a6c04223d38474b4f7e Mon Sep 17 00:00:00 2001 From: Sander Schaminee Date: Tue, 8 Oct 2024 11:16:43 +0200 Subject: [PATCH] Fix sample feature expression [GeoCat/qgis-bridge-plugin#189], fix regex char escape warning --- bridgestyle/qgis/expressions.py | 34 +++++++++++++++++++++++--------- bridgestyle/sld/fromgeostyler.py | 6 ++++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/bridgestyle/qgis/expressions.py b/bridgestyle/qgis/expressions.py index 3fd2825..ccc698e 100644 --- a/bridgestyle/qgis/expressions.py +++ b/bridgestyle/qgis/expressions.py @@ -4,7 +4,7 @@ from qgis.core import ( QgsExpressionNode, QgsExpression, QgsExpressionNodeBinaryOperator, QgsMapLayer, QgsVectorLayer, QgsFeatureRequest, QgsExpressionContext, - QgsExpressionContextUtils, QgsFields + QgsExpressionContextUtils, QgsFields, QgsFeature ) except (ImportError, ModuleNotFoundError): QgsExpressionNodeBinaryOperator = None @@ -150,18 +150,13 @@ def __init__(self, layer: QgsMapLayer): self.layer = layer - if not isinstance(layer, QgsVectorLayer): - # Can't sample features from non-vector layers - return - - feature = next(layer.getFeatures(QgsFeatureRequest().setFilterFid(0))) + feature = self._get_feature(layer) if feature is None: - # Empty layer: can't get a feature - self.warnings.add(f"Layer '{layer.name()}' contains no features") + # Not a vector layer or no features found return # Get fields - self.fields = layer.fields() + self.fields = feature.fields() # Set context to the first feature try: @@ -172,6 +167,27 @@ def __init__(self, layer: QgsMapLayer): except Exception as e: self.warnings.add(f"Can't get expression context for layer '{layer.name()}': {str(e)}") + def _get_feature(self, layer: QgsMapLayer) -> Optional[QgsFeature]: + """ Returns the first feature of the given vector layer, or None if there aren't any. """ + if not isinstance(layer, QgsVectorLayer): + # Can't sample features from non-vector layers + return None + + try: + feature = None + for ft in layer.getFeatures(QgsFeatureRequest().setLimit(10)): + # Sample 10 features and use the first valid one + if ft and ft.isValid(): + feature = ft + break + if not feature: + raise ValueError("no valid feature found") + except Exception as e: + self.warnings.add(f"Can't get sample feature for layer '{layer.name()}': {str(e)}") + return None + + return feature + def __del__(self): """ Cleans up the expression converter instance. """ self.layer = None diff --git a/bridgestyle/sld/fromgeostyler.py b/bridgestyle/sld/fromgeostyler.py index 3216f10..e0e0333 100644 --- a/bridgestyle/sld/fromgeostyler.py +++ b/bridgestyle/sld/fromgeostyler.py @@ -1,5 +1,5 @@ import os -import re +from re import compile from xml.dom import minidom from xml.etree import ElementTree from xml.etree.ElementTree import Element, SubElement @@ -14,6 +14,8 @@ from ..version import __version__ from ..geostyler.custom_properties import WellKnownText +REGEX_NONWORDCHARS = compile(r'\W') + _warnings = [] @@ -87,7 +89,7 @@ def _replaceSpecialCharacters(replacement, text=""): """ Replace all characters that are not matching one of [a-zA-Z0-9_]. """ - return re.sub('[^\w]', replacement, text) + return REGEX_NONWORDCHARS.sub(replacement, text) def _createSymbolizers(symbolizers) -> list: