Show track properties (distance, elevation gain, duration)
Maptool::TrackStats computes these once, shared by Gpx and Fit so both track feature shapes carry the same properties regardless of source format. Hovering a track line (not clicking — these describe the whole line, not one point) shows them in a popup, same mechanism GPX waypoints already use.
This commit is contained in:
1 parent
fc174d4267
commit
a3c31ace06
5 files changed
+172
-21
No files matched your search
@@ -304,6 +304,7 @@ export class GpxPanelControl {
|
||||
});
|
||||
|
||||
this._registerPointInteractivity(`${id}-points`);
|
||||
this._registerLineInteractivity(`${id}-line`);
|
||||
}
|
||||
|
||||
// Registered once per layer id, ever — map.on(type, layerId, fn) is a
|
||||
@@ -330,6 +331,30 @@ export class GpxPanelControl {
|
||||
});
|
||||
}
|
||||
|
||||
// Hover rather than click: unlike a waypoint, a track's properties
|
||||
// (distance, elevation gain, duration) describe the whole line, not
|
||||
// whatever point along it happens to get clicked.
|
||||
_registerLineInteractivity(layerId) {
|
||||
if (this._interactiveLayers.has(layerId)) return;
|
||||
this._interactiveLayers.add(layerId);
|
||||
|
||||
let popup = null;
|
||||
this._map.on("mouseenter", layerId, (event) => {
|
||||
this._map.getCanvas().style.cursor = "pointer";
|
||||
const feature = event.features[0];
|
||||
if (!feature) return;
|
||||
popup = new Popup({ closeButton: false, closeOnClick: false, maxWidth: "260px" })
|
||||
.setLngLat(event.lngLat)
|
||||
.setHTML(trackPopupHtml(feature.properties))
|
||||
.addTo(this._map);
|
||||
});
|
||||
this._map.on("mouseleave", layerId, () => {
|
||||
this._map.getCanvas().style.cursor = "";
|
||||
popup?.remove();
|
||||
popup = null;
|
||||
});
|
||||
}
|
||||
|
||||
_removeLayers(path) {
|
||||
const id = sourceId(path);
|
||||
[`${id}-line`, `${id}-points`].forEach((layerId) => {
|
||||
@@ -462,6 +487,26 @@ function popupHtml({ name, description, time }) {
|
||||
return parts.length > 0 ? parts.join("") : "<em>No details</em>";
|
||||
}
|
||||
|
||||
// properties come straight from an uploaded GPX/FIT file — untrusted input.
|
||||
function trackPopupHtml({ name, distance_meters, elevation_gain_meters, duration_secs }) {
|
||||
const parts = [];
|
||||
if (name) parts.push(`<strong>${escapeHtml(name)}</strong>`);
|
||||
|
||||
const stats = [];
|
||||
if (typeof distance_meters === "number") stats.push(`${(distance_meters / 1000).toFixed(1)} km`);
|
||||
if (typeof elevation_gain_meters === "number") stats.push(`↑ ${Math.round(elevation_gain_meters)} m`);
|
||||
if (typeof duration_secs === "number") stats.push(formatDuration(duration_secs));
|
||||
if (stats.length > 0) parts.push(`<p>${stats.join(" · ")}</p>`);
|
||||
|
||||
return parts.length > 0 ? parts.join("") : "<em>No details</em>";
|
||||
}
|
||||
|
||||
function formatDuration(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
|
||||
Reference in new issue
Block a user