forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstyle.ts
More file actions
137 lines (126 loc) · 4.58 KB
/
Copy pathstyle.ts
File metadata and controls
137 lines (126 loc) · 4.58 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Builds the MapLibre style for the trail map.
//
// Two rules from WIREFRAMES.md's "Trail line rendering — blazes" are
// load-bearing rather than cosmetic:
//
// 1. ONE `match` expression drives line-color across every trail source, so a
// source imported later inherits the rule instead of needing its own layer.
// That expression lives in lib/blaze.ts and is imported, never re-spelled.
//
// 2. Dash rhythm is a SECOND, hue-independent channel. Yellow, orange and red
// are nearly indistinguishable once desaturated by glare or a greyscale
// pass (WIREFRAMES.md `9d`), so rhythm - not hue - is what keeps them
// apart. MapLibre v6 types `line-dasharray` as `cross-faded-data-driven`,
// which means one data-driven expression covers every blaze in a single
// layer; this needs no per-blaze layer fan-out.
//
// Not handled here: blaze "Black" (code 8). WIREFRAMES.md's table describes it
// as "wide casing, no fill — drawn by absence," but the real data has zero
// Black features today and lib/blaze.ts has no colour for it, so it falls to
// the neutral-grey defensive fallback and logs a warning. Giving it a real
// treatment needs a design decision and at least one real feature to look at.
import type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
import { BLAZE_MATCH_EXPRESSION } from '../lib/blaze'
export const TOPO_SOURCE_ID = 'usgs-topo'
export const TRAILS_SOURCE_ID = 'trails'
export const TOPO_LAYER_ID = 'topo'
export const TRAIL_CASING_LAYER_ID = 'trail-casing'
export const BLAZE_LAYER_ID = 'trail-blaze'
// ODbL requires a visible "© OpenStreetMap". WIREFRAMES.md's map-corner mockup
// shows the shorthand "© OSM", but its own Assets section states the full form
// is required - the abbreviation does not satisfy the licence, so the full
// form is what ships.
export const ATTRIBUTION = 'USGS US Topo · © OpenStreetMap contributors'
export const BLAZE_LINE_WIDTH = 2
export const CASING_LINE_WIDTH = BLAZE_LINE_WIDTH + 1.5
/**
* Dash rhythms exactly as WIREFRAMES.md specifies them, in PIXELS.
*
* MapLibre measures `line-dasharray` in multiples of the line's own width, not
* pixels, so these are divided by {@link BLAZE_LINE_WIDTH} on the way into the
* style. Keeping the table in the spec's own units is what lets it be checked
* against WIREFRAMES.md by eye.
*/
export const BLAZE_DASH_RHYTHMS: Record<string, [number, number]> = {
White: [10, 6],
Blue: [10, 6],
Yellow: [6, 5],
Orange: [10, 5],
Red: [15, 5],
Green: [13, 5],
Purple: [10, 6],
// Sparse dotted: undecoded lines should read as uncertain at a glance.
None: [4, 6],
Other: [4, 6],
}
const NEUTRAL_RHYTHM: [number, number] = [4, 6]
function toLineWidthUnits([on, off]: [number, number]): [number, number] {
return [on / BLAZE_LINE_WIDTH, off / BLAZE_LINE_WIDTH]
}
export const BLAZE_DASH_MATCH_EXPRESSION = [
'match',
['get', 'blaze_color'],
...Object.entries(BLAZE_DASH_RHYTHMS).flatMap(([blaze, rhythm]) => [
blaze,
['literal', toLineWidthUnits(rhythm)],
]),
['literal', toLineWidthUnits(NEUTRAL_RHYTHM)],
]
export interface MapStyleOptions {
/** `pmtiles://` URL for the downloaded topo archive. */
topoArchiveUrl: string
/** Local URL of the exported trail lines. No network path. */
trailsUrl: string
}
export function buildMapStyle({
topoArchiveUrl,
trailsUrl,
}: MapStyleOptions): StyleSpecification {
return {
version: 8,
sources: {
[TOPO_SOURCE_ID]: {
type: 'raster',
url: topoArchiveUrl,
tileSize: 512,
attribution: ATTRIBUTION,
},
[TRAILS_SOURCE_ID]: {
type: 'geojson',
data: trailsUrl,
attribution: ATTRIBUTION,
},
},
layers: [
{
id: TOPO_LAYER_ID,
type: 'raster',
source: TOPO_SOURCE_ID,
},
{
// Hairline dark casing, drawn under every blaze so the trail stays
// readable over busy topo contours.
id: TRAIL_CASING_LAYER_ID,
type: 'line',
source: TRAILS_SOURCE_ID,
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: {
'line-color': '#2b2620',
'line-width': CASING_LINE_WIDTH,
'line-opacity': 0.55,
},
},
{
id: BLAZE_LAYER_ID,
type: 'line',
source: TRAILS_SOURCE_ID,
layout: { 'line-cap': 'butt', 'line-join': 'round' },
paint: {
'line-color': BLAZE_MATCH_EXPRESSION as unknown as string,
'line-dasharray': BLAZE_DASH_MATCH_EXPRESSION as unknown as number[],
'line-width': BLAZE_LINE_WIDTH,
},
},
],
}
}