167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
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
|