1033 lines
33 KiB
Python
1033 lines
33 KiB
Python
"""
|
||
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]
|
||
|
||
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,
|
||
)
|
||
|
||
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
|
||
|
||
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
|
||
|
||
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)
|
||
|
||
def setId(self, item_id: str) -> None:
|
||
self.item_id = item_id
|
||
|
||
def setExtent(self, extent: Any) -> None:
|
||
self.extent = extent
|
||
|
||
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
|
||
|
||
# ---------------------------------------------------------
|
||
# 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
|
||
|
||
# ---------------------------------------------------------
|
||
# 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
|