Initial commit
This commit is contained in:
166
layer_manager.py
Normal file
166
layer_manager.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import sip
|
||||
from qgis.core import (
|
||||
QgsVectorLayer,
|
||||
QgsFeature,
|
||||
QgsGeometry,
|
||||
QgsPointXY,
|
||||
QgsField,
|
||||
QgsProject,
|
||||
QgsMessageLog,
|
||||
QgsPalLayerSettings,
|
||||
QgsVectorLayerSimpleLabeling,
|
||||
QgsTextFormat,
|
||||
QgsTextBufferSettings,
|
||||
QgsMarkerSymbol,
|
||||
QgsSingleSymbolRenderer,
|
||||
Qgis,
|
||||
)
|
||||
from PyQt5.QtCore import QVariant
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
from .api_client import ApiClient
|
||||
|
||||
|
||||
class LocationLayerManager:
|
||||
"""Creates and manages the 'BAPE Bridge' memory vector layer."""
|
||||
|
||||
_NAME_OK = "BAPE Bridge"
|
||||
_NAME_ERR = "🔴 BAPE Bridge"
|
||||
|
||||
def __init__(self, api_client: ApiClient):
|
||||
self._api_client = api_client
|
||||
self.layer: QgsVectorLayer | None = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def create_layer(self) -> None:
|
||||
"""Create the memory layer, add it to the project and load initial features.
|
||||
|
||||
Raises:
|
||||
Exception: if the initial API fetch fails (layer is still added to the
|
||||
project but will be empty until reload_layer() succeeds).
|
||||
"""
|
||||
self.layer = QgsVectorLayer("Point?crs=EPSG:4326", self._NAME_OK, "memory")
|
||||
if not self.layer.isValid():
|
||||
raise RuntimeError("Failed to create memory layer.")
|
||||
|
||||
provider = self.layer.dataProvider()
|
||||
provider.addAttributes([
|
||||
QgsField("id", QVariant.String),
|
||||
QgsField("name", QVariant.String),
|
||||
])
|
||||
self.layer.updateFields()
|
||||
|
||||
QgsProject.instance().addMapLayer(self.layer)
|
||||
self._configure_marker()
|
||||
self._configure_labels()
|
||||
|
||||
# Initial load — set error name on failure so the layer is visible
|
||||
# in the panel but clearly marked as not yet connected.
|
||||
try:
|
||||
self._do_reload()
|
||||
except Exception:
|
||||
self.layer.setName(self._NAME_ERR)
|
||||
raise
|
||||
|
||||
def reload_layer(self) -> bool:
|
||||
"""Fetch fresh data and replace all features in the layer.
|
||||
|
||||
Returns:
|
||||
True on success, False on failure (existing features are kept).
|
||||
"""
|
||||
if self.layer is None or not self.layer.isValid():
|
||||
return False
|
||||
|
||||
try:
|
||||
self._do_reload()
|
||||
return True
|
||||
except Exception as exc:
|
||||
QgsMessageLog.logMessage(
|
||||
f"Layer reload failed, keeping existing features: {exc}",
|
||||
"BAPEBridge",
|
||||
Qgis.Warning,
|
||||
)
|
||||
self.layer.setName(self._NAME_ERR)
|
||||
self.layer.emitStyleChanged()
|
||||
return False
|
||||
|
||||
def clear_layer(self) -> None:
|
||||
"""Remove all features from the layer without fetching new data."""
|
||||
if self.layer is None or not self.layer.isValid():
|
||||
return
|
||||
self.layer.dataProvider().truncate()
|
||||
self.layer.updateExtents()
|
||||
self.layer.triggerRepaint()
|
||||
|
||||
def remove_layer(self) -> None:
|
||||
"""Remove the layer from the project and release the reference."""
|
||||
if self.layer is not None:
|
||||
if not sip.isdeleted(self.layer):
|
||||
layer_id = self.layer.id()
|
||||
if QgsProject.instance().mapLayer(layer_id) is not None:
|
||||
QgsProject.instance().removeMapLayer(layer_id)
|
||||
self.layer = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _configure_marker(self) -> None:
|
||||
"""Set a green filled triangle marker at size 3."""
|
||||
symbol = QgsMarkerSymbol.createSimple({
|
||||
"name": "triangle",
|
||||
"color": "72,123,182,255",
|
||||
"size": "3",
|
||||
})
|
||||
self.layer.setRenderer(QgsSingleSymbolRenderer(symbol))
|
||||
|
||||
def _configure_labels(self) -> None:
|
||||
"""Enable simple labels showing the 'name' field next to each point."""
|
||||
buffer = QgsTextBufferSettings()
|
||||
buffer.setEnabled(True)
|
||||
buffer.setColor(QColor("black"))
|
||||
buffer.setSize(0.4)
|
||||
|
||||
text_format = QgsTextFormat()
|
||||
text_format.setColor(QColor("white"))
|
||||
text_format.setBuffer(buffer)
|
||||
|
||||
pal = QgsPalLayerSettings()
|
||||
pal.fieldName = "name"
|
||||
pal.enabled = True
|
||||
pal.setFormat(text_format)
|
||||
self.layer.setLabeling(QgsVectorLayerSimpleLabeling(pal))
|
||||
self.layer.setLabelsEnabled(True)
|
||||
|
||||
def _do_reload(self) -> None:
|
||||
"""Core fetch-and-replace logic. Raises on any failure."""
|
||||
locations = self._api_client.fetch_locations()
|
||||
|
||||
provider = self.layer.dataProvider()
|
||||
provider.truncate()
|
||||
|
||||
features = [self._build_feature(loc) for loc in locations]
|
||||
provider.addFeatures(features)
|
||||
|
||||
self.layer.updateExtents()
|
||||
self.layer.triggerRepaint()
|
||||
self.layer.setName(self._NAME_OK)
|
||||
self.layer.emitStyleChanged()
|
||||
|
||||
QgsMessageLog.logMessage(
|
||||
f"Loaded {len(features)} location(s).",
|
||||
"BAPEBridge",
|
||||
Qgis.Info,
|
||||
)
|
||||
|
||||
def _build_feature(self, loc: dict) -> QgsFeature:
|
||||
feature = QgsFeature(self.layer.fields())
|
||||
feature.setGeometry(
|
||||
QgsGeometry.fromPointXY(QgsPointXY(loc["longitude"], loc["latitude"]))
|
||||
)
|
||||
feature["id"] = loc["id"]
|
||||
feature["name"] = loc["name"]
|
||||
return feature
|
||||
Reference in New Issue
Block a user