viewof mode = Inputs.radio(new Map([[search_label_choice, "search"], [explorer_label_choice, "explorer"]]), {value: "explorer"})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
}availableHeight = window.innerHeight
screenHeight = 0.9*(availableHeight - document.getElementById("quarto-header").offsetHeight)
availableWidth = window.innerWidth
mapWidth = (width > 400) ? 0.4*width : 0.95*width
mapHeight = (width > 400) ? 0.85*screenHeight : 0.5*screenHeightfunction 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>`classificationDetails = `
<ul>
<li>Cat 4: 80 - 159m</li>
<li>Cat 3: 160 - 319m</li>
<li>Cat 2: 320 - 639m</li>
<li>Cat 1: 640 - 799m</li>
<li>HC: 800m and more </li>
</ul>
`function get_nearest_point(e, points){
// 'e.latlng' gives you the LatLng object of where the mouse currently is
const mouseLatLng = e.latlng;
const targetPoint = turf.point([mouseLatLng.lng, mouseLatLng.lat]);
var nearest = turf.nearestPoint(targetPoint, points);
return nearest
}// 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
);label_click_map = (lang == "fr") ? "Cliquer sur ce sommet pour en savoir plus" : "Click to learn more on that ascent"no_ascent_label = (lang == "fr") ? "Pas d'ascension sélectionnée, cliquer sur la carte pour afficher le profil de l'ascension." : "No selected ascent, click on the map to see ascent details."finding_ascent_label = (lang == "fr") ? "Ascensions correspondant à la recherche." : "Ascent corresponding to search criterium."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 (%)"search_label_choice = (lang == "fr") ? "Rechercher une ascension" : "Finding an ascent"
explorer_label_choice = (lang == "fr") ? "Laissez moi explorer !" : "Let me explore !"switch_label = (lang == "fr") ? "Switch to English version 🇬🇧 🇺🇸" : "Passer à la version française 🇫🇷"message_category = lang == 'fr' ? `Version simplifiée de la classification UCI ${classificationDetails}` : `Simplified UCI classification ${classificationDetails}`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"label_find_adress = (lang == "fr") ?
"1️⃣ Taper une adresse ou une localisation dans la barre de recherche 👇️" :
"1️⃣ Search for a location below 👇️"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 getColorForCategory(category) {
switch (category) {
case 'Cat 1':
return 'blue';
case 'Cat 2':
return 'green';
case 'Cat 3':
return 'yellow';
case 'Cat 4':
return 'orange';
case 'HC':
return 'red';
default:
return 'gray';
}
}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 tooltipSummit(feature){
const tooltipMessage = `<b>${feature.properties.nom}</b><br><i>${label_click_map}</i>`
return tooltipMessage
}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
}max_length = d3.max(
sommets.features.map(d=>d.properties.longueur)
)
max_denivelation = d3.max(
sommets.features.map(d=>d.properties.denivellation)
)
max_slope = d3.max(
sommets.features.map(d=>d.properties.percent_moyen)
)selectedRoute = d3.json(`https://minio.lab.sspcloud.fr/lgaliana/cyclisme/data/geojson/split/${selectedClimb.id}.geojson`)details_altitude = selectedRoute.features[0].geometry.coordinates.map((coord, index) => {
return { index: index, altitude: coord[2]}
});printed_points = {
let printed_points;
if (mode === "explorer") {
printed_points = geo.filter(sommets, (d) => filter_active_width(d, filter_choice));
} else {
const searchIds = search.map(s => s.id);
printed_points = geo.filter(sommets, (d) => searchIds.includes(d.id));
if (width < 400) {
console.log(printed_points)
printed_points = geo.properties.head({
x: printed_points, // a geojson
field: "nom", // a colname (string)
nb: 100 // default:10. Number of features to get.
})
}
}
return printed_points
}function sparkbar(max) {
return x => htl.html`<div class="sparkbar" style="width: ${100 * x / max}%;">${x.toLocaleString("en")}</div>`
}widget_to_print = {
switch (filter_choice) {
case filter_checkbox_length:
console.log("Choosing filter by length")
return Inputs.bind(
Range([1, max_length], {unit: "km"}),
viewof climb_length
)
case filter_checkbox_deniv:
console.log("Choosing filter by height gain")
return Inputs.bind(
Range([1, max_denivelation], {unit: "m", value: 1200}),
viewof climb_height
)
case filter_checkbox_category:
console.log("Choosing filter by category")
return Inputs.bind(
Inputs.checkbox(
categories,
{
sort: "descending",
value: ["HC", "Cat 1"],
//label: category_label
}
),
viewof category
)
case filter_checkbox_slope:
console.log("Choosing filter by average slope")
return Inputs.bind(
Range([0, max_slope], {unit: "%", step: 0.2}),
viewof climb_slope
)
case filter_multiselect_group:
console.log("Choosing filter by climbing group")
return Inputs.bind(
multiAutoSelect({
options: massifs.sort(),
placeholder: label_search_massif,
value: "Arves et Grandes Rousses, France"
}),
viewof selected_massif
)
default:
return false
}
}
function filter_active_width(d, widget) {
switch (widget) {
case filter_checkbox_deniv:
return d.denivellation > climb_height;
case filter_checkbox_length:
return d.longueur > climb_length;
case filter_checkbox_category:
return category.includes(d.category);
case filter_checkbox_slope:
return d.percent_moyen > climb_slope;
case filter_multiselect_group :
return selected_massif.includes(d.massif);
default:
return false; // or any default case handling you prefer
}
}viewof search = Inputs.search(
cols, {placeholder: label_search_widget, datalist: ["nom", "départ", "massif"]}
)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
})viewof filter_choice = Inputs.select(
[filter_checkbox_length, filter_checkbox_deniv, filter_checkbox_category, filter_checkbox_slope, filter_multiselect_group],
{value: filter_checkbox_deniv}
)viewof selected_massif = multiAutoSelect({
options: massifs.sort(),
placeholder: label_search_massif,
value: "Arves et Grandes Rousses, France"
})viewof climb_slope = Inputs.range([0, max_slope], {step: 0.2})
viewof climb_length = Inputs.range([0, max_length], {step: 1})
viewof climb_height = Inputs.range([0, max_denivelation], {step: 1})
viewof category = Inputs.checkbox(
categories,
{sort: "descending", value: ["HC", "Cat 1"]}
)html
`
<div class="lang-selector">${viewof lang}</div>
<div class="container">
<div class="mode-selector">${viewof mode}</div>
<div/>
`selectors = {
let selectors ;
if (mode == "explorer"){
selectors = html
`
<div class="container">
<div class="select-container">
<div>${viewof filter_choice}</div>
<div class="visible-widget">${widget_to_print}</div>
</div>
</div>
`
return selectors
}
selectors = html`<div class="search-widget">${viewof search}</div>`
return selectors
}(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>
`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([0, 0], 5);
// Add the layer control to the map
let baseLayers = availableLayers(L, map) ;
L.control.layers(baseLayers).addTo(map);
// 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');
});
}
var geojsonLayer = L.geoJson(printed_points, {
onEachFeature: popEventSummit,
pointToLayer: function(feature, coordinates) {
return createIcon(feature, coordinates)
}
}).addTo(map);
geojsonLayer.setStyle({
color: 'red',
weight: 5
});
map.fitBounds(geojsonLayer.getBounds())
}