@mailwoman/cartographer 9.2.0 → 9.4.0

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.
Files changed (116) hide show
  1. package/{base → lib/base}/buildings.ts +4 -3
  2. package/{base → lib/base}/composition.ts +41 -12
  3. package/lib/base/index.ts +10 -0
  4. package/{base → lib/base}/layers.ts +4 -4
  5. package/{base → lib/base}/terrain.ts +1 -1
  6. package/{base → lib/base}/theme.ts +3 -3
  7. package/{bdc → lib/bdc}/index.ts +21 -10
  8. package/{coverage → lib/coverage}/index.ts +3 -3
  9. package/{hspa → lib/hspa}/index.ts +2 -2
  10. package/lib/index.ts +12 -0
  11. package/lib/planetary/index.ts +9 -0
  12. package/lib/planetary/layers.ts +123 -0
  13. package/lib/planetary/sources.ts +38 -0
  14. package/lib/planetary/style.ts +66 -0
  15. package/{race-dots → lib/race-dots}/index.ts +2 -2
  16. package/lib/styles/fonts.ts +19 -0
  17. package/{tiles → lib/styles}/index.ts +3 -3
  18. package/{styles → lib/styles}/sources.ts +5 -1
  19. package/{tiger → lib/tiger}/index.ts +3 -3
  20. package/{tiles → lib/tiles}/api.ts +3 -3
  21. package/{styles → lib/tiles}/index.ts +3 -2
  22. package/out/base/buildings.d.ts.map +1 -1
  23. package/out/base/buildings.js +4 -3
  24. package/out/base/buildings.js.map +1 -1
  25. package/out/base/composition.d.ts +29 -3
  26. package/out/base/composition.d.ts.map +1 -1
  27. package/out/base/composition.js +24 -10
  28. package/out/base/composition.js.map +1 -1
  29. package/out/base/index.d.ts +4 -4
  30. package/out/base/index.d.ts.map +1 -1
  31. package/out/base/index.js +4 -4
  32. package/out/base/index.js.map +1 -1
  33. package/out/base/layers.d.ts.map +1 -1
  34. package/out/base/layers.js +4 -4
  35. package/out/base/layers.js.map +1 -1
  36. package/out/base/terrain.d.ts +1 -1
  37. package/out/base/terrain.d.ts.map +1 -1
  38. package/out/base/terrain.js +1 -1
  39. package/out/base/terrain.js.map +1 -1
  40. package/out/base/theme.d.ts +3 -3
  41. package/out/base/theme.d.ts.map +1 -1
  42. package/out/base/theme.js +3 -3
  43. package/out/base/theme.js.map +1 -1
  44. package/out/bdc/index.d.ts +14 -3
  45. package/out/bdc/index.d.ts.map +1 -1
  46. package/out/bdc/index.js +1 -1
  47. package/out/bdc/index.js.map +1 -1
  48. package/out/coverage/index.d.ts +3 -3
  49. package/out/coverage/index.d.ts.map +1 -1
  50. package/out/coverage/index.js +3 -3
  51. package/out/coverage/index.js.map +1 -1
  52. package/out/hspa/index.d.ts +1 -1
  53. package/out/hspa/index.d.ts.map +1 -1
  54. package/out/hspa/index.js +2 -2
  55. package/out/hspa/index.js.map +1 -1
  56. package/out/index.d.ts +6 -6
  57. package/out/index.d.ts.map +1 -1
  58. package/out/index.js +6 -6
  59. package/out/index.js.map +1 -1
  60. package/out/planetary/index.d.ts +9 -0
  61. package/out/planetary/index.d.ts.map +1 -0
  62. package/out/planetary/index.js +9 -0
  63. package/out/planetary/index.js.map +1 -0
  64. package/out/planetary/layers.d.ts +61 -0
  65. package/out/planetary/layers.d.ts.map +1 -0
  66. package/out/planetary/layers.js +96 -0
  67. package/out/planetary/layers.js.map +1 -0
  68. package/out/planetary/sources.d.ts +22 -0
  69. package/out/planetary/sources.d.ts.map +1 -0
  70. package/out/planetary/sources.js +32 -0
  71. package/out/planetary/sources.js.map +1 -0
  72. package/out/planetary/style.d.ts +24 -0
  73. package/out/planetary/style.d.ts.map +1 -0
  74. package/out/planetary/style.js +35 -0
  75. package/out/planetary/style.js.map +1 -0
  76. package/out/race-dots/index.d.ts +2 -2
  77. package/out/race-dots/index.d.ts.map +1 -1
  78. package/out/race-dots/index.js +2 -2
  79. package/out/race-dots/index.js.map +1 -1
  80. package/out/styles/fonts.d.ts +19 -0
  81. package/out/styles/fonts.d.ts.map +1 -0
  82. package/out/styles/fonts.js +19 -0
  83. package/out/styles/fonts.js.map +1 -0
  84. package/out/styles/index.d.ts +3 -2
  85. package/out/styles/index.d.ts.map +1 -1
  86. package/out/styles/index.js +3 -2
  87. package/out/styles/index.js.map +1 -1
  88. package/out/styles/layers.d.ts.map +1 -1
  89. package/out/styles/layers.js.map +1 -1
  90. package/out/styles/sources.d.ts.map +1 -1
  91. package/out/styles/sources.js +4 -1
  92. package/out/styles/sources.js.map +1 -1
  93. package/out/tiger/index.d.ts +2 -2
  94. package/out/tiger/index.d.ts.map +1 -1
  95. package/out/tiger/index.js +2 -2
  96. package/out/tiger/index.js.map +1 -1
  97. package/out/tiles/api.d.ts +1 -1
  98. package/out/tiles/api.d.ts.map +1 -1
  99. package/out/tiles/api.js +1 -1
  100. package/out/tiles/api.js.map +1 -1
  101. package/out/tiles/coords.d.ts.map +1 -1
  102. package/out/tiles/coords.js.map +1 -1
  103. package/out/tiles/index.d.ts +3 -3
  104. package/out/tiles/index.d.ts.map +1 -1
  105. package/out/tiles/index.js +3 -3
  106. package/out/tiles/index.js.map +1 -1
  107. package/out/tiles/schema.d.ts.map +1 -1
  108. package/out/tiles/schema.js.map +1 -1
  109. package/out/tsconfig.test.tsbuildinfo +1 -1
  110. package/package.json +49 -6
  111. package/base/index.ts +0 -10
  112. package/index.ts +0 -12
  113. package/out/tsconfig.tsbuildinfo +0 -1
  114. /package/{styles → lib/styles}/layers.ts +0 -0
  115. /package/{tiles → lib/tiles}/coords.ts +0 -0
  116. /package/{tiles → lib/tiles}/schema.ts +0 -0
