Files
Plugin_SN_Basis/functions/qgiscore_wrapper.py
T

1033 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 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
# ---------------------------------------------------------
# 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