""" sn_basis/functions/qgiscore_wrapper.py – zentrale QGIS-Core-Abstraktion """ from typing import Type, Any, Optional from sn_basis.functions.qt_wrapper import ( QUrl, QEventLoop, QNetworkRequest, ) # --------------------------------------------------------- # QGIS-Symbole (werden dynamisch gesetzt) # --------------------------------------------------------- QgsProject: Type[Any] QgsVectorLayer: Type[Any] QgsRasterLayer: Type[Any] QgsNetworkAccessManager: Type[Any] Qgis: Type[Any] QgsMapLayerProxyModel: Type[Any] QgsVectorFileWriter: Type[Any] # neu: Schreib-API QgsFeature: Type[Any] QgsField: Type[Any] QgsGeometry: Type[Any] QgsFeatureRequest: Type[Any] QgsRectangle: Type[Any] QgsCoordinateTransform: Type[Any] QgsCoordinateReferenceSystem: Type[Any] QgsPrintLayout: Type[Any] QgsLayoutItemMap: Type[Any] QgsLayoutItemMapGrid: Type[Any] QgsTextFormat: Type[Any] QgsTextBackgroundSettings: Type[Any] QgsLayoutItemLabel: Type[Any] QgsLayoutItemLegend: Type[Any] QgsLayoutPoint: Type[Any] QgsLayoutSize: Type[Any] QgsUnitTypes: Type[Any] QgsLayoutItem: Type[Any] QgsBlockingNetworkRequest: Type[Any] QgsJsonExporter: Type[Any] QgsJsonUtils: Type[Any] GEOM_POINT: Any = None GEOM_LINE: Any = None GEOM_POLYGON: Any = None MAP_GRID_STYLE_MARKERS: Any = None MAP_GRID_STYLE_FRAME_ANNOTATIONS: Any = None MAP_GRID_FRAME_STYLE_INTERIOR_TICKS: Any = None MAP_GRID_UNIT_MAP_UNIT: Any = None MAP_GRID_ANNOTATION_FORMAT_DECIMAL: Any = None MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL: Any = None MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL: Any = None MAP_GRID_ANNOTATION_DISPLAY_SHOW_X: Any = None MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y: Any = None MAP_GRID_BORDER_LEFT: Any = None MAP_GRID_BORDER_RIGHT: Any = None MAP_GRID_BORDER_TOP: Any = None MAP_GRID_BORDER_BOTTOM: Any = None MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME: Any = None TEXT_BACKGROUND_SHAPE_RECTANGLE: Any = None TEXT_BACKGROUND_SIZE_BUFFER: Any = None TEXT_RENDER_UNIT_MILLIMETERS: Any = None TEXT_RENDER_UNIT_POINTS: Any = None QGIS_AVAILABLE = False # --------------------------------------------------------- # Versuch: QGIS-Core importieren # --------------------------------------------------------- try: from qgis.core import ( QgsProject as _QgsProject, QgsVectorLayer as _QgsVectorLayer, QgsRasterLayer as _QgsRasterLayer, QgsNetworkAccessManager as _QgsNetworkAccessManager, Qgis as _Qgis, QgsMapLayerProxyModel as _QgsMaplLayerProxyModel, QgsVectorFileWriter as _QgsVectorFileWriter, QgsFeature as _QgsFeature, QgsField as _QgsField, QgsGeometry as _QgsGeometry, QgsFeatureRequest as _QgsFeatureRequest, QgsRectangle as _QgsRectangle, QgsCoordinateTransform as _QgsCoordinateTransform, QgsCoordinateReferenceSystem as _QgsCoordinateReferenceSystem, QgsPrintLayout as _QgsPrintLayout, QgsLayoutItemMap as _QgsLayoutItemMap, QgsLayoutItemMapGrid as _QgsLayoutItemMapGrid, QgsTextFormat as _QgsTextFormat, QgsTextBackgroundSettings as _QgsTextBackgroundSettings, QgsLayoutItemLabel as _QgsLayoutItemLabel, QgsLayoutItemLegend as _QgsLayoutItemLegend, QgsLayoutPoint as _QgsLayoutPoint, QgsLayoutSize as _QgsLayoutSize, QgsUnitTypes as _QgsUnitTypes, QgsLayoutItem as _QgsLayoutItem, QgsBlockingNetworkRequest as _QgsBlockingNetworkRequest, QgsJsonExporter as _QgsJsonExporter, QgsJsonUtils as _QgsJsonUtils, ) QgsProject = _QgsProject QgsVectorLayer = _QgsVectorLayer QgsRasterLayer = _QgsRasterLayer QgsNetworkAccessManager = _QgsNetworkAccessManager Qgis = _Qgis QgsMapLayerProxyModel = _QgsMaplLayerProxyModel QgsVectorFileWriter = _QgsVectorFileWriter QgsFeature = _QgsFeature QgsField = _QgsField QgsGeometry = _QgsGeometry QgsFeatureRequest = _QgsFeatureRequest QgsRectangle = _QgsRectangle QgsCoordinateTransform = _QgsCoordinateTransform QgsCoordinateReferenceSystem = _QgsCoordinateReferenceSystem QgsPrintLayout = _QgsPrintLayout QgsLayoutItemMap = _QgsLayoutItemMap QgsLayoutItemMapGrid = _QgsLayoutItemMapGrid QgsTextFormat = _QgsTextFormat QgsTextBackgroundSettings = _QgsTextBackgroundSettings QgsLayoutItemLabel = _QgsLayoutItemLabel QgsLayoutItemLegend = _QgsLayoutItemLegend QgsLayoutPoint = _QgsLayoutPoint QgsLayoutSize = _QgsLayoutSize QgsUnitTypes = _QgsUnitTypes QgsLayoutItem = _QgsLayoutItem QgsBlockingNetworkRequest = _QgsBlockingNetworkRequest QgsJsonExporter = _QgsJsonExporter QgsJsonUtils = _QgsJsonUtils # Geometrietyp-Konstanten (QGIS 4 / QGIS 3.30+ vs. ältere QGIS-3) def _resolve_geom_type(*paths): for obj, *attrs in paths: val = obj for attr in attrs: val = getattr(val, attr, None) if val is None: break else: if val is not None: return val return None GEOM_POINT = _resolve_geom_type( (_Qgis, "GeometryType", "Point"), ) or getattr(__import__("qgis.core", fromlist=["QgsWkbTypes"]), "QgsWkbTypes", type("_", (), {"PointGeometry": 0})).PointGeometry GEOM_LINE = _resolve_geom_type( (_Qgis, "GeometryType", "Line"), ) or getattr(__import__("qgis.core", fromlist=["QgsWkbTypes"]), "QgsWkbTypes", type("_", (), {"LineGeometry": 1})).LineGeometry GEOM_POLYGON = _resolve_geom_type( (_Qgis, "GeometryType", "Polygon"), ) or getattr(__import__("qgis.core", fromlist=["QgsWkbTypes"]), "QgsWkbTypes", type("_", (), {"PolygonGeometry": 2})).PolygonGeometry def _resolve_qgis_enum(*paths: tuple[Any, ...]) -> Any: for path in paths: value: Any = path[0] ok = True for name in path[1:]: value = getattr(value, name, None) if value is None: ok = False break if ok: return value return None MAP_GRID_STYLE_MARKERS = _resolve_qgis_enum( (_Qgis, "MapGridStyle", "Markers"), (_QgsLayoutItemMapGrid, "Markers"), ) MAP_GRID_STYLE_FRAME_ANNOTATIONS = _resolve_qgis_enum( (_Qgis, "MapGridStyle", "FrameAndAnnotationsOnly"), (_QgsLayoutItemMapGrid, "FrameAndAnnotationsOnly"), (_Qgis, "MapGridStyle", "FrameAnnotations"), (_QgsLayoutItemMapGrid, "FrameAnnotations"), ) MAP_GRID_FRAME_STYLE_INTERIOR_TICKS = _resolve_qgis_enum( (_Qgis, "MapGridFrameStyle", "InteriorTicks"), (_QgsLayoutItemMapGrid, "InteriorTicks"), ) MAP_GRID_UNIT_MAP_UNIT = _resolve_qgis_enum( (_Qgis, "MapGridUnit", "MapUnits"), (_QgsLayoutItemMapGrid, "MapUnits"), (_Qgis, "MapGridUnit", "MapUnit"), (_QgsLayoutItemMapGrid, "MapUnit"), ) MAP_GRID_ANNOTATION_FORMAT_DECIMAL = _resolve_qgis_enum( (_Qgis, "MapGridAnnotationFormat", "Decimal"), (_QgsLayoutItemMapGrid, "Decimal"), ) MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL = _resolve_qgis_enum( (_Qgis, "MapGridComponentVisibility", "HideAll"), (_QgsLayoutItemMapGrid, "HideAll"), ) MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL = _resolve_qgis_enum( (_Qgis, "MapGridComponentVisibility", "ShowAll"), (_QgsLayoutItemMapGrid, "ShowAll"), ) MAP_GRID_ANNOTATION_DISPLAY_SHOW_X = _resolve_qgis_enum( (_Qgis, "MapGridComponentVisibility", "LongitudeOnly"), (_QgsLayoutItemMapGrid, "LongitudeOnly"), (_Qgis, "MapGridComponentVisibility", "ShowLongitudeOnly"), (_QgsLayoutItemMapGrid, "ShowLongitudeOnly"), ) MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y = _resolve_qgis_enum( (_Qgis, "MapGridComponentVisibility", "LatitudeOnly"), (_QgsLayoutItemMapGrid, "LatitudeOnly"), (_Qgis, "MapGridComponentVisibility", "ShowLatitudeOnly"), (_QgsLayoutItemMapGrid, "ShowLatitudeOnly"), ) MAP_GRID_BORDER_LEFT = _resolve_qgis_enum( (_Qgis, "MapGridBorderSide", "Left"), (_QgsLayoutItemMapGrid, "Left"), ) MAP_GRID_BORDER_RIGHT = _resolve_qgis_enum( (_Qgis, "MapGridBorderSide", "Right"), (_QgsLayoutItemMapGrid, "Right"), ) MAP_GRID_BORDER_TOP = _resolve_qgis_enum( (_Qgis, "MapGridBorderSide", "Top"), (_QgsLayoutItemMapGrid, "Top"), ) MAP_GRID_BORDER_BOTTOM = _resolve_qgis_enum( (_Qgis, "MapGridBorderSide", "Bottom"), (_QgsLayoutItemMapGrid, "Bottom"), ) MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME = _resolve_qgis_enum( (_Qgis, "MapGridAnnotationPosition", "InsideMapFrame"), (_QgsLayoutItemMapGrid, "InsideMapFrame"), ) TEXT_BACKGROUND_SHAPE_RECTANGLE = _resolve_qgis_enum( (_QgsTextBackgroundSettings, "ShapeRectangle"), ) TEXT_BACKGROUND_SIZE_BUFFER = _resolve_qgis_enum( (_QgsTextBackgroundSettings, "SizeBuffer"), ) TEXT_RENDER_UNIT_MILLIMETERS = _resolve_qgis_enum( (_Qgis, "RenderUnit", "Millimeters"), (_QgsUnitTypes, "RenderMillimeters"), ) TEXT_RENDER_UNIT_POINTS = _resolve_qgis_enum( (_Qgis, "RenderUnit", "Points"), (_QgsUnitTypes, "RenderPoints"), ) QGIS_AVAILABLE = True # --------------------------------------------------------- # Mock-Modus # --------------------------------------------------------- except Exception: QGIS_AVAILABLE = False class _MockLayoutManager: def layoutByName(self, name: str): return None def addLayout(self, layout: Any) -> bool: return True class _MockQgsProject: def __init__(self): self._variables = {} self._layout_manager = _MockLayoutManager() @staticmethod def instance() -> "_MockQgsProject": return _MockQgsProject() def read(self) -> bool: return True def layoutManager(self): return self._layout_manager def transformContext(self): return None def addMapLayer(self, layer: Any) -> Any: return layer QgsProject = _MockQgsProject class _MockQgsVectorLayer: def __init__(self, *args, **kwargs): self._valid = True def isValid(self) -> bool: return self._valid def loadNamedStyle(self, path: str): return True, "" def triggerRepaint(self) -> None: pass def dataProvider(self): return None QgsVectorLayer = _MockQgsVectorLayer class _MockQgsNetworkAccessManager: @staticmethod def instance(): return _MockQgsNetworkAccessManager() def head(self, request: Any): return None class _MockQgsRasterLayer: """ Minimaler Mock für QgsRasterLayer, ausreichend für Tests und um im Datenabruf ein Raster-Layer-Objekt im pruef_ergebnis kontext mitzugeben. """ def __init__(self, source: str, name: str = "Raster", provider: str = "wms"): self.source = source self._name = name self.provider = provider self._valid = True def isValid(self) -> bool: return self._valid def name(self) -> str: return self._name def dataProvider(self): return None QgsRasterLayer = _MockQgsRasterLayer class _MockQgsPrintLayout: def __init__(self, project: Any): self.project = project self._name = "" self._page = _MockQgsLayoutPage() self.items: list[Any] = [] def initializeDefaults(self) -> None: pass def setName(self, name: str) -> None: self._name = name def pageCollection(self): return self def page(self, index: int): return self._page def addLayoutItem(self, item: Any) -> None: self.items.append(item) class _MockQgsLayoutPage: def setPageSize(self, size: Any) -> None: self.size = size class _MockQgsLayoutItem: class ReferencePoint: LowerLeft = 0 class _MockQgsLayoutItemMap: def __init__(self, layout: Any): self.layout = layout self.item_id = "" self.extent = None self.scale = None self.follow_visibility_preset = False self.follow_visibility_preset_name = "" self.keep_layer_set = False self._grids = _MockQgsLayoutItemMapGridStack(self) self._crs = None self.crs_vor_extent = None # war das KBS gesetzt, als die Ausdehnung kam? def setId(self, item_id: str) -> None: self.item_id = item_id def setCrs(self, crs: Any) -> None: self._crs = crs def crs(self) -> Any: return self._crs def setExtent(self, extent: Any) -> None: self.extent = extent self.crs_vor_extent = self._crs is not None def setScale(self, scale: float) -> None: self.scale = scale def attemptMove(self, point: Any) -> None: pass def attemptResize(self, size: Any) -> None: pass def setFollowVisibilityPreset(self, active: bool) -> None: self.follow_visibility_preset = active def setFollowVisibilityPresetName(self, name: str) -> None: self.follow_visibility_preset_name = name def setKeepLayerSet(self, enabled: bool) -> None: self.keep_layer_set = enabled def grids(self): return self._grids def updateBoundingRect(self) -> None: pass def refresh(self) -> None: pass class _MockQgsLayoutItemMapGrid: Markers = "Markers" FrameAndAnnotationsOnly = "FrameAndAnnotationsOnly" InteriorTicks = "InteriorTicks" MapUnits = "MapUnits" Decimal = "Decimal" HideAll = "HideAll" ShowAll = "ShowAll" LongitudeOnly = "LongitudeOnly" LatitudeOnly = "LatitudeOnly" Left = "Left" Right = "Right" Top = "Top" Bottom = "Bottom" InsideMapFrame = "InsideMapFrame" def __init__(self, name: str, map_item: Any): self.name = name self.map_item = map_item self.enabled = False self.style = None self.units = None self.interval_x = None self.interval_y = None self.annotation_enabled = False self.annotation_format = None self.annotation_precision = None self.annotation_display: dict[Any, Any] = {} self.annotation_position: dict[Any, Any] = {} self.frame_style = None self.annotation_text_format = None def setEnabled(self, enabled: bool) -> None: self.enabled = enabled def setStyle(self, style: Any) -> None: self.style = style def setUnits(self, units: Any) -> None: self.units = units def setIntervalX(self, interval: float) -> None: self.interval_x = interval def setIntervalY(self, interval: float) -> None: self.interval_y = interval def setAnnotationEnabled(self, enabled: bool) -> None: self.annotation_enabled = enabled def setAnnotationFormat(self, annotation_format: Any) -> None: self.annotation_format = annotation_format def setAnnotationPrecision(self, precision: int) -> None: self.annotation_precision = precision def setAnnotationDisplay(self, display: Any, border: Any) -> None: self.annotation_display[border] = display def setAnnotationPosition(self, position: Any, border: Any) -> None: self.annotation_position[border] = position def setFrameStyle(self, frame_style: Any) -> None: self.frame_style = frame_style def setAnnotationTextFormat(self, annotation_text_format: Any) -> None: self.annotation_text_format = annotation_text_format class _MockQgsTextBackgroundSettings: ShapeRectangle = "ShapeRectangle" SizeBuffer = "SizeBuffer" def __init__(self): self.enabled = False self.shape_type = None self.size_type = None self.size = None self.size_unit = None self.offset = None self.offset_unit = None self.fill_color = None def setEnabled(self, enabled: bool) -> None: self.enabled = enabled def setType(self, shape_type: Any) -> None: self.shape_type = shape_type def setSizeType(self, size_type: Any) -> None: self.size_type = size_type def setSize(self, size: Any) -> None: self.size = size def setSizeUnit(self, unit: Any) -> None: self.size_unit = unit def setOffset(self, offset: Any) -> None: self.offset = offset def setOffsetUnit(self, unit: Any) -> None: self.offset_unit = unit def setFillColor(self, color: Any) -> None: self.fill_color = color class _MockQgsTextFormat: def __init__(self): self.font = None self.size = None self.size_unit = None self.background = None def setFont(self, font: Any) -> None: self.font = font def setSize(self, size: float) -> None: self.size = size def setSizeUnit(self, unit: Any) -> None: self.size_unit = unit def setBackground(self, background: Any) -> None: self.background = background class _MockQgsLayoutItemMapGridStack: def __init__(self, map_item: Any): self.map_item = map_item self._grids: list[_MockQgsLayoutItemMapGrid] = [] def addGrid(self, grid: Any) -> None: self._grids.append(grid) def removeGrid(self, grid_id: str) -> None: self._grids = [grid for grid in self._grids if getattr(grid, "name", "") != grid_id] def asList(self) -> list[Any]: return list(self._grids) class _MockQgsLayoutItemLabel: ModeHtml = 1 def __init__(self, layout: Any): self.layout = layout def setId(self, item_id: str) -> None: pass def setText(self, text: str) -> None: pass def setMode(self, mode: Any) -> None: pass def setFont(self, font: Any) -> None: pass def setReferencePoint(self, point: Any) -> None: pass def setVAlign(self, alignment: Any) -> None: pass def attemptMove(self, point: Any) -> None: pass def attemptResize(self, size: Any) -> None: pass class _MockLegendRootGroup: def __init__(self) -> None: self.layers: list[Any] = [] def clear(self) -> None: self.layers = [] def addLayer(self, layer: Any) -> None: self.layers.append(layer) class _MockLegendModel: def __init__(self) -> None: self._root_group = _MockLegendRootGroup() def rootGroup(self) -> _MockLegendRootGroup: return self._root_group class _MockQgsLayoutItemLegend: def __init__(self, layout: Any): self.layout = layout self._model = _MockLegendModel() self.item_id = "" self.object_name = "" self.linked_map = None self.auto_update_model = None self.legend_filter_by_map_enabled = None self.title = "" def setId(self, item_id: str) -> None: self.item_id = item_id def setObjectName(self, name: str) -> None: self.object_name = name def setLinkedMap(self, map_item: Any) -> None: self.linked_map = map_item def setAutoUpdateModel(self, enabled: bool) -> None: self.auto_update_model = enabled def setLegendFilterByMapEnabled(self, enabled: bool) -> None: self.legend_filter_by_map_enabled = enabled def setTitle(self, title: str) -> None: self.title = title def setReferencePoint(self, point: Any) -> None: pass def attemptMove(self, point: Any) -> None: pass def attemptResize(self, size: Any) -> None: pass def model(self) -> _MockLegendModel: return self._model def refresh(self) -> None: pass class _MockQgsLayoutPoint: def __init__(self, x: float, y: float, unit: Any): self.x = x self.y = y self.unit = unit class _MockQgsLayoutSize: def __init__(self, width: float, height: float, unit: Any): self.width = width self.height = height self.unit = unit class _MockQgsUnitTypes: LayoutMillimeters = 0 QgsPrintLayout = _MockQgsPrintLayout QgsLayoutItemMap = _MockQgsLayoutItemMap QgsLayoutItemMapGrid = _MockQgsLayoutItemMapGrid QgsTextFormat = _MockQgsTextFormat QgsTextBackgroundSettings = _MockQgsTextBackgroundSettings QgsLayoutItemLabel = _MockQgsLayoutItemLabel QgsLayoutItemLegend = _MockQgsLayoutItemLegend QgsLayoutPoint = _MockQgsLayoutPoint QgsLayoutSize = _MockQgsLayoutSize QgsUnitTypes = _MockQgsUnitTypes QgsLayoutItem = _MockQgsLayoutItem MAP_GRID_STYLE_MARKERS = _MockQgsLayoutItemMapGrid.Markers MAP_GRID_STYLE_FRAME_ANNOTATIONS = _MockQgsLayoutItemMapGrid.FrameAndAnnotationsOnly MAP_GRID_FRAME_STYLE_INTERIOR_TICKS = _MockQgsLayoutItemMapGrid.InteriorTicks MAP_GRID_UNIT_MAP_UNIT = _MockQgsLayoutItemMapGrid.MapUnits MAP_GRID_ANNOTATION_FORMAT_DECIMAL = _MockQgsLayoutItemMapGrid.Decimal MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL = _MockQgsLayoutItemMapGrid.HideAll MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL = _MockQgsLayoutItemMapGrid.ShowAll MAP_GRID_ANNOTATION_DISPLAY_SHOW_X = _MockQgsLayoutItemMapGrid.LongitudeOnly MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y = _MockQgsLayoutItemMapGrid.LatitudeOnly MAP_GRID_BORDER_LEFT = _MockQgsLayoutItemMapGrid.Left MAP_GRID_BORDER_RIGHT = _MockQgsLayoutItemMapGrid.Right MAP_GRID_BORDER_TOP = _MockQgsLayoutItemMapGrid.Top MAP_GRID_BORDER_BOTTOM = _MockQgsLayoutItemMapGrid.Bottom MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME = _MockQgsLayoutItemMapGrid.InsideMapFrame TEXT_BACKGROUND_SHAPE_RECTANGLE = _MockQgsTextBackgroundSettings.ShapeRectangle TEXT_BACKGROUND_SIZE_BUFFER = _MockQgsTextBackgroundSettings.SizeBuffer TEXT_RENDER_UNIT_MILLIMETERS = "Millimeters" TEXT_RENDER_UNIT_POINTS = "Points" class _MockQgsFeature: def __init__(self): self._geometry = None self._attributes = [] def setGeometry(self, geometry: Any) -> None: self._geometry = geometry def setAttributes(self, attributes: list[Any]) -> None: self._attributes = attributes class _MockQgsField: def __init__(self, name: str = "", field_type: Any = None): self.name = name self.field_type = field_type class _MockQgsGeometry: @staticmethod def unaryUnion(_geometries: list[Any]): return None def isEmpty(self) -> bool: return True QgsFeature = _MockQgsFeature QgsField = _MockQgsField QgsGeometry = _MockQgsGeometry class _MockQgsFeatureRequest: def __init__(self): self._filter_rect = None def setFilterRect(self, rect): self._filter_rect = rect return self QgsFeatureRequest = _MockQgsFeatureRequest class _MockQgsCoordinateTransform: def __init__(self, *args, **kwargs): pass def transformBoundingBox(self, rect): return rect class _MockQgsCoordinateReferenceSystem: def __init__(self, *args, **kwargs): pass QgsCoordinateTransform = _MockQgsCoordinateTransform QgsCoordinateReferenceSystem = _MockQgsCoordinateReferenceSystem QgsNetworkAccessManager = _MockQgsNetworkAccessManager class _MockQgis: class MessageLevel: Success = 0 Info = 1 Warning = 2 Critical = 3 Qgis = _MockQgis class _MockQgsMapLayerProxyModel: # Layer-Typen (entsprechen QGIS-Konstanten) NoLayer = 0 VectorLayer = 1 RasterLayer = 2 PluginLayer = 3 MeshLayer = 4 VectorTileLayer = 5 PointCloudLayer = 6 def __init__(self, *args, **kwargs): pass QgsMapLayerProxyModel = _MockQgsMapLayerProxyModel # --------------------------------------------------------- # Mock für QgsVectorFileWriter # --------------------------------------------------------- class _MockSaveVectorOptions: """ Minimaler Ersatz für QgsVectorFileWriter.SaveVectorOptions. Felder werden als einfache Attribute bereitgestellt. """ def __init__(self): self.driverName: str = "GPKG" self.layerName: Optional[str] = None self.fileEncoding: str = "UTF-8" # Action-Konstanten werden symbolisch verwendet self.actionOnExistingFile: Optional[int] = None class _MockQgsVectorFileWriter: """ Minimaler Mock für QgsVectorFileWriter mit der benötigten API: - SaveVectorOptions (als Klasse) - writeAsVectorFormatV3(layer, path, transformContext, options) -> error_code - NoError (Konstante) - CreateOrOverwriteFile / CreateOrOverwriteLayer (Konstanten) """ # Fehlerkonstanten (0 = NoError) NoError = 0 # Action-Konstanten (Werte nur symbolisch) CreateOrOverwriteFile = 1 CreateOrOverwriteLayer = 2 # SaveVectorOptions-Klasse SaveVectorOptions = _MockSaveVectorOptions @staticmethod def writeAsVectorFormatV3(layer: Any, path: str, transform_context: Any, options: Any) -> int: """ Mock-Schreibfunktion. Verhalten im Mock: - Wenn 'layer' None oder options.layerName fehlt, geben wir NoError zurück, aber schreiben nichts (Tests erwarten nur Rückgabecode). - Diese Implementierung versucht nicht, echte Dateien zu schreiben. - Rückgabewert: 0 (NoError) bei Erfolg, sonst eine positive Fehlernummer. """ try: # Sehr einfache Validierung: wenn path leer -> Fehler if not path: return 999 # Simuliere Erfolg return _MockQgsVectorFileWriter.NoError except Exception: return 999 # generischer Fehlercode QgsVectorFileWriter = _MockQgsVectorFileWriter # Geometrietyp-Konstanten (Mock-Werte entsprechen QgsWkbTypes) GEOM_POINT = 0 GEOM_LINE = 1 GEOM_POLYGON = 2 class _MockQgsBlockingNetworkRequest: NoError = 0 def __init__(self): self._reply = None def get(self, request: Any) -> int: return self.NoError def post(self, request: Any, data: bytes) -> int: return self.NoError def put(self, request: Any, data: bytes) -> int: return self.NoError def reply(self) -> Any: return self._reply def errorMessage(self) -> str: return "" QgsBlockingNetworkRequest = _MockQgsBlockingNetworkRequest class _MockQgsJsonUtils: @staticmethod def stringToFields(text: str) -> Any: return type("_Fields", (), {"toList": lambda self: []})() @staticmethod def stringToFeatureList(text: str, fields: Any) -> list: return [] QgsJsonUtils = _MockQgsJsonUtils class _MockQgsJsonExporter: def __init__(self, layer: Any = None): self._layer = layer def setSourceCrs(self, crs: Any) -> None: pass def setTransformGeometries(self, transform: bool) -> None: pass def exportFeatures(self, features: list) -> str: return '{"type":"FeatureCollection","features":[]}' QgsJsonExporter = _MockQgsJsonExporter # --------------------------------------------------------- # Vektorkacheln, Ausdrücke, Symbole und Legende (Basemap-Legende, Basis #88) # Eigener Block, damit ein fehlendes Symbol den übrigen Wrapper nicht in den Mock-Modus zwingt. # --------------------------------------------------------- QgsVectorTileLayer: Type[Any] QgsSelectionContext: Type[Any] QgsTileXYZ: Type[Any] QgsExpression: Type[Any] QgsExpressionContext: Type[Any] QgsExpressionContextScope: Type[Any] QgsProperty: Type[Any] QgsCategorizedSymbolRenderer: Type[Any] QgsRendererCategory: Type[Any] QgsLegendRenderer: Type[Any] QgsLegendStyle: Type[Any] QgsMapLayer: Type[Any] QgsApplication: Type[Any] QgsSymbolLayerUtils: Type[Any] QgsLayoutMeasurement: Type[Any] QgsLayoutUtils: Type[Any] QgsSimpleLineSymbolLayer: Type[Any] QgsLayoutExporter: Type[Any] SELECT_BEHAVIOR_SET: Any = None SELECT_RELATION_INTERSECT: Any = None PROPERTY_TYPE_EXPRESSION: Any = None PROPERTY_CUSTOM_DASH: Any = None LEGEND_STYLE_HIDDEN: Any = None LEGEND_STYLE_SUBGROUP: Any = None STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG: Any = None VECTOR_TILES_AVAILABLE = False try: from qgis.core import ( QgsVectorTileLayer as _QgsVectorTileLayer, QgsSelectionContext as _QgsSelectionContext, QgsTileXYZ as _QgsTileXYZ, QgsExpression as _QgsExpression, QgsExpressionContext as _QgsExpressionContext, QgsExpressionContextScope as _QgsExpressionContextScope, QgsProperty as _QgsProperty, QgsCategorizedSymbolRenderer as _QgsCategorizedSymbolRenderer, QgsRendererCategory as _QgsRendererCategory, QgsLegendRenderer as _QgsLegendRenderer, QgsLegendStyle as _QgsLegendStyle, QgsMapLayer as _QgsMapLayer, QgsApplication as _QgsApplication, QgsSymbolLayerUtils as _QgsSymbolLayerUtils, QgsLayoutMeasurement as _QgsLayoutMeasurement, QgsLayoutUtils as _QgsLayoutUtils, QgsSymbolLayer as _QgsSymbolLayer, QgsSimpleLineSymbolLayer as _QgsSimpleLineSymbolLayer, QgsLayoutExporter as _QgsLayoutExporter, Qgis as _QgisVT, ) QgsVectorTileLayer = _QgsVectorTileLayer QgsSelectionContext = _QgsSelectionContext QgsTileXYZ = _QgsTileXYZ QgsExpression = _QgsExpression QgsExpressionContext = _QgsExpressionContext QgsExpressionContextScope = _QgsExpressionContextScope QgsProperty = _QgsProperty QgsCategorizedSymbolRenderer = _QgsCategorizedSymbolRenderer QgsRendererCategory = _QgsRendererCategory QgsLegendRenderer = _QgsLegendRenderer QgsLegendStyle = _QgsLegendStyle QgsMapLayer = _QgsMapLayer QgsApplication = _QgsApplication QgsSymbolLayerUtils = _QgsSymbolLayerUtils QgsLayoutMeasurement = _QgsLayoutMeasurement QgsLayoutUtils = _QgsLayoutUtils QgsSimpleLineSymbolLayer = _QgsSimpleLineSymbolLayer QgsLayoutExporter = _QgsLayoutExporter SELECT_BEHAVIOR_SET = _QgisVT.SelectBehavior.SetSelection SELECT_RELATION_INTERSECT = _QgisVT.SelectGeometryRelationship.Intersect PROPERTY_TYPE_EXPRESSION = getattr(getattr(_QgsProperty, "Type", None), "ExpressionBasedProperty", None) if PROPERTY_TYPE_EXPRESSION is None: PROPERTY_TYPE_EXPRESSION = getattr(_QgsProperty, "ExpressionBasedProperty", None) # Schlüssel der datendefinierten Eigenschaft „eigenes Strichmuster“ als int (Basis #90); # propertyKeys() liefert ints, das Enum ist je nach QGIS-Version gescoped oder nicht. _strichmuster = getattr(getattr(_QgsSymbolLayer, "Property", None), "CustomDash", None) if _strichmuster is None: _strichmuster = getattr(_QgsSymbolLayer, "PropertyCustomDash", None) PROPERTY_CUSTOM_DASH = int(getattr(_strichmuster, "value", _strichmuster)) LEGEND_STYLE_HIDDEN = getattr(getattr(_QgsLegendStyle, "Style", _QgsLegendStyle), "Hidden", None) LEGEND_STYLE_SUBGROUP = getattr(getattr(_QgsLegendStyle, "Style", _QgsLegendStyle), "Subgroup", None) _kategorie = getattr(_QgsMapLayer, "StyleCategory", _QgsMapLayer) STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG = _kategorie.Symbology | _kategorie.Labeling VECTOR_TILES_AVAILABLE = True except Exception: VECTOR_TILES_AVAILABLE = False class _MockQgsVectorTileLayer: """Platzhalter: Vektorkachel-Layer gibt es im Mock-Modus nicht.""" class _MockQgsSelectionContext: def __init__(self): self._scale = 0.0 def setScale(self, scale: float) -> None: self._scale = scale def scale(self) -> float: return self._scale class _MockQgsTileXYZ: def __init__(self, column: int = 0, row: int = 0, zoom: int = 0): self._column, self._row, self._zoom = column, row, zoom def column(self) -> int: return self._column def row(self) -> int: return self._row def zoomLevel(self) -> int: return self._zoom class _MockQgsExpression: def __init__(self, text: str = ""): self._text = text def prepare(self, context: Any) -> bool: return True def evaluate(self, context: Any = None) -> Any: return True class _MockQgsExpressionContextScope: def __init__(self, name: str = ""): self._variables: dict[str, Any] = {} def setVariable(self, name: str, value: Any, *args: Any) -> None: self._variables[name] = value def variable(self, name: str) -> Any: return self._variables.get(name) class _MockQgsExpressionContext: def __init__(self): self._scopes: list[Any] = [] self._feature: Any = None def appendScope(self, scope: Any) -> None: self._scopes.append(scope) def setFeature(self, feature: Any) -> None: self._feature = feature class _MockQgsProperty: def __init__(self, value: Any = None): self._value = value @staticmethod def fromValue(value: Any) -> "_MockQgsProperty": return _MockQgsProperty(value) @staticmethod def fromExpression(expression: str, is_active: bool = True) -> "_MockQgsProperty": eigenschaft = _MockQgsProperty() eigenschaft._ausdruck = expression return eigenschaft def expressionString(self) -> str: return getattr(self, "_ausdruck", "") class _MockQgsRendererCategory: def __init__(self, value: Any = None, symbol: Any = None, label: str = ""): self._value, self._symbol, self._label = value, symbol, label def value(self) -> Any: return self._value def symbol(self) -> Any: return self._symbol def label(self) -> str: return self._label class _MockQgsCategorizedSymbolRenderer: def __init__(self, attribute: str = "", categories: list | None = None): self._attribute = attribute self._categories = list(categories or []) def categories(self) -> list: return list(self._categories) class _MockQgsLegendStyle: Hidden = 0 Group = 1 Subgroup = 2 class _MockQgsLegendRenderer: @staticmethod def setNodeLegendStyle(node: Any, style: Any) -> None: setattr(node, "_legend_style", style) class _MockQgsMapLayer: class StyleCategory: Symbology = 1 Labeling = 2 class _MockQgsApplication: @staticmethod def qgisSettingsDirPath() -> str: return "" class _MockQgsSymbolLayerUtils: @staticmethod def symbolPreviewIcon(symbol: Any, size: Any, *args: Any) -> Any: return None QgsVectorTileLayer = _MockQgsVectorTileLayer QgsSelectionContext = _MockQgsSelectionContext QgsTileXYZ = _MockQgsTileXYZ QgsExpression = _MockQgsExpression QgsExpressionContext = _MockQgsExpressionContext QgsExpressionContextScope = _MockQgsExpressionContextScope QgsProperty = _MockQgsProperty QgsCategorizedSymbolRenderer = _MockQgsCategorizedSymbolRenderer QgsRendererCategory = _MockQgsRendererCategory QgsLegendRenderer = _MockQgsLegendRenderer QgsLegendStyle = _MockQgsLegendStyle QgsMapLayer = _MockQgsMapLayer QgsApplication = _MockQgsApplication QgsSymbolLayerUtils = _MockQgsSymbolLayerUtils class _MockQgsLayoutMeasurement: def __init__(self, length: float = 0.0, units: Any = None): self._length = length def length(self) -> float: return self._length QgsLayoutMeasurement = _MockQgsLayoutMeasurement class _MockQgsLayoutUtils: @staticmethod def createRenderContextForLayout(layout: Any, painter: Any, dpi: float = -1) -> Any: return None QgsLayoutUtils = _MockQgsLayoutUtils class _MockQgsSimpleLineSymbolLayer: def __init__(self, eigenschaften: dict | None = None): self.eigenschaften = dict(eigenschaften or {}) @staticmethod def create(eigenschaften: dict) -> "_MockQgsSimpleLineSymbolLayer": return _MockQgsSimpleLineSymbolLayer(eigenschaften) def layerType(self) -> str: return "SimpleLine" QgsSimpleLineSymbolLayer = _MockQgsSimpleLineSymbolLayer class _MockQgsLayoutExporter: def __init__(self, layout: Any = None): self._layout = layout def renderRegionToImage(self, region: Any, size: Any = None, dpi: float = -1) -> Any: return None QgsLayoutExporter = _MockQgsLayoutExporter # QgsRectangle hatte im allgemeinen Mock-Block keinen Ersatz (nur Typannotation). if "QgsRectangle" not in globals(): class _MockQgsRectangle: def __init__(self, x_min: float = 0.0, y_min: float = 0.0, x_max: float = 0.0, y_max: float = 0.0): self._werte = (x_min, y_min, x_max, y_max) def xMinimum(self) -> float: return self._werte[0] def yMinimum(self) -> float: return self._werte[1] def xMaximum(self) -> float: return self._werte[2] def yMaximum(self) -> float: return self._werte[3] def width(self) -> float: return self._werte[2] - self._werte[0] def height(self) -> float: return self._werte[3] - self._werte[1] QgsRectangle = _MockQgsRectangle SELECT_BEHAVIOR_SET = 0 SELECT_RELATION_INTERSECT = 0 PROPERTY_TYPE_EXPRESSION = 2 PROPERTY_CUSTOM_DASH = 36 LEGEND_STYLE_HIDDEN = _MockQgsLegendStyle.Hidden LEGEND_STYLE_SUBGROUP = _MockQgsLegendStyle.Subgroup STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG = 3 # --------------------------------------------------------- # Netzwerk # --------------------------------------------------------- class NetworkReply: """ Minimaler Wrapper für Netzwerkantworten. """ def __init__(self, error: int): self.error = error def network_head(url: str) -> NetworkReply | None: """ Führt einen HTTP-HEAD-Request aus. Rückgabe: - NetworkReply(error=0) → erreichbar - NetworkReply(error!=0) → nicht erreichbar - None → Netzwerk nicht verfügbar / Fehler beim Request """ if not QGIS_AVAILABLE: return None if QUrl is None or QNetworkRequest is None: return None try: manager = QgsNetworkAccessManager.instance() request = QNetworkRequest(QUrl(url)) reply = manager.head(request) # synchron warten (kurz) if QEventLoop is not None: loop = QEventLoop() reply.finished.connect(loop.quit) loop.exec() return NetworkReply(error=reply.error()) except Exception: return None # --------------------------------------------------------- # Layer-Geometrie / Extent # --------------------------------------------------------- def get_layer_extent(layer: Any) -> Any: """ Gibt die Ausdehnung (Extent) eines Layers zurück. Diese Funktion kapselt den Zugriff auf ``layer.extent()`` und dient als zentrale Abstraktion für alle Stellen, die die Bounding Box eines Layers benötigen (z.B. für räumliche Filter im Datenabruf). Verhalten --------- - Wenn QGIS verfügbar ist und der Layer eine ``extent()``-Methode besitzt, wird deren Rückgabewert zurückgegeben. - Wenn QGIS nicht verfügbar ist oder der Layer keine ``extent()``-Methode hat, wird ``None`` zurückgegeben. """ if not QGIS_AVAILABLE or layer is None: return None extent_func = getattr(layer, "extent", None) if callable(extent_func): try: return extent_func() except Exception: return None return None def _crs_kennung(crs: Any) -> str: authid = getattr(crs, "authid", None) try: return str(authid() or "") if callable(authid) else "" except Exception: return "" def _crs_gueltig(crs: Any) -> bool: if crs is None: return False is_valid = getattr(crs, "isValid", None) try: return bool(is_valid()) if callable(is_valid) else bool(_crs_kennung(crs)) except Exception: return False def transformiere_rechteck(rechteck: Any, quell_crs: Any, ziel_crs: Any, project: Any = None) -> Any: """Rechnet ein Rechteck (z. B. eine Layer-Ausdehnung) von ``quell_crs`` nach ``ziel_crs`` um (Basis #99). Ausdehnungen von Layern stehen im KBS des Layers, Kartenansicht und Layoutkarte rechnen im eigenen KBS. Unverändert zurück kommt das Rechteck, wenn es fehlt, eines der KBS fehlt oder ungültig ist, beide gleich sind oder die Umrechnung scheitert. """ if rechteck is None or not _crs_gueltig(quell_crs) or not _crs_gueltig(ziel_crs): return rechteck quelle, ziel = _crs_kennung(quell_crs), _crs_kennung(ziel_crs) if quelle and quelle == ziel: return rechteck try: if project is None: project = QgsProject.instance() return QgsCoordinateTransform(quell_crs, ziel_crs, project).transformBoundingBox(rechteck) except Exception: return rechteck # --------------------------------------------------------- # Buffer-Layer erzeugen # --------------------------------------------------------- def create_buffer_layer( source_layer: Any, distance_m: float, layer_name: str = "BufferLayer" ) -> Optional[Any]: """ Erzeugt einen Pufferlayer um alle Features eines Quelllayers. Diese Funktion dient als zentrale Abstraktion für die Erzeugung eines Pufferlayers in QGIS. Sie wird z.B. im Datenabruf verwendet, wenn der Raumfilter ``"Pufferlayer"`` aktiv ist. Verhalten --------- - Wenn QGIS verfügbar ist und der ``source_layer`` gültig ist, wird ein temporärer Vektorlayer erzeugt, der die gepufferten Geometrien enthält. - Der Puffer wird in Metern angegeben. - Der zurückgegebene Layer ist **nicht gespeichert**, sondern ein temporärer Speicherlayer, der anschließend über den UI‑Wrapper ins Projekt geladen werden kann. - Wenn QGIS nicht verfügbar ist oder ein Fehler auftritt, wird ``None`` zurückgegeben. """ if not QGIS_AVAILABLE: return None if source_layer is None or not hasattr(source_layer, "getFeatures"): return None try: # Geometrien puffern buffered_geoms = [] for feat in source_layer.getFeatures(): geom = feat.geometry() if geom is None: continue buf = geom.buffer(distance_m, 8) if buf is not None: buffered_geoms.append(buf) if not buffered_geoms: return None # Neuen Memory-Layer erzeugen crs = source_layer.crs().authid() if hasattr(source_layer, "crs") else "EPSG:4326" mem_layer = QgsVectorLayer(f"Polygon?crs={crs}", layer_name, "memory") prov = mem_layer.dataProvider() prov.addAttributes([]) mem_layer.updateFields() # Features hinzufügen from qgis.core import QgsFeature for geom in buffered_geoms: f = QgsFeature() f.setGeometry(geom) prov.addFeature(f) mem_layer.updateExtents() return mem_layer except Exception: return None #Hilfsfunktion, keine qgiscore-Entsprechung def layer_exists_in_gpkg(gpkg_path: str, layer_name: str) -> bool: """ Prüft, ob ein Layer mit dem Namen `layer_name` in `gpkg_path` existiert. - bevorzugt: SQLite-Abfrage auf gpkg_contents - fallback: kurzer Versuch, mit QgsVectorLayer zu laden (wenn QGIS verfügbar) """ import os, sqlite3 if not gpkg_path or not layer_name or not os.path.exists(gpkg_path): return False # 1) SQLite-Check (schnell) try: conn = sqlite3.connect(gpkg_path) cur = conn.cursor() cur.execute("SELECT COUNT(1) FROM gpkg_contents WHERE table_name = ?", (layer_name,)) row = cur.fetchone() conn.close() if row and row[0] > 0: return True except Exception: # falls sqlite fehlschlägt, weiter zum QGIS-Fallback pass # 2) QGIS-Fallback: versuche kurz, den Layer zu laden try: if getattr(QgsVectorLayer, "__call__", None) and QGIS_AVAILABLE: uri = f"{gpkg_path}|layername={layer_name}" layer = QgsVectorLayer(uri, layer_name, "ogr") return bool(layer and getattr(layer, "isValid", lambda: False)()) except Exception: pass return False # --------------------------------------------------------- # QGIS-3/4-kompatible Geometrie-Hilfsfunktionen # --------------------------------------------------------- def polygon_zu_linie(geom: Any) -> Optional[Any]: """Konvertiert ein Polygon/MultiPolygon zur Umringslinie (QGIS 3 + 4). In QGIS 3.x existiert ``QgsGeometry.boundary()``, in QGIS 4.x wurde sie entfernt. Diese Funktion kapselt die Versionsunterschiede und stellt dem Fachplugin eine stabile API bereit. Strategie (dreistufig): 1. ``geom.boundary()`` – funktioniert in QGIS 3.x 2. ``geom.convertToType(1, False)`` – ``1`` = LineGeometry-Wert, identisch in ``QgsWkbTypes.LineGeometry`` (3.x) und ``Qgis.GeometryType.Line`` (4.x) 3. Manuelle Ring-Extraktion via ``asPolygon()`` / ``asMultiPolygon()`` + ``QgsGeometry.fromPolylineXY()`` Parameters ---------- geom : Polygon- oder MultiPolygon-``QgsGeometry``. Returns ------- Optional[QgsGeometry] Liniengeometrie (Umring) oder ``None`` bei Fehler / leerer Eingabe. """ if geom is None: return None try: if geom.isEmpty(): return None except Exception: return None # Stufe 1: QGIS 3.x – .boundary() existiert und liefert alle Ringe korrekt if hasattr(geom, "boundary"): try: result = geom.boundary() if result is not None and not result.isEmpty(): return result except Exception: pass # Stufe 2: Manuelle Ring-Extraktion (primärer QGIS-4.x-Pfad). # convertToType(1, False) würde bei MultiPolygon nur den ersten Außenring # zurückgeben und alle weiteren Polygone sowie Innenringe verwerfen → daher # direkt die vollständige manuelle Extraktion als nächste Stufe. lines = [] try: if geom.isMultipart(): for polygon in geom.asMultiPolygon(): for ring in polygon: lines.append(QgsGeometry.fromPolylineXY(ring)) else: for ring in geom.asPolygon(): lines.append(QgsGeometry.fromPolylineXY(ring)) except Exception: pass if lines: return QgsGeometry.unaryUnion(lines) # Stufe 3: Letzter Fallback – convertToType mit destMultipart=True, damit # zumindest alle Außenringe eines MultiPolygons erfasst werden. try: result = geom.convertToType(1, True) if result is not None and not result.isEmpty(): return result except Exception: pass return None