Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: 3D buildings and terrain #142

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"chart.js": "^4.4.1",
"install": "^0.13.0",
"lodash": "^4.17.21",
"maplibre-gl": "^4.3.2",
"maplibre-gl": "^4.5.0",
"npm": "^10.5.0",
"proj4": "^2.10.0"
},
Expand Down
Binary file added src/html/img/layers/3D.BUILDINGS.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/html/img/layers/3D.TERRAIN.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/js/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import SignalementOSM from "./signalement-osm";
import Landmark from "./landmark";
import MapboxAccessibility from "./poi-accessibility";
import DOM from "./dom";
import ThreeD from "./three-d";

import LocationLayers from "./services/location-styles";
import compareLandmark from "./compare-landmark";
Expand Down Expand Up @@ -133,6 +134,10 @@ const addControls = () => {
});
Globals.compareLandmark = new compareLandmark(Globals.mapRLT1, Globals.mapRLT2, {});

// 3d
Globals.threeD = new ThreeD(map, {});
Globals.manager.layerCatalogue.add3DThematicLayers();

// contrôle filtres POI
Globals.poi = new POI(map, {});
Globals.poi.load() // promise !
Expand Down
2 changes: 2 additions & 0 deletions src/js/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const $selectOnMap = document.getElementById("selectOnMap");
const $geolocateBtn = document.getElementById("geolocateBtn");
const $backTopLeftBtn = document.getElementById("backTopLeftBtn");
const $compassBtn = document.getElementById("compassBtn");
const $threeDBtn = document.getElementById("threeDBtn");
const $layerManagerBtn = document.getElementById("layerManagerBtn");
const $sideBySideBtn = document.getElementById("sideBySideBtn");
const $compareMode = document.getElementById("compareMode");
Expand Down Expand Up @@ -136,4 +137,5 @@ export default {
$map,
$createCompareLandmarkBtn,
$compareLandmarkWindow,
$threeDBtn,
};
4 changes: 4 additions & 0 deletions src/js/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ let signalementOSM = null;
let landmark = null;
let compareLandmark = null;

// Global control 3d
let threeD = null;

// Global flag: is the device connected to the internet?
let online = true;

Expand Down Expand Up @@ -155,4 +158,5 @@ export default {
osmPoiAccessibility,
landmark,
compareLandmark,
threeD,
};
199 changes: 199 additions & 0 deletions src/js/immersive-position.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Copyright (c) Institut national de l'information géographique et forestière
*
* This program and the accompanying materials are made available under the terms of the GPL License, Version 3.0.
*/

import proj4 from "proj4";
proj4.defs("EPSG:2154","+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs");

const queryConfig = [
{
layer: "LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune",
attributes: ["nom", "population"],
},
{
layer: "LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:departement",
attributes: ["nom"],
},
{
layer: "BDTOPO_V3:parc_ou_reserve",
attributes: ["nature", "toponyme"],
geom_name: "geometrie",
},
{
layer: "BDTOPO_V3:foret_publique",
attributes: ["toponyme"],
geom_name: "geometrie",
around: 5,
},
{
layer: "BDTOPO_V3:toponymie_lieux_nommes",
attributes: ["graphie_du_toponyme"],
geom_name: "geometrie",
around: 5,
additional_cql: "AND nature_de_l_objet='Bois'",
},
{
layer: "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale",
attributes: ["essence"],
around: 5,
epsg: 2154,
},
{
layer: "RPG.LATEST:parcelles_graphiques",
attributes: ["code_cultu"],
around: 5,
},
{
layer: "BDTOPO_V3:zone_d_activite_ou_d_interet",
attributes: ["nature", "toponyme"],
geom_name: "geometrie",
around: 5,
additional_cql: "AND categorie='Culture et loisirs' AND nature IN ('Abri de montagne', 'Aire de détente', 'Camping', 'Construction', 'Ecomusée', 'Hébergement de loisirs', 'Monument', 'Musée', 'Office de tourisme', 'Parc de loisirs', 'Parc zoologique', 'Point de vue', 'Refuge', 'Vestige archéologique')",
},
{
layer: "BDTOPO_V3:cours_d_eau",
attributes: ["toponyme"],
geom_name: "geometrie",
around: 5,
},
{
layer: "BDTOPO_V3:plan_d_eau",
attributes: ["nature", "toponyme"],
geom_name: "geometrie",
around: 5,
},
];

