From 93223e095c5faea7f9acfc0b18b4ea02a9fb67cd Mon Sep 17 00:00:00 2001 From: gaetanbrl Date: Tue, 6 Jan 2026 15:45:20 +0100 Subject: [PATCH] 1102 addon superset geo2france --- superset/README.md | 183 ++++++++++++++++++++++++++++++++++ superset/cc_superset_ocs2d.js | 183 ++++++++++++++++++++++++++++++++++ superset/config.json | 5 + 3 files changed, 371 insertions(+) create mode 100644 superset/README.md create mode 100644 superset/cc_superset_ocs2d.js create mode 100644 superset/config.json diff --git a/superset/README.md b/superset/README.md new file mode 100644 index 0000000..d0d28d4 --- /dev/null +++ b/superset/README.md @@ -0,0 +1,183 @@ +# 🚀 Custom Component - MViewer Integration + +[![forthebadge](https://forthebadge.com/images/badges/built-with-swag.svg)](https://forthebadge.com) +[![forthebadge](https://forthebadge.com/images/badges/ctrl-c-ctrl-v.svg)](https://forthebadge.com) + +Bienvenue dans le Custom Component MViewer 🎉 ! Ce composant est conçu pour ajouter une interaction transparente entre votre tableau de bord Apache Superset et MViewer. Il permet d'envoyer des filtres depuis Superset vers MViewer via l'URL. Simple et efficace ! 😎 + +**DEMO** : [Tableau de bord de la consommation ENAF à partir d'OCS2D](https://www.geo2france.fr/superset/superset/dashboard/conso-enaf/) + +# Gestion des Entités Géographiques sur une Carte + +## 🚀 Description Générale + +Ce projet implémente une solution pour afficher des entités géographiques (SCoT, EPCI, communes) sur une carte interactive en se basant sur des paramètres passés dans l'URL. Il utilise la bibliothèque OpenLayers pour gérer les données géographiques et manipuler les couches affichées sur la carte. + +--- + +## 🧰 Fonctionnalités Principales + +1. **Chargement des entités géographiques** depuis des sources WFS. +2. **Application de styles personnalisés** pour chaque type d'entité. +3. **Filtrage dynamique des entités géographiques** basé sur des paramètres d'URL. +4. **Affichage et surbrillance des entités sélectionnées** pour améliorer l'expérience utilisateur. + +--- + +## 🛠️ Fonctionnement Technique + +### **1. Sources de Données** + +Les données géographiques sont récupérées à partir de services Web WFS (`Web Feature Service`). Ces services permettent de fournir des entités géographiques en format GeoJSON. + +Les sources de données configurées sont : + +- **EPCI** : Entités publiques intercommunales. +- **Communes** : Municipalités locales. +- **SCOT** : Schémas de cohérence territoriale. + +Chaque source inclut : + +- Une URL WFS. +- Un attribut utilisé pour filtrer les données. +- Un style spécifique défini via OpenLayers. + +### **2. Gestion des Styles** + +Chaque type d'entité a un style par défaut : + +- Trait noir de 3 pixels de largeur. +- Styles personnalisables en modifiant les constantes. + +### **3. Filtrage des Entités : Côté Client vs. Côté Serveur** + +#### **Côté Client pour QGIS Server** + +Pour certaines entités (par ex., SCOT), les données complètes sont récupérées en une fois depuis le serveur, puis filtrées localement dans le navigateur. Cette méthode est utilisée lorsqu'il n'est pas possible d'appliquer un filtre directement à la requête serveur. + +#### **Côté Serveur pour GeoServer** + +Pour les autres entités (par ex., communes, EPCI), les filtres sont appliqués directement à la requête envoyée au serveur en utilisant le paramètre `CQL_FILTER`. Ce filtre réduit les données récupérées à ce qui est strictement nécessaire. + +> [!CAUTION] +>Le filtrage CQL pour les SCOTs ne fonctionnent pas car c'est un flux QGIS Server, il faut donc filtrer côté client. +--- + +### **4. Chargement et Filtrage des Données** + +La fonction principale `loadAndFilterFeatures` : + +1. Récupère les données d'une source WFS. +2. Applique un filtre : + - **Côté serveur** : Utilisation d'une requête enrichie avec un filtre CQL. + - **Côté client** : Filtrage des entités après récupération complète des données. +3. Affiche les entités filtrées sur la carte. + +--- + +### **5. Gestion des Clés à partir de l'URL** + +La fonction `getKeysFromUrl` permet d'extraire les valeurs de paramètres d'URL. Par exemple : + +- `?communes=12345,67890` filtre les communes avec les codes 12345 et 67890. + +Des règles spécifiques sont appliquées : + +- Un maximum de deux clés est autorisé pour éviter des affichages trop complexes. + +--- + +### **6. Surbrillance des Zones** + +La fonction `highlightZone` ajoute une surbrillance sur la première entité récupérée : + +- Elle crée un polygone avec un "trou" représentant la zone sélectionnée. +- Ajuste la vue de la carte pour centrer la géométrie sur l'écran. + +--- + +## 📂 Structure du Code + +- **`defaultStyles`** : Définition des styles par défaut des entités. +- **`dataSources`** : Configuration des sources de données WFS. +- **`fetchJson(url)`** : Fonction générique pour récupérer et parser des données JSON depuis une URL. +- **`highlightZone(zoneGeometry)`** : Ajoute une surbrillance à une zone spécifique. +- **`loadAndFilterFeatures(keys, config)`** : Filtre les entités et les affiche sur la carte. +- **`getKeysFromUrl(param)`** : Extrait les valeurs d'un paramètre d'URL. +- **`handleDataFromUrl()`** : Gère le flux principal pour charger et afficher les entités en fonction des paramètres d'URL. + +--- + +## 🔗 Exemple d'Usage + +Supposons que vous souhaitez afficher des communes spécifiques : + +```url +https://votreapplication.com/?communes=12345 +``` + +Le code : + +1. Extrait les valeurs `12345` de l'URL. +2. Récupère les entités correspondantes depuis la source WFS des communes. +3. Affiche ces entités sur la carte avec les styles prédéfinis. + +--- + +# Installation + +## 🚧 Prérequis + +Avant de commencer la configuration, assurez-vous d'avoir : + +- 🐍Apache Superset (version 4.0 ou supérieure) +- 🗺️ Mviewer (version 3.10 ou supérieure) + +## 🛠️ 1. Configuration du code + +Au début du fichier [cc_superset_ocs2d.js](./cc_superset_ocs2d.js) vous pouvez modifier librement la configuration de vos styles ou de vos différents flux wfs. + +## 🛠️ 2. Configuration Mviewer + +### 📍 Ajouter le Custom Component à MViewer + +1. Copiez le dossier dans le dossier apps de votre projet MViewer. 📂 + +2. Ajoutez cette ligne à la fin du fichier de configuration de MViewer (`default.xml`) : + +```xml + +``` + +L'id correspond au nom du dossier + +3. Vérifiez dans la console du navigateur (F12) si l'extension est bien chargée. Vous devriez voir : + +```text +cc_superset_ocs2d is successfully loaded ✅ +``` + +> ✔️ Bravo vous venez de rajouter votre Custom Component à MViewer + +## 🖥️ 3. Configuration de Superset + +### 🗺️ Intégration HandleBar pour Superset + +Pour afficher une carte MViewer dans un tableau de bord Superset, créez un chart de type HandleBar : + +1. Limitez les dimensions à etat_eolie et epci. + +2. Dans l'onglet Personnaliser, ajoutez ce code HTML pour intégrer une carte MViewer dynamique : + +```html + + ``` + +Cette iframe affiche votre carte MViewer avec les filtres appliqués en fonction des données sélectionnées dans le tableau de bord. + +- Les `{{#each data}}` permettent de parcourir chaque element. +- Les `{{if}}` permettent de séparer chaque élément par des virgules. + +> ✔️ Vous êtes désormais prêt à ajouter des cartes Mviewer à vos tableaux de bord. + + diff --git a/superset/cc_superset_ocs2d.js b/superset/cc_superset_ocs2d.js new file mode 100644 index 0000000..6b7a0e8 --- /dev/null +++ b/superset/cc_superset_ocs2d.js @@ -0,0 +1,183 @@ +//------------------- CONSTANTES ET UTILITAIRES ----------------- +// 🎨 Styles par défaut appliqués aux entités géographiques (EPCI, communes, SCOT). +// Chaque style est défini par une couleur et une largeur de trait. +const defaultStyles = { + epci: new ol.style.Style({ + stroke: new ol.style.Stroke({ color: "black", width: 3 }), + }), + communes: new ol.style.Style({ + stroke: new ol.style.Stroke({ color: "black", width: 3 }), + }), + scot: new ol.style.Style({ + stroke: new ol.style.Stroke({ color: "black", width: 3 }), + }), +}; + +// 🌐 Configuration des sources de données WFS +const dataSources = { + epci: { + url: "https://www.geo2france.fr/geoserver/spld/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=epci&outputFormat=application/json&srsname=EPSG:3857", + property: "code_epci", + style: defaultStyles.epci, + }, + communes: { + url: "https://www.geo2france.fr/geoserver/spld/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=communes&outputFormat=application/json&srsname=EPSG:3857", + property: "insee_com", + style: defaultStyles.communes, + }, + scot: { + url: "https://qgisserver.hautsdefrance.fr/cgi-bin/qgis_mapserv.fcgi?MAP=/var/www/data/qgis/applications/sraddet_2024_11.qgz&service=WFS&version=1.0.0&request=GetFeature&typeName=scot_synth_2024_11&outputFormat=application/json&srsname=EPSG:3857", + property: "idurba_scot_synth", + style: defaultStyles.scot, + }, +}; + +// 🛠️ Fonction pour récupérer des données JSON à partir d'une URL +async function fetchJson(url) { + console.log("📡 Récupération des données depuis:", url); + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`❌ Erreur réseau: ${response.statusText} (${response.status})`); + } + const data = await response.json(); + console.log("✅ Données récupérées avec succès:", data); + return data; + } catch (error) { + console.error("⚠️ Erreur lors de la récupération des données:", error); + throw error; + } +} + +// 🖌️ Fonction pour ajouter une surbrillance sur une zone spécifique +function highlightZone(zoneGeometry) { + const mapExtent = ol.proj.get('EPSG:3857').getExtent(); + const outerRing = ol.geom.Polygon.fromExtent(mapExtent); + + let innerRingCoordinates; + + // Vérification du type de géométrie + if (zoneGeometry.getType() === 'Polygon') { + innerRingCoordinates = zoneGeometry.getCoordinates()[0]; // Premier anneau + } else if (zoneGeometry.getType() === 'MultiPolygon') { + // Extraction du premier polygone dans un MultiPolygon + innerRingCoordinates = zoneGeometry.getCoordinates()[0][0]; + } else { + console.error('❌ Type de géométrie non supporté pour la surbrillance :', zoneGeometry.getType()); + return; + } + + // Ajout d’un trou correspondant à la géométrie sélectionnée + const highlightPolygon = new ol.geom.Polygon(outerRing.getCoordinates()); + highlightPolygon.appendLinearRing(new ol.geom.LinearRing(innerRingCoordinates)); + + const highlightSource = new ol.source.Vector({ + features: [new ol.Feature({ geometry: highlightPolygon })], + }); + + const highlightLayer = new ol.layer.Vector({ + source: highlightSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ + color: 'rgba(255, 255, 255, 0.5)', // Blanc semi-transparent + }), + }), + }); + + mviewer.getMap().addLayer(highlightLayer); + + mviewer.getMap().getView().fit(zoneGeometry.getExtent(), { + size: mviewer.getMap().getSize(), + maxZoom: 16, + duration: 1000, + }); +} + +// 📦 Fonction pour charger des entités depuis une source, appliquer un filtre et les afficher sur la carte +async function loadAndFilterFeatures(keys, config) { + const { url, property, style } = config; + console.log("⚙️ Chargement des données avec configuration:", config); + + try { + let features; + + if (property === "idurba_scot_synth") { + console.log("🟢 Filtrage côté client pour QGIS Server"); + const data = await fetchJson(url); + const allFeatures = new ol.format.GeoJSON().readFeatures(data); + features = allFeatures.filter((feature) => + keys.includes(feature.get(property)) + ); + } else { + console.log("🔵 Filtrage côté serveur pour GeoServer"); + const cqlFilter = keys.map((key) => `${property}='${key}'`).join(' OR '); + const filteredUrl = `${url}&CQL_FILTER=${encodeURIComponent(cqlFilter)}`; + const data = await fetchJson(filteredUrl); + features = new ol.format.GeoJSON().readFeatures(data); + } + + if (features.length === 0) { + console.warn(`⚠️ Aucun élément trouvé pour les clés fournies (${keys.join(", ")}).`); + return; + } + + const filteredSource = new ol.source.Vector({ features }); + const customLayer = new ol.layer.Vector({ source: filteredSource, style }); + mviewer.getMap().addLayer(customLayer); + + const extent = ol.extent.createEmpty(); + features.forEach((feature) => + ol.extent.extend(extent, feature.getGeometry().getExtent()) + ); + + // Mise en surbrillance de la première géométrie + highlightZone(features[0].getGeometry()); + } catch (error) { + console.error(`❌ Erreur lors du chargement et du filtrage des entités:`, error); + } +} + +// 🔗 Fonction pour extraire et parser les valeurs d'un paramètre spécifique depuis l'URL +function getKeysFromUrl(param) { + const urlParams = new URLSearchParams(window.location.search); + const paramValues = urlParams.get(param); + if (!paramValues) return []; + const keys = [...new Set(paramValues.split(",").map((key) => key.trim()))]; + if (keys.length > 1) { + console.warn( + `⚠️ Le nombre de clés dépasse la limite autorisée (2). Aucun élément ne sera affiché.` + ); + return []; + } + console.log(`🔑 Clés récupérées pour le paramètre ${param}:`, keys); + return keys; +} + +// 🔄 Fonction principale pour gérer les données en fonction des paramètres d'URL. +function handleDataFromUrl() { + const keys = { + communes: getKeysFromUrl("communes"), + epci: getKeysFromUrl("epci"), + scot: getKeysFromUrl("scot"), + }; + + if (keys.communes.length > 0) { + console.log("🔵 Priorité aux communes."); + loadAndFilterFeatures(keys.communes, dataSources.communes); + return; + } + + if (keys.epci.length > 0) { + console.log("🟢 Priorité aux EPCI."); + loadAndFilterFeatures(keys.epci, dataSources.epci); + return; + } + + if (keys.scot.length > 0) { + console.log("🟣 Chargement des SCOT."); + loadAndFilterFeatures(keys.scot, dataSources.scot); + } +} + +// 🚀 Point d'entrée principal : initialisation de la logique d'affichage. +handleDataFromUrl(); diff --git a/superset/config.json b/superset/config.json new file mode 100644 index 0000000..82ec23d --- /dev/null +++ b/superset/config.json @@ -0,0 +1,5 @@ +{ + "js": ["cc_superset_ocs2d.js"], + "target": "page-content-wrapper" + +}