mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
c1d55c463c
The Atlas country check timed out. It asked Dawarich for a year of visited cities in one request, and Dawarich computes that over every point in the window with nothing cached, so a dense recorder never got under the 15 s the server allows. The year is now asked for a month at a time, one request after another, and folded back together. The windows sit a second apart because Dawarich reads the bounds as inclusive. The recorded route only appeared after a reload. Every Dawarich call from the browser carried the shared 8 s timeout, so the browser gave up while the server carried on and cached the answer for the next page load. Calls that make the server talk to Dawarich now wait two minutes, the Atlas four. On the GL renderers the draw also gave up whenever isStyleLoaded() was false, which it is while any tile is still loading, and only retried on style.load. It now waits for idle. The hazard overlay had the same guard and gets the same wait. The pill shows a ring while the route loads, so a slow first fetch no longer looks like "done, nothing there". A collapsed day now takes its recorded route off the map, following the same folds the planner already applies to places, the road trip sidebar's included.
112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
import { useEffect } from 'react'
|
|
import type { Map } from 'mapbox-gl'
|
|
import type { GeoJSONSource } from 'mapbox-gl'
|
|
import type { DawarichTrack } from '@trek/shared'
|
|
import { DAWARICH_TRAIL_CASING, trailGeoJson, trailSegments } from './dawarichTrail'
|
|
|
|
/**
|
|
* The recorded route on the GL renderer — the imperative twin of
|
|
* `DawarichTrailLayer`.
|
|
*
|
|
* The ids start with `trek-` because `applySatellite` finds the first layer
|
|
* whose id carries one of TREK's own prefixes and inserts the satellite tile
|
|
* beneath it. A layer named `dawarich-trail` would end up *under* the
|
|
* satellite image and simply not be there when someone switched basemap on.
|
|
*
|
|
* Everything is rebuilt on `style.load`, because the whole GL map is torn down
|
|
* and reassembled when the provider, the style or the token changes, and a
|
|
* source added outside that effect does not survive it.
|
|
*/
|
|
const SOURCE = 'trek-dawarich-trail'
|
|
const CASING_LAYER = 'trek-dawarich-trail-casing'
|
|
const LINE_LAYER = 'trek-dawarich-trail-line'
|
|
|
|
export function useDawarichTrailGL(
|
|
map: Map | null,
|
|
ready: boolean,
|
|
track: DawarichTrack | null | undefined,
|
|
selectedDate?: string | null,
|
|
/** Insert beneath this layer, so the planned route stays on top. */
|
|
beforeId?: string,
|
|
/** Local dates whose day is folded away in the day plan. */
|
|
hiddenDates?: ReadonlySet<string> | null,
|
|
): void {
|
|
useEffect(() => {
|
|
if (!map || !ready) return
|
|
|
|
const data = trailGeoJson(trailSegments(track ?? null, selectedDate, hiddenDates))
|
|
|
|
// `isStyleLoaded()` does not mean "the style has loaded". It is false while
|
|
// any source still has tiles in flight, which is the usual state right after
|
|
// someone switches the overlay on and the map is still filling in. Giving up
|
|
// there left the line undrawn until something rebuilt the style, which in
|
|
// practice meant reloading the page. `idle` is the map saying everything it
|
|
// was fetching has arrived, so the draw waits for it once and tries again.
|
|
let waiting = false
|
|
const draw = () => {
|
|
if (!map.isStyleLoaded()) {
|
|
if (!waiting) {
|
|
waiting = true
|
|
map.once('idle', retry)
|
|
}
|
|
return
|
|
}
|
|
const existing = map.getSource(SOURCE) as GeoJSONSource | undefined
|
|
if (existing) {
|
|
// setData rather than remove-and-add: a rebuilt source flickers, and a
|
|
// refresh every two minutes would flicker every two minutes.
|
|
existing.setData(data as never)
|
|
return
|
|
}
|
|
map.addSource(SOURCE, { type: 'geojson', data: data as never })
|
|
const anchor = beforeId && map.getLayer(beforeId) ? beforeId : undefined
|
|
map.addLayer(
|
|
{
|
|
id: CASING_LAYER,
|
|
type: 'line',
|
|
source: SOURCE,
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
// theme-lint-disable — map paint
|
|
paint: { 'line-color': DAWARICH_TRAIL_CASING, 'line-width': 6, 'line-opacity': 0.55 },
|
|
},
|
|
anchor,
|
|
)
|
|
map.addLayer(
|
|
{
|
|
id: LINE_LAYER,
|
|
type: 'line',
|
|
source: SOURCE,
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: {
|
|
// Per-day colour off the feature, with the same fallback the GPX
|
|
// layer uses when a feature somehow carries none.
|
|
'line-color': ['coalesce', ['get', 'color'], DAWARICH_TRAIL_CASING],
|
|
'line-width': 3,
|
|
'line-opacity': 0.85,
|
|
// Dashed, so the recording is distinguishable from the planned
|
|
// route without relying on colour alone.
|
|
'line-dasharray': [2, 1.6],
|
|
},
|
|
},
|
|
anchor,
|
|
)
|
|
}
|
|
|
|
const retry = () => {
|
|
waiting = false
|
|
draw()
|
|
}
|
|
|
|
draw()
|
|
map.on('style.load', draw)
|
|
return () => {
|
|
map.off('style.load', draw)
|
|
map.off('idle', retry)
|
|
for (const layer of [LINE_LAYER, CASING_LAYER]) {
|
|
if (map.getLayer(layer)) map.removeLayer(layer)
|
|
}
|
|
if (map.getSource(SOURCE)) map.removeSource(SOURCE)
|
|
}
|
|
}, [map, ready, track, selectedDate, beforeId, hiddenDates])
|
|
}
|