/**
* Gestion de la "position immersive" avec des requêtes faites aux données autour d'une position
* @fires dataLoaded
*/
class ImmersivePosion extends EventTarget {
/**
* constructeur
* @param {*} options -
* @param {*} options.lat - latitude
* @param {*} options.lng - longitude
*/
constructor(options) {
super();
this.options = options || {
lat : 0,
lng : 0,
};
this.lat = this.options.lat;
this.lng = this.options.lng;

this.data = {};

// récupération des codes culture pour RPG
this.codes_culture = {};
fetch(
"https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=RPG.LATEST:codes_cultures&outputFormat=json&count=1000"
).then((resp) => resp.json()).then( (resp) => {
resp.features.forEach((feature) => {
this.codes_culture[feature.properties.code] = feature.properties.libelle;
});
});
}

/**
* Computes html string from availmable data
*/
computeHtml() {
const htmlTemplate = `
<p>Ville : ${this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune"] ? this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune"][0][0] : "chargement..."}, ${this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune"] ? this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune"][0][1] : "chargement..."} habitants</p>
<p>Département : ${this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:departement"] ? this.data["LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:departement"][0] : "chargement..."}</p>
<p>Parcs naturels : ${this.data["BDTOPO_V3:parc_ou_reserve"] ? JSON.stringify(this.data["BDTOPO_V3:parc_ou_reserve"]) : "aucun"}</p>
<p>Foret : ${this.data["BDTOPO_V3:foret_publique"] ? JSON.stringify(this.data["BDTOPO_V3:foret_publique"]) : "..."} ${this.data["BDTOPO_V3:toponymie_lieux_nommes"] ? JSON.stringify(this.data["BDTOPO_V3:toponymie_lieux_nommes"]) : "..."}</p>
<p>Essence principale : ${this.data["LANDCOVER.FORESTINVENTORY.V2:formation_vegetale"] ? JSON.stringify(this.data["LANDCOVER.FORESTINVENTORY.V2:formation_vegetale"]) : "..."}</p>
<p>Cultures : ${this.data["RPG.LATEST:parcelles_graphiques"] ? JSON.stringify(this.data["RPG.LATEST:parcelles_graphiques"]) : "..."}</p>
<p>ZAI loisirs : ${this.data["BDTOPO_V3:zone_d_activite_ou_d_interet"] ? JSON.stringify(this.data["BDTOPO_V3:zone_d_activite_ou_d_interet"]) : "..."}</p>
<p>Cours d'eau : ${this.data["BDTOPO_V3:cours_d_eau"] ? JSON.stringify(this.data["BDTOPO_V3:cours_d_eau"]) : "..."}</p>
<p>Plans d'eau : ${this.data["BDTOPO_V3:plan_d_eau"] ? JSON.stringify(this.data["BDTOPO_V3:plan_d_eau"]) : "..."}</p>
`;
return htmlTemplate;
}

/**
* Computes all data queries
*/
computeAll() {
queryConfig.forEach( (config) => {
this.#computeFromConfig(config);
});
}

/**
* Queries GPF's WFS for info defined in the config
*/
async #computeFromConfig(config) {
const result = await this.#computeGenericGPFWFS(
config.layer,
config.attributes,
config.around || 0,
config.geom_name || "geom",
config.additional_cql || "",
config.epsg || 4326,
);

this.data[config.layer] = result;

this.dispatchEvent(
new CustomEvent("dataLoaded", {
bubbles: true,
})
);
}

/**
* Computes data for a given layer of Geoplateforme's WFS
* @param {string} layer name of the WFS layer
* @param {Array} attributes list of strings of the relevant attributes to return
* @param {number} around distance around the point in km for the query, default 0
* @param {string} geom_name name of the geometry column, default "geom"
* @param {string} additional_cql cql filter needed other than geometry, e.g. "AND nature_de_l_objet='Bois'", default ""
* @param {number} epsg epsg number of the layer's CRS, default 4326
*/
async #computeGenericGPFWFS(layer, attributes, around=0, geom_name="geom", additional_cql="", epsg=4326) {
let coord1 = this.lat;
let coord2 = this.lng;
if (epsg !== 4326) {
[coord1, coord2] = proj4(proj4.defs("EPSG:4326"), proj4.defs(`EPSG:${epsg}`), [this.lng, this.lat]);
}
let cql_filter = `INTERSECTS(${geom_name},Point(${coord1}%20${coord2}))`;
if (around > 0) {
cql_filter = `DWITHIN(${geom_name},Point(${coord1}%20${coord2}),${around},kilometers)`;
}
if (additional_cql) {
cql_filter += ` ${additional_cql}`;
}

const results = await fetch(
`https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=${layer}&outputFormat=json&count=10&CQL_FILTER=${cql_filter}`
);
const json = await results.json();

const results_attributes = [];
json.features.forEach((feature) => {
const feature_attributes = [];
attributes.forEach((attribute) => {
// Cas particulier du RPG : décodage de la culture en libellé
if (layer === "RPG.LATEST:parcelles_graphiques" && attribute === "code_cultu" && Object.keys(this.codes_culture).length) {
feature.properties[attribute] = this.codes_culture[feature.properties[attribute]];
}
feature_attributes.push(feature.properties[attribute]);
});
if (attributes.length === 1) {
results_attributes.push(feature_attributes[0]);
} else {
results_attributes.push(feature_attributes);
}
});
return Array.from( new Set(results_attributes) );
}
}

export default ImmersivePosion;
2 changes: 1 addition & 1 deletion src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function app() {
attributionControl: false,
maxZoom: 21,
locale: "fr",
maxPitch: 45,
maxPitch: 60,
crossSourceCollisions: false,
});

Expand Down
Loading
Loading