summaryrefslogtreecommitdiff
path: root/web/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'web/frontend')
-rw-r--r--web/frontend/ride-detail-map.ts18
-rw-r--r--web/frontend/ride-detail.ts8
-rw-r--r--web/frontend/types.d.ts7
3 files changed, 29 insertions, 4 deletions
diff --git a/web/frontend/ride-detail-map.ts b/web/frontend/ride-detail-map.ts
index dcc2b60..81367f3 100644
--- a/web/frontend/ride-detail-map.ts
+++ b/web/frontend/ride-detail-map.ts
@@ -1,5 +1,5 @@
import { clamp } from "./ride-detail-logic.js";
-import type { ClimbBounds, RideMapColors, RideProfilePoint, RideRoute } from "./types.js";
+import type { ClimbBounds, RideMapColors, RideMapPass, RideProfilePoint, RideRoute } from "./types.js";
import type { CircleMarker, LeafletMouseEvent, Map as LeafletMap, Polyline } from "leaflet";
type LeafletApi = typeof import("leaflet");
@@ -10,6 +10,7 @@ interface RideDetailMapOptions {
route: RideRoute;
points: RideProfilePoint[];
climbs: ClimbBounds[];
+ passes: RideMapPass[];
colors: RideMapColors;
}
@@ -21,7 +22,7 @@ export class RideDetailMap {
private readonly climbLayers: (Polyline | null)[];
private readonly routeCursor: CircleMarker;
- constructor({ leaflet, element, route, points, climbs, colors }: RideDetailMapOptions) {
+ constructor({ leaflet, element, route, points, climbs, passes, colors }: RideDetailMapOptions) {
this.leaflet = leaflet;
this.points = points;
this.colors = colors;
@@ -37,6 +38,19 @@ export class RideDetailMap {
})
.addTo(this.map);
const bounds = routeLayer.getBounds();
+ const passIcon = leaflet.divIcon({
+ className: "ride-map-pass-icon",
+ html: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 19 10.5 6l3.25 5.5L16 8l5 11H3Z" fill="currentColor"/></svg>',
+ iconSize: [28, 28],
+ iconAnchor: [14, 14],
+ });
+ for (const pass of passes) {
+ const marker = leaflet.marker([pass.latitude, pass.longitude], { icon: passIcon, alt: pass.name || "Mountain pass" }).addTo(this.map);
+ const tooltip = document.createElement("span");
+ tooltip.textContent = `${pass.name || "Mountain pass"} | ${Math.round(pass.elevationM)} m`;
+ marker.bindTooltip(tooltip);
+ bounds.extend(marker.getLatLng());
+ }
if (bounds.isValid()) this.map.fitBounds(bounds, { padding: [24, 24], maxZoom: 15 });
this.climbLayers = climbs.map((climb) => this.createClimbLayer(climb));
this.routeCursor = leaflet
diff --git a/web/frontend/ride-detail.ts b/web/frontend/ride-detail.ts
index 7394d5a..045372e 100644
--- a/web/frontend/ride-detail.ts
+++ b/web/frontend/ride-detail.ts
@@ -3,17 +3,19 @@ import { RideBoundaryController } from "./ride-detail-boundaries.js";
import { RideProfileCanvas } from "./ride-detail-canvas.js";
import { RideDetailMap } from "./ride-detail-map.js";
import { formatDistance, formatElevation } from "./ride-detail-logic.js";
-import type { ClimbBounds, RideMapColors, RideProfile, RideProfileColors, RideProfilePoint, RideRoute } from "./types.js";
+import type { ClimbBounds, RideMapColors, RideMapPass, RideProfile, RideProfileColors, RideProfilePoint, RideRoute } from "./types.js";
export const mountRideDetail = (): void => {
const mapElement = document.getElementById("ride-map");
const routeElement = document.getElementById("ride-route");
const profileElement = document.getElementById("ride-profile");
+ const passesElement = document.getElementById("ride-passes");
const canvas = document.getElementById("ride-profile-chart");
const hoverOutput = document.getElementById("ride-profile-hover");
- if (!mapElement || !routeElement || !profileElement || !canvas || !hoverOutput) return;
+ if (!mapElement || !routeElement || !profileElement || !passesElement || !canvas || !hoverOutput) return;
const routeScript = routeElement as HTMLScriptElement;
const profileScript = profileElement as HTMLScriptElement;
+ const passesScript = passesElement as HTMLScriptElement;
const profileCanvasElement = canvas as HTMLCanvasElement;
const leaflet = window.L;
if (!leaflet) return;
@@ -49,6 +51,7 @@ export const mountRideDetail = (): void => {
const route = JSON.parse(routeScript.textContent ?? "") as RideRoute;
const profile = JSON.parse(profileScript.textContent ?? "") as RideProfile;
+ const passes = JSON.parse(passesScript.textContent ?? "") as RideMapPass[];
const points: RideProfilePoint[] = profile.points;
if (points.length === 0) return;
@@ -69,6 +72,7 @@ export const mountRideDetail = (): void => {
route,
points,
climbs: climbBounds,
+ passes,
colors: mapColors,
});
const officialProfileController = new OfficialClimbProfileController(points);
diff --git a/web/frontend/types.d.ts b/web/frontend/types.d.ts
index 0b59dac..07c8204 100644
--- a/web/frontend/types.d.ts
+++ b/web/frontend/types.d.ts
@@ -57,6 +57,13 @@ export interface RideRouteFeature {
properties?: Record<string, unknown>;
}
+export interface RideMapPass {
+ name: string;
+ elevationM: number;
+ latitude: number;
+ longitude: number;
+}
+
export interface ClimbBounds {
startIndex: number;
endIndex: number;