""" 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)