raw = {
// URLs of the GeoJSON files
const geojsonUrl1 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/alpes-nord-sommets.geojson";
const geojsonUrl2 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/alpes-sud-sommets.geojson";
const geojsonUrl3 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/pyrenees.geojson";
const geojsonUrl4 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/massif-central.geojson";
const geojsonUrl5 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/vosges.geojson";
const geojsonUrl6 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/autres-massifs.geojson";
const geojsonUrl7 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/autres.geojson";
const geojsonUrl8 = "https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/missed.geojson";
// Fetch
const fetchGeoJSON1 = fetch(geojsonUrl1).then(response => response.json());
const fetchGeoJSON2 = fetch(geojsonUrl2).then(response => response.json());
const fetchGeoJSON3 = fetch(geojsonUrl3).then(response => response.json());
const fetchGeoJSON4 = fetch(geojsonUrl4).then(response => response.json());
const fetchGeoJSON5 = fetch(geojsonUrl5).then(response => response.json());
const fetchGeoJSON6 = fetch(geojsonUrl6).then(response => response.json());
const fetchGeoJSON7 = fetch(geojsonUrl7).then(response => response.json());
const fetchGeoJSON8 = fetch(geojsonUrl8).then(response => response.json());
// When both GeoJSONs are fetched, combine them
let raw = Promise.all([
fetchGeoJSON1, fetchGeoJSON2,
fetchGeoJSON3, fetchGeoJSON4,
fetchGeoJSON5, fetchGeoJSON6,
fetchGeoJSON7, fetchGeoJSON8
]).then(values => {
const combinedFeatures = [
...values[0].features,
...values[1].features,
...values[2].features,
...values[3].features,
...values[4].features,
...values[5].features,
...values[6].features,
...values[7].features
]; // Combine features from both GeoJSON files
// Create a new GeoJSON object with combined features
const combinedGeoJSON = {
type: "FeatureCollection",
features: combinedFeatures
};
return combinedGeoJSON
}).catch(error => {
console.error("Error fetching or combining GeoJSONs:", error);
});
return raw
}function Range(range, options = {}) {
const [min, max] = range;
const {
className = "Range",
vertical = false,
label = null,
format = (x) => +x,
step = 1,
value = (min + max) / 2,
style = "",
labelStyle = "",
rangeStyle = "",
valueStyle = "",
unit = "" // New option for specifying a unit
} = options;
const rangeWrap = htl.html`<div class=${className} style="${style}"></div>`;
Object.assign(rangeWrap.style, {
display: "inline-flex",
position: "relative",
userSelect: "none",
alignItems: "center",
gap: "4px"
});
const valueDisplay = htl.html`<output style="${valueStyle}"></output>`;
Object.assign(valueDisplay.style, {
display: "inline-block"
});
const rangeInput = htl.html`<input type=range min=${min} max=${max} step=${step} value=${value} style=${rangeStyle}></input>`;
Object.assign(rangeInput.style, {
display: "inline-block"
});
if (vertical) {
rangeInput.setAttribute("orient", "vertical");
rangeInput.style.writingMode = "bt-lr";
rangeInput.style["-webkit-appearance"] = "slider-vertical";
rangeInput.style.width = "8px";
}
rangeWrap.append(rangeInput, valueDisplay);
if (label) {
const labelElement = htl.html`<label style=${labelStyle}>${label}</label>`;
rangeWrap.prepend(labelElement);
}
rangeInput.oninput = () => {
// Append the unit to the formatted number
valueDisplay.textContent = `${format(rangeInput.valueAsNumber)}${unit}`;
rangeWrap.value = rangeWrap.valueAsNumber = rangeInput.valueAsNumber;
rangeWrap.dispatchEvent(new CustomEvent("input"));
};
rangeInput.oninput(); // Initialize the displayed value
return rangeWrap;
}startStop = () => {
const buttons = html`
<form class="form-language-switcher">
<input value="🇫🇷" type="button" name="stop" id="button_french" class="button_lang current_lang">
<input value="🇬🇧 🇺🇸" type="button" name="start" id="button_english" class="button_lang">
</form>
`
buttons.value = "fr"
// French button
buttons.stop.onclick = event => {
buttons.value = "fr";
event.preventDefault(); // Don’t submit the form.
buttons.dispatchEvent(new CustomEvent("input"));
const french = document.getElementById("button_french") ;
const english = document.getElementById("button_english") ;
french.classList.add("current_lang")
english.classList.remove("current_lang")
}
// English button
buttons.start.onclick = event => {
buttons.value = "en";
event.preventDefault(); // Don’t submit the form.
buttons.dispatchEvent(new CustomEvent("input"));
const french = document.getElementById("button_french") ;
const english = document.getElementById("button_english") ;
english.classList.add("current_lang")
french.classList.remove("current_lang")
}
return buttons
}category_label = html`<span class="info-container">
<span style="color: orange" class="infopicto">${getIconSvg("info")}
<span class="tooltip-content">
${lang == 'fr' ? 'Version simplifiée de la classification UCI <br>Dénivelé:' : 'Simplified UCI classification<br>Gain:'}
${classificationDetails}
</span>
</span>
</span>`// Function to convert array of coordinate arrays to a FeatureCollection
function toGeoJSONFeatureCollection(data) {
const coordsArray = data.features.map(d => d.geometry.coordinates);
// Map each coordinate array to a GeoJSON Point feature
const features = coordsArray.flat().map(coords => ({
"type": "Feature",
"properties": {
// If you have properties to include, add them here
"altitude": coords[2] // Example property
},
"geometry": {
"type": "Point",
"coordinates": [coords[0], coords[1]] // Only longitude and latitude are used for Point
}
}));
// Return as a FeatureCollection
return {
"type": "FeatureCollection",
"features": features
};
}function normalize(gj) {
if (!gj || !gj.type) return null;
var types = {
Point: 'geometry',
MultiPoint: 'geometry',
LineString: 'geometry',
MultiLineString: 'geometry',
Polygon: 'geometry',
MultiPolygon: 'geometry',
GeometryCollection: 'geometry',
Feature: 'feature',
FeatureCollection: 'featurecollection'
};
var type = types[gj.type];
if (!type) return null;
if (type === 'geometry') {
return {
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: gj
}]
};
} else if (type === 'feature') {
return {
type: 'FeatureCollection',
features: [gj]
};
} else if (type === 'featurecollection') {
return gj;
}
}function fetchAndNormalizeGeoJSON(id, baseUrl) {
return fetch(`${baseUrl}/${id}.geojson`)
.then(response => response.json())
.then(normalize) // Assuming `normalize` function is available in the scope
.catch(error => {
console.error(`Failed to fetch or normalize GeoJSON for ID: ${id}`, error);
return null;
}) ;
}function mergeGeoJSONs(ids, baseUrl) {
const promises = ids.map(id => fetchAndNormalizeGeoJSON(id, baseUrl));
return Promise.all(promises)
.then(normalizedGeoJSONs => {
return normalizedGeoJSONs.reduce((output, geojson) => {
if (geojson && geojson.features) {
output.features.push(...geojson.features);
}
return output;
}, {
type: 'FeatureCollection',
features: []
});
});
}async function getCombinedGeoJSON(ids, baseUrl) {
try {
let combinedGeoJSON = await mergeGeoJSONs(ids, baseUrl);
console.log('Combined GeoJSON:', combinedGeoJSON);
// Handle the combined GeoJSON (e.g., display on map)
return combinedGeoJSON; // This will be a GeoJSON object
} catch (error) {
console.error('An error occurred while merging GeoJSONs:', error);
// Handle the error appropriately
}
}function rank_ascent(sommetsGeoJson, variableToPlot, selectedClimb, lang = "fr"){
const namelabel = (lang == "fr") ? "Ascension" : "Ascent"
const gainlabel = (lang == "fr") ? "Dénivelé positif" : "Elevation gain"
const massiflabel = (lang == "fr") ? "Massif" : "Mountain side"
const percentlabel = (lang == "fr") ? "Pente moyenne" : "Average slope"
let p = Plot.plot({
color: {
type: "sequential",
scheme: "Turbo"
},
marks: [
Plot.ruleX(
sommetsGeoJson.features.map(d=>d.properties),
{
x: variableToPlot, stroke: variableToPlot,
channels: {
nom: {label: namelabel, value: "nom"},
deniv: {label: gainlabel, value: (d) => `${d.denivellation}m`},
massif: {label: massiflabel, value: "massif"},
percent: {value: (d) => `${d.percent_moyen}%`, label: percentlabel},
},
tip: {
format: {
stroke : false
},
anchor: "top"
},
strokeOpacity: 0.2}
),
Plot.ruleX([selectedClimb], {x : variableToPlot, stroke: "red", strokeWidth: 2})
]
})
return p
}english = document.getElementById("button_english")
french = document.getElementById("button_french")
function change(lang_on, lang_off) {
if (!lang_on.classList.contains("current_lang")) {
// if the span that the user clicks on does not have the "current_lang" class
lang_on.classList.add("current_lang");
// add the "current_lang" class to it
lang_off.classList.remove("current_lang");
// remove the "current_lang" class from the other span
}
}
english.addEventListener("click", function() {
change(english, french);
}, false
);header_table_name = (lang == "fr") ? "Ascension" : "Ascent"
header_table_departure = (lang == "fr") ? "Départ" : "Start"
header_table_alt = (lang == "fr") ? "Altitude d'arrivée" : "Arrival height"
header_table_avgpercent = (lang == "fr") ? "Pente moyenne (%)" : "Average slope (%)"
header_table_maxpercent = (lang == "fr") ? "Pente maximale (%)" : "Maximum slope (%)"filter_checkbox_deniv = (lang == "fr") ? "Dénivelé" : "Height gain"
filter_checkbox_category = (lang == "fr") ? "Catégorie de l'ascension" : "Ascent category"
filter_checkbox_length = (lang == "fr") ? `Longueur de l'ascension` : "Ascension length"
filter_checkbox_slope = (lang == "fr") ? "Pente moyenne" : "Average slope"
filter_multiselect_group = (lang == "fr") ? "Massif de l'ascension" : "Mountain chain"function availableLayers(L, map){
// Define tile layers
var defaultLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors'
}).addTo(map); // Set as the default layer
var topoLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: '© OpenStreetMap contributors, © OpenTopoMap (CC-BY-SA)'
});
var cycleLayer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '© OpenStreetMap contributors, © CyclOSM'
});
// Add the base layers to a layer control
var baseLayers = {
"Default": defaultLayer,
"Topographic": topoLayer,
"Cycling": cycleLayer
};
return baseLayers
}function fetchAndDisplayAscentRoute(map, feature, color = 'green') {
const id = feature.properties.id;
const polylineUrl = `https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/split/${id}.geojson`;
fetch(polylineUrl)
.then(response => response.json())
.then(data => {
// Modify the ascentTrackPolyline function or create a similar one to accept a color parameter
ascentTrackPolyline(map, data, feature, color);
})
.catch(error => console.error('Error loading the polyline GeoJSON:', error));
}function createIcon(feature, coordinates, color = null){
const color_value = (color == null) ? getColorForCategory(feature.properties.category) : color ;
const iconUrl = `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-${color_value}.png`;
var icon_color = new L.Icon({
iconUrl: iconUrl,
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
return L.marker(coordinates, {icon: icon_color})
}function ascentTrackPolyline(map, data, feature, color){
// Create a polyline from the GeoJSON and add it to the map
const polyline = L.geoJson(data, {
color: color, // You can customize the polyline's style here
weight: 3
}).addTo(map);
// Mouseover for ascent characteristics
polyline.on('mouseover', function() {
this.bindTooltip(
tooltipRoute(feature),
{permanent: false, direction: "top"}).openTooltip();
});
// Get mouse location on the map to interactively update a figure
const points = toGeoJSONFeatureCollection(data);
// polyline.on('mousemove', function(e) {
// var nearest = get_nearest_point(e, points)
// Update the mutable hover_alt to the altitude
// mutable hover_alt = nearest;
// });
return polyline
}function popUpSummit(feature, lang){
const AscentName = (lang == "fr") ? "Ascension" : "Ascent" ;
const departure = (lang == "fr") ? "Départ" : "Start" ;
const maxAlt = (lang == "fr") ? "Sommet" : "Summit" ;
const climbLength = (lang == "fr") ? "Longueur" : "Length" ;
const gain = (lang == "fr") ? "Dénivelé" : "Gain" ;
const averageSlope = (lang == "fr") ? "Pente moyenne" : "Average slope" ;
const maxSlope = (lang == "fr") ? "Pente maximale" : "Maximum slope" ;
const hrefText = (lang == "fr") ? "Voir sur" : "See on"
const popup = `
<b>${AscentName}</b>: ${feature.properties.nom}<br>
<b>${departure}</b>: ${feature.properties.depart} (${feature.properties.massif})<br>
<b>${maxAlt}</b>: ${feature.properties.alt}m<br>
<b>${climbLength}</b>: ${feature.properties.longueur}km<br>
<b>${gain}</b>: ${feature.properties.denivellation}m (${feature.properties.category})<br>
<b>${averageSlope}</b>: ${feature.properties.percent_moyen}%<br>
<b>${maxSlope}</b>: ${feature.properties.percent_maximal}%<br>
<a href="${feature.properties.href}" target="_blank">${hrefText} <code>https://cols-cyclisme.com/</code></a>
`
return popup
}function tooltipRoute(feature){
const departure = (lang == "fr") ? "Départ de" : "Start from" ;
const maxAlt = (lang == "fr") ? "altitude d'arrivée:" : "summit at" ;
const climbLength = (lang == "fr") ? "Ascension de" : "Climbing length is" ;
const averageSlope = (lang == "fr") ? "de moyenne" : "on average" ;
const denivelle = (lang == "fr") ? "D+" : "gain" ;
const maxSlope = (lang == "fr") ? "Pente maximale" : "Maximum slope" ;
const popup = `
<b>${feature.properties.nom}</b><br>
${departure} ${feature.properties.depart} (${maxAlt} ${feature.properties.altitude}m)<br>
${climbLength} ${feature.properties.longueur}km, ${feature.properties.percent_moyen}% ${averageSlope} (${feature.properties.denivellation}m ${denivelle})<br>
<i>${maxSlope}: ${feature.properties.percent_maximal}%</i>
`
return popup
}function container_ascent(feature, lang, width){
const imageInitial = feature.properties.profil_image_url ;
const imageURL = imageInitial.replace(
'profils.cols-cyclisme.com',
'minio.lab.sspcloud.fr/lgaliana/cyclisme/data/images'
) ;
let container ;
if (width > 400){
container = `
<div id="container-statistics" style="display: flex; flex-direction: column;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; width: 100%;">
<div style="width: 20%;">
${popUpSummit(feature, lang, width > 400)}
</div>
<div id="ascent-image" style="width: 80%;">
<img src="${imageURL}" alt="" style="max-width: 100%; height: auto; max-height:${screenHeight*0.5};">
</div>
</div>
<div id="stats-denivellation" style="width: 100%;">
<!-- Content for stats-denivellation goes here -->
</div>
</div>
` ;
} else{
container = `
<div id="container-statistics" style="display: flex; flex-direction: column;">
<div style="width: 100%;">
${popUpSummit(feature, lang, width > 400)}
</div>
<div id="stats-denivellation" style="width: 100%;">
<!-- Content for stats-denivellation goes here -->
</div>
<div id="ascent-image" style="width: 80%;">
<img src="${imageURL}" alt="" style="max-width: 100%; height: auto; max-height:${screenHeight*0.5};">
</div>
</div>
`
}
return container
}L.GeocoderBAN = L.Control.extend({
options: {
position: 'topleft',
style: 'control',
placeholder: 'adresse',
resultsNumber: 7,
collapsed: true,
serviceUrl: 'https://api-adresse.data.gouv.fr/search/',
minIntervalBetweenRequests: 250,
defaultMarkgeocode: true,
autofocus: true
},
includes: L.Evented.prototype || L.Mixin.Events,
initialize: function (options) {
L.Util.setOptions(this, options);
},
onRemove: function (map) {
map.off('click', this.collapseHack, this);
},
onAdd: function (map) {
var className = 'leaflet-control-geocoder-ban';
var container = this.container = L.DomUtil.create('div', className + ' leaflet-bar');
var icon = this.icon = L.DomUtil.create('button', className + '-icon', container);
var form = this.form = L.DomUtil.create('div', className + '-form', container);
var input;
map.on('click', this.collapseHack, this);
icon.innerHTML = ' ';
icon.type = 'button';
input = this.input = L.DomUtil.create('input', '', form);
input.type = 'text';
input.placeholder = this.options.placeholder;
this.alts = L.DomUtil.create('ul',
className + '-alternatives ' + className + '-alternatives-minimized',
container);
L.DomEvent.on(icon, 'click', function (e) {
this.toggle();
L.DomEvent.preventDefault(e);
}, this);
L.DomEvent.addListener(input, 'keyup', this.keyup, this);
L.DomEvent.disableScrollPropagation(container);
L.DomEvent.disableClickPropagation(container);
if (!this.options.collapsed) {
this.expand();
if (this.options.autofocus) {
setTimeout(function () { input.focus(); }, 250);
}
}
if (this.options.style === 'searchBar') {
L.DomUtil.addClass(container, 'searchBar');
var rootEl = document.getElementsByClassName('leaflet-control-container')[0];
rootEl.appendChild(container);
return L.DomUtil.create('div', 'hidden');
} else {
return container;
}
},
minimizeControl: function () {
if (this.options.style === 'control') {
this.collapse();
} else {
// for the searchBar: only hide results, not the bar
L.DomUtil.addClass(this.alts, 'leaflet-control-geocoder-ban-alternatives-minimized');
}
},
toggle: function () {
if (this.style != 'searchBar') {
if (L.DomUtil.hasClass(this.container, 'leaflet-control-geocoder-ban-expanded')) {
this.collapse();
} else {
this.expand();
}
}
},
expand: function () {
L.DomUtil.addClass(this.container, 'leaflet-control-geocoder-ban-expanded');
if (this.geocodeMarker) {
this._map.removeLayer(this.geocodeMarker);
}
this.input.select();
},
collapse: function () {
L.DomUtil.removeClass(this.container, 'leaflet-control-geocoder-ban-expanded');
L.DomUtil.addClass(this.alts, 'leaflet-control-geocoder-ban-alternatives-minimized');
this.input.blur();
},
collapseHack: function (e) {
// leaflet bug (see #5507) before v1.1.0 that converted enter keypress to click.
if (e.originalEvent instanceof MouseEvent) {
this.minimizeControl();
}
},
moveSelection: function (direction) {
var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected');
var el;
if (!s.length) {
el = this.alts[direction < 0 ? 'firstChild' : 'lastChild'];
L.DomUtil.addClass(el, 'leaflet-control-geocoder-ban-selected');
} else {
var currentSelection = s[0];
L.DomUtil.removeClass(currentSelection, 'leaflet-control-geocoder-ban-selected');
if (direction > 0) {
el = currentSelection.previousElementSibling ? currentSelection.previousElementSibling : this.alts['lastChild'];
} else {
el = currentSelection.nextElementSibling ? currentSelection.nextElementSibling : this.alts['firstChild'];
}
}
if (el) {
L.DomUtil.addClass(el, 'leaflet-control-geocoder-ban-selected');
}
},
keyup: function (e) {
switch (e.keyCode) {
case 27:
// escape
this.minimizeControl();
L.DomEvent.preventDefault(e);
break;
case 38:
// down
this.moveSelection(1);
L.DomEvent.preventDefault(e);
break;
case 40:
// up
this.moveSelection(-1);
L.DomEvent.preventDefault(e);
break;
case 13:
// enter
var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected');
if (s.length) {
this.geocodeResult(s[0].geocodedFeatures);
}
L.DomEvent.preventDefault(e);
break;
default:
if (this.input.value) {
var params = {q: this.input.value, limit: this.options.resultsNumber};
var t = this;
if (this.setTimeout) {
clearTimeout(this.setTimeout);
}
// avoid responses collision if typing quickly
this.setTimeout = setTimeout(function () {
getJSON(t.options.serviceUrl, params, t.displayResults(t));
}, this.options.minIntervalBetweenRequests);
} else {
this.clearResults();
}
L.DomEvent.preventDefault(e);
}
},
clearResults: function () {
while (this.alts.firstChild) {
this.alts.removeChild(this.alts.firstChild);
}
},
displayResults: function (t) {
t.clearResults();
return function (res) {
if (res && res.features) {
var features = res.features;
L.DomUtil.removeClass(t.alts, 'leaflet-control-geocoder-ban-alternatives-minimized');
for (var i = 0; i < Math.min(features.length, t.options.resultsNumber); i++) {
t.alts.appendChild(t.createAlt(features[i], i));
}
}
};
},
createAlt: function (feature, index) {
var li = L.DomUtil.create('li', '');
var a = L.DomUtil.create('a', '', li);
li.setAttribute('data-result-index', index);
a.innerHTML = '<strong>' + feature.properties.label + '</strong>, ' + feature.properties.context;
li.geocodedFeatures = feature;
var clickHandler = function (e) {
this.minimizeControl();
this.geocodeResult(feature);
};
var mouseOverHandler = function (e) {
var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected');
if (s.length) {
L.DomUtil.removeClass(s[0], 'leaflet-control-geocoder-ban-selected');
}
L.DomUtil.addClass(li, 'leaflet-control-geocoder-ban-selected');
};
var mouseOutHandler = function (e) {
L.DomUtil.removeClass(li, 'leaflet-control-geocoder-ban-selected');
};
L.DomEvent.on(li, 'click', clickHandler, this);
L.DomEvent.on(li, 'mouseover', mouseOverHandler, this);
L.DomEvent.on(li, 'mouseout', mouseOutHandler, this);
return li;
},
geocodeResult: function (feature) {
this.minimizeControl();
this.markGeocode(feature);
},
markGeocode: function (feature) {
var latlng = [feature.geometry.coordinates[1], feature.geometry.coordinates[0]];
this._map.setView(latlng, 14);
this.geocodeMarker = new L.Marker(latlng)
.bindPopup(feature.properties.label)
.addTo(this._map)
.openPopup();
}
});getJSON = function (url, params, callback) {
var xmlHttp = new XMLHttpRequest()
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState !== 4) {
return
}
if (xmlHttp.status !== 200 && xmlHttp.status !== 304) {
return
}
callback(JSON.parse(xmlHttp.response))
}
xmlHttp.open('GET', url + L.Util.getParamString(params), true)
xmlHttp.setRequestHeader('Accept', 'application/json')
xmlHttp.send(null)
}table_selected = Inputs.table(
search,
{
columns: [
"nom",
"depart",
"longueur",
"denivellation",
"altitude",
"percent_moyen",
"percent_maximal"
],
header: {
nom: header_table_name,
depart: header_table_departure,
longueur: filter_checkbox_length,
denivellation: filter_checkbox_deniv,
altitude: header_table_alt,
percent_moyen: header_table_avgpercent,
percent_maximal: header_table_maxpercent
},
sort: "denivellation", reverse: true
})function messageRadius(foundfeatures){
if (foundfeatures == null){
return null
}
const message = (lang == "fr") ?
`2️⃣ Choisissez un rayon autour de <span class="foundplace">${foundfeatures.properties.label}</span> puis explorez la carte 👇️` :
`2️⃣ Choose a radius size around <span class="foundplace">${foundfeatures.properties.label}</span> and explore the map 👇️`
return message
}(width > 400) ?
html`
<div class="main-container">
<div id = "leafletmap" style="width: 40%;">${leafletmap}</div>
<div class="column spacer" style="width: 2%;">
<!-- Spacer column -->
</div>
<div style="width: 58%;">
<div id="available-ascent">
<div><i>${finding_ascent_label}</i></div>
<br>
${table_selected}
<br>
</div>
<div id="statsElement"><i>${no_ascent_label}</i></div>
</div>
</div>
` :
html`
<div id = "leafletmap" style="width: 100%;">${leafletmap}</div>
<div style="width: 100%;">
<div id="available-ascent">
<div><i>${finding_ascent_label}</i></div>
<br>
${table_selected}
<br>
</div>
<div id="statsElement"><i>${no_ascent_label}</i></div>
</div>
`function addGeoJSONToMap(geoJSON, map, pointsWithin) {
L.geoJSON(geoJSON, {
style: function(feature) {
// Define the style of the polyline here if needed
return { color: feature.properties.stroke || 'blue', weight: 4 };
},
onEachFeature: function(feature, layer) {
const id = feature.properties.url.replace(".gpx", '') ;
const summit_info = geo.filter(pointsWithin, (d) => d.id == id)
let selectedLayer = null;
layer.bindTooltip(
tooltipSummit(
summit_info.features[0]
),
{permanent: false, direction: 'auto'}
);
layer.bindPopup(
popUpSummit(summit_info.features[0], lang)
);
layer.on({
click: (event) => {
if (selectedLayer && selectedLayer != layer) {
selectedLayer.setStyle({
color: selectedLayer.feature.properties.stroke || 'blue', // Reset to original color
weight: 4 // Reset to original weight
});
}
mutable selectedClimb = summit_info.features[0] ;
// Set the clicked layer as the new selected layer
selectedLayer = layer;
layer.setStyle({
color: 'red', // Set color to red for the selected polyline
weight: 4
});
},
});
// Existing event listener for popup opens
layer.on('popupopen', function() {
const statsElement = document.getElementById('statsElement');
const availableAscents = document.getElementById('available-ascent');
// Update the content of 'statsElement' with the desired statistics from 'feature.properties'
statsElement.innerHTML = container_ascent(summit_info.features[0], lang, width) ;
availableAscents.innerHTML = "" ;
// Fetch route for selected ascent
fetchAndDisplayAscentRoute(map, summit_info.features[0], 'red');
});
layer.on('click', function() {
this.setStyle({
color: 'red', // Change polyline color on mouseover
weight: 4 // Increase polyline weight on mouseover
});
});
layer.on('mouseover', function() {
if (layer !== selectedLayer) {
this.setStyle({
color: 'yellow', // Change polyline color on mouseover
weight: 6 // Increase polyline weight on mouseover
});
}
});
layer.on('mouseout', function() {
if (layer !== selectedLayer) {
this.setStyle({
color: feature.properties.stroke || 'blue', // Reset color
weight: 2 // Reset weight
});
}
});
}
}).addTo(map);
}function getPointsWithinCircle(centerCircle, sommets){
var options = {steps: 64, units: 'kilometers'};
var circle = turf.circle(
centerCircle , radiusInner, options
);
// Perform spatial query with Turf.js using the drawn circle
var pointsWithin = turf.pointsWithinPolygon(sommets, circle);
return pointsWithin
}leafletmap = {
// Create a container element for leaflet map
let parent = DOM.element('div', { style: `width:${mapWidth}px;height:${mapHeight}px` });
yield parent;
var map = L.map(parent).setView([45.853459, 2.349312], 5);
const baseUrl = 'https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/split';
var radiustemp = radius ;
// Add the layer control to the map
let baseLayers = availableLayers(L, map) ;
L.control.layers(baseLayers).addTo(map);
var geocoder = L.geocoderBAN({
collapsed: false, style: 'searchBar'
}).addTo(map)
geocoder.markGeocode = function(feature) {
var latlng = [feature.geometry.coordinates[1], feature.geometry.coordinates[0]]
map.setView(latlng, 8)
mutable selectlatlng = latlng;
mutable foundfeatures = feature ;
}
if (selectlatlng != null){
console.log(selectlatlng)
// Center of the circle
var foundpoint = createIcon(
null, selectlatlng, "black"
).addTo(map);
foundpoint.bindPopup(foundfeatures.properties.label).openPopup() ;
// Circle
var circle = L.circle(selectlatlng, { radius: radiusInner*1000 }).addTo(map);
map.fitBounds(circle.getBounds())
// Detect points within circle
const pointsWithin = getPointsWithinCircle(selectlatlng.reverse(), sommets) ;
mutable aroundClimb = pointsWithin
// Add rot
// Add summit markers
var geojsonLayer = L.geoJson(
pointsWithin, {
onEachFeature: popEventSummit,
pointToLayer: function(feature, coordinates) {
return createIcon(feature, coordinates)
}
}).addTo(map);
// Add ascent routes tracks
const ids = pointsWithin.features.map(d=> d.properties.id) ;
getCombinedGeoJSON(ids, baseUrl)
.then(combinedGeoJSON => {
// Add each feature from the combined GeoJSON as a separate polyline
combinedGeoJSON.features.forEach((feature) => {
addGeoJSONToMap(feature, map, pointsWithin);
});
})
.catch(error => {
console.error('An error occurred:', error);
});
}
// Define a function that will be called for each feature in your GeoJSON layer
function popEventSummit(feature, layer) {
// Add mouseover event listener
layer.on('mouseover', function() {
const tooltipMessage = tooltipSummit(feature) ;
layer.bindTooltip(
tooltipMessage, {permanent: false, direction: "auto"}
).openTooltip();
});
// Create popup for ascent summit
layer.bindPopup(
popUpSummit(feature, lang)
);
layer.on({
click: whenClicked, //callback functions
});
// Existing event listener for popup opens
layer.on('popupopen', function() {
const statsElement = document.getElementById('statsElement');
const availableAscents = document.getElementById('available-ascent');
// Update the content of 'statsElement' with the desired statistics from 'feature.properties'
statsElement.innerHTML = container_ascent(feature, lang, width) ;
availableAscents.innerHTML = "" ;
// Fetch route for selected ascent
fetchAndDisplayAscentRoute(map, feature, 'red');
});
}
}