@@ -10,8 +10,9 @@ import type {
10
10
  LayerSpecification,
11
11
  } from "@maplibre/maplibre-gl-style-spec"
12
12
 
13
- import { LayerID } from "../styles/layers.ts"
14
- import { MailwomanBaseTileSetID } from "./theme.ts"
13
+ import { MailwomanBaseTileSetID } from "#base/theme"
14
+ import { PROTOMAPS_FONT_REGULAR } from "#styles/fonts"
15
+ import { LayerID } from "#styles/layers"
15
16
 
16
17
  const BuildingLayerID = LayerID.bind(null, "buildings")
17
18
 
@@ -114,7 +115,7 @@ export const BuildingLayers: LayerSpecification[] = [
114
115
  "text-field": ["to-string", ["id"]],
115
116
  "text-offset": [0, 0],
116
117
  "text-anchor": "center",
117
- "text-font": ["Fira Sans Regular"],
118
+ "text-font": [PROTOMAPS_FONT_REGULAR],
118
119
  },
119
120
 
120
121
  paint: {
@@ -4,17 +4,17 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- import type { SourceSpecification } from "@maplibre/maplibre-gl-style-spec"
7
+ import type { LayerSpecification, SourceSpecification } from "@maplibre/maplibre-gl-style-spec"
8
8
  import type { LightSpecification, SkySpecification, StyleSpecification, TerrainSpecification } from "maplibre-gl"
9
9
 
10
+ import { BaseLayers } from "#base/layers"
11
+ import { createTerrainDEMSource, HillshadeTileSetID } from "#base/terrain"
10
12
  import {
11
13
  LayerSpecificationList,
12
14
  type LayerSpecificationListInput,
13
15
  type LayerSpecificationListItem,
14
- } from "../styles/layers.ts"
15
- import type { TileSetSourceRecord } from "../styles/sources.ts"
16
- import { BaseLayers } from "./layers.ts"
17
- import { createTerrainDEMSource, HillshadeTileSetID } from "./terrain.ts"
16
+ } from "#styles/layers"
17
+ import type { TileSetSourceRecord } from "#styles/sources"
18
18
 
19
19
  //#endregion
20
20
 
@@ -50,12 +50,38 @@ export function createSkySpec(spec?: Partial<SkySpecification>): SkySpecificatio
50
50
 
51
51
  //#region Style Composition
52
52
 
