Files
boc/iom/integrations/qgis/plugin.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

317 lines
9.9 KiB
Python

"""
Landvex QGIS Plugin
Integrates IOM data into QGIS desktop application
"""
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QMessageBox
from qgis.core import (
QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
QgsPointXY, QgsField, QgsSymbol, QgsRendererCategory,
QgsCategorizedSymbolRenderer, QgsMarkerSymbol
)
from PyQt5.QtCore import QVariant
import requests
import json
class LandvexQGISPlugin:
"""QGIS Plugin for Landvex Urban Intelligence"""
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
self.menu = "Landvex"
self.toolbar = self.iface.addToolBar("Landvex")
self.toolbar.setObjectName("LandvexToolbar")
# API Configuration
self.api_url = "https://api.landvex.com/v1"
self.api_key = None
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None
):
"""Add action to toolbar and menu"""
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
self.toolbar.addAction(action)
if add_to_menu:
self.iface.addPluginToMenu(self.menu, action)
self.actions.append(action)
return action
def initGui(self):
"""Initialize plugin GUI"""
# Load Observations action
self.add_action(
"icon.png",
text="Load Observations",
callback=self.load_observations,
parent=self.iface.mainWindow()
)
# Load RGI Layer action
self.add_action(
"icon.png",
text="Load RGI Layer",
callback=self.load_rgi_layer,
parent=self.iface.mainWindow()
)
# Load UMI Layer action
self.add_action(
"icon.png",
text="Load UMI Layer",
callback=self.load_umi_layer,
parent=self.iface.mainWindow()
)
# Settings action
self.add_action(
"icon.png",
text="Settings",
callback=self.show_settings,
parent=self.iface.mainWindow()
)
def unload(self):
"""Unload plugin"""
for action in self.actions:
self.iface.removePluginMenu(self.menu, action)
self.iface.removeToolBarIcon(action)
del self.toolbar
def load_observations(self):
"""Load observations from Landvex API"""
try:
# Fetch observations
response = requests.get(
f"{self.api_url}/observations",
headers={"Authorization": f"Bearer {self.api_key}"},
params={"limit": 1000}
)
response.raise_for_status()
data = response.json()
# Create vector layer
layer = QgsVectorLayer(
"Point?crs=EPSG:4326",
"Landvex Observations",
"memory"
)
# Add fields
provider = layer.dataProvider()
provider.addAttributes([
QgsField("id", QVariant.String),
QgsField("type", QVariant.String),
QgsField("condition", QVariant.Int),
QgsField("zoomer", QVariant.String),
QgsField("status", QVariant.String),
QgsField("date", QVariant.String),
QgsField("rgi", QVariant.Double),
])
layer.updateFields()
# Add features
features = []
for obs in data.get("observations", []):
feature = QgsFeature()
feature.setGeometry(QgsGeometry.fromPointXY(
QgsPointXY(obs["lng"], obs["lat"])
))
feature.setAttributes([
obs["id"],
obs["type"],
obs["condition"],
obs["zoomer"],
obs["status"],
obs["date"],
obs.get("rgi", 0),
])
features.append(feature)
provider.addFeatures(features)
layer.updateExtents()
# Style by condition
self.style_by_condition(layer)
# Add to project
QgsProject.instance().addMapLayer(layer)
QMessageBox.information(
self.iface.mainWindow(),
"Success",
f"Loaded {len(features)} observations"
)
except Exception as e:
QMessageBox.critical(
self.iface.mainWindow(),
"Error",
f"Failed to load observations: {str(e)}"
)
def style_by_condition(self, layer):
"""Style layer by condition (1-5)"""
categories = []
# Condition 1: Excellent (Green)
symbol = QgsMarkerSymbol.createSimple({
"name": "circle",
"color": "#34C759",
"size": "6"
})
categories.append(QgsRendererCategory("1", symbol, "Excellent"))
# Condition 2: Good (Light Green)
symbol = QgsMarkerSymbol.createSimple({
"name": "circle",
"color": "#99D98C",
"size": "6"
})
categories.append(QgsRendererCategory("2", symbol, "Good"))
# Condition 3: Fair (Yellow)
symbol = QgsMarkerSymbol.createSimple({
"name": "circle",
"color": "#F9C74F",
"size": "6"
})
categories.append(QgsRendererCategory("3", symbol, "Fair"))
# Condition 4: Poor (Orange)
symbol = QgsMarkerSymbol.createSimple({
"name": "circle",
"color": "#F8961E",
"size": "6"
})
categories.append(QgsRendererCategory("4", symbol, "Poor"))
# Condition 5: Critical (Red)
symbol = QgsMarkerSymbol.createSimple({
"name": "circle",
"color": "#FF3B30",
"size": "8"
})
categories.append(QgsRendererCategory("5", symbol, "Critical"))
renderer = QgsCategorizedSymbolRenderer("condition", categories)
layer.setRenderer(renderer)
def load_rgi_layer(self):
"""Load RGI (Reality Gap Index) heatmap layer"""
try:
# Fetch RGI data for grid
response = requests.get(
f"{self.api_url}/indexes/rgi/grid",
headers={"Authorization": f"Bearer {self.api_key}"},
params={"bounds": "17.5,59.0,19.0,60.0", "resolution": 100}
)
response.raise_for_status()
data = response.json()
# Create grid layer
layer = QgsVectorLayer(
"Polygon?crs=EPSG:4326",
"RGI Heatmap",
"memory"
)
provider = layer.dataProvider()
provider.addAttributes([
QgsField("rgi", QVariant.Double),
QgsField("category", QVariant.String),
])
layer.updateFields()
# Add grid cells
features = []
for cell in data.get("grid", []):
feature = QgsFeature()
# Create polygon from bounds
points = [
QgsPointXY(cell["west"], cell["south"]),
QgsPointXY(cell["east"], cell["south"]),
QgsPointXY(cell["east"], cell["north"]),
QgsPointXY(cell["west"], cell["north"]),
QgsPointXY(cell["west"], cell["south"]),
]
feature.setGeometry(QgsGeometry.fromPolygonXY([points]))
feature.setAttributes([
cell["rgi"],
cell["category"],
])
features.append(feature)
provider.addFeatures(features)
layer.updateExtents()
# Style by RGI value
self.style_rgi_heatmap(layer)
QgsProject.instance().addMapLayer(layer)
QMessageBox.information(
self.iface.mainWindow(),
"Success",
f"Loaded RGI grid with {len(features)} cells"
)
except Exception as e:
QMessageBox.critical(
self.iface.mainWindow(),
"Error",
f"Failed to load RGI: {str(e)}"
)
def style_rgi_heatmap(self, layer):
"""Style RGI heatmap with graduated colors"""
# Use graduated renderer for RGI values 0-10
# Low (green) → High (red)
pass # Implementation depends on QGIS version
def load_umi_layer(self):
"""Load UMI (Urban Morphology Index) layer"""
QMessageBox.information(
self.iface.mainWindow(),
"Info",
"UMI layer loading - implementation in progress"
)
def show_settings(self):
"""Show plugin settings dialog"""
from .settings_dialog import SettingsDialog
dialog = SettingsDialog(self.iface.mainWindow())
dialog.exec_()
def classFactory(iface):
"""Load plugin"""
return LandvexQGISPlugin(iface)