@mailwoman/cartographer 4.15.0 → 4.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/base/buildings.ts +56 -44
- package/base/composition.test.ts +195 -0
- package/base/composition.ts +8 -8
- package/base/layers.ts +3 -1
- package/base/theme.ts +17 -7
- package/coverage/index.ts +9 -8
- package/out/base/buildings.d.ts.map +1 -1
- package/out/base/buildings.js +46 -41
- package/out/base/buildings.js.map +1 -1
- package/out/base/composition.d.ts +0 -1
- package/out/base/composition.d.ts.map +1 -1
- package/out/base/composition.js +8 -8
- package/out/base/composition.js.map +1 -1
- package/out/base/layers.d.ts.map +1 -1
- package/out/base/layers.js +3 -1
- package/out/base/layers.js.map +1 -1
- package/out/base/theme.d.ts.map +1 -1
- package/out/base/theme.js +13 -7
- package/out/base/theme.js.map +1 -1
- package/out/coverage/index.d.ts +9 -8
- package/out/coverage/index.d.ts.map +1 -1
- package/out/coverage/index.js +9 -8
- package/out/coverage/index.js.map +1 -1
- package/out/race-dots/index.d.ts +4 -2
- package/out/race-dots/index.d.ts.map +1 -1
- package/out/race-dots/index.js +4 -2
- package/out/race-dots/index.js.map +1 -1
- package/out/styles/layers.d.ts +1 -1
- package/out/styles/layers.d.ts.map +1 -1
- package/out/styles/layers.js +6 -2
- package/out/styles/layers.js.map +1 -1
- package/out/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/race-dots/index.ts +4 -2
- package/styles/layers.ts +7 -2
- package/tiles/coords.test.ts +119 -0
- package/tsconfig.json +1 -1
package/base/buildings.ts
CHANGED
|
@@ -4,13 +4,38 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
type ExpressionSpecification,
|
|
9
|
+
type FillExtrusionLayerSpecification,
|
|
10
|
+
type LayerSpecification,
|
|
11
|
+
} from "@maplibre/maplibre-gl-style-spec"
|
|
9
12
|
import { LayerID } from "../styles/layers.js"
|
|
10
13
|
import { MailwomanBaseTileSetID } from "./theme.js"
|
|
11
14
|
|
|
12
15
|
const BuildingLayerID = LayerID.bind(null, "buildings")
|
|
13
16
|
|
|
17
|
+
const POP_START = 12.5 // zoom where buildings start rising
|
|
18
|
+
const POP_END = 13 // zoom where they reach full height (Protomaps fully loaded here)
|
|
19
|
+
const ROOF_GAP = 0.1 // meters: tiny lift so the band clears the roof (kills z-fight)
|
|
20
|
+
const ROOF_CAP = 1 // meters: band thickness
|
|
21
|
+
|
|
22
|
+
const buildingHeight = (offset = 0): ExpressionSpecification => [
|
|
23
|
+
"+",
|
|
24
|
+
["coalesce", ["get", "height"], ["get", "minheight"], 10],
|
|
25
|
+
offset,
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
/** Wrap a target-height expression in the zoom-driven pop interpolation. */
|
|
29
|
+
const popHeight = (target: ExpressionSpecification): ExpressionSpecification => [
|
|
30
|
+
"interpolate",
|
|
31
|
+
["linear"],
|
|
32
|
+
["zoom"],
|
|
33
|
+
POP_START,
|
|
34
|
+
0,
|
|
35
|
+
POP_END,
|
|
36
|
+
target,
|
|
37
|
+
]
|
|
38
|
+
|
|
14
39
|
/**
|
|
15
40
|
* Layer definitions for building data.
|
|
16
41
|
*/
|
|
@@ -19,28 +44,35 @@ export const BuildingLayers: LayerSpecification[] = [
|
|
|
19
44
|
id: BuildingLayerID("buildings-extruded"),
|
|
20
45
|
|
|
21
46
|
paint: {
|
|
22
|
-
"fill-extrusion-color":
|
|
23
|
-
// ---
|
|
24
|
-
"interpolate",
|
|
25
|
-
// ["exponential", 0.25],
|
|
26
|
-
["linear"],
|
|
27
|
-
[
|
|
28
|
-
// --
|
|
29
|
-
"coalesce",
|
|
30
|
-
["get", "height"],
|
|
31
|
-
["get", "minheight"],
|
|
32
|
-
10,
|
|
33
|
-
],
|
|
34
|
-
...Array.from({ length: 10 }, (_, i) => {
|
|
35
|
-
const sizeScalingFactor = 250 - 250 / i
|
|
36
|
-
|
|
37
|
-
return [sizeScalingFactor, interpolateTurbo(sizeScalingFactor / 250)] as [number, string]
|
|
38
|
-
}).flat(),
|
|
39
|
-
],
|
|
47
|
+
"fill-extrusion-color": "hsl(276.52deg 10.83% 10.31%)",
|
|
40
48
|
"fill-extrusion-translate": [0.1, 0.1],
|
|
49
|
+
"fill-extrusion-opacity": ["interpolate", ["linear"], ["zoom"], 14, 0, 14.5, 1],
|
|
41
50
|
},
|
|
42
51
|
}),
|
|
43
52
|
|
|
53
|
+
// Roof-edge outline: thin extruded band sitting at the top of each building.
|
|
54
|
+
{
|
|
55
|
+
id: "basemap-buildings-roof-outline",
|
|
56
|
+
type: "fill-extrusion",
|
|
57
|
+
source: MailwomanBaseTileSetID,
|
|
58
|
+
"source-layer": "buildings",
|
|
59
|
+
minzoom: 10,
|
|
60
|
+
paint: {
|
|
61
|
+
// "fill-extrusion-color": "hsl(240deg 100% 80%)", // match footprint outline
|
|
62
|
+
"fill-extrusion-color": "hsl(276.52deg 13% 12.31%)",
|
|
63
|
+
|
|
64
|
+
"fill-extrusion-vertical-gradient": false,
|
|
65
|
+
"fill-extrusion-translate-anchor": "map",
|
|
66
|
+
"fill-extrusion-opacity": 1,
|
|
67
|
+
// top of the band = full building height
|
|
68
|
+
// "fill-extrusion-height": popHeight(buildingHeight()),
|
|
69
|
+
// // base of the band = full height minus the cap thickness (clamped ≥ 0)
|
|
70
|
+
// "fill-extrusion-base": popHeight([ "max", [ "-", buildingHeight(), ROOF_CAP ], 0 ]),
|
|
71
|
+
"fill-extrusion-height": popHeight(["+", buildingHeight(), ROOF_GAP, ROOF_CAP]),
|
|
72
|
+
"fill-extrusion-base": popHeight(["+", buildingHeight(), ROOF_GAP]),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
44
76
|
{
|
|
45
77
|
id: "basemap-buildings-outline",
|
|
46
78
|
type: "line",
|
|
@@ -89,34 +121,14 @@ function createBuildingFillStyleLayer(fillStyleSpec: BuildingFillStyleSpec): Lay
|
|
|
89
121
|
type: "fill-extrusion",
|
|
90
122
|
source: MailwomanBaseTileSetID,
|
|
91
123
|
"source-layer": "buildings",
|
|
92
|
-
minzoom:
|
|
124
|
+
minzoom: 10,
|
|
93
125
|
paint: {
|
|
94
126
|
...fillStyleSpec.paint,
|
|
95
127
|
"fill-extrusion-vertical-gradient": true,
|
|
96
|
-
|
|
97
|
-
"fill-extrusion-
|
|
98
|
-
"+",
|
|
99
|
-
[
|
|
100
|
-
// --
|
|
101
|
-
"coalesce",
|
|
102
|
-
["get", "height"],
|
|
103
|
-
["get", "minheight"],
|
|
104
|
-
10,
|
|
105
|
-
],
|
|
106
|
-
fillStyleSpec.heightOffset || 0,
|
|
107
|
-
],
|
|
128
|
+
"fill-extrusion-height": popHeight(buildingHeight(fillStyleSpec.heightOffset)),
|
|
129
|
+
"fill-extrusion-base": 0,
|
|
108
130
|
"fill-extrusion-translate-anchor": "map",
|
|
109
|
-
|
|
110
|
-
"fill-extrusion-opacity": [
|
|
111
|
-
// We fade out the outline at higher zoom levels
|
|
112
|
-
"interpolate",
|
|
113
|
-
["linear"],
|
|
114
|
-
["zoom"],
|
|
115
|
-
11,
|
|
116
|
-
0.6,
|
|
117
|
-
16,
|
|
118
|
-
0.95,
|
|
119
|
-
],
|
|
131
|
+
"fill-extrusion-opacity": 0.95,
|
|
120
132
|
},
|
|
121
133
|
}
|
|
122
134
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { expect, test } from "vitest"
|
|
8
|
+
import { createLightSpec, createSkySpec, StyleSpecificationComposer } from "./composition.js"
|
|
9
|
+
|
|
10
|
+
//#region createLightSpec
|
|
11
|
+
|
|
12
|
+
test("createLightSpec: returns the house defaults when called bare", () => {
|
|
13
|
+
expect(createLightSpec()).toEqual({
|
|
14
|
+
color: "white",
|
|
15
|
+
intensity: 0.85,
|
|
16
|
+
anchor: "viewport",
|
|
17
|
+
position: [10, 20, -5],
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("createLightSpec: an override shadows the matching default key", () => {
|
|
22
|
+
const spec = createLightSpec({ intensity: 0.5 })
|
|
23
|
+
|
|
24
|
+
expect(spec.intensity).toBe(0.5)
|
|
25
|
+
// non-overridden defaults survive
|
|
26
|
+
expect(spec.color).toBe("white")
|
|
27
|
+
expect(spec.anchor).toBe("viewport")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("createLightSpec: overriding position replaces the whole tuple", () => {
|
|
31
|
+
expect(createLightSpec({ position: [1, 2, 3] }).position).toEqual([1, 2, 3])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
|
|
36
|
+
//#region createSkySpec
|
|
37
|
+
|
|
38
|
+
test("createSkySpec: returns the house atmospheric defaults when called bare", () => {
|
|
39
|
+
expect(createSkySpec()).toEqual({
|
|
40
|
+
"sky-color": "#000535",
|
|
41
|
+
"horizon-color": "hsl(54deg 100% 16%)",
|
|
42
|
+
"fog-color": "hsl(54deg 100% 5%)",
|
|
43
|
+
"sky-horizon-blend": 0.75,
|
|
44
|
+
"horizon-fog-blend": 0.75,
|
|
45
|
+
"fog-ground-blend": 0.1,
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("createSkySpec: an override shadows only the named key", () => {
|
|
50
|
+
const spec = createSkySpec({ "sky-color": "#123456" })
|
|
51
|
+
|
|
52
|
+
expect(spec["sky-color"]).toBe("#123456")
|
|
53
|
+
expect(spec["fog-ground-blend"]).toBe(0.1) // untouched default
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
|
|
58
|
+
//#region StyleSpecificationComposer — construction
|
|
59
|
+
|
|
60
|
+
test("Composer: light/sky default to the house specs when unset", () => {
|
|
61
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
62
|
+
|
|
63
|
+
expect(composer.light).toEqual(createLightSpec())
|
|
64
|
+
expect(composer.sky).toEqual(createSkySpec())
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("Composer: partial light/sky options merge over the defaults", () => {
|
|
68
|
+
const composer = new StyleSpecificationComposer({
|
|
69
|
+
sources: {},
|
|
70
|
+
light: { intensity: 0.3 },
|
|
71
|
+
sky: { "sky-color": "#abcdef" },
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
expect(composer.light.intensity).toBe(0.3)
|
|
75
|
+
expect(composer.light.color).toBe("white") // default preserved
|
|
76
|
+
expect(composer.sky["sky-color"]).toBe("#abcdef")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Terrain + DEM-source tests parked (mobile performance): the `terrain` getter and the terrain DEM
|
|
80
|
+
// source are commented out in `composition.ts`. Re-enable the code and these tests together when DEM
|
|
81
|
+
// is reconsidered. (The hillshade DEM source is still injected.)
|
|
82
|
+
/*
|
|
83
|
+
test("Composer: terrain source defaults to the terrain tileset id", () => {
|
|
84
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
85
|
+
|
|
86
|
+
expect(composer.terrain.source).toBe("terrain")
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("Composer: a terrain override merges over the default source", () => {
|
|
90
|
+
const composer = new StyleSpecificationComposer({ sources: {}, terrain: { exaggeration: 1.5 } })
|
|
91
|
+
|
|
92
|
+
expect(composer.terrain.source).toBe("terrain") // default kept
|
|
93
|
+
expect(composer.terrain.exaggeration).toBe(1.5)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("Composer: injects DEM sources for terrain + hillshade alongside the caller's sources", () => {
|
|
97
|
+
const userSource = { type: "vector", url: "https://example.test/tiles.json" } as const
|
|
98
|
+
const composer = new StyleSpecificationComposer({ sources: { mine: userSource } })
|
|
99
|
+
|
|
100
|
+
// the caller's source + the runtime-injected DEM sources aren't all in the static source-record type.
|
|
101
|
+
const sources = composer.sources as Record<string, unknown>
|
|
102
|
+
expect(sources.mine).toEqual(userSource)
|
|
103
|
+
expect(sources.terrain).toMatchObject({ type: "raster-dem", encoding: "terrarium" })
|
|
104
|
+
expect(sources.hillshade).toMatchObject({ type: "raster-dem", encoding: "terrarium" })
|
|
105
|
+
})
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
|
|
110
|
+
//#region StyleSpecificationComposer — layers
|
|
111
|
+
|
|
112
|
+
test("Composer.layers: exposes the base layer list as an array", () => {
|
|
113
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
114
|
+
|
|
115
|
+
expect(Array.isArray(composer.layers)).toBe(true)
|
|
116
|
+
expect(composer.layers.length).toBeGreaterThan(0)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("Composer.layers: an inserted layer is present in the composed list", () => {
|
|
120
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
121
|
+
// `insert` requires an anchor (`beforeID`/`afterID`) — anchor against an existing base layer.
|
|
122
|
+
const anchorID = composer.layers[0]!.id
|
|
123
|
+
|
|
124
|
+
const withCustom = new StyleSpecificationComposer({
|
|
125
|
+
sources: {},
|
|
126
|
+
layers: [
|
|
127
|
+
{
|
|
128
|
+
id: "mw-test-layer",
|
|
129
|
+
type: "background",
|
|
130
|
+
paint: { "background-color": "#ff0000" },
|
|
131
|
+
afterID: anchorID,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const inserted = withCustom.layers.find((l) => l.id === "mw-test-layer")
|
|
137
|
+
expect(inserted).toBeDefined()
|
|
138
|
+
expect(inserted!.type).toBe("background")
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test("Composer.layers: an inserted layer sits immediately after its anchor", () => {
|
|
142
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
143
|
+
const anchorID = composer.layers[0]!.id
|
|
144
|
+
|
|
145
|
+
const withCustom = new StyleSpecificationComposer({
|
|
146
|
+
sources: {},
|
|
147
|
+
layers: [{ id: "mw-anchored-layer", type: "background", paint: {}, afterID: anchorID }],
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const ids = withCustom.layers.map((l) => l.id)
|
|
151
|
+
const anchorIdx = ids.indexOf(anchorID)
|
|
152
|
+
expect(anchorIdx).toBeGreaterThanOrEqual(0)
|
|
153
|
+
expect(ids[anchorIdx + 1]).toBe("mw-anchored-layer")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
|
|
158
|
+
//#region StyleSpecificationComposer — serialization
|
|
159
|
+
|
|
160
|
+
test("Composer.toJSON: emits a v8 style with the self-host glyph + sprite endpoints", () => {
|
|
161
|
+
const style = new StyleSpecificationComposer({ sources: {} }).toJSON()
|
|
162
|
+
|
|
163
|
+
expect(style.version).toBe(8)
|
|
164
|
+
expect(style.glyphs).toBe("https://public.sister.software/protomaps/fonts/{fontstack}/{range}.pbf")
|
|
165
|
+
expect(style.sprite).toBe("https://public.sister.software/protomaps/sprites/v4/light")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test("Composer.toJSON: carries the composed light, sky, terrain, sources and layers through", () => {
|
|
169
|
+
const composer = new StyleSpecificationComposer({ sources: {}, light: { intensity: 0.2 } })
|
|
170
|
+
const style = composer.toJSON()
|
|
171
|
+
|
|
172
|
+
expect(style.light).toEqual(createLightSpec(composer.light))
|
|
173
|
+
expect(style.sky).toEqual(createSkySpec(composer.sky))
|
|
174
|
+
expect(style.terrain).toEqual(composer.terrain)
|
|
175
|
+
expect(style.sources).toBe(composer.sources)
|
|
176
|
+
expect(style.layers).toEqual(composer.layers)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("Composer.toJS: is an alias of toJSON", () => {
|
|
180
|
+
const composer = new StyleSpecificationComposer({ sources: {} })
|
|
181
|
+
|
|
182
|
+
expect(composer.toJS()).toEqual(composer.toJSON())
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test("Composer: two instances from the shared BaseLayers have independent layer lists", () => {
|
|
186
|
+
// Regression for the shared-mutable-link bug: LayerSpecificationList now copies its input, so
|
|
187
|
+
// constructing a second composer no longer rewrites the first's kNext/kPrev links in place.
|
|
188
|
+
const a = new StyleSpecificationComposer({ sources: {} })
|
|
189
|
+
const baseCount = a.layers.length
|
|
190
|
+
const b = new StyleSpecificationComposer({ sources: {} })
|
|
191
|
+
expect(b.layers.length).toBe(baseCount) // b built a full list, not a corrupted remnant
|
|
192
|
+
expect(a.layers.length).toBe(baseCount) // a's list wasn't mutated by b's construction
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
//#endregion
|
package/base/composition.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../styles/layers.js"
|
|
14
14
|
import { type TileSetSourceRecord } from "../styles/sources.js"
|
|
15
15
|
import { BaseLayers } from "./layers.js"
|
|
16
|
-
import { createTerrainDEMSource, HillshadeTileSetID
|
|
16
|
+
import { createTerrainDEMSource, HillshadeTileSetID } from "./terrain.js"
|
|
17
17
|
|
|
18
18
|
//#endregion
|
|
19
19
|
|
|
@@ -64,21 +64,21 @@ export class StyleSpecificationComposer {
|
|
|
64
64
|
layersList: LayerSpecificationList
|
|
65
65
|
light: LightSpecification
|
|
66
66
|
sky: SkySpecification
|
|
67
|
-
terrain: TerrainSpecification
|
|
67
|
+
// terrain: TerrainSpecification
|
|
68
68
|
sources: TileSetSourceRecord
|
|
69
69
|
|
|
70
70
|
constructor(spec: StyleSpecificationComposition) {
|
|
71
71
|
this.light = createLightSpec(spec.light)
|
|
72
72
|
this.sky = createSkySpec(spec.sky)
|
|
73
73
|
|
|
74
|
-
this.terrain = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
74
|
+
// this.terrain = {
|
|
75
|
+
// source: TerrainTileSetID,
|
|
76
|
+
// ...spec.terrain,
|
|
77
|
+
// }
|
|
78
78
|
|
|
79
79
|
this.sources = {
|
|
80
80
|
...spec.sources,
|
|
81
|
-
[TerrainTileSetID]: createTerrainDEMSource(),
|
|
81
|
+
// [TerrainTileSetID]: createTerrainDEMSource(),
|
|
82
82
|
[HillshadeTileSetID]: createTerrainDEMSource(),
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -103,7 +103,7 @@ export class StyleSpecificationComposer {
|
|
|
103
103
|
sprite: "https://public.sister.software/protomaps/sprites/v4/light",
|
|
104
104
|
light: createLightSpec(this.light),
|
|
105
105
|
sky: createSkySpec(this.sky),
|
|
106
|
-
terrain: this.terrain,
|
|
106
|
+
// terrain: this.terrain,
|
|
107
107
|
sources: this.sources,
|
|
108
108
|
layers: this.layers,
|
|
109
109
|
}
|
package/base/layers.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { layers } from "@protomaps/basemaps"
|
|
8
8
|
import { type LayerSpecification } from "maplibre-gl"
|
|
9
|
-
import { BuildingLayers } from "../base/buildings.js"
|
|
10
9
|
import { MailwomanBaseFlavor, MailwomanBaseTileSetID } from "../base/theme.js"
|
|
11
10
|
import { LayerID } from "../styles/layers.js"
|
|
11
|
+
import { BuildingLayers } from "./buildings.js"
|
|
12
12
|
import { HillshadeTileSetID } from "./terrain.js"
|
|
13
13
|
|
|
14
14
|
export const HillsLayerID = LayerID(HillshadeTileSetID, "hills")
|
|
@@ -41,10 +41,12 @@ export const BaseLayers: LayerSpecification[] = [
|
|
|
41
41
|
id: HillsLayerID,
|
|
42
42
|
type: "hillshade",
|
|
43
43
|
source: HillshadeTileSetID,
|
|
44
|
+
minzoom: 7,
|
|
44
45
|
paint: {
|
|
45
46
|
"hillshade-exaggeration": 0.25,
|
|
46
47
|
"hillshade-accent-color": "hsl(240deg 100% 95%)",
|
|
47
48
|
"hillshade-shadow-color": "hsl(240deg 100% 5%)",
|
|
49
|
+
"hillshade-highlight-color": "hsl(240deg 100% 75%)",
|
|
48
50
|
},
|
|
49
51
|
},
|
|
50
52
|
...BuildingLayers,
|
package/base/theme.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { type Flavor } from "@protomaps/basemaps"
|
|
7
|
+
import { type Flavor, namedFlavor } from "@protomaps/basemaps"
|
|
8
8
|
import { TileSetSourceID } from "../styles/sources.js"
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -14,12 +14,18 @@ import { TileSetSourceID } from "../styles/sources.js"
|
|
|
14
14
|
*/
|
|
15
15
|
export const MailwomanBaseTileSetID = TileSetSourceID("basemap-v4")
|
|
16
16
|
|
|
17
|
+
const darkFlavor = namedFlavor("dark")
|
|
18
|
+
|
|
19
|
+
console.log("MailwomanBaseFlavor:", darkFlavor)
|
|
20
|
+
|
|
17
21
|
/**
|
|
18
22
|
* The Mailwoman theme for Protomaps via MapLibre. Keys follow `@protomaps/basemaps@5.x` which
|
|
19
23
|
* targets the v4 tile schema (`["==","kind","..."]` filters), matching the basemap-v4 PMTiles on
|
|
20
24
|
* R2.
|
|
21
25
|
*/
|
|
22
26
|
export const MailwomanBaseFlavor: Flavor = {
|
|
27
|
+
...darkFlavor,
|
|
28
|
+
|
|
23
29
|
//#region Base
|
|
24
30
|
background: "hsl(0deg 10% 5%)",
|
|
25
31
|
earth: "hsl(0deg 10% 5%)",
|
|
@@ -96,13 +102,7 @@ export const MailwomanBaseFlavor: Flavor = {
|
|
|
96
102
|
bridges_highway: "#474747",
|
|
97
103
|
|
|
98
104
|
//#endregion
|
|
99
|
-
//#region Roads
|
|
100
|
-
roads_label_minor: "hsl(240deg 100% 90%)",
|
|
101
|
-
roads_label_minor_halo: "#1f1f1f",
|
|
102
|
-
roads_label_major: "hsl(240deg 100% 90%)",
|
|
103
|
-
roads_label_major_halo: "#1f1f1f",
|
|
104
105
|
|
|
105
|
-
//#endregion
|
|
106
106
|
ocean_label: "#717784",
|
|
107
107
|
subplace_label: "hsl(50deg 50% 70%)",
|
|
108
108
|
subplace_label_halo: "hsl(40deg 100% 10%)",
|
|
@@ -115,4 +115,14 @@ export const MailwomanBaseFlavor: Flavor = {
|
|
|
115
115
|
country_label: "hsl(240deg 50% 80% / 0.5)",
|
|
116
116
|
address_label: "hsl(240deg 100% 90%)",
|
|
117
117
|
address_label_halo: "hsl(240deg 100% 10%)",
|
|
118
|
+
|
|
119
|
+
landcover: {
|
|
120
|
+
grassland: "hsl(120deg 45.75% 1%)",
|
|
121
|
+
barren: "hsl(0deg 100% 2.84%)",
|
|
122
|
+
farmland: "hsl(120deg 45.75% 1%)",
|
|
123
|
+
forest: "hsl(120deg 45.75% 1%)",
|
|
124
|
+
glacier: "hsl(240deg 100% 10%)",
|
|
125
|
+
scrub: "hsl(0deg 10% 5%)",
|
|
126
|
+
urban_area: "hsl(0deg 10% 5%)",
|
|
127
|
+
},
|
|
118
128
|
}
|
package/coverage/index.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* The "fog of war" address-COVERAGE overlay. An H3 hexbin tileset (built by
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The "fog of war" address-COVERAGE overlay. An H3 hexbin tileset (built by `mailwoman coverage
|
|
7
|
+
* build`) shading "where do we need data" at a glance. Two layers in one tileset: the US rooftop
|
|
8
|
+
* fine map (address-point density, street/block detail) and a GLOBAL "work to do" layer — populated
|
|
9
|
+
* places (WOF, importance-weighted) we can't geocode yet show as gray-fog holes, and postcode/rooftop
|
|
10
|
+
* coverage clears them. Covered → clear (basemap shows through), uncovered civilization → gray fog.
|
|
10
11
|
*
|
|
11
12
|
* Each cell carries TWO baked fog values in [0,1] (0 = covered, 1 = empty), so the same tiles drive
|
|
12
13
|
* either reading without a rebuild:
|
|
@@ -16,8 +17,8 @@
|
|
|
16
17
|
* We expose each as its OWN default-off fill layer (`coverage-opt-fog`, `coverage-honest-fog`) so the
|
|
17
18
|
* demo's LayerToggleControl gives each its own checkbox — pick the reading you want, no extra UI.
|
|
18
19
|
*
|
|
19
|
-
* Served as XYZ vector tiles by the tile worker from `nexus-assets/tiles/coverage-
|
|
20
|
-
* as `basemap-v4`); the consumer passes its TileJSON URL (`tiles.sister.software/coverage-
|
|
20
|
+
* Served as XYZ vector tiles by the tile worker from `nexus-assets/tiles/coverage-v5.pmtiles` (same
|
|
21
|
+
* as `basemap-v4`); the consumer passes its TileJSON URL (`tiles.sister.software/coverage-v5.json`)
|
|
21
22
|
* to `createCoverageSource`.
|
|
22
23
|
*/
|
|
23
24
|
|
|
@@ -27,7 +28,7 @@ import type {
|
|
|
27
28
|
} from "@maplibre/maplibre-gl-style-spec"
|
|
28
29
|
import { TileSetSourceID } from "../styles/sources.js"
|
|
29
30
|
|
|
30
|
-
export const CoverageTileSetID = TileSetSourceID("coverage-
|
|
31
|
+
export const CoverageTileSetID = TileSetSourceID("coverage-v5")
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* The single source-layer the coverage PMTiles ships (see `build-coverage-tiles.ts` → tippecanoe `-l`).
|
|
@@ -56,7 +57,7 @@ export const CoverageLayerID = {
|
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Build the coverage source spec from the tile worker's TileJSON endpoint
|
|
59
|
-
* (`https://tiles.sister.software/coverage-
|
|
60
|
+
* (`https://tiles.sister.software/coverage-v5.json`).
|
|
60
61
|
*/
|
|
61
62
|
export function createCoverageSource(url: string): VectorSourceSpecification {
|
|
62
63
|
return { type: "vector", url }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildings.d.ts","sourceRoot":"","sources":["../../base/buildings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"buildings.d.ts","sourceRoot":"","sources":["../../base/buildings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,kCAAkC,CAAA;AA4BzC;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,kBAAkB,EAqE9C,CAAA"}
|
package/out/base/buildings.js
CHANGED
|
@@ -4,10 +4,28 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*/
|
|
6
6
|
import {} from "@maplibre/maplibre-gl-style-spec";
|
|
7
|
-
import { interpolateTurbo } from "d3-scale-chromatic";
|
|
8
7
|
import { LayerID } from "../styles/layers.js";
|
|
9
8
|
import { MailwomanBaseTileSetID } from "./theme.js";
|
|
10
9
|
const BuildingLayerID = LayerID.bind(null, "buildings");
|
|
10
|
+
const POP_START = 12.5; // zoom where buildings start rising
|
|
11
|
+
const POP_END = 13; // zoom where they reach full height (Protomaps fully loaded here)
|
|
12
|
+
const ROOF_GAP = 0.1; // meters: tiny lift so the band clears the roof (kills z-fight)
|
|
13
|
+
const ROOF_CAP = 1; // meters: band thickness
|
|
14
|
+
const buildingHeight = (offset = 0) => [
|
|
15
|
+
"+",
|
|
16
|
+
["coalesce", ["get", "height"], ["get", "minheight"], 10],
|
|
17
|
+
offset,
|
|
18
|
+
];
|
|
19
|
+
/** Wrap a target-height expression in the zoom-driven pop interpolation. */
|
|
20
|
+
const popHeight = (target) => [
|
|
21
|
+
"interpolate",
|
|
22
|
+
["linear"],
|
|
23
|
+
["zoom"],
|
|
24
|
+
POP_START,
|
|
25
|
+
0,
|
|
26
|
+
POP_END,
|
|
27
|
+
target,
|
|
28
|
+
];
|
|
11
29
|
/**
|
|
12
30
|
* Layer definitions for building data.
|
|
13
31
|
*/
|
|
@@ -15,26 +33,32 @@ export const BuildingLayers = [
|
|
|
15
33
|
createBuildingFillStyleLayer({
|
|
16
34
|
id: BuildingLayerID("buildings-extruded"),
|
|
17
35
|
paint: {
|
|
18
|
-
"fill-extrusion-color":
|
|
19
|
-
// ---
|
|
20
|
-
"interpolate",
|
|
21
|
-
// ["exponential", 0.25],
|
|
22
|
-
["linear"],
|
|
23
|
-
[
|
|
24
|
-
// --
|
|
25
|
-
"coalesce",
|
|
26
|
-
["get", "height"],
|
|
27
|
-
["get", "minheight"],
|
|
28
|
-
10,
|
|
29
|
-
],
|
|
30
|
-
...Array.from({ length: 10 }, (_, i) => {
|
|
31
|
-
const sizeScalingFactor = 250 - 250 / i;
|
|
32
|
-
return [sizeScalingFactor, interpolateTurbo(sizeScalingFactor / 250)];
|
|
33
|
-
}).flat(),
|
|
34
|
-
],
|
|
36
|
+
"fill-extrusion-color": "hsl(276.52deg 10.83% 10.31%)",
|
|
35
37
|
"fill-extrusion-translate": [0.1, 0.1],
|
|
38
|
+
"fill-extrusion-opacity": ["interpolate", ["linear"], ["zoom"], 14, 0, 14.5, 1],
|
|
36
39
|
},
|
|
37
40
|
}),
|
|
41
|
+
// Roof-edge outline: thin extruded band sitting at the top of each building.
|
|
42
|
+
{
|
|
43
|
+
id: "basemap-buildings-roof-outline",
|
|
44
|
+
type: "fill-extrusion",
|
|
45
|
+
source: MailwomanBaseTileSetID,
|
|
46
|
+
"source-layer": "buildings",
|
|
47
|
+
minzoom: 10,
|
|
48
|
+
paint: {
|
|
49
|
+
// "fill-extrusion-color": "hsl(240deg 100% 80%)", // match footprint outline
|
|
50
|
+
"fill-extrusion-color": "hsl(276.52deg 13% 12.31%)",
|
|
51
|
+
"fill-extrusion-vertical-gradient": false,
|
|
52
|
+
"fill-extrusion-translate-anchor": "map",
|
|
53
|
+
"fill-extrusion-opacity": 1,
|
|
54
|
+
// top of the band = full building height
|
|
55
|
+
// "fill-extrusion-height": popHeight(buildingHeight()),
|
|
56
|
+
// // base of the band = full height minus the cap thickness (clamped ≥ 0)
|
|
57
|
+
// "fill-extrusion-base": popHeight([ "max", [ "-", buildingHeight(), ROOF_CAP ], 0 ]),
|
|
58
|
+
"fill-extrusion-height": popHeight(["+", buildingHeight(), ROOF_GAP, ROOF_CAP]),
|
|
59
|
+
"fill-extrusion-base": popHeight(["+", buildingHeight(), ROOF_GAP]),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
38
62
|
{
|
|
39
63
|
id: "basemap-buildings-outline",
|
|
40
64
|
type: "line",
|
|
@@ -75,33 +99,14 @@ function createBuildingFillStyleLayer(fillStyleSpec) {
|
|
|
75
99
|
type: "fill-extrusion",
|
|
76
100
|
source: MailwomanBaseTileSetID,
|
|
77
101
|
"source-layer": "buildings",
|
|
78
|
-
minzoom:
|
|
102
|
+
minzoom: 10,
|
|
79
103
|
paint: {
|
|
80
104
|
...fillStyleSpec.paint,
|
|
81
105
|
"fill-extrusion-vertical-gradient": true,
|
|
82
|
-
"fill-extrusion-height":
|
|
83
|
-
|
|
84
|
-
[
|
|
85
|
-
// --
|
|
86
|
-
"coalesce",
|
|
87
|
-
["get", "height"],
|
|
88
|
-
["get", "minheight"],
|
|
89
|
-
10,
|
|
90
|
-
],
|
|
91
|
-
fillStyleSpec.heightOffset || 0,
|
|
92
|
-
],
|
|
106
|
+
"fill-extrusion-height": popHeight(buildingHeight(fillStyleSpec.heightOffset)),
|
|
107
|
+
"fill-extrusion-base": 0,
|
|
93
108
|
"fill-extrusion-translate-anchor": "map",
|
|
94
|
-
|
|
95
|
-
"fill-extrusion-opacity": [
|
|
96
|
-
// We fade out the outline at higher zoom levels
|
|
97
|
-
"interpolate",
|
|
98
|
-
["linear"],
|
|
99
|
-
["zoom"],
|
|
100
|
-
11,
|
|
101
|
-
0.6,
|
|
102
|
-
16,
|
|
103
|
-
0.95,
|
|
104
|
-
],
|
|
109
|
+
"fill-extrusion-opacity": 0.95,
|
|
105
110
|
},
|
|
106
111
|
};
|
|
107
112
|
return baseStyle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildings.js","sourceRoot":"","sources":["../../base/buildings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"buildings.js","sourceRoot":"","sources":["../../base/buildings.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,6 @@ export declare class StyleSpecificationComposer {
|
|
|
23
23
|
layersList: LayerSpecificationList;
|
|
24
24
|
light: LightSpecification;
|
|
25
25
|
sky: SkySpecification;
|
|
26
|
-
terrain: TerrainSpecification;
|
|
27
26
|
sources: TileSetSourceRecord;
|
|
28
27
|
constructor(spec: StyleSpecificationComposition);
|
|
29
28
|
get layers(): LayerSpecificationListItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../base/composition.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACjH,OAAO,EACN,sBAAsB,EACtB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAQ/D,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAYtF;AAED,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAUhF;AAMD,MAAM,WAAW,6BAA6B;IAC7C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,2BAA2B,EAAE,CAAA;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACnC,GAAG,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,qBAAa,0BAA0B;IACtC,UAAU,EAAE,sBAAsB,CAAA;IAClC,KAAK,EAAE,kBAAkB,CAAA;IACzB,GAAG,EAAE,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../base/composition.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACjH,OAAO,EACN,sBAAsB,EACtB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAQ/D,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAYtF;AAED,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAUhF;AAMD,MAAM,WAAW,6BAA6B;IAC7C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,2BAA2B,EAAE,CAAA;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACnC,GAAG,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,qBAAa,0BAA0B;IACtC,UAAU,EAAE,sBAAsB,CAAA;IAClC,KAAK,EAAE,kBAAkB,CAAA;IACzB,GAAG,EAAE,gBAAgB,CAAA;IAErB,OAAO,EAAE,mBAAmB,CAAA;gBAEhB,IAAI,EAAE,6BAA6B;IAsB/C,IAAW,MAAM,IAAI,0BAA0B,EAAE,CAEhD;IAED,MAAM,IAAI,kBAAkB;IAkB5B,IAAI,IAAI,kBAAkB;CAG1B"}
|