53
+ /**
54
+ * The glyph host every style composed here reads fonts from. The Protomaps font set, mirrored under the public bucket
55
+ * so a style never depends on an upstream host at render time.
56
+ */
57
+ export const PROTOMAPS_GLYPHS_URL = "https://public.mailwoman.ai/protomaps/fonts/{fontstack}/{range}.pbf"
58
+
59
+ /**
60
+ * The Earth sprite. It must match the basemap schema version: the v4 sprite carries the icons the v4 theme's layers
61
+ * reference by name, so a style over a different basemap version needs a different sprite.
62
+ */
63
+ export const PROTOMAPS_SPRITE_URL = "https://public.mailwoman.ai/protomaps/sprites/v4/light"
64
+
53
65
  export interface StyleSpecificationComposition {
54
66
  sources: Record<string, SourceSpecification>
55
67
  layers?: LayerSpecificationListInput[]
56
68
  light?: Partial<LightSpecification>
57
69
  sky?: Partial<SkySpecification>
58
70
  terrain?: Partial<TerrainSpecification>
71
+ /**
72
+ * The layer list every `layers` entry inserts into. Earth's basemap layers by default; a body with no roads, water or
73
+ * buildings brings its own.
74
+ */
75
+ baseLayers?: LayerSpecification[]
76
+ /**
77
+ * The `hillshade` source. Earth's terrarium DEM by default; `null` adds no such source.
78
+ */
79
+ hillshadeSource?: SourceSpecification | null
80
+ /**
81
+ * The sprite URL. Earth's Protomaps v4 sprite by default; `null` omits the key, for a style with no icons.
82
+ */
83
+ sprite?: string | null
84
+ glyphs?: string
59
85
  }
60
86
 
61
87
  /**
@@ -67,23 +93,29 @@ export class StyleSpecificationComposer {
67
93
  sky: SkySpecification
68
94
  // terrain: TerrainSpecification
69
95
  sources: TileSetSourceRecord
96
+ sprite: string | null
97
+ glyphs: string
70
98
 
71
99
  constructor(spec: StyleSpecificationComposition) {
72
100
  this.light = createLightSpec(spec.light)
73
101
  this.sky = createSkySpec(spec.sky)
102
+ this.sprite = spec.sprite === undefined ? PROTOMAPS_SPRITE_URL : spec.sprite
103
+ this.glyphs = spec.glyphs ?? PROTOMAPS_GLYPHS_URL
74
104
 
75
105
  // this.terrain = {
76
106
  // source: TerrainTileSetID,
77
107
  // ...spec.terrain,
78
108
  // }
79
109
 
110
+ const hillshadeSource = spec.hillshadeSource === undefined ? createTerrainDEMSource() : spec.hillshadeSource
111
+
80
112
  this.sources = {
81
113
  ...spec.sources,
82
114
  // [TerrainTileSetID]: createTerrainDEMSource(),
83
- [HillshadeTileSetID]: createTerrainDEMSource(),
115
+ ...(hillshadeSource ? { [HillshadeTileSetID]: hillshadeSource } : {}),
84
116
  }
85
117
 
86
- this.layersList = new LayerSpecificationList(BaseLayers)
118
+ this.layersList = new LayerSpecificationList(spec.baseLayers ?? BaseLayers)
87
119
 
88
120
  for (const layer of spec.layers || []) {
89
121
  this.layersList.insert(layer)
@@ -97,11 +129,8 @@ export class StyleSpecificationComposer {
97
129
  toJSON(): StyleSpecification {
98
130
  const styleSpec: StyleSpecification = {
99
131
  version: 8,
100
- // Sprite must match the basemap schema version — v4 sprites carry the icons referenced
101
- // by the v4 theme spec. Currently upstream URLs; we mirror these to nexus-assets/{fonts,
102
- // sprites/v4}/ for self-hosting, but no public route fronts that bucket yet.
103
- glyphs: "https://public.sister.software/protomaps/fonts/{fontstack}/{range}.pbf",
104
- sprite: "https://public.sister.software/protomaps/sprites/v4/light",
132
+ glyphs: this.glyphs,
133
+ ...(this.sprite === null ? {} : { sprite: this.sprite }),
105
134
  light: createLightSpec(this.light),
106
135
  sky: createSkySpec(this.sky),
107
136
  // terrain: this.terrain,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ export * from "#base/buildings"
8
+ export * from "#base/composition"
9
+ export * from "#base/layers"
10
+ export * from "#base/theme"
@@ -7,10 +7,10 @@
7
7
  import { layers } from "@protomaps/basemaps"
8
8
  import type { LayerSpecification } from "maplibre-gl"
9
9
 
10
- import { MailwomanBaseFlavor, MailwomanBaseTileSetID } from "../base/theme.ts"
11
- import { LayerID } from "../styles/layers.ts"
12
- import { BuildingLayers } from "./buildings.ts"
13
- import { HillshadeTileSetID } from "./terrain.ts"
10
+ import { BuildingLayers } from "#base/buildings"
11
+ import { HillshadeTileSetID } from "#base/terrain"
12
+ import { MailwomanBaseFlavor, MailwomanBaseTileSetID } from "#base/theme"
13
+ import { LayerID } from "#styles/layers"
14
14
 
15
15
  /**
16
16
  * Layer id for the hillshade raster, referenced by style definitions that draw terrain beneath the base map.
@@ -6,7 +6,7 @@
6
6
 
7
7
  import type { RasterDEMSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
8
8
 
9
- import { TileSetSourceID } from "../styles/sources.ts"
9
+ import { TileSetSourceID } from "#styles/sources"
10
10
 
11
11
  /**
12
12
  * Creates a raster DEM source specification for terrain data.
@@ -6,12 +6,12 @@
6
6
 
7
7
  import { type Flavor, namedFlavor } from "@protomaps/basemaps"
8
8
 
9
- import { TileSetSourceID } from "../styles/sources.ts"
9
+ import { TileSetSourceID } from "#styles/sources"
10
10
 
11
11
  /**
12
12
  * Identifier for the Mailwoman base tileset. Matches the R2 object basename (`nexus-assets/tiles/basemap-v4.pmtiles`) —
13
- * `tiles.sister.software/basemap-v4.json` returns its tilejson, `tiles.sister.software/basemap-v4/{z}/{x}/{y}.mvt`
14
- * returns vector tiles.
13
+ * `tiles.mailwoman.ai/basemap-v4.json` returns its tilejson, `tiles.mailwoman.ai/basemap-v4/{z}/{x}/{y}.mvt` returns
14
+ * vector tiles.
15
15
  */
