loko/streetup/common/static/common/base_map.js

332 lines
No EOL
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var map;
const popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
anchor: 'bottom-left'
});
/**
* Creates a handler to load the map with custom events.
*/
class MapLoader extends EventTarget {
constructor() {
super();
}
/**
* Loads the map wihtin the given container
*
* @param {string} url - name of the container
*/
async loadMap(containerId) {
try {
map = new maplibregl.Map({
container: containerId,
//style: 'https://demotiles.maplibre.org/style.json',
style: window.MAP_STYLE_LIGHT,
//style: 'https://tiles.openfreemap.org/styles/positron',
center: [4.3572, 50.8477],
zoom: 11,
minzoom: 0,
maxzoom: 24
});
map.on("load", () => {
this.dispatchEvent(new CustomEvent("mapLoaded", { detail: map }));
});
} catch (error) {
console.error("Erreur when loading the map :", error.message);
// Déclenche un événement pour signaler une erreur
this.dispatchEvent(new CustomEvent("mapError", { detail: error }));
}
}
}
const maploader = new MapLoader();
/**
* Creates a handler to load a GeoJSON with custom events.
*/
class GeojsonLoader extends EventTarget {
constructor() {
super();
}
/**
* Loads a GeoJSON from a URL and triggers an event once loaded.
*
* @param {string} url - The URL of the GeoJSON file.
*/
async loadGeojson(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur when loading GeoJSON : ${response.statusText}`);
}
var geojson = await response.json();
if (!geojson || geojson.type !== "FeatureCollection") {
throw new Error("The GeoJSON must be an object of type FeatureCollection.");
}
// Triggers a custom event with the loaded GeoJSON
this.dispatchEvent(new CustomEvent("geojsonLoaded", { detail: geojson }));
} catch (error) {
console.error("Error loading GeoJSON: ", error.message);
// Triggers an event to report an error
this.dispatchEvent(new CustomEvent("geojsonError", { detail: error }));
}
}
}
/**
* updates the map when geojson is loaded and map is loaded.
*/
async function handleGeojsonMapLoading(init=true, geojsonloader, geojsonsource, geojsonlayers=[], cluster=false) {
try {
var geojson = {};
if (init) {
// Wait for both events simultaneously
[geojson, map] = await Promise.all([
waitForEvent(geojsonloader, "geojsonLoaded"),
waitForEvent(maploader, "mapLoaded")
]);
}
else {
// Wait for new geojson loading
[geojson] = await Promise.all([
waitForEvent(geojsonloader, "geojsonLoaded"),
]);
}
// Update Map
if (map.getSource(geojsonsource)) {
map.getSource(geojsonsource).setData(reverseGeoJson(geojson));
}
else {
if (cluster) {
map.addSource(geojsonsource, {
'type': 'geojson',
'data': geojson,
cluster: true,
clusterMaxZoom: 24,
clusterRadius: 25,
clusterMinPoints: 1
});
}
else {
map.addSource(geojsonsource, {
'type': 'geojson',
'data': reverseGeoJson(geojson)
});
}
geojsonlayers.forEach(layer => {
if (!map.getLayer(layer.id)) {
map.addLayer(layer);
}
});
}
} catch (error) {
console.error("Error while updating map :", error.message);
}
}
// Function to update a table with the elements visible on the map
function filterTableByMapBounds(map, geojson_data, callback) {
// Récupérer les limites actuelles de la carte
const bounds = map.getBounds();
// Filtrer les points visibles dans les limites de la carte
const visibleFeatures = geojson_data.features.filter((feature) => {
if (feature.geometry && feature.geometry.type === 'Point') {
const [lon, lat] = feature.geometry.coordinates;
return bounds.contains([lon, lat]); // Vérifie si le point est dans les limites
}
return false; // Ignorer les autres types de géométries
});
callback(visibleFeatures);
}
/**
* Registers a mouseenter event on a specific MapLibre layer.
* Displays a popup with feature properties if the zoom level is above the threshold.
*
* @param {object} map - The MapLibre GL map instance.
* @param {string} layerId - The ID of the layer to listen for mouseenter events.
* @param {object} popup - The MapLibre popup instance to display information.
* @param {string} property - The feature property to display in the popup.
* @param {number} zoomThreshold - The minimum zoom level required to show the popup.
*/
function registerLayerMouseEnter(map, layerId, popup, property, zoomThreshold = 13) {
map.on('mouseenter', layerId, (e) => {
const currentZoom = map.getZoom();
if (currentZoom > zoomThreshold) {
// Change the cursor style to indicate interactivity
map.getCanvas().style.cursor = 'pointer';
const description = e.features[0].properties[property] || 'No data';
// const coordinates = e.features[0].geometry.coordinates.slice();
// // Handle longitude
// while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
// coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
// }
const coordinates = e.lngLat;
// Set and display the popup
popup.setLngLat(coordinates).setHTML(description).addTo(map);
}
});
}
/**
* Registers a mouseleave event on a specific MapLibre layer.
* Resets the cursor and removes the popup when the mouse leaves the layer.
*
* @param {object} map - The MapLibre GL map instance.
* @param {string} layerId - The ID of the layer to listen for mouseleave events.
* @param {object} popup - The MapLibre popup instance to be removed.
*/
function registerLayerMouseLeave(map, layerId, popup) {
map.on('mouseleave', layerId, () => {
// Reset cursor style
map.getCanvas().style.cursor = '';
// Remove popup from the map
popup.remove();
});
}
/**
* Registers a mousemove event on a specific MapLibre layer.
* Displays a popup with feature properties if the zoom level is above the threshold.
*
* @param {object} map - The MapLibre GL map instance.
* @param {string} layerId - The ID of the layer to listen for mouseenter events.
* @param {object} popup - The MapLibre popup instance to display information.
* @param {string} property - The feature property to display in the popup.
* @param {number} zoomThreshold - The minimum zoom level required to show the popup.
*/
function registerLayerMouseMove(map, layerId, popup, property, zoomThreshold = 13) {
map.on('mousemove', layerId, (e) => {
const currentZoom = map.getZoom();
if (currentZoom > zoomThreshold) {
map.getCanvas().style.cursor = 'pointer';
const coordinates = e.lngLat;
const description = e.features[0].properties[property] || 'No data';
popup.setLngLat(coordinates).setHTML(description);
if (!popup.isOpen()) {
popup.addTo(map);
}
}
});
}
/**
* Registers a click event on a specific table for zooming to a location on the map.
*
* @param {string} tableSelector - The selector for the table(s) containing zoom buttons (e.g., '#intersections-table').
* @param {string} buttonClass - The class of the zoom button (e.g., '.zoom-btn').
* @param {object} map - The MapLibre GL map instance.
* @param {number} zoomLevel - The zoom level to apply when flying to the location (default: 16).
*/
function registerZoomAction(tableSelector, buttonClass, map, zoomLevel = 16) {
$(tableSelector).on('click', buttonClass, function () {
const lon = parseFloat($(this).data('lon'));
const lat = parseFloat($(this).data('lat'));
if (!isNaN(lon) && !isNaN(lat)) {
map.flyTo({
center: [lon, lat],
zoom: zoomLevel,
essential: true // Ensures smooth animation
});
}
});
}
/**
* Highlights a specific feature on a MapLibre layer while removing the highlight from the previous feature.
*
* @param {object} map - The MapLibre GL map instance.
* @param {string} sourceId - The source ID containing the features.
* @param {string} featureId - The ID of the feature to highlight.
* @param {object} highlightState - Object to track the currently highlighted feature.
*/
function highlightMapFeature(map, sourceId, featureId, highlightState) {
if (!highlightState || !map) return;
// Remove highlight from the previous feature
if (highlightState.previousId !== null && highlightState.previousId !== undefined) {
try {
map.setFeatureState(
{ source: sourceId, id: highlightState.previousId },
{ highlighted: false }
);
} catch (e) {
// Ignore feature state errors if layer/source is not present
}
}
// Set highlight on the newly selected feature
if (featureId !== null && featureId !== undefined) {
highlightState.previousId = featureId;
try {
map.setFeatureState(
{ source: sourceId, id: featureId },
{ highlighted: true }
);
} catch (e) {
// Ignore feature state errors if layer/source is not present
}
} else {
highlightState.previousId = null;
}
}
function reverseGeoJson(data) {
if (data.type === 'FeatureCollection' && Array.isArray(data.features)) {
return {
...data,
features: [...data.features].reverse()
};
}
return data;
}
// ---------------------------------------------------------------------------
// Companion controls (loaded separately, depend on maplibre-gl.js)
// ---------------------------------------------------------------------------
// address_search_control.js – AddressSearchControl
// A reusable MapLibre IControl that queries the internal autocomplete_street
// endpoint and places a draggable marker on the selected address.
// Usage: map.addControl(new AddressSearchControl({ autocompleteUrl, ... }), 'top-left');
// Events: 'address-selected' | 'marker-placed' | 'marker-moved' | 'marker-cleared'
// ---------------------------------------------------------------------------