Files
Plugin_SN_Basis/functions/qgiscore_wrapper.py
T

1122 lines
36 KiB
Python
Raw Normal View History

"""
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]
2026-01-08 17:13:51 +01:00
QgsMapLayerProxyModel: Type[Any]
QgsVectorFileWriter: Type[Any] # neu: Schreib-API
2026-03-12 16:14:02 +01:00
QgsFeature: Type[Any]
QgsField: Type[Any]
QgsGeometry: Type[Any]
QgsFeatureRequest: Type[Any]
2026-04-22 14:53:34 +02:00
QgsRectangle: Type[Any]
2026-03-12 16:14:02 +01:00
QgsCoordinateTransform: Type[Any]
QgsCoordinateReferenceSystem: Type[Any]
QgsPrintLayout: Type[Any]
QgsLayoutItemMap: Type[Any]
QgsLayoutItemMapGrid: Type[Any]
QgsTextFormat: Type[Any]
QgsTextBackgroundSettings: Type[Any]
QgsLayoutItemLabel: Type[Any]
2026-03-24 07:53:04 +01:00
QgsLayoutItemLegend: Type[Any]
QgsLayoutPoint: Type[Any]
QgsLayoutSize: Type[Any]
QgsUnitTypes: Type[Any]
QgsLayoutItem: Type[Any]
2026-06-19 11:47:36 +02:00
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,
2026-03-12 16:14:02 +01:00
QgsFeature as _QgsFeature,
QgsField as _QgsField,
2026-03-12 16:14:02 +01:00
QgsGeometry as _QgsGeometry,
QgsFeatureRequest as _QgsFeatureRequest,
2026-04-22 14:53:34 +02:00
QgsRectangle as _QgsRectangle,
2026-03-12 16:14:02 +01:00
QgsCoordinateTransform as _QgsCoordinateTransform,
QgsCoordinateReferenceSystem as _QgsCoordinateReferenceSystem,
QgsPrintLayout as _QgsPrintLayout,
QgsLayoutItemMap as _QgsLayoutItemMap,
QgsLayoutItemMapGrid as _QgsLayoutItemMapGrid,
QgsTextFormat as _QgsTextFormat,
QgsTextBackgroundSettings as _QgsTextBackgroundSettings,
QgsLayoutItemLabel as _QgsLayoutItemLabel,
2026-03-24 07:53:04 +01:00
QgsLayoutItemLegend as _QgsLayoutItemLegend,
QgsLayoutPoint as _QgsLayoutPoint,
QgsLayoutSize as _QgsLayoutSize,
QgsUnitTypes as _QgsUnitTypes,
QgsLayoutItem as _QgsLayoutItem,
2026-06-19 11:47:36 +02:00
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
2026-03-12 16:14:02 +01:00
QgsGeometry = _QgsGeometry
QgsFeatureRequest = _QgsFeatureRequest
2026-04-22 14:53:34 +02:00
QgsRectangle = _QgsRectangle
2026-03-12 16:14:02 +01:00
QgsCoordinateTransform = _QgsCoordinateTransform
QgsCoordinateReferenceSystem = _QgsCoordinateReferenceSystem
QgsPrintLayout = _QgsPrintLayout
QgsLayoutItemMap = _QgsLayoutItemMap
QgsLayoutItemMapGrid = _QgsLayoutItemMapGrid
QgsTextFormat = _QgsTextFormat
QgsTextBackgroundSettings = _QgsTextBackgroundSettings
QgsLayoutItemLabel = _QgsLayoutItemLabel
2026-03-24 07:53:04 +01:00
QgsLayoutItemLegend = _QgsLayoutItemLegend
QgsLayoutPoint = _QgsLayoutPoint
QgsLayoutSize = _QgsLayoutSize
QgsUnitTypes = _QgsUnitTypes
QgsLayoutItem = _QgsLayoutItem
2026-06-19 11:47:36 +02:00
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
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
2026-03-24 07:53:04 +01:00
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 = ""
2026-03-24 07:53:04 +01:00
def setId(self, item_id: str) -> None:
self.item_id = item_id
2026-03-24 07:53:04 +01:00
def setObjectName(self, name: str) -> None:
self.object_name = name
2026-03-24 07:53:04 +01:00
def setLinkedMap(self, map_item: Any) -> None:
self.linked_map = map_item
2026-03-24 07:53:04 +01:00
def setAutoUpdateModel(self, enabled: bool) -> None:
self.auto_update_model = enabled
2026-03-24 07:53:04 +01:00
def setLegendFilterByMapEnabled(self, enabled: bool) -> None:
self.legend_filter_by_map_enabled = enabled
2026-03-24 07:53:04 +01:00
def setTitle(self, title: str) -> None:
self.title = title
2026-03-24 07:53:04 +01:00
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
2026-03-24 07:53:04 +01:00
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
2026-03-12 16:14:02 +01:00
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
2026-01-08 17:13:51 +01:00
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
2026-06-19 11:47:36 +02:00
# 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
# ---------------------------------------------------------
# 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 UIWrapper 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
2026-04-23 22:23:50 +02:00
# ---------------------------------------------------------
# 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