16
16
  export const MailwomanBaseTileSetID = TileSetSourceID("basemap-v4")
17
17
 
@@ -15,8 +15,8 @@ import type {
15
15
  } from "@maplibre/maplibre-gl-style-spec"
16
16
  import { interpolateTurbo } from "d3-scale-chromatic"
17
17
 
18
- import type { LayerSpecificationListInput } from "../styles/layers.ts"
19
- import { TileSetSourceID } from "../styles/sources.ts"
18
+ import type { LayerSpecificationListInput } from "#styles/layers"
19
+ import { TileSetSourceID } from "#styles/sources"
20
20
 
21
21
  /**
22
22
  * Identifier for the Broadband Data Collection tile set.
@@ -46,17 +46,28 @@ export type BaseBDCLayerSpecification<T> = T extends BDCLayerSpecificationInput
46
46
  ? Omit<T, "source-layer" | "source">
47
47
  : never
48
48
 
49
- function createBDCBlockLayer<T extends BDCLayerSpecificationInput>(spec: BaseBDCLayerSpecification<T>): T {
49
+ /**
50
+ * The three builders below take a spec WITHOUT the source fields and return one with them filled in.
51
+ *
52
+ * They are not generic, and the assertion each ends with is single rather than through `unknown`, both for the same
53
+ * reason: TypeScript cannot verify that `Omit<T, K>` plus the omitted keys reconstitutes `T`, so a builder declared
54
+ * `<T>(…): T` can only reach its return type by defeating the checker entirely — which then covers the `id`, `minzoom`
55
+ * and `maxzoom` values too. Nothing wanted the narrowing: the sole consumer, {@linkcode BroadbandDataCollectionLayers},
56
+ * annotates the union.
57
+ */
58
+ type BDCLayerBuilderInput = BaseBDCLayerSpecification<BDCLayerSpecificationInput>
59
+
60
+ function createBDCBlockLayer(spec: BDCLayerBuilderInput): BDCLayerSpecificationInput {
50
61
  return {
51
62
  ...spec,
52
63
  id: `bdc_${TIGERLevel.Block}_${spec.id}`,
53
64
  source: BDCTileSetID,
54
65
  "source-layer": `bdc_${TIGERLevel.Block}`,
55
66
  minzoom: 12,
56
- } as unknown as T
67
+ } as BDCLayerSpecificationInput
57
68
  }
58
69
 
