diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3ffc51d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: Publish dashboard to Pages + +on: + push: + branches: + - "leaflet-clement" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + publish-dashboard: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + + steps: + + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install Python requirements + run: pip install -r requirements.txt + + - name: Build dashboard + run: | + npm install + npm run build + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_S3_ENDPOINT: ${{ secrets.AWS_S3_ENDPOINT }} + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload artifacts to GitHub Pages + uses: actions/upload-pages-artifact@v3 + with: + path: dist + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + - name: Print Site URL based on branch + run: | + echo "Site URL: https://clement2323.github.io/${{ github.repository }}/leaflet-clement/" diff --git a/README.md b/README.md index 8d354c9..f7eb512 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # CRaTT +## Petit ajout Clément : +- les données sont générées au moment du buil de la page par les codes présents dans le répertrtoire data. +ces codes peuvent être en n'importe quels langages et doivent juste printer à la fin les data au foormat souhaité (json parquet..) +- les fichiers de code doivent porter le nom et le format du fichier exemple fibonacci.json.py, sera appelé lorsqu'on voudra +ouvrir le fichier généré par ce dernier avec FileAttachement("data/fibonacci.json").json() -> .py python n'est pas que le seul langage possible ici ! +C'est l'idée des data laoaders +- l'ordre d'apparition des pages dépend de l'ordre alphabétique du nom des .md correspondant, le titre de la page est donné par la balise title +- index donne la page d'accueil +## La suite This is an [Observable Framework](https://observablehq.com/framework) app. To start the local preview server, run: ``` diff --git a/observablehq.config.js b/observablehq.config.js index fd0d134..43bafae 100644 --- a/observablehq.config.js +++ b/observablehq.config.js @@ -2,20 +2,26 @@ export default { // The app’s title; used in the sidebar and webpage titles. title: "CRaTT", - - // The pages and sections in the sidebar. If you don’t specify this option, - // all pages will be listed in alphabetical order. Listing pages explicitly - // lets you organize them into sections and have unlisted pages. - // pages: [ - // { - // name: "Examples", - // pages: [ - // {name: "Dashboard", path: "/example-dashboard"}, - // {name: "Report", path: "/example-report"} - // ] - // } - // ], - + dynamicPaths: [ + "/departement", + ], + pages: [ + { + name: "Departements", + path: "/departement/", + pages: [ + {name: "Mayotte", path: "/departement/mayotte"}, + {name: "Reunion", path: "/departement/reunion"} + ] + }, + { + name: "Non Dynamique", + pages: [ + {name: "Mayotte", path: "/1.Mayotte"}, + + ] + } + ], // Content to add to the head of the page, e.g. for a favicon: head: '', diff --git a/requirements.txt b/requirements.txt index 7567a7c..ff92385 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ duckdb geopandas s3fs -pyarrow +pyarrow \ No newline at end of file diff --git a/src/1.Mayotte.md b/src/1.Mayotte.md new file mode 100644 index 0000000..97625d6 --- /dev/null +++ b/src/1.Mayotte.md @@ -0,0 +1,166 @@ +--- +title: Mayotte +--- + +# Cartographie +```js +// Importation de Leaflet depuis npm pour gérer la carte +import * as L from "npm:leaflet"; +import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7.6.1/dist/d3.min.js"; +import { calculateQuantiles, getColor, createStyle, onEachFeature, getWMSTileLayer,createGeoJsonLayer,updateLegend} from "./utils/fonctions.js"; +import { quantileProbs, colorScales, departementConfig } from './utils/config.js'; +``` + +```js +const statistics = FileAttachment("./data/clusters_statistics.json").json(); +const geojsonData = statistics; +``` + +```js +// Choix du département Mayotte +const departement = 'mayotte'; +const config = departementConfig[departement]; +const { name, center, availableYears } = config; + +// Initialisation de la carte Leaflet +const mapDiv = display(document.createElement("div")); +mapDiv.style = "height: 600px; width: 100%; margin: 0 auto;"; + +// Initialiser la carte avec la position centrale du département +const map = L.map(mapDiv).setView(center, 14,10.4); + +// Ajout d'une couche de base OpenStreetMap +const baseLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', +}); + +// Ajout d'une couche de base sombre pour le mode sombre +const darkBaseLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap contributors © CartoDB', + subdomains: 'abcd', + maxZoom: 19, +}); + +// Ajouter la couche de base par défaut +baseLayer.addTo(map); + +// Définition des couches de base +const baseLayers = { + 'Clair': baseLayer, + 'Sombre': darkBaseLayer, +}; +``` + +```js +// Assuming statistics, map, availableYears, name, quantileProbs, and colorScales are already defined +const overlays = {}; + +const styleName = "contour_rouge"; // Defined in GeoServer + +// Adding layers for available years +for (const year of availableYears) { + const pleiadesLayer = getWMSTileLayer(`${name}_${year}`); + overlays[`PLEIADES ${year}`] = pleiadesLayer; + + const predictionLayer = getWMSTileLayer(`${name}_PREDICTIONS_${year}`, styleName); + overlays[`Prédiction ${year}`] = predictionLayer; +} + +// Labels and indicators with associated units +const labels = [ + { indicator: 'pct_building_2023', label: 'Pourcentage de bâti 2023', colorScale: 'redScale', unit: '%' }, + { indicator: 'building_2023', label: 'Surface bâti', colorScale: 'greenScale', unit: 'm²' }, + { indicator: 'area_building_change_absolute', label: 'Variation de Surface absolue', colorScale: 'blueScale', unit: 'm²' }, + { indicator: 'area_building_change_relative', label: 'Variation de Surface relative', colorScale: 'yellowScale', unit: '%' } +]; + +// Create and add GeoJSON layers +let isFirstLayer = true; +for (const { indicator, label, colorScale, unit } of labels) { + const geojsonLayer = createGeoJsonLayer(statistics, indicator, label, quantileProbs, colorScales[colorScale], unit); + overlays[label] = geojsonLayer; + + // Add only the first layer to the map by default + if (isFirstLayer) { + geojsonLayer.addTo(map); + isFirstLayer = false; + } +} + +``` + +```js +// Layer controls +L.control.layers(baseLayers, overlays).addTo(map); + +// Legend setup and event handlers +const legend = L.control({ position: 'bottomright' }); + +legend.onAdd = function (map) { + return L.DomUtil.create('div', 'info legend'); +}; + +// Function to update the legend based on the currently active layer +function updateLegendForLayer(layerName) { + const selectedIndicator = labels.find(label => label.label === layerName); + if (selectedIndicator) { + // Calculate quantiles for the selected indicator + const quantiles = calculateQuantiles( + statistics.features.map(f => f.properties[selectedIndicator.indicator]), + quantileProbs + ); + // Update the legend with the correct information and units + legend.getContainer().innerHTML = updateLegend( + selectedIndicator, + colorScales[selectedIndicator.colorScale], + quantiles, + selectedIndicator.unit + ).innerHTML; + } +} + +// Check if the default layer is active and add the legend accordingly +if (map.hasLayer(overlays['Pourcentage de bâti 2023'])) { + legend.addTo(map); + updateLegendForLayer('Pourcentage de bâti 2023'); +} + +// Event handler for adding an overlay layer +map.on('overlayadd', function (eventLayer) { + if (labels.some(label => label.label === eventLayer.name)) { + legend.addTo(map); + updateLegendForLayer(eventLayer.name); + } +}); + +// Event handler for removing an overlay layer +map.on('overlayremove', function (eventLayer) { + if (labels.some(label => label.label === eventLayer.name)) { + map.removeControl(legend); + } +}); + +``` +```js +// VARIABILISER !! routing dynamique !! +// tableau de statistiques interactives avec sélection des ilots de Jean françois §> interactivité, je clique sur l'ilot dans une table il me le highlight sur la carte + +// Tableau +// slider avant après pour les cartes leaflet +//slider et trouting dynamique : overkill le truc + +// ajouter fond de carte 2022 §> MAyotte etc ??? -> +// meme fichier pour réunion + +// slider +// injection images, prédictions pour Mayotte 2022 +// Réunion images prédictions, pour 2022 2023 et statistiques + +// 1 page par dep ? +// slider sur une autre page +// GT en live excalidraw sur la structure applicative + +// + +``` + diff --git a/src/components/timeline.js b/src/components/timeline.js deleted file mode 100644 index 23d692c..0000000 --- a/src/components/timeline.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as Plot from "npm:@observablehq/plot"; - -export function timeline(events, {width, height} = {}) { - return Plot.plot({ - width, - height, - marginTop: 30, - x: {nice: true, label: null, tickFormat: ""}, - y: {axis: null}, - marks: [ - Plot.ruleX(events, {x: "year", y: "y", markerEnd: "dot", strokeWidth: 2.5}), - Plot.ruleY([0]), - Plot.text(events, {x: "year", y: "y", text: "name", lineAnchor: "bottom", dy: -10, lineWidth: 10, fontSize: 12}) - ] - }); -} diff --git a/src/data/clusters_statistics.json.py b/src/data/clusters_statistics.json.py index 79411ae..3b83c57 100644 --- a/src/data/clusters_statistics.json.py +++ b/src/data/clusters_statistics.json.py @@ -75,4 +75,4 @@ def merge_gdfs( + ["geometry"] # Put geometry at the end ) -print(merged_gdf[ordered_columns].set_index("ident_ilot").to_crs("EPSG:4326").to_json()) +print(merged_gdf[ordered_columns].set_index("ident_ilot").to_crs("EPSG:4326").to_json()) \ No newline at end of file diff --git a/src/data/dft-road-collisions.csv.sh b/src/data/dft-road-collisions.csv.sh deleted file mode 100644 index 6df76c9..0000000 --- a/src/data/dft-road-collisions.csv.sh +++ /dev/null @@ -1,18 +0,0 @@ -URL="https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-1979-latest-published-year.csv" - -# Use the data loader cache directory to store the downloaded data. -TMPDIR="src/.observablehq/cache" - -# Download the data (if it’s not already in the cache). -if [ ! -f "$TMPDIR/dft-collisions.csv" ]; then - curl -f "$URL" -o "$TMPDIR/dft-collisions.csv" -fi - -# Generate a CSV file using DuckDB. -duckdb :memory: << EOF -COPY ( - SELECT longitude, latitude - FROM read_csv_auto('$TMPDIR/dft-collisions.csv') - WHERE accident_year = 2022 -) TO STDOUT WITH (FORMAT 'csv'); -EOF diff --git a/src/departement/[departement].md b/src/departement/[departement].md new file mode 100644 index 0000000..dcaeb8d --- /dev/null +++ b/src/departement/[departement].md @@ -0,0 +1,162 @@ +--- +toc: true +--- + + +# Cartographie +```js +// Importation de Leaflet depuis npm pour gérer la carte +import * as L from "npm:leaflet"; +import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7.6.1/dist/d3.min.js"; +import { calculateQuantiles, getColor, createStyle, onEachFeature, getWMSTileLayer,createGeoJsonLayer,updateLegend} from "./utils/fonctions.js"; +import { quantileProbs, colorScales, departementConfig } from './utils/config.js'; +``` + +```js +const statistics = FileAttachment("../data/clusters_statistics.json").json(); +const geojsonData = statistics; +``` + +```js +import {parseArgs} from "node:util"; + +const { + values: {departement} +} = parseArgs({ + options: {departement: {type: "string"}} +}); + +console.log(The current product is ${departement}) +// Choix du département Mayotte +const departement = departement; +const config = departementConfig[departement]; +const { name, center, availableYears } = config; + +// Initialisation de la carte Leaflet +const mapDiv = display(document.createElement("div")); +mapDiv.style = "height: 600px; width: 100%; margin: 0 auto;"; + +// Initialiser la carte avec la position centrale du département +const map = L.map(mapDiv).setView(center, 14,10.4); + +// Ajout d'une couche de base OpenStreetMap +const baseLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', +}); + +// Ajout d'une couche de base sombre pour le mode sombre +const darkBaseLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap contributors © CartoDB', + subdomains: 'abcd', + maxZoom: 19, +}); + +// Ajouter la couche de base par défaut +baseLayer.addTo(map); + +// Définition des couches de base +const baseLayers = { + 'Clair': baseLayer, + 'Sombre': darkBaseLayer, +}; +``` + +```js +// Assuming statistics, map, availableYears, name, quantileProbs, and colorScales are already defined +const overlays = {}; + +const styleName = "contour_rouge"; // Defined in GeoServer + +// Adding layers for available years +for (const year of availableYears) { + const pleiadesLayer = getWMSTileLayer(`${name}_${year}`); + overlays[`PLEIADES ${year}`] = pleiadesLayer; + + const predictionLayer = getWMSTileLayer(`${name}_PREDICTIONS_${year}`, styleName); + overlays[`Prédiction ${year}`] = predictionLayer; +} + +// Labels and indicators with associated units +const labels = [ + { indicator: 'pct_building_2023', label: 'Pourcentage de bâti 2023', colorScale: 'redScale', unit: '%' }, + { indicator: 'building_2023', label: 'Surface bâti', colorScale: 'greenScale', unit: 'm²' }, + { indicator: 'area_building_change_absolute', label: 'Variation de Surface absolue', colorScale: 'blueScale', unit: 'm²' }, + { indicator: 'area_building_change_relative', label: 'Variation de Surface relative', colorScale: 'yellowScale', unit: '%' } +]; + +// Create and add GeoJSON layers +let isFirstLayer = true; +for (const { indicator, label, colorScale, unit } of labels) { + const geojsonLayer = createGeoJsonLayer(statistics, indicator, label, quantileProbs, colorScales[colorScale], unit); + overlays[label] = geojsonLayer; + + // Add only the first layer to the map by default + if (isFirstLayer) { + geojsonLayer.addTo(map); + isFirstLayer = false; + } +} + +``` + +```js +// Layer controls +L.control.layers(baseLayers, overlays).addTo(map); + +// Legend setup and event handlers +const legend = L.control({ position: 'bottomright' }); + +legend.onAdd = function (map) { + return L.DomUtil.create('div', 'info legend'); +}; + +// Function to update the legend based on the currently active layer +function updateLegendForLayer(layerName) { + const selectedIndicator = labels.find(label => label.label === layerName); + if (selectedIndicator) { + // Calculate quantiles for the selected indicator + const quantiles = calculateQuantiles( + statistics.features.map(f => f.properties[selectedIndicator.indicator]), + quantileProbs + ); + // Update the legend with the correct information and units + legend.getContainer().innerHTML = updateLegend( + selectedIndicator, + colorScales[selectedIndicator.colorScale], + quantiles, + selectedIndicator.unit + ).innerHTML; + } +} + +// Check if the default layer is active and add the legend accordingly +if (map.hasLayer(overlays['Pourcentage de bâti 2023'])) { + legend.addTo(map); + updateLegendForLayer('Pourcentage de bâti 2023'); +} + +// Event handler for adding an overlay layer +map.on('overlayadd', function (eventLayer) { + if (labels.some(label => label.label === eventLayer.name)) { + legend.addTo(map); + updateLegendForLayer(eventLayer.name); + } +}); + +// Event handler for removing an overlay layer +map.on('overlayremove', function (eventLayer) { + if (labels.some(label => label.label === eventLayer.name)) { + map.removeControl(legend); + } +}); + +``` +```js +// VARIABILISER !! routing dynamique !! +// tableau de statistiques interactives avec sélection des ilots de Jean françois §> interactivité, je clique sur l'ilot dans une table il me le highlight sur la carte + +// Tableau +// slider avant après pour les cartes leaflet +//slidrer et trouting dynamique : overkill le truc +// ajouter fond de carte 2022 §> MAyotte etc ??? +``` diff --git a/src/exploration data.py b/src/exploration data.py new file mode 100644 index 0000000..1420ba8 --- /dev/null +++ b/src/exploration data.py @@ -0,0 +1,28 @@ +import json + +# Define the path to the JSON file +file_path = '.observablehq/cache/data/clusters_statistics.json' + +# Attempt to read and load the JSON file + +with open(file_path, 'r') as file: + statistics_data = json.load(file) + +statistics_data["features"][0] + +import geopandas as gpd +from shapely.geometry import shape + +# Extract features from statistics_data +features = statistics_data["features"] + +# Convert features into GeoDataFrame format +geometry = [shape(feature["geometry"]) for feature in features] +properties = [feature["properties"] for feature in features] + +# Create a GeoDataFrame +gdf = gpd.GeoDataFrame(properties, geometry=geometry) + +# Display the GeoDataFrame structure to the user +gdf + diff --git a/src/index.md b/src/index.md index 3e2d97f..1ac69ad 100644 --- a/src/index.md +++ b/src/index.md @@ -4,97 +4,14 @@ toc: false

