forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdroughtLayers.ts
More file actions
105 lines (96 loc) · 3.9 KB
/
Copy pathdroughtLayers.ts
File metadata and controls
105 lines (96 loc) · 3.9 KB
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
// Putting the drought bands on the map: the source, and the two imperative
// pokes the shell makes at a live map (#720).
//
// The same split closureLayers.ts makes against lib/closureStyle.ts:
// lib/droughtStyle.ts is the drawing spec and knows nothing about MapLibre,
// and this is the module that knows about MapLibre.
//
// Simpler than the closures in one way and harder in another. Simpler because
// the pipeline ships real polygons - there is no mile marker to turn into
// coordinates, so `lib/trailPosition.ts` is not involved and the features go
// on the source exactly as published. Harder because this layer has a switch,
// and a switch has to be instant: `setDroughtVisible` sets `visibility` on a
// layer that is always present rather than adding and removing it, so flipping
// it costs no source re-parse and no flicker.
import type { GeoJSONSourceSpecification } from '@maplibre/maplibre-gl-style-spec'
import type { GeoJSONSource, Map as MapLibreMap } from 'maplibre-gl'
import { DROUGHT_LAYER_ID } from '../lib/droughtStyle'
import { whenStyleReady } from './styleReady'
export const DROUGHT_SOURCE_ID = 'drought'
/**
* One published band: a class, its label, the trail miles at that class, and
* where it is.
*
* `trailMiles` is the mileage at EXACTLY this class rather than "this class
* or worse" - NDMC's polygons are mutually exclusive, measured, and
* pipeline/export_drought.py refuses to publish a release where they are not.
* The distinction is worth carrying into the type name's neighbourhood
* because reading it the other way overstated the trail under drought by 511
* miles the first time round.
*/
export interface DroughtBand {
dm: number
label: string
trailMiles: number
geometry: unknown
}
export interface DroughtFeatureCollection {
type: 'FeatureCollection'
features: Array<{
type: 'Feature'
geometry: unknown
properties: { dm: number; label: string; trail_miles: number }
}>
}
export function droughtFeatureCollection(
bands: readonly DroughtBand[],
): DroughtFeatureCollection {
return {
type: 'FeatureCollection',
features: bands.map((band) => ({
type: 'Feature',
geometry: band.geometry,
properties: { dm: band.dm, label: band.label, trail_miles: band.trailMiles },
})),
}
}
/** An empty source, filled once the artifact lands - same shape and same
* reason as `buildClosureSource`: a style that names a source it does not
* have would drop the WebGL context underneath the hiker. */
export function buildDroughtSource(): GeoJSONSourceSpecification {
return { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }
}
/** Pushes the bands onto the live map's source, and returns a detach. */
export function attachDroughtData(
map: MapLibreMap,
bands: readonly DroughtBand[],
): () => void {
return whenStyleReady(
map,
() => map.getSource(DROUGHT_SOURCE_ID) !== undefined,
() => {
// `getSource` answers with the union of every source kind, and only the
// GeoJSON one can be handed new data - same guard as attachClosureData.
const source = map.getSource<GeoJSONSource>(DROUGHT_SOURCE_ID)
if (source === undefined || typeof source.setData !== 'function') return
source.setData(droughtFeatureCollection(bands) as never)
},
'drought bands',
)
}
/** Shows or hides the band layer, and returns a detach.
*
* Separate from `attachDroughtData` because the two carry different rhythms:
* the data arrives once when the app comes online, and the switch moves
* whenever a hiker taps it. Folding them together would re-push 14 KB of
* polygons on every tap. */
export function setDroughtVisible(map: MapLibreMap, visible: boolean): () => void {
return whenStyleReady(
map,
() => map.getLayer(DROUGHT_LAYER_ID) !== undefined,
() => {
map.setLayoutProperty(DROUGHT_LAYER_ID, 'visibility', visible ? 'visible' : 'none')
},
'drought visibility',
)
}