59
- function createBDCTractLayer<T extends BDCLayerSpecificationInput>(spec: BaseBDCLayerSpecification<T>): T {
70
+ function createBDCTractLayer(spec: BDCLayerBuilderInput): BDCLayerSpecificationInput {
60
71
  return {
61
72
  ...spec,
62
73
  id: `bdc_${TIGERLevel.Tract}_${spec.id}`,
@@ -64,10 +75,10 @@ function createBDCTractLayer<T extends BDCLayerSpecificationInput>(spec: BaseBDC
64
75
  "source-layer": `bdc_${TIGERLevel.Tract}`,
65
76
  maxzoom: 12,
66
77
  minzoom: 9,
67
- } as unknown as T
78
+ } as BDCLayerSpecificationInput
68
79
  }
69
80
 
70
- function createBDCCountyLayer<T extends BDCLayerSpecificationInput>(spec: BaseBDCLayerSpecification<T>): T {
81
+ function createBDCCountyLayer(spec: BDCLayerBuilderInput): BDCLayerSpecificationInput {
71
82
  return {
72
83
  ...spec,
73
84
  id: `bdc_${TIGERLevel.County}_${spec.id}`,
@@ -75,11 +86,11 @@ function createBDCCountyLayer<T extends BDCLayerSpecificationInput>(spec: BaseBD
75
86
  "source-layer": `bdc_${TIGERLevel.County}`,
76
87
  maxzoom: 9,
77
88
  minzoom: 2,
78
- } as unknown as T
89
+ } as BDCLayerSpecificationInput
79
90
  }
80
91
 
81
- export function createBDCLayer<T extends BDCLayerSpecificationInput>(spec: BaseBDCLayerSpecification<T>): T[] {
82
- return [createBDCBlockLayer(spec), createBDCTractLayer(spec), createBDCCountyLayer(spec)] as T[]
92
+ export function createBDCLayer(spec: BDCLayerBuilderInput): BDCLayerSpecificationInput[] {
93
+ return [createBDCBlockLayer(spec), createBDCTractLayer(spec), createBDCCountyLayer(spec)]
83
94
  }
84
95
 
85
96
  const GIGABIT_BROADBAND_SPEED = 1000
@@ -18,13 +18,13 @@
18
18
  * demo's LayerToggleControl gives each its own checkbox — pick the reading you want, no extra UI.
19
19
  *
20
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
+ * as `basemap-v4`); the consumer passes its TileJSON URL (`tiles.mailwoman.ai/coverage-v5.json`)
22
22
  * to `createCoverageSource`.
23
23
  */
24
24
 
25
25
  import type { FillLayerSpecification, VectorSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
26
26
 
27
- import { TileSetSourceID } from "../styles/sources.ts"
27
+ import { TileSetSourceID } from "#styles/sources"
28
28
 
29
29
  export const CoverageTileSetID = TileSetSourceID("coverage-v5")
30
30
 
@@ -55,7 +55,7 @@ export const CoverageLayerID = {
55
55
 
56
56
  /**
57
57
  * Build the coverage source spec from the tile worker's TileJSON endpoint
58
- * (`https://tiles.sister.software/coverage-v5.json`).
58
+ * (`https://tiles.mailwoman.ai/coverage-v5.json`).
59
59
  */
60
60
  export function createCoverageSource(url: string): VectorSourceSpecification {
61
61
  return { type: "vector", url }
@@ -4,8 +4,8 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- // import { LayerSpecificationListInput } from "../styles/layers.ts"
8
- import { TileSetSourceID } from "../styles/sources.ts"
7
+ // import { LayerSpecificationListInput } from "#styles/layers"
8
+ import { TileSetSourceID } from "#styles/sources"
9
9
 
10
10
  /**
11
11
  * Identifier for the Health Service Provider Access tile set.
package/lib/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ export * from "#base/index"
8
+ export * from "#bdc/index"
9
+ export * from "#hspa/index"
10
+ export * from "#styles/index"
11
+ export * from "#tiger/index"
12
+ export * from "#tiles/index"
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ export * from "#planetary/layers"
8
+ export * from "#planetary/sources"
9
+ export * from "#planetary/style"
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import type {
8
+ BackgroundLayerSpecification,
9
+ CircleLayerSpecification,
10
+ RasterLayerSpecification,
11
+ SymbolLayerSpecification,
12
+ } from "@maplibre/maplibre-gl-style-spec"
13
+
14
+ import { PlanetaryHillshadeSourceID, PlanetaryNomenclatureSourceID } from "#planetary/sources"
15
+ import { PROTOMAPS_FONT_REGULAR } from "#styles/fonts"
16
+ import { LayerID } from "#styles/layers"
17
+
18
+ export interface PlanetaryPalette {
19
+ space: string
20
+ labelColor: string
21
+ labelHalo: string
22
+ selection: string
23
+ }
24
+
25
+ /**
26
+ * One palette per body with a published style. The record's keys are the bodies a style can be composed for, so a body
27
+ * without a row here has no style rather than a wrong one.
28
+ */
29
+ export const PALETTES = {
30
+ moon: { space: "#05070d", labelColor: "#e8eef7", labelHalo: "#05070d", selection: "#7fd1ff" },
31
+ mars: { space: "#0a0604", labelColor: "#f6e3d1", labelHalo: "#1a0c06", selection: "#ffb37f" },
32
+ } as const satisfies Record<string, PlanetaryPalette>
33
+
34
+ export type PlanetaryStyleBody = keyof typeof PALETTES
35
+
36
+ const PLANETARY_NAMESPACE = "planetary"
37
+
38
+ /**
39
+ * The background layer: the body's space color, drawn first.
40
+ */
41
+ export const PlanetarySpaceLayerID = LayerID(PLANETARY_NAMESPACE, "space")
42
+
43
+ /**
44
+ * The hillshade raster layer, present only when the body has a hillshade tileset.
45
+ */
46
+ export const PlanetaryHillshadeLayerID = LayerID(PLANETARY_NAMESPACE, "hillshade")
47
+
48
+ /**
49
+ * The nomenclature label layer, the one an app reads feature properties from on a click.
50
+ */
51
+ export const PlanetaryLabelsLayerID = LayerID(PLANETARY_NAMESPACE, "nomenclature-labels")
52
+
53
+ /**
54
+ * The selection ring layer, the one an app sets a feature `id` filter on.
55
+ */
56
+ export const PlanetarySelectionLayerID = LayerID(PLANETARY_NAMESPACE, "selection")
57
+
58
+ /**
59
+ * The IAU feature-type codes drawn as spaced capitals: a mare, planitia, oceanus or terra spans a region rather than
60
+ * marking a point, and the wide setting reads as an area name the way a printed atlas sets one.
61
+ */
62
+ const REGION_FEATURE_TYPE_CODES = ["ME", "PL", "OC", "TA"]
63
+
64
+ export function spaceLayer(palette: PlanetaryPalette): BackgroundLayerSpecification {
65
+ return {
66
+ id: PlanetarySpaceLayerID,
67
+ type: "background",
68
+ paint: { "background-color": palette.space },
69
+ }
70
+ }
71
+
72
+ export function hillshadeLayer(): RasterLayerSpecification {
73
+ return {
74
+ id: PlanetaryHillshadeLayerID,
75
+ type: "raster",
76
+ source: PlanetaryHillshadeSourceID,
77
+ paint: { "raster-opacity": 0.9, "raster-resampling": "linear" },
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Labels by feature scale: the tile's per-feature `minzoom` already hides a small crater at a low zoom, so the symbol
83
+ * layer only sizes and prioritizes. `featureTypeCode` is the IAU feature-type code carried in the tile properties (AA
84
+ * crater, MO mons, VA vallis, ME mare, PL planitia, SF satellite feature); `diameterKm` is the feature's diameter.
85
+ */
86
+ export function labelLayer(palette: PlanetaryPalette): SymbolLayerSpecification {
87
+ return {
88
+ id: PlanetaryLabelsLayerID,
89
+ type: "symbol",
90
+ source: PlanetaryNomenclatureSourceID,
91
+ "source-layer": "nomenclature",
92
+ layout: {
93
+ "text-field": ["get", "name"],
94
+ "text-font": [PROTOMAPS_FONT_REGULAR],
95
+ "text-size": ["interpolate", ["linear"], ["coalesce", ["get", "diameterKm"], 0], 0, 11, 100, 14, 1000, 18],
96
+ "text-transform": ["match", ["get", "featureTypeCode"], REGION_FEATURE_TYPE_CODES, "uppercase", "none"],
97
+ "text-letter-spacing": ["match", ["get", "featureTypeCode"], REGION_FEATURE_TYPE_CODES, 0.15, 0.02],
98
+ "symbol-sort-key": ["-", 0, ["coalesce", ["get", "diameterKm"], 0]],
99
+ "text-allow-overlap": false,
100
+ },
101
+ paint: { "text-color": palette.labelColor, "text-halo-color": palette.labelHalo, "text-halo-width": 1.2 },
102
+ }
103
+ }
104
+
105
+ /**
106
+ * The selection ring. Its filter matches no feature until the app sets one by `id`, so the layer ships in the style and
107
+ * the app never inserts a layer at runtime.
108
+ */
109
+ export function selectionLayer(palette: PlanetaryPalette): CircleLayerSpecification {
110
+ return {
111
+ id: PlanetarySelectionLayerID,
112
+ type: "circle",
113
+ source: PlanetaryNomenclatureSourceID,
114
+ "source-layer": "nomenclature",
115
+ filter: ["==", ["get", "id"], ""],
116
+ paint: {
117
+ "circle-radius": 14,
118
+ "circle-color": "transparent",
119
+ "circle-stroke-color": palette.selection,
120
+ "circle-stroke-width": 2,
121
+ },
122
+ }
123
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import type { RasterSourceSpecification, VectorSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
8
+
9
+ import { TileSetSourceID } from "#styles/sources"
10
+
11
+ /**
12
+ * The vector source carrying a body's IAU nomenclature: one `nomenclature` source layer of named features.
13
+ */
14
+ export const PlanetaryNomenclatureSourceID = TileSetSourceID("nomenclature")
15
+
16
+ /**
17
+ * The raster source carrying a body's hillshade, rendered from its DEM at build time.
18
+ */
19
+ export const PlanetaryHillshadeSourceID = TileSetSourceID("hillshade")
20
+
21
+ /**
22
+ * MapLibre resolves a TileJSON `url` itself: tiles, bounds, zoom range and attribution all come from the tile worker's
23
+ * document, so nothing here restates them.
24
+ */
25
+ export function nomenclatureSource(tileJSONURL: string): VectorSourceSpecification {
26
+ return {
27
+ type: "vector",
28
+ url: tileJSONURL,
29
+ }
30
+ }
31
+
32
+ export function hillshadeSource(tileJSONURL: string): RasterSourceSpecification {
33
+ return {
34
+ type: "raster",
35
+ url: tileJSONURL,
36
+ tileSize: 256,
37
+ }
38
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import type { SourceSpecification, StyleSpecification } from "@maplibre/maplibre-gl-style-spec"
8
+
9
+ import { StyleSpecificationComposer } from "#base/composition"
10
+ import {
11
+ hillshadeLayer,
12
+ labelLayer,
13
+ PALETTES,
14
+ type PlanetaryStyleBody,
15
+ selectionLayer,
16
+ spaceLayer,
17
+ } from "#planetary/layers"
18
+ import {
19
+ hillshadeSource,
20
+ nomenclatureSource,
21
+ PlanetaryHillshadeSourceID,
22
+ PlanetaryNomenclatureSourceID,
23
+ } from "#planetary/sources"
24
+
25
+ export interface PlanetaryStyleOptions {
26
+ body: PlanetaryStyleBody
27
+ /**
28
+ * The TileJSON document of the body's nomenclature tileset.
29
+ */
30
+ nomenclatureTileJSONURL: string
31
+ /**
32
+ * The TileJSON document of the body's hillshade tileset. Without one the style draws labels over bare space.
33
+ */
34
+ hillshadeTileJSONURL?: string
35
+ }
36
+
37
+ /**
38
+ * A body's whole style: space, the hillshade when a tileset exists, the nomenclature labels, and the selection ring. No
39
+ * Earth layer, no Earth DEM and no sprite reach the result, so a viewer loads nothing Earth-shaped.
40
+ */
41
+ export function createPlanetaryStyle(options: PlanetaryStyleOptions): StyleSpecification {
42
+ const palette = PALETTES[options.body]
43
+
44
+ const sources: Record<string, SourceSpecification> = {
45
+ [PlanetaryNomenclatureSourceID]: nomenclatureSource(options.nomenclatureTileJSONURL),
46
+ }
47
+
48
+ if (options.hillshadeTileJSONURL) {
49
+ sources[PlanetaryHillshadeSourceID] = hillshadeSource(options.hillshadeTileJSONURL)
50
+ }
51
+
52
+ const baseLayers = [
53
+ spaceLayer(palette),
54
+ ...(options.hillshadeTileJSONURL ? [hillshadeLayer()] : []),
55
+ labelLayer(palette),
56
+ selectionLayer(palette),
57
+ ]
58
+
59
+ return new StyleSpecificationComposer({
60
+ sources,
61
+ baseLayers,
62
+ hillshadeSource: null,
63
+ sprite: null,
64
+ sky: { "sky-color": palette.space, "horizon-color": palette.space },
65
+ }).toJSON()
66
+ }
@@ -15,7 +15,7 @@
15
15
  * Same idiom as the coverage overlay.
16
16
  *
17
17
  * Served as XYZ vector tiles by the tile worker from `nexus-assets/tiles/race-dots-la.pmtiles`; the
18
- * consumer passes its TileJSON URL (`tiles.sister.software/race-dots-la.json`) to
18
+ * consumer passes its TileJSON URL (`tiles.mailwoman.ai/race-dots-la.json`) to
19
19
  * `createRaceDotsSource`.
20
20
  *
21
21
  * The dot is a _representation_, not a record: a random position inside the block, standing in for
@@ -24,7 +24,7 @@
24
24
 
25
25
  import type { CircleLayerSpecification, VectorSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
26
26
 
27
- import { TileSetSourceID } from "../styles/sources.ts"
27
+ import { TileSetSourceID } from "#styles/sources"
28
28
 
29
29
  /**
30
30
  * Tile set id for the dot-density race layer, one dot per N people (see `tiger/tools/race-dots.ts`).
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The glyph stacks a symbol layer may name. MapLibre draws NO text at all when a glyph range answers 404 — not a
7
+ * fallback face, nothing — so a `text-font` naming an absent stack blanks every label in the style at every zoom,
8
+ * with no console error a reader would connect to the missing labels.
9
+ *
10
+ * The mirror behind `PROTOMAPS_GLYPHS_URL` serves five stacks: `Noto Sans Regular`, `Noto Sans Medium`,
11
+ * `Noto Sans Italic`, `Fira Code Regular` and `Fira Code Medium`. It serves no Fira Sans, and no
12
+ * `Open Sans Regular` — which is MapLibre's own default, so a symbol layer that omits `text-font` also draws
13
+ * nothing. Name a constant from this file rather than a string literal.
14
+ */
15
+
16
+ /**
17
+ * The regular text stack: body labels, point features, place names.
18
+ */
19
+ export const PROTOMAPS_FONT_REGULAR = "Noto Sans Regular"
@@ -4,6 +4,6 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- export * from "./api.ts"
8
- export * from "./coords.ts"
9
- export * from "./schema.ts"
7
+ export * from "#styles/fonts"
8
+ export * from "#styles/layers"
9
+ export * from "#styles/sources"
@@ -18,7 +18,11 @@ export type TileSetSourceID<T extends string = string> = Tagged<string, "TileSet
18
18
  * Declares a tileset identifier.
19
19
  */
20
20
  export function TileSetSourceID<T extends string>(value: T): TileSetSourceID<T> {
21
- return value as unknown as TileSetSourceID<T>
21
+ // Widen to `string` before the tag: a generic `T` defers the comparability check and the assertion is refused,
22
+ // while `string` to the tagged type is the ordinary mint this function exists to be the only site of.
23
+ const raw: string = value
24
+
25
+ return raw as TileSetSourceID<T>
22
26
  }
23
27
 
24
28
  /**
@@ -4,9 +4,9 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- import { HillsLayerID } from "../base/layers.ts"
8
- import type { LayerSpecificationListInput } from "../styles/layers.ts"
9
- import { TileSetSourceID } from "../styles/sources.ts"
7
+ import { HillsLayerID } from "#base/layers"
8
+ import type { LayerSpecificationListInput } from "#styles/layers"
9
+ import { TileSetSourceID } from "#styles/sources"
10
10
 
11
11
  /**
12
12
  * Tile set id for US Census tract polygons.
@@ -7,8 +7,8 @@
7
7
  import { APIClient, pluckResponseData } from "@mailwoman/core/api"
8
8
  import type { VectorSourceSpecification } from "maplibre-gl"
9
9
 
10
- import type { TileSetSourceID, TileSetSourceRecord } from "../styles/sources.ts"
11
- import type { TileJSON } from "./schema.ts"
10
+ import type { TileSetSourceID, TileSetSourceRecord } from "#styles/sources"
11
+ import type { TileJSON } from "#tiles/schema"
12
12
 
13
13
  /**
14
14
  * API Client for fetching Protomap tile data.
@@ -16,7 +16,7 @@ import type { TileJSON } from "./schema.ts"
16
16
  export const TileAPI = new APIClient({
17
17
  displayName: "TileAPI",
18
18
  axios: {
19
- baseURL: "https://tiles.sister.software",
19
+ baseURL: "https://tiles.mailwoman.ai",
20
20
  },
21
21
  })
22
22
 
@@ -4,5 +4,6 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- export * from "./layers.ts"
8
- export * from "./sources.ts"
7
+ export * from "#tiles/api"
8
+ export * from "#tiles/coords"
9
+ export * from "#tiles/schema"
@@ -1 +1 @@
1
- {"version":3,"file":"buildings.d.ts","sourceRoot":"","sources":["../../base/buildings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAGX,kBAAkB,EAClB,MAAM,kCAAkC,CAAA;AA2CzC;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,kBAAkB,EAqE9C,CAAA"}
1
+ {"version":3,"file":"buildings.d.ts","sourceRoot":"","sources":["../../lib/base/buildings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAGX,kBAAkB,EAClB,MAAM,kCAAkC,CAAA;AA4CzC;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,kBAAkB,EAqE9C,CAAA"}