CRaTT

+

Petite application pour observer les performance des algorithmes de segmentation

-```js -// npm imports -import {DuckDBClient} from "npm:@observablehq/duckdb"; -``` - -```js -const variable = view(Inputs.select(["pct_building_2023", "area_building_change_absolute", "area_building_change_relative"], {value: "pct_building_2023", label: "Choisissez la variable à afficher"})); -``` - -```js -Plot.plot({ - width: 975, - height: 610, -// projection: "identity", - x: {axis: null}, - y: {axis: null}, - color: createColorConfig(variable), - marks: [ - Plot.geo(test2, Plot.centroid({ - fill: d => d.properties.pct_building_2023, - tip: true, - channels: { - Cluster_ID: d => d.id - - } - })), - ] -}) -``` - - - - ```js -Inputs.table(test2.features.map(d => d.properties)) +const statistics = FileAttachment("./data/clusters_statistics.json").json(); ``` - -```js -const test2 = FileAttachment("./data/clusters_statistics.json").json(); -``` - -```js -test2 -``` - - -```js -function createColorConfig(value) { - // const domain = [d3.quantile(test2.features.map(obj => obj.properties[variable]), 0.1), d3.quantile(test2.features.map(obj => obj.properties[variable]), 0.9)] - const domain = d3.extent(test2.features.map(obj => obj.properties[variable])) - - switch (value) { - case "pct_building_2023": - return { - label: "Pourcentage de bâti (%)", - type: "linear", - scheme: "reds", - legend: true, - domain: domain, - range: [0, 1] - }; - case "area_building_change_absolute": - return { - label: "Variation absolue de bâtis (m2)", - type: "diverging-symlog", - scheme: "burd", - domain:domain, - legend: true, - pivot: 0, - symmetric: true - }; - case "area_building_change_relative": - return { - label: "Variation relative de bâtis (%)", - type: "linear", - scheme: "reds", - legend: true, - domain: domain, - range: [0, 1] - }; - default: - throw new Error("Invalid value provided"); - } -} -``` - -