Skip to content

Commit 7259a3e

Browse files
committed
show current position on elevation graph
1 parent 9bac521 commit 7259a3e

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

app/javascript/maplibre/controls/geolocate.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ export function initializeGeoLocateControl() {
3030
status('Error detecting location', 'warning')
3131
})
3232

33-
geolocate.on('geolocate', () => {
33+
geolocate.on('geolocate', (position) => {
3434
pitchCompassView()
35+
window.dispatchEvent(new CustomEvent('gps-position', {
36+
detail: { lng: position.coords.longitude, lat: position.coords.latitude }
37+
}))
3538
})
3639

3740
// follow mode
@@ -70,6 +73,7 @@ export function initializeGeoLocateControl() {
7073
wakeLock = null
7174
// probably mapbox draw bug: map can lose drag capabilities
7275
map.dragPan.enable()
76+
window.dispatchEvent(new CustomEvent('gps-position', { detail: null }))
7377
})
7478

7579
map.addControl(geolocate, 'top-right')

app/javascript/maplibre/feature/elevation.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { featureColor } from 'maplibre/styles/styles'
66
let marker
77
let syncChartToViewport
88
let canvasAbort
9+
let lastGpsPosition = null
910

1011
export async function showElevationChart (feature) {
1112
const chartElement = document.getElementById('route-elevation-chart')
@@ -53,8 +54,38 @@ export async function showElevationChart (feature) {
5354
filterToViewport(active, allLabels, allValues, allCoords)
5455
showElevationStats(active.values)
5556

57+
// GPS position indicator plugin
58+
const gpsPlugin = {
59+
id: 'gpsPosition',
60+
afterDraw (chart) {
61+
if (chart._gpsChartIndex == null) return
62+
const meta = chart.getDatasetMeta(0)
63+
const element = meta.data[chart._gpsChartIndex]
64+
if (!element) return
65+
66+
const ctx = chart.ctx
67+
const yAxis = chart.scales.y
68+
ctx.save()
69+
ctx.beginPath()
70+
ctx.moveTo(element.x, yAxis.top)
71+
ctx.lineTo(element.x, yAxis.bottom)
72+
ctx.lineWidth = 2
73+
ctx.strokeStyle = '#3b82f6'
74+
ctx.setLineDash([4, 4])
75+
ctx.stroke()
76+
77+
// Draw a dot at the elevation value
78+
ctx.beginPath()
79+
ctx.arc(element.x, element.y, 5, 0, Math.PI * 2)
80+
ctx.fillStyle = '#3b82f6'
81+
ctx.fill()
82+
ctx.restore()
83+
}
84+
}
85+
5686
let chart = new Chart(canvas, {
5787
type: 'line',
88+
plugins: [gpsPlugin],
5889
data: {
5990
labels: active.labels,
6091
datasets: [{
@@ -153,6 +184,25 @@ export async function showElevationChart (feature) {
153184
map.flyTo({ center: [coord[0], coord[1]], duration: 1000, curve: 0.3 })
154185
}, { signal })
155186

187+
// Update GPS position indicator on the chart
188+
window.addEventListener('gps-position', (event) => {
189+
if (!event.detail) {
190+
lastGpsPosition = null
191+
chart._gpsChartIndex = null
192+
chart.update('none')
193+
return
194+
}
195+
196+
lastGpsPosition = event.detail
197+
const idx = findNearestTrackIndex(event.detail, allCoords)
198+
if (idx === -1 || idx < active.firstIdx || idx > active.lastIdx) {
199+
chart._gpsChartIndex = null
200+
} else {
201+
chart._gpsChartIndex = idx - active.firstIdx
202+
}
203+
chart.update('none')
204+
}, { signal })
205+
156206
// Sync chart with map viewport — show only the track section currently visible
157207
syncChartToViewport = () => {
158208
if (!chart.canvas || !chart.canvas.isConnected) {
@@ -164,6 +214,16 @@ export async function showElevationChart (feature) {
164214
chart.data.labels = active.labels
165215
chart.data.datasets[0].data = active.values
166216
showElevationStats(active.values)
217+
218+
// Update GPS position indicator for new viewport
219+
if (lastGpsPosition) {
220+
const idx = findNearestTrackIndex(lastGpsPosition, allCoords)
221+
chart._gpsChartIndex = (idx !== -1 && idx >= active.firstIdx && idx <= active.lastIdx)
222+
? idx - active.firstIdx : null
223+
} else {
224+
chart._gpsChartIndex = null
225+
}
226+
167227
chart.update('none')
168228
}
169229

@@ -184,6 +244,21 @@ function computeDistances (coords) {
184244
return distances
185245
}
186246

247+
// Find the nearest track point to the given GPS position
248+
// Returns index if within 25m threshold, else -1
249+
function findNearestTrackIndex (lngLat, coords) {
250+
let minDist = Infinity
251+
let minIdx = -1
252+
for (let i = 0; i < coords.length; i++) {
253+
const d = distance(point([lngLat.lng, lngLat.lat]), point(coords[i]), { units: 'meters' })
254+
if (d < minDist) {
255+
minDist = d
256+
minIdx = i
257+
}
258+
}
259+
return minDist <= 25 ? minIdx : -1
260+
}
261+
187262
// Filter active data to only include points visible in the current map viewport
188263
function filterToViewport (active, allLabels, allValues, allCoords) {
189264
const bounds = map.getBounds()
@@ -198,10 +273,14 @@ function filterToViewport (active, allLabels, allValues, allCoords) {
198273
active.labels = allLabels
199274
active.values = allValues
200275
active.coords = allCoords
276+
active.firstIdx = 0
277+
active.lastIdx = allCoords.length - 1
201278
} else {
202279
active.labels = allLabels.slice(firstIdx, lastIdx + 1)
203280
active.values = allValues.slice(firstIdx, lastIdx + 1)
204281
active.coords = allCoords.slice(firstIdx, lastIdx + 1)
282+
active.firstIdx = firstIdx
283+
active.lastIdx = lastIdx
205284
}
206285
}
207286

0 commit comments

Comments
 (0)