// à étudier pour un switch layer
// https://geotribu.fr/articles/2021/2021-02-23_carte_ligne_libre/
// doc IGN pas tellement détaillée...
//https://geoservices.ign.fr/documentation/services/services-geoplateforme/diffusion#70062
// pour consulter une metadonnée https://data.geopf.fr/tms/1.0.0/ELEVATION.ELEVATIONGRIDCOVERAGE/metadata.json
// https://data.geopf.fr/wms-r/wmts?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities
// un programme par un gars de l'IGN qui a tout compris...
// https://github.com/IGNF/openlayers-vs-maplibre/blob/main/assets/js/components/maplibre/maplibre-ext.js
// des exemples !
// https://sites-formations.univ-rennes2.fr/mastersigat/MaplibreGL/
// exemple france par maptiler
// https://www.maptiler.com/news/2022/05/maplibre-v2-add-3d-terrain-to-your-map/
// ma clef avec mon compte google : qui est dans le fichier d'environnement
// TODO
// gérer proprement pour pas avoir deux fonds différents l'un par dessus l'autre mais bien un choix
// un menu pour switcher les layers et les fonds
// à étudier :
// https://github.com/watergis/maplibre-gl-legend
// Un graphe en apache echart qui dépend de la sélection
myMap = {
// on définit le container de la carte et on l'attend , on mettant fill: false la card prend la taille de la map et en réglant le height en vh en tatonnant on trouve la bonne proportion, cf custom.css
//const container = html`<div style="height:55vh;">`;
const container = html`<div class="map-container"></div>`;
yield container;
// add the PMTiles plugin to the maplibregl global.
const protocol = new pmtiles.Protocol();
maplibregl.addProtocol('pmtiles', (request) => {
return new Promise((resolve, reject) => {
const callback = (err, data) => {
if (err) {
reject(err);
} else {
resolve({data});
}
};
protocol.tile(request, callback);
});
});
//const PMTILES_URL_B1 = 'https://guillaumechretiencerema.github.io/mydatas/recoquartiers/bat_48_58.pmtiles?raw=true';
//const PMTILES_URL_B1 = 'http://162.19.124.68/recoquartiers/0-pmtiles/bat_48_58.pmtiles';
const PMTILES_URL_B1 = `${LAYERS_PATH}/batiment_bdnb_48_58_corrige_pour_map.pmtiles`;
const b1 = new pmtiles.PMTiles(PMTILES_URL_B1);
// this is so we share one instance across the JS code and the map renderer
protocol.add(b1);
const PMTILES_URL_BBIS =`${LAYERS_PATH}/bat_hors48_58.pmtiles`;
const bbis = new pmtiles.PMTiles(PMTILES_URL_BBIS);
protocol.add(bbis);
const PMTILES_URL_C =`${LAYERS_PATH}/communes.pmtiles`;
const c = new pmtiles.PMTiles(PMTILES_URL_C);
protocol.add(c);
const PMTILES_URL_Q =`${LAYERS_PATH}/quartiers.pmtiles`;
const q = new pmtiles.PMTiles(PMTILES_URL_Q);
protocol.add(q);
const PMTILES_URL_P =`${LAYERS_PATH}/parcelle_48_58.pmtiles`;
const p = new pmtiles.PMTiles(PMTILES_URL_P);
protocol.add(p);
const sourceOrtho = {style:'normal', format:'image/jpeg',layer:'HR.ORTHOIMAGERY.ORTHOPHOTOS'};
const sourcePlanIGN = {style:'normal', format:'image/png',layer:'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2'};
const sourceHillShade = {style:'estompage_grayscale',format:'image/png',layer:'ELEVATION.ELEVATIONGRIDCOVERAGE.SHADOW'};
const map = new maplibregl.Map({
container,
center: [0.1920, 49.2381],
zoom: 7,
pitch: 30,
//maxPitch: 75,
style: {
version: 8,
sources: {
'raster-planign': {
type: 'raster',
//'tiles': ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tiles: [
`https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourcePlanIGN.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourcePlanIGN.format}&layer=${sourcePlanIGN.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
],
tileSize: 256,
attribution: '© <a href="https://www.ign.fr/">IGN</a>',
minzoom: 0,
maxzoom: 22
},
'raster-ortho': {
type: 'raster',
//'tiles': ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tiles: [
`https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourceOrtho.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourceOrtho.format}&layer=${sourceOrtho.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
],
tileSize: 256,
attribution: '© <a href="https://www.ign.fr/">IGN</a>',
minzoom: 0,
maxzoom: 22
},
'hillshadeSource': {
type: 'raster',
//'tiles': ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tiles: [
`https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourceHillShade.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourceHillShade.format}&layer=${sourceHillShade.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
],
tileSize: 256,
attribution: '© <a href="https://www.ign.fr/">IGN</a>',
minzoom: 0,
maxzoom: 22
},
'source_c': {
type: 'vector',
url: `pmtiles://${PMTILES_URL_C}`
},
'source_q': {
type: 'vector',
url: `pmtiles://${PMTILES_URL_Q}`,
attribution: '© <a href="https://www.cerema.fr/fr">CEREMA</a>',
},
'source_b1': {
type: 'vector',
url: `pmtiles://${PMTILES_URL_B1}`
},
'source_bbis': {
type: 'vector',
url: `pmtiles://${PMTILES_URL_BBIS}`
},
'source_p': {
type: 'vector',
url: `pmtiles://${PMTILES_URL_P}`
},
},
layers: [
{
id: 'raster-planign',
type: 'raster',
source: 'raster-planign',
layout: {
// Make the layer visible by default.
visibility: 'visible' //ou none
},
},
{
id: 'raster-ortho',
type: 'raster',
source: 'raster-ortho',
layout: {
// Make the layer visible by default.
visibility: 'none' //ou none
},
},
{
id: 'hillshadeSource',
type: 'raster',
source: 'hillshadeSource',
layout: {
// Make the layer visible by default.
visibility: 'visible' //ou none
},
},
{
id: "communes",
source: "source_c",
'source-layer': "communes",
type: "line",
paint: {
'line-color': '#e619e5',
'line-width': 2
}
},
{
id: "quartiers",
source: "source_q",
'source-layer': 'quartiers',
type: "fill",
paint: {
//"fill-color": "green",
'fill-color': [
'match',
['get', 'clust_bis'], // Récupère la valeur de l'attribut 'clust_bis'
'1bis', '#ffff75',
'2bis', '#ff3c6a',
'3bis', '#914cd6',
'4bis', '#56cd62',
'5bis', '#6bf2ee',
'6bis', '#2e77d6',
'7bis', '#ffae21',
// Définissez une couleur par défaut si nécessaire :
'#808080' // Couleur grise par défaut pour les valeurs non spécifiées
],
"fill-opacity": 0.5//,
//'fill-outline-color': '#000000'
}
},
{
id: "quartier_line",
source: "source_q",
'source-layer': 'quartiers',
type: "line",
paint: {
'line-color': "black",
'line-width': 2
}
},
{
id: "selectedQ",
source: "source_q",
'source-layer': 'quartiers',
type: "line",
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": "yellow",
"line-width": 8
},
filter: ["==", "id", 225] // Filtre initial vide
},
{
id: "parcelle_48_58",
source: "source_p",
'source-layer': 'parcelle_48_58',
type: "line",
paint: {
'line-color': "pink",
'line-width': 2
}
},
{
id: 'batiment_bdnb_48_58_corrige_pour_map',
source: 'source_b1',
'source-layer': 'batiment_bdnb_48_58_corrige_pour_map',
type: "fill-extrusion", // Changer le type de couche en "fill-extrusion"
paint: {
"fill-extrusion-color": "#fdb8ff", // Couleur de remplissage extrudée
"fill-extrusion-height": ["get", "bdtopo_bat_hauteur_mean"], // Propriété de hauteur à utiliser pour extruder les polygones
"fill-extrusion-base": 0, // Hauteur de base de l'extrusion
"fill-extrusion-opacity": 0.7
}
},
{
id: "batiment_bdnb_recoq_hors48_58_hors_parcelle_48_58",
source: 'source_bbis',
'source-layer': "batiment_bdnb_recoq_hors48_58_hors_parcelle_48_58",
type: "fill-extrusion", // Changer le type de couche en "fill-extrusion"
paint: {
"fill-extrusion-color": "white", // Couleur de remplissage extrudée
"fill-extrusion-height": ["get", "bdtopo_bat_hauteur_mean"], // Propriété de hauteur à utiliser pour extruder les polygones
"fill-extrusion-base": 0, // Hauteur de base de l'extrusion
"fill-extrusion-opacity": 0.7
}
}
]
}
});
//------------------------------------------------------------------------------------------------
map.addControl(new maplibregl.NavigationControl(
{
visualizePitch: true,
//showZoom: true,
//showCompass: true
}),
'top-left'
);
map.addControl(new maplibregl.FullscreenControl()), 'top-right';
const scale = new maplibregl.ScaleControl({
maxWidth: 80,
unit: 'metric'
});
map.addControl(scale);
//------------------------------------------------------------------------------------------------
map.on('click', 'quartiers', (e) => {
// Center the map on the coordinates of any clicked symbol from the 'symbols' layer.
const centroid = turf.centroid(e.features[0].geometry);
map.flyTo({
center: centroid.geometry.coordinates,
zoom: 15,
pitch: 45
});
// POP UP on click
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
Quartier n° ${e.features[0].properties.id}<br>
Commune: ${e.features[0].properties.nom_com_rattacht}<br>
${e.features[0].properties.nb_batiment_reco} bâtiments,
${e.features[0].properties.somme_nlogh_reco} logements de la Reconstruction<br>
Typologie de quartier : ${e.features[0].properties.lib_clust_bis}
`)
.addTo(map);
mutable myId = e.features[0].properties.id;
// test highlight NEW !
// Mettre à jour le filtre de la couche de surbrillance pour montrer uniquement l'élément cliqué
map.setFilter('selectedQ', ['==', 'id', e.features[0].properties.id]);
});
const mySelectCommune = document.getElementById('mySelectCommune');
mySelectCommune.addEventListener('change', () => {
map.fitBounds( JSON.parse(mySelectCommune.value) );
});
//------------------------------------------------------------------------------------------------
// Change the cursor to a pointer when the it enters a feature in the 'symbols' layer.
map.on('mouseenter', 'quartiers', () => {
map.getCanvas().style.cursor = 'pointer';
});
// Change it back to a pointer when it leaves.
map.on('mouseleave', 'quartiers', () => {
map.getCanvas().style.cursor = '';
});
//------------------------------------------------------------------------------------------------
// pris sur un exemple, permet de refaire un coup de propre
invalidation.then(() => map.remove());
//------------------------------------------------------------------------------------------------
// TEST API ADRESSE GOUV FR cf header pour les lib css et js et exemple pris sur
// https://github.com/webgeodatavore/photon-geocoder-autocomplete/blob/master/demo/photon-demo-maplibre.js
function AddDomControl(dom) {
this._dom = dom;
}
AddDomControl.prototype.onAdd = function (map) {
this._map = map;
this._container = document.createElement("div");
this._container.className = "maplibregl-ctrl photon-geocoder-autocomplete";
this._container.appendChild(this._dom);
return this._container;
};
AddDomControl.prototype.onRemove = function () {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
};
// Format result in the search input autocomplete
var formatResult = function (feature, el) {
var title = document.createElement("strong");
el.appendChild(title);
var detailsContainer = document.createElement("small");
el.appendChild(detailsContainer);
var details = [];
title.innerHTML = feature.properties.label || feature.properties.name;
var types = {
housenumber: "numéro",
street: "rue",
locality: "lieu-dit",
municipality: "commune",
};
if (types[feature.properties.type]) {
var spanType = document.createElement("span");
spanType.className = "type";
title.appendChild(spanType);
spanType.innerHTML = types[feature.properties.type];
}
if (
feature.properties.city &&
feature.properties.city !== feature.properties.name
) {
details.push(feature.properties.city);
}
if (feature.properties.context) {
details.push(feature.properties.context);
}
detailsContainer.innerHTML = details.join(", ");
};
// Function to show you can do something with the returned elements
function myHandler(featureCollection) {
console.log(featureCollection);
}
// We reused the default function to center and zoom on selected feature.
// You can make your own. For instance, you could center, zoom
// and add a point on the map
function onSelected(feature) {
console.log(feature);
map.setCenter(feature.geometry.coordinates);
map.setZoom(16);
}
// URL for API
var API_URL = "//api-adresse.data.gouv.fr";
// Create search by adresses component
var searchCcontainer = new Photon.Search({
resultsHandler: myHandler,
onSelected: onSelected,
placeholder: "Tapez une adresse",
formatResult: formatResult,
url: API_URL + "/search/?",
feedbackEmail: null,
});
map.addControl(new AddDomControl(searchCcontainer), "bottom-left");
//------------------------------------------------------------------------------------------------
// Ebauche de menu sur mesure
const mapMenuDiv = document.createElement('div');
mapMenuDiv.classList.add('mapFondDePlan');
//mapMenuDiv.innerHTML = '<strong>Fond de plan :</strong>';
map.getContainer().appendChild(mapMenuDiv);
// Ajouter un style pour le z-index
mapMenuDiv.style.position = 'absolute'; // new
mapMenuDiv.style.zIndex = '1000'; // new
/*const customDivHTML = `
<div>
<label for="orthoCheckbox">Orthophoto</label>
<input type="checkbox" id="orthoCheckbox" unchecked>
</div>
`;*/
const customDivHTML = `
<strong>Fond de plan :</strong>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="orthoCheckbox">
<label class="form-check-label" for="orthoCheckbox">Photos aériennes</label>
</div>
`;
mapMenuDiv.innerHTML += customDivHTML;
// doc à creuser
// https://docs.mapbox.com/mapbox-gl-js/example/toggle-layers/
document.getElementById('orthoCheckbox').addEventListener('change', function (e) {
e.preventDefault();
e.stopPropagation();
const valeurCheckbox = document.getElementById('orthoCheckbox').checked;
if (valeurCheckbox) {
map.setLayoutProperty('raster-ortho', 'visibility', 'visible');
map.setLayoutProperty('raster-planign', 'visibility', 'none');
} else {
map.setLayoutProperty('raster-ortho', 'visibility', 'none');
map.setLayoutProperty('raster-planign', 'visibility', 'visible');
}
});
// Brouillon pour ajouter mon menu layer !
const mapLayersDiv = document.createElement('div');
mapLayersDiv.classList.add('mapLayers');
//mapMenuDiv.innerHTML = '<strong>Fond de plan :</strong>';
map.getContainer().appendChild(mapLayersDiv);
// Ajouter un style pour le z-index
mapLayersDiv.style.position = 'absolute'; // new
mapLayersDiv.style.zIndex = '1000'; // new
const customDivHTML_2 = `
<button id="toggleMapMenuButton" class="btn btn-light">
<img src="images/icons/layers-half.svg" alt="Icône SVG" width="25" height="25">
</button>
<div id="layersList" ></div>
`
mapLayersDiv.innerHTML += customDivHTML_2;
const toggleableLayerIds= [
"communes",
"quartiers",
"quartier_line",
"parcelle_48_58",
"batiment_bdnb_48_58_corrige_pour_map",
"batiment_bdnb_recoq_hors48_58_hors_parcelle_48_58"];
const aliasLayerIds = {
"communes":"Communes",
"quartiers":"RecoQuartiers",
"quartier_line":"Contours",
"parcelle_48_58":"Parcelles reco",
"batiment_bdnb_48_58_corrige_pour_map":"Bâtis reco",
"batiment_bdnb_recoq_hors48_58_hors_parcelle_48_58":"Bâtis non reco"
};
// https://docs.mapbox.com/mapbox-gl-js/example/toggle-layers/
// Set up the corresponding toggle button for each layer.
for (const id of toggleableLayerIds) {
// Skip layers that already have a button set up.
if (document.getElementById(id)) {
continue;
}
// Create a link.
const link = document.createElement('a');
link.id = id;
link.href = '#';
//link.textContent = id;
link.textContent = aliasLayerIds[id] || id;
link.className = 'active';
// Show or hide layer when the toggle is clicked.
link.onclick = function (e) {
const clickedLayer = this.id;
e.preventDefault();
e.stopPropagation();
const visibility = map.getLayoutProperty(
clickedLayer,
'visibility'
) || 'visible';
// NB : je rajoute ou visible car la première c'est undefined ? ? ?
// Toggle layer visibility by changing the layout object's visibility property.
if (visibility === 'visible') {
map.setLayoutProperty(clickedLayer, 'visibility', 'none');
this.className = '';
} else {
this.className = 'active';
map.setLayoutProperty(
clickedLayer,
'visibility',
'visible'
);
}
};
const layers = document.getElementById('layersList');
layers.appendChild(link);
}
}
db = DuckDBClient.of({
referentiel_geographique : FileAttachment("data/referentiel_geographique.csv"),
donnees_combined : FileAttachment("data/donnees_combined.csv"),
dico_des_indicateurs : FileAttachment("data/dico_des_indicateurs.csv")
})
md`*${myId==0 ? 'Veuillez sélectionner un quartier en cliquant sur la carte':'Vous avez sélectionné le quartier n°'+myId}*`;
res_myId = db.sql`
SELECT id, echelle, indicateur, valeur, liblong, thematique, retenu_portail_numerique
FROM donnees_combined
left join dico_des_indicateurs on indicateur = libcourt
WHERE id =${myId} and echelle='recoquartier'
`
//res_myId
//res_myId.filter(d=>d.indicateur=='surface_ha').map(d=>d.valeur)[0]
md`
**Commune(s) :** ${res_myId.filter(d=>d.indicateur=='commune').map(d=>d.valeur)[0]}
**Typologie de commune :** ${res_myId.filter(d=>d.indicateur=='classification_ville_lib').map(d=>d.valeur)[0]}
**Typologie de quartier :** ${res_myId.filter(d=>d.indicateur=='lib_clust_bis').map(d=>d.valeur)[0]}
---
## <i class="bi bi-house-heart"></i> ${res_myId.filter(d=>d.indicateur=='somme_nlogh_reco').map(d=>d.valeur)[0]} logements de la reconstruction
- ${Math.floor(res_myId.filter(d=>d.indicateur=='part_nloghpp_reco').map(d=>d.valeur)[0]*100)}% de propriétaires
- ${Math.floor(res_myId.filter(d=>d.indicateur=='part_nlogh_orga_hlm_reco').map(d=>d.valeur)[0]*100)}% de logements sociaux
## <i class="bi bi-buildings"></i> ${res_myId.filter(d=>d.indicateur=='nb_batiment_reco').map(d=>d.valeur)[0]} bâtiments de la reconstruction
- ${Math.floor(res_myId.filter(d=>d.indicateur=='part_copro_reco').map(d=>d.valeur)[0]*100)}% de copropriété (*${Math.floor(res_myId.filter(d=>d.indicateur=='part_syndic_pro_copro_reco').map(d=>d.valeur)[0]*100)}% d'entre elles ont un syndic professionnel*)
## <i class="bi bi-person-arms-up"></i> ${res_myId.filter(d=>d.indicateur=='tmm').map(d=>d.valeur)[0]} personnes par logement
- ${Math.floor(res_myId.filter(d=>d.indicateur=='part_men_pauvre').map(d=>d.valeur)[0]*100)}% de ménages pauvres
## <i class="bi bi-tree"></i> ${res_myId.filter(d=>d.indicateur=='surface_ha').map(d=>d.valeur)[0]} hectares
- ${res_myId.filter(d=>d.indicateur=='taux_impermeabilisation').map(d=>d.valeur)[0]} % de taux d'imperméabilisation
- ${res_myId.filter(d=>d.indicateur=='densite_tous_logh_ha').map(d=>d.valeur)[0]} logements par hectare
---
[En savoir plus sur les indicateurs, leurs sources et millésimes](./methodo.html#les-indicateurs)
`
Inputs.table(res_myId.filter(d=>d.thematique=='Bâtiment' && d.retenu_portail_numerique), {
columns: ["liblong","valeur"],
header: {liblong: "Indicateur",valeur: "Valeur"},
width: {valeur: 100},
rows: 50,
maxWidth: 1000,
multiple: false,
layout: "fixed"
})
Inputs.table(res_myId.filter(d=>d.thematique=='Habitat' && d.retenu_portail_numerique), {
columns: ["liblong","valeur"],
header: {liblong: "Indicateur",valeur: "Valeur"},
width: {valeur: 100},
rows: 50,
maxWidth: 1000,
multiple: false,
layout: "fixed"
})
Inputs.table(res_myId.filter(d=>d.thematique=='Socio-économique' && d.retenu_portail_numerique), {
columns: ["liblong","valeur"],
header: {liblong: "Indicateur",valeur: "Valeur"},
width: {valeur: 100},
rows: 50,
maxWidth: 1000,
multiple: false,
layout: "fixed"
})
Inputs.table(res_myId.filter(d=>d.thematique=='Nature en ville et cadre de vie' && d.retenu_portail_numerique), {
columns: ["liblong","valeur"],
header: {liblong: "Indicateur",valeur: "Valeur"},
width: {valeur: 100},
rows: 50,
maxWidth: 1000,
multiple: false,
layout: "fixed"
})
Inputs.table(res_myId.filter(d=>d.thematique=='Sans objet' && d.retenu_portail_numerique), {
columns: ["liblong","valeur"],
header: {liblong: "Indicateur",valeur: "Valeur"},
width: {valeur: 150},
rows: 50,
maxWidth: 1000,
multiple: false,
layout: "fixed"
})
html`
<style>
.observablehq td, .observablehq th {
white-space: normal;
word-break: break-word;
}
</style>
`
md`
---
[En savoir plus sur les indicateurs, leurs sources et millésimes](./methodo.html#les-indicateurs)
`
res_geo = db.sql`SELECT * FROM referentiel_geographique WHERE id = ${myId}`
//Inputs.table(res_geo)
// Variable pour simplifier l'écriture
insee = res_geo[0].insee
commune = res_geo[0].commune
insee_dep = res_geo[0].insee_dep
myNomDep = res_geo[0].departement
res_tranche_d_age = db.sql`
SELECT
id,
case
when echelle='recoquartier' then 'RecoQuartier\nn°'||id
when echelle='multi_recoquartiers_region' then 'Ensemble des\nrecoquartiers'
end as echelle,
indicateur,
valeur
FROM donnees_combined
WHERE indicateur ilike '%ind%ans%'
and (
(echelle='recoquartier' and id =${myId})
or (echelle='multi_recoquartiers_region')
)
order by case
when echelle='recoquartier' then 1
when echelle='multi_recoquartiers_region' then 4
end desc
`;
//Inputs.table(res_tranche_d_age)
Plot.plot({
grid: true,
height: 350,
color: {
legend: true,
// https://observablehq.com/plot/features/scales#color-scale-options
scheme: "Paired",
},
y: {
tickFormat: "%"/*,
percent: true*/
},
style: {
fontSize: 12
},
marks: [
Plot.barY(res_tranche_d_age,{
x:"echelle",
y:"valeur",
sort:{x:"-x"},
// pour forcer à 100% même qd prb arrondi
// https://observablehq.com/plot/transforms/stack#stack-options
offset: "normalize",
fill:"indicateur",
tip: true
}),
Plot.textY(res_tranche_d_age,
Plot.stackY({
x: "echelle",
y: "valeur",
text: d => (d.valeur * 100).toFixed(0) +'%'
})
)
]
});
res_nb_piece = db.sql`
SELECT
id,
case
when echelle='recoquartier' then 'RecoQuartier\nn°'||id
when echelle='multi_recoquartiers_region' then 'Ensemble des\nrecoquartiers'
end as echelle,
indicateur,
valeur
FROM donnees_combined
WHERE indicateur ilike 'part_logh_%p%'
and (
(echelle='recoquartier' and id =${myId})
or (echelle='multi_recoquartiers_region')
)
order by case
when echelle='recoquartier' then 1
when echelle='multi_recoquartiers_region' then 4
end desc
`
Plot.plot({
grid: true,
height: 350,
color: {
legend: true,
// https://observablehq.com/plot/features/scales#color-scale-options
scheme: "BuPu",
},
y: {
tickFormat: "%"/*,
percent: true*/
},
style: {
fontSize: 12
},
marks: [
Plot.barY(res_nb_piece,{
x:"echelle",
y:"valeur",
sort:{x:"-x"},
// pour forcer à 100% même qd prb arrondi
// https://observablehq.com/plot/transforms/stack#stack-options
offset: "normalize",
fill:"indicateur",
tip: true
}),
Plot.textY(res_nb_piece,
Plot.stackY({
x: "echelle",
y: "valeur",
text: d => (d.valeur * 100).toFixed(0) +'%'
})
)
]
})
res_part_logh_rs = db.sql`
SELECT
id,
case
when echelle='recoquartier' then 'RecoQuartier\nn°'||id
when echelle='multi_recoquartiers_region' then 'Ensemble des\nrecoquartiers'
end as echelle,
indicateur,
valeur
FROM donnees_combined
WHERE indicateur = 'part_logh_rs'
and (
(echelle='recoquartier' and id =${myId})
or (echelle='multi_recoquartiers_region')
)
`
Plot.plot({
grid: true,
height: 350,
color: {
legend: true,
// https://observablehq.com/plot/features/scales#color-scale-options
scheme: "Greens",
},
y: {
tickFormat: "%"/*,
percent: true*/
},
style: {
fontSize: 12
},
marks: [
Plot.barY(res_part_logh_rs,{
x:"echelle",
y:"valeur",
sort:{x:"-x"},
// pour forcer à 100% même qd prb arrondi
// https://observablehq.com/plot/transforms/stack#stack-options
fill:"indicateur",
tip: true
}),
Plot.textY(res_part_logh_rs,
Plot.stackY({
x: "echelle",
y: "valeur",
text: d => (d.valeur * 100).toFixed(0) +'%'
})
)
]
})
md`
---
[En savoir plus sur les indicateurs, leurs sources et millésimes](./methodo.html#les-indicateurs)
`
md`
Ce module est à destination des habitués des indicateurs ! Explorez les indicateurs d'un quartier et visualisez comment il se situe par rapport aux autres quartiers ainsi qu'à ses référents géographiques (ensemble des recoquartiers agrégés au niveaux régional, départemental et communal).
`
res_ind = db.sql`SELECT * FROM dico_des_indicateurs where retenu_portail_numerique and libcourt not in ('classification_ville_lib','lib_clust_bis')`
//res_ind
viewof myInd = Inputs.select(res_ind, {value:"densite_tous_logh_ha",label:"Indicateur", format:d => `${d.liblong}`,width:800})
//myInd.libcourt
md`
### Quartier n° ${myId} : ${myInd.liblong}
Source : ${myInd.source}
Millésime : ${myInd.millesime}
`
res_myInd_allId = db.sql`
SELECT id, 'Quartier n°'||id as lib, echelle, indicateur,
case when valeur='' then null else valeur::float end as valeur
FROM donnees_combined
WHERE indicateur = ${myInd.libcourt}
and echelle='recoquartier'
and valeur is not null
ORDER by id::integer asc
`
// On fera un filter pour se mettre au niveau de 1 seul quartier
//Inputs.table(res_myInd_allId)
// REQUETE POUR AVOIR LA VALEUR DU REFERENT MULTIQ COMMUNE
res_myInd_multiq_commune = db.sql`
SELECT id, ${commune} as commune, echelle, indicateur,
case when valeur='' then null else valeur::float end as valeur
FROM donnees_combined
WHERE indicateur = ${myInd.libcourt}
and echelle ilike '%multi_recoquartiers%commune%'
and id=${insee.toString()}
and valeur is not null
`
//md`res_myInd_multiq_commune`
//Inputs.table(res_myInd_multiq_commune)
// REQUETE POUR AVOIR LA VALEUR DU REFERENT MULTIQ DEPARTEMENT
res_myInd_multiq_dep = db.sql`
SELECT id, ${myNomDep} as dep, echelle, indicateur,
case when valeur='' then null else valeur::float end as valeur
FROM donnees_combined
WHERE indicateur = ${myInd.libcourt}
and echelle ilike '%multi_recoquartiers%dep%'
and id=${insee_dep.toString()}
and valeur is not null
`
// REQUETE POUR AVOIR LA VALEUR DU REFERENT MULTIQ REGION
res_myInd_allId_ref_multiq_region = db.sql`
SELECT id, 'normandie' as region, echelle, indicateur,
case when valeur='' then null else valeur::float end as valeur
FROM donnees_combined
WHERE indicateur = ${myInd.libcourt}
and echelle ilike '%multi_recoquartiers%region%'
and valeur is not null
`
Plot.plot({
grid: true,
marginLeft: 150,
marginRight: 50,
//marginBottom: 50,
height: 350,
style: {
fontSize: 12
},
x:{
//domain: [0, 1],
//tickFormat: "%"
},
y:{
type: "band"
},
/*color: {
legend: true,
scheme:"cool"
},*/
marks: [
// un cadre
//Plot.frame(),
// Texte à gauche pour expliquer la lecture du graphe
Plot.text(['Recoquartier sélectionné → \n (point orange) '], {
x:0,
dy: -120,
fontSize:12,
fill:"black",
textAnchor: "end"
}),
/*
Plot.text(['→ \n Référents bâti 48-58 → \n → '], {
x:0,
dy: -80,
fontSize:12,
fill:"black",
textAnchor: "end"
}),
*/
Plot.text(['répartition → \n (1 point par recoquartier) '], {
x:0,
dy: 4,
fontSize:12,
fill:"black",
textAnchor: "end"
}),
/*
Plot.text(['→ \n Référents bâti toute période → \n → '], {
x:0,
dy: 100,
fontSize:12,
fill:"black",
textAnchor: "end"
}),
*/
//Plot.axisY({textAnchor: "start", fill: "black", dx: 14})
Plot.axisX({
anchor: "top",
//tickFormat: "%"
}),
Plot.axisX({
anchor: "bottom",
label: null,
//tickFormat: "%"
}),
// REFERENT RECOQ
// une barre verticale
Plot.ruleX(res_myInd_allId_ref_multiq_region.map(d => d.valeur), {
stroke: "purple",
opacity: 0.25
}),
Plot.dot(res_myInd_allId_ref_multiq_region,{
x:"valeur",
//y:'lib',
r:4,
stroke:"purple",
// symbole dispo dans https://observablehq.com/plot/marks/dot
symbol:"times"
}),
Plot.text(res_myInd_allId_ref_multiq_region, {
x: "valeur",
//y: "lib",
//text: (d) => 'Moyenne '+d.id+' \n'+ (d.valeur * 100).toFixed(0) +'%',
text: (d) => `${d.region} ${(d.valeur !== null && d.valeur !== undefined) ? d.valeur.toFixed(2) : d.valeur}`,
dy: -94,
dx:10,
textAnchor: "end",
fontSize:12,
fontStyle:"italic",
fill:"purple",
stroke:"white",
}),
Plot.text(res_myInd_multiq_dep, {
x: "valeur",
//y: "lib",
//text: (d) => 'Moyenne '+d.id+' \n'+ (d.valeur * 100).toFixed(0) +'%',
text: (d) => `${d.dep} ${(d.valeur !== null && d.valeur !== undefined) ? d.valeur.toFixed(2) : d.valeur}`,
dy: -80,
dx:10,
textAnchor: "end",
fontSize:12,
fontStyle:"italic",
fill:"blue",
stroke:"white",
}),
Plot.text(res_myInd_multiq_commune, {
x: "valeur",
//y: "lib",
//text: (d) => 'Moyenne '+d.id+' \n'+ (d.valeur * 100).toFixed(0) +'%',
text: (d) => `${d.commune} ${(d.valeur !== null && d.valeur !== undefined) ? d.valeur.toFixed(2) : d.valeur}`,
dy: -68,
dx:10,
textAnchor: "end",
fontSize:12,
fontStyle:"italic",
fill:"green",
stroke:"white",
}),
// et la boîte à moustache
/*Plot.boxX(res_myInd_allId,{
x:"valeur",
//y:"echelle",
fill:"grey",
opacity: 0.5
}),*/
// avec DODGEY on peut refaire comme un violin plot
Plot.dot(res_myInd_allId,
Plot.dodgeY("middle", {
x: "valeur",
//y:"echelle",
r: 2,
//fill:"valeur",
//stroke:"valeur",
fill:"grey",
stroke:"black",
opacity: 0.1,
dy:-152
})
),
// VALEUR RECOQ
// le même point de valeur de quartier mais sur la ligne avec le dodge
Plot.dot(res_myInd_allId.filter(d => d.id == myId),{
x:"valeur",
//y:"echelle",
r:3.5,
fill:"orange",
stroke:"black"
}),
// une barre verticale
Plot.ruleX(res_myInd_allId.filter(d => d.id == myId).map(d => d.valeur), {
stroke: "orange",
opacity: 0.5
}),
// un texte sur la première ligne
Plot.text(res_myInd_allId, {
// une méthode lisible pour mettre un filtre
filter:d => d.id == myId,
x: "valeur",
//y: "lib",
//text: (d) => 'Quartier n°'+d.id+' \n'+ (d.valeur * 100).toFixed(0) ' %', dy: -10,
text: (d) => `Quartier n°${d.id} \n ${(d.valeur !== null && d.valeur !== undefined) ? d.valeur.toFixed(2) : d.valeur}`,
lineAnchor: "bottom",
fontSize:14,
fill:"white",
stroke:"black",
strokeWidth:5,
dy:-110
}),
]
});
md`
---
[En savoir plus sur les indicateurs, leurs sources et millésimes](./methodo.html#les-indicateurs)
`
md`
Ce module a vocation à synthétiser les différents indicateurs traités sur un quartier, avec une gradation selon le niveau d'importance à traiter des enjeux associés. Il n’a pas vocation à se substituer à un diagnostic. Actuellement, la méthode de *scoring* représentée ci-dessous est une moyenne de quelques indicateurs clefs transformés en note selon la méthode des ruptures naturelles sur l'ensemble des quartiers (classements relatifs des quartiers entre eux).
[Découvrir les fiches actions <i class="bi bi-tools"></i> .](./fiches_actions.html)
`
data = FileAttachment("data/donnees_enjeux.csv").csv({ typed: true })
mean_score_socio_eco = data.filter( d => d.id == myId).map(d=>d.mean_score_socio_eco)[0]
mean_score_nature = data.filter( d => d.id == myId).map(d=>d.mean_score_nature)[0]
mean_score_habitat = data.filter( d => d.id == myId).map(d=>d.mean_score_habitat)[0]
mean_score_batiment = data.filter( d => d.id == myId).map(d=>d.mean_score_batiment)[0]
filtered_scores_t2 = [
{"indicateur": "Socio-économique", "valeur": mean_score_socio_eco, img:"./images/icons/person-arms-up.svg"},
{"indicateur": "Nature en ville et cadre de vie", "valeur": mean_score_nature, img:"./images/icons/tree.svg"},
{"indicateur": "Habitat", "valeur": mean_score_habitat, img:"./images/icons/house-heart.svg"},
{"indicateur": "Bâtiment", "valeur": mean_score_batiment, img:"./images/icons/buildings.svg"}
]
Plot.plot({
height: 300,
marginLeft: 200,
marginTop: 50,
style: {
fontSize: 14
},
//axis: null,
//grid: true,
x: {
axis: "top",
label: "Note d'enjeux",
},
y: {
type: "band",
label: null
},
color: {
//type: "diverging",
scheme: "oranges",
domain: [1,5], // in age order
//range: [0,1],
//pivot: 2.5,
legend: true
},
marks: [
//Plot.ruleX([0]),
//Plot.frame(),
Plot.ruleY(filtered_scores_t2, {x: 5, y: "indicateur", stroke : "grey", opacity:0.05, strokeWidth: 8}),
Plot.ruleY(filtered_scores_t2, {x: "valeur", y: "indicateur", stroke : "valeur", strokeWidth: 8}),
Plot.dot(filtered_scores_t2, {x: "valeur", y: "indicateur", fill : "valeur", r: 18, tip: true}),
Plot.image(filtered_scores_t2, {
x: "valeur", y: "indicateur",
src: "img",
width: 20
})
]
})
md`
Sur le graphique ci-dessus, pour chacune thématique d'intervention, une note élevée traduira de manière visuelle une plus forte nécessité d'intervenir.
*A titre d'exemple pour le volet socio-économique : la note permet de jauger la fragilité des habitants du quartier et leur capacité à agir sur leur logement (note elevé = forte fragilité).*
`
md`
---
[En savoir plus sur les indicateurs, leurs sources et millésimes](./methodo.html#les-indicateurs)
`
md`
## Des quartiers à partir de 2 bâtiments et 12 logements
Les seuils retenus amènent une grande hétérogénéité des quartiers qui traduisent la diversité des situations. Ainsi, autour des quartiers emblématiques (groupe "Les Quartiers centres des grandes villes normandes"), il existe une multitude de quartiers de taille modeste mais qui représentent à l'échelle d'une commune ou des départements une part du parc qui mérite une attention toute particulière.
## Les logements individuels ne sont pas représentés
Seuls les immeubles de la reconstruction ont été étudiés dans cette étude. Si une commune contient uniquement du bâti reconstruit disséminé, elle n'est pas identifiée comme une commune reconstruite.
## Une identification des bâtiments qui peut présenter des erreurs
Pour une meilleure lecture des formes urbaines, les bâtiments sont présentés en 3D (extrusion selon la hauteur présente dans la BD TOPO en fonction de l'année de construction de la BDNB), certains bâtiments ne sont pas correctement identifiés dans ces sources ou y sont absents. Aussi, les contours des parcelles avec du bâti de la période 1948-1958 sont également représentés pour aider à la compréhension de la forme retenue des quartiers, qui s'appuie sur un faisceau d'indicateurs (cf partie [méthodo](./methodo.html)).
## Représentation des parcelles / bâtis isolés
Les bâtiments et parcelles de la période 1948-1958 qui ne respectent pas les seuils de "2 bâtiments, 12 logements" sont tout de même représentés. Certaines communes, comme Cherbourg, peuvent présenter un parc de la reconstruction disséminé sur la commune.
## Une précision limitée des indicateurs
La qualification des quartiers en typologie repose sur l'analyse d'une quarantaine d'indicateurs, tous ne sont pas précis à l'échelle géographique retenue (cf partie [méthodo](./methodo.html#les-indicateurs)) :
- les choix de classement reflètent parfois l'environnement immédiat du quartier,
- certains quartiers relèvent de plusieurs réalités et problématiques ou peuvent être atypiques, la classification en 7 groupes est à voir comme une grille de lecture à une échelle macro.
`
Incliner et faire tourner la carte
Maintenez enfoncé le bouton droit de la souris et déplacez la pour incliner le point de vue ou faire tourner la carte.
Sélectionner un quartier
Cliquez sur quartier (bouton gauche de la souris) pour en visualiser les indicateurs.
Une illustration pour bien comprendre la carte interactive