1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
import { clamp } from "./ride-detail-logic.js";
export class RideDetailMap {
constructor({ leaflet, element, route, points, climbs, colors }) {
this.leaflet = leaflet;
this.points = points;
this.colors = colors;
this.map = leaflet.map(element);
const tiles = leaflet.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
});
tiles.addTo(this.map);
const routeLayer = leaflet
.geoJSON(route, {
style: { color: colors.accent, weight: 4, opacity: 0.9 },
})
.addTo(this.map);
const bounds = routeLayer.getBounds();
if (bounds.isValid()) this.map.fitBounds(bounds, { padding: [24, 24], maxZoom: 15 });
this.climbLayers = climbs.map((climb) => this.createClimbLayer(climb));
this.routeCursor = leaflet
.circleMarker([points[0].latitude, points[0].longitude], {
color: colors.forest,
fillColor: colors.accent,
fillOpacity: 0,
opacity: 0,
radius: 7,
weight: 3,
interactive: false,
})
.addTo(this.map);
}
createClimbLayer(climb) {
if (!this.isValidBounds(climb)) return null;
const coordinates = this.points.slice(climb.startIndex, climb.endIndex + 1).map((point) => [point.latitude, point.longitude]);
return this.leaflet
.polyline(coordinates, {
color: this.colors.climbRoute,
weight: 7,
opacity: 0.65,
lineCap: "round",
lineJoin: "round",
interactive: false,
})
.addTo(this.map);
}
isValidBounds(bounds) {
return (
bounds &&
Number.isInteger(bounds.startIndex) &&
Number.isInteger(bounds.endIndex) &&
bounds.startIndex >= 0 &&
bounds.endIndex < this.points.length &&
bounds.startIndex < bounds.endIndex
);
}
updateClimbLayer(index, bounds, active) {
const layer = this.climbLayers[index];
if (!layer) return;
if (!this.isValidBounds(bounds)) {
layer.setLatLngs([]);
return;
}
layer.setLatLngs(this.points.slice(bounds.startIndex, bounds.endIndex + 1).map((point) => [point.latitude, point.longitude]));
layer.setStyle({ weight: active ? 9 : 7, opacity: active ? 1 : 0.65 });
}
nearestPointIndex(latitude, longitude) {
let nearestIndex = 0;
let nearestDistance = Infinity;
for (let index = 0; index < this.points.length; index++) {
const point = this.points[index];
const distance = this.leaflet.latLng(point.latitude, point.longitude).distanceTo([latitude, longitude]);
if (distance < nearestDistance) {
nearestIndex = index;
nearestDistance = distance;
}
}
return nearestIndex;
}
showPoint(index) {
const point = this.points[clamp(index, 0, this.points.length - 1)];
this.routeCursor.setLatLng([point.latitude, point.longitude]);
this.routeCursor.setStyle({ opacity: 1, fillOpacity: 0.9 });
}
clearPoint() {
this.routeCursor.setStyle({ opacity: 0, fillOpacity: 0 });
}
zoomToClimb(bounds) {
if (!this.isValidBounds(bounds)) return;
const climbPoints = this.points.slice(bounds.startIndex, bounds.endIndex + 1);
const mapBounds = this.leaflet.latLngBounds(climbPoints.map((point) => [point.latitude, point.longitude]));
if (mapBounds.isValid()) this.map.fitBounds(mapBounds, { padding: [32, 32], maxZoom: 15 });
}
onClick(listener) {
this.map.on("click", listener);
}
}
|