@mailwoman/cartographer 1.0.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 (100) hide show
  1. package/README.md +3 -0
  2. package/base/buildings.ts +124 -0
  3. package/base/composition.ts +119 -0
  4. package/base/index.ts +10 -0
  5. package/base/layers.ts +52 -0
  6. package/base/terrain.ts +33 -0
  7. package/base/theme.ts +118 -0
  8. package/bdc/index.ts +395 -0
  9. package/coverage/index.ts +89 -0
  10. package/hspa/index.ts +20 -0
  11. package/index.ts +12 -0
  12. package/out/base/buildings.d.ts +11 -0
  13. package/out/base/buildings.d.ts.map +1 -0
  14. package/out/base/buildings.js +109 -0
  15. package/out/base/buildings.js.map +1 -0
  16. package/out/base/composition.d.ts +33 -0
  17. package/out/base/composition.d.ts.map +1 -0
  18. package/out/base/composition.js +86 -0
  19. package/out/base/composition.js.map +1 -0
  20. package/out/base/index.d.ts +10 -0
  21. package/out/base/index.d.ts.map +1 -0
  22. package/out/base/index.js +10 -0
  23. package/out/base/index.js.map +1 -0
  24. package/out/base/layers.d.ts +9 -0
  25. package/out/base/layers.d.ts.map +1 -0
  26. package/out/base/layers.js +49 -0
  27. package/out/base/layers.js.map +1 -0
  28. package/out/base/terrain.d.ts +20 -0
  29. package/out/base/terrain.d.ts.map +1 -0
  30. package/out/base/terrain.js +29 -0
  31. package/out/base/terrain.js.map +1 -0
  32. package/out/base/theme.d.ts +20 -0
  33. package/out/base/theme.d.ts.map +1 -0
  34. package/out/base/theme.js +103 -0
  35. package/out/base/theme.js.map +1 -0
  36. package/out/bdc/index.d.ts +27 -0
  37. package/out/bdc/index.d.ts.map +1 -0
  38. package/out/bdc/index.js +344 -0
  39. package/out/bdc/index.js.map +1 -0
  40. package/out/coverage/index.d.ts +58 -0
  41. package/out/coverage/index.d.ts.map +1 -0
  42. package/out/coverage/index.js +77 -0
  43. package/out/coverage/index.js.map +1 -0
  44. package/out/hspa/index.d.ts +12 -0
  45. package/out/hspa/index.d.ts.map +1 -0
  46. package/out/hspa/index.js +18 -0
  47. package/out/hspa/index.js.map +1 -0
  48. package/out/index.d.ts +12 -0
  49. package/out/index.d.ts.map +1 -0
  50. package/out/index.js +12 -0
  51. package/out/index.js.map +1 -0
  52. package/out/race-dots/index.d.ts +61 -0
  53. package/out/race-dots/index.d.ts.map +1 -0
  54. package/out/race-dots/index.js +59 -0
  55. package/out/race-dots/index.js.map +1 -0
  56. package/out/styles/index.d.ts +8 -0
  57. package/out/styles/index.d.ts.map +1 -0
  58. package/out/styles/index.js +8 -0
  59. package/out/styles/index.js.map +1 -0
  60. package/out/styles/layers.d.ts +33 -0
  61. package/out/styles/layers.d.ts.map +1 -0
  62. package/out/styles/layers.js +108 -0
  63. package/out/styles/layers.js.map +1 -0
  64. package/out/styles/sources.d.ts +22 -0
  65. package/out/styles/sources.d.ts.map +1 -0
  66. package/out/styles/sources.js +12 -0
  67. package/out/styles/sources.js.map +1 -0
  68. package/out/tiger/index.d.ts +11 -0
  69. package/out/tiger/index.d.ts.map +1 -0
  70. package/out/tiger/index.js +64 -0
  71. package/out/tiger/index.js.map +1 -0
  72. package/out/tiles/api.d.ts +20 -0
  73. package/out/tiles/api.d.ts.map +1 -0
  74. package/out/tiles/api.js +41 -0
  75. package/out/tiles/api.js.map +1 -0
  76. package/out/tiles/coords.d.ts +16 -0
  77. package/out/tiles/coords.d.ts.map +1 -0
  78. package/out/tiles/coords.js +34 -0
  79. package/out/tiles/coords.js.map +1 -0
  80. package/out/tiles/index.d.ts +9 -0
  81. package/out/tiles/index.d.ts.map +1 -0
  82. package/out/tiles/index.js +9 -0
  83. package/out/tiles/index.js.map +1 -0
  84. package/out/tiles/schema.d.ts +41 -0
  85. package/out/tiles/schema.d.ts.map +1 -0
  86. package/out/tiles/schema.js +7 -0
  87. package/out/tiles/schema.js.map +1 -0
  88. package/out/tsconfig.tsbuildinfo +1 -0
  89. package/package.json +46 -0
  90. package/race-dots/index.ts +68 -0
  91. package/styles/index.ts +8 -0
  92. package/styles/layers.ts +148 -0
  93. package/styles/sources.ts +26 -0
  94. package/tiger/index.ts +71 -0
  95. package/tiles/api.ts +53 -0
  96. package/tiles/coords.ts +46 -0
  97. package/tiles/index.ts +9 -0
  98. package/tiles/schema.ts +42 -0
  99. package/tsconfig.json +22 -0
  100. package/typedoc.json +4 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Nexus Cartographer
2
+
3
+ This package contains a collection of modules for working rendering maps and spatial data.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import { type FillExtrusionLayerSpecification, type LayerSpecification } from "@maplibre/maplibre-gl-style-spec"
8
+ import { interpolateTurbo } from "d3-scale-chromatic"
9
+ import { LayerID } from "../styles/layers.js"
10
+ import { MailwomanBaseTileSetID } from "./theme.js"
11
+
12
+ const BuildingLayerID = LayerID.bind(null, "buildings")
13
+
14
+ /**
15
+ * Layer definitions for building data.
16
+ */
17
+ export const BuildingLayers: LayerSpecification[] = [
18
+ createBuildingFillStyleLayer({
19
+ id: BuildingLayerID("buildings-extruded"),
20
+
21
+ 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
+ ],
40
+ "fill-extrusion-translate": [0.1, 0.1],
41
+ },
42
+ }),
43
+
44
+ {
45
+ id: "basemap-buildings-outline",
46
+ type: "line",
47
+ source: MailwomanBaseTileSetID,
48
+ "source-layer": "buildings",
49
+ minzoom: 11,
50
+ filter: ["==", ["%", ["to-number", ["id"]], 2], 0],
51
+ paint: {
52
+ "line-width": 1,
53
+ "line-color": "hsl(240deg 100% 80%)",
54
+ "line-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 20, 1],
55
+ "line-dasharray": [2, 3],
56
+ },
57
+ },
58
+
59
+ {
60
+ id: "basemap-buildings-kind",
61
+ type: "symbol",
62
+ source: MailwomanBaseTileSetID,
63
+ "source-layer": "buildings",
64
+ minzoom: 11,
65
+ layout: {
66
+ visibility: "none",
67
+ "text-field": ["to-string", ["id"]],
68
+ "text-offset": [0, 0],
69
+ "text-anchor": "center",
70
+ "text-font": ["Fira Sans Regular"],
71
+ },
72
+
73
+ paint: {
74
+ "text-color": "black",
75
+ "text-halo-color": "red",
76
+ "text-halo-width": 1,
77
+ },
78
+ },
79
+ ]
80
+
81
+ type BuildingFillStyleSpec = Pick<FillExtrusionLayerSpecification, "id"> &
82
+ Partial<FillExtrusionLayerSpecification> & {
83
+ heightOffset?: number
84
+ }
85
+
86
+ function createBuildingFillStyleLayer(fillStyleSpec: BuildingFillStyleSpec): LayerSpecification {
87
+ const baseStyle: LayerSpecification = {
88
+ ...fillStyleSpec,
89
+ type: "fill-extrusion",
90
+ source: MailwomanBaseTileSetID,
91
+ "source-layer": "buildings",
92
+ minzoom: 12,
93
+ paint: {
94
+ ...fillStyleSpec.paint,
95
+ "fill-extrusion-vertical-gradient": true,
96
+
97
+ "fill-extrusion-height": [
98
+ "+",
99
+ [
100
+ // --
101
+ "coalesce",
102
+ ["get", "height"],
103
+ ["get", "minheight"],
104
+ 10,
105
+ ],
106
+ fillStyleSpec.heightOffset || 0,
107
+ ],
108
+ "fill-extrusion-translate-anchor": "map",
109
+ // "fill-extrusion-opacity": 0.7,
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
+ ],
120
+ },
121
+ }
122
+
123
+ return baseStyle
124
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import type { SourceSpecification } from "@maplibre/maplibre-gl-style-spec"
8
+ import type { LightSpecification, SkySpecification, StyleSpecification, TerrainSpecification } from "maplibre-gl"
9
+ import {
10
+ LayerSpecificationList,
11
+ type LayerSpecificationListInput,
12
+ type LayerSpecificationListItem,
13
+ } from "../styles/layers.js"
14
+ import { type TileSetSourceRecord } from "../styles/sources.js"
15
+ import { BaseLayers } from "./layers.js"
16
+ import { createTerrainDEMSource, HillshadeTileSetID, TerrainTileSetID } from "./terrain.js"
17
+
18
+ //#endregion
19
+
20
+ //#region Spec Creators
21
+
22
+ export function createLightSpec(spec?: Partial<LightSpecification>): LightSpecification {
23
+ return {
24
+ color: "white",
25
+ intensity: 0.85,
26
+ anchor: "viewport",
27
+ position: [
28
+ 10, // Radial
29
+ 20, // Azimuthal
30
+ -5, // Polar
31
+ ],
32
+ ...spec,
33
+ }
34
+ }
35
+
36
+ export function createSkySpec(spec?: Partial<SkySpecification>): SkySpecification {
37
+ return {
38
+ "sky-color": "#000535",
39
+ "horizon-color": "hsl(54deg 100% 16%)",
40
+ "fog-color": "hsl(54deg 100% 5%)",
41
+ "sky-horizon-blend": 0.75,
42
+ "horizon-fog-blend": 0.75,
43
+ "fog-ground-blend": 0.1,
44
+ ...spec,
45
+ }
46
+ }
47
+
48
+ //#endregion
49
+
50
+ //#region Style Composition
51
+
52
+ export interface StyleSpecificationComposition {
53
+ sources: Record<string, SourceSpecification>
54
+ layers?: LayerSpecificationListInput[]
55
+ light?: Partial<LightSpecification>
56
+ sky?: Partial<SkySpecification>
57
+ terrain?: Partial<TerrainSpecification>
58
+ }
59
+
60
+ /**
61
+ * A stateful class for composing a style specification.
62
+ */
63
+ export class StyleSpecificationComposer {
64
+ layersList: LayerSpecificationList
65
+ light: LightSpecification
66
+ sky: SkySpecification
67
+ terrain: TerrainSpecification
68
+ sources: TileSetSourceRecord
69
+
70
+ constructor(spec: StyleSpecificationComposition) {
71
+ this.light = createLightSpec(spec.light)
72
+ this.sky = createSkySpec(spec.sky)
73
+
74
+ this.terrain = {
75
+ source: TerrainTileSetID,
76
+ ...spec.terrain,
77
+ }
78
+
79
+ this.sources = {
80
+ ...spec.sources,
81
+ [TerrainTileSetID]: createTerrainDEMSource(),
82
+ [HillshadeTileSetID]: createTerrainDEMSource(),
83
+ }
84
+
85
+ this.layersList = new LayerSpecificationList(BaseLayers)
86
+
87
+ for (const layer of spec.layers || []) {
88
+ this.layersList.insert(layer)
89
+ }
90
+ }
91
+
92
+ public get layers(): LayerSpecificationListItem[] {
93
+ return Array.from(this.layersList)
94
+ }
95
+
96
+ toJSON(): StyleSpecification {
97
+ const styleSpec: StyleSpecification = {
98
+ version: 8,
99
+ // Sprite must match the basemap schema version — v4 sprites carry the icons referenced
100
+ // by the v4 theme spec. Currently upstream URLs; we mirror these to nexus-assets/{fonts,
101
+ // sprites/v4}/ for self-hosting, but no public route fronts that bucket yet.
102
+ glyphs: "https://public.sister.software/protomaps/fonts/{fontstack}/{range}.pbf",
103
+ sprite: "https://public.sister.software/protomaps/sprites/v4/light",
104
+ light: createLightSpec(this.light),
105
+ sky: createSkySpec(this.sky),
106
+ terrain: this.terrain,
107
+ sources: this.sources,
108
+ layers: this.layers,
109
+ }
110
+
111
+ return styleSpec
112
+ }
113
+
114
+ toJS(): StyleSpecification {
115
+ return this.toJSON()
116
+ }
117
+ }
118
+
119
+ //#endregion
package/base/index.ts ADDED
@@ -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 "./buildings.js"
8
+ export * from "./composition.js"
9
+ export * from "./layers.js"
10
+ export * from "./theme.js"
package/base/layers.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import { layers } from "@protomaps/basemaps"
8
+ import { type LayerSpecification } from "maplibre-gl"
9
+ import { BuildingLayers } from "../base/buildings.js"
10
+ import { MailwomanBaseFlavor, MailwomanBaseTileSetID } from "../base/theme.js"
11
+ import { LayerID } from "../styles/layers.js"
12
+ import { HillshadeTileSetID } from "./terrain.js"
13
+
14
+ export const HillsLayerID = LayerID(HillshadeTileSetID, "hills")
15
+
16
+ /**
17
+ * Splits `layers()` into non-label + label groups so building footprints, water outlines, and
18
+ * hillshade sit between base geometry and labels. `@protomaps/basemaps@5.x` doesn't expose a
19
+ * `noLabels` helper — `labelsOnly: true` gives only the label layers; subtracting that set from the
20
+ * full list yields the non-label group.
21
+ */
22
+ const allBaseLayers = layers(MailwomanBaseTileSetID, MailwomanBaseFlavor, { lang: "en" })
23
+ const labelLayers = layers(MailwomanBaseTileSetID, MailwomanBaseFlavor, { lang: "en", labelsOnly: true })
24
+ const labelLayerIDs = new Set(labelLayers.map((layer) => layer.id))
25
+ const nonLabelLayers = allBaseLayers.filter((layer) => !labelLayerIDs.has(layer.id))
26
+
27
+ export const BaseLayers: LayerSpecification[] = [
28
+ ...nonLabelLayers,
29
+ {
30
+ id: LayerID(MailwomanBaseTileSetID, "water-outline"),
31
+ type: "line",
32
+ source: MailwomanBaseTileSetID,
33
+ "source-layer": "water",
34
+ filter: ["any", ["in", "water", "river", "lake", "other"]],
35
+ paint: {
36
+ "line-color": "hsl(194deg 100% 30% / 0.5)",
37
+ "line-width": 1,
38
+ },
39
+ },
40
+ {
41
+ id: HillsLayerID,
42
+ type: "hillshade",
43
+ source: HillshadeTileSetID,
44
+ paint: {
45
+ "hillshade-exaggeration": 0.25,
46
+ "hillshade-accent-color": "hsl(240deg 100% 95%)",
47
+ "hillshade-shadow-color": "hsl(240deg 100% 5%)",
48
+ },
49
+ },
50
+ ...BuildingLayers,
51
+ ...labelLayers,
52
+ ]
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import { type RasterDEMSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
8
+ import { TileSetSourceID } from "../styles/sources.js"
9
+
10
+ /**
11
+ * Creates a raster DEM source specification for terrain data.
12
+ */
13
+ export function createTerrainDEMSource(): RasterDEMSourceSpecification {
14
+ const terrainSource: RasterDEMSourceSpecification = {
15
+ type: "raster-dem",
16
+ encoding: "terrarium",
17
+ tiles: ["https://elevation-tiles-prod.s3.amazonaws.com/terrarium/{z}/{x}/{y}.png"],
18
+ tileSize: 256,
19
+ maxzoom: 15,
20
+ }
21
+
22
+ return terrainSource
23
+ }
24
+
25
+ /**
26
+ * Identifier for the Nexus terrain tileset.
27
+ */
28
+ export const TerrainTileSetID = TileSetSourceID("terrain")
29
+
30
+ /**
31
+ * Identifier for the Nexus terrain tileset.
32
+ */
33
+ export const HillshadeTileSetID = TileSetSourceID("hillshade")
package/base/theme.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import { type Flavor } from "@protomaps/basemaps"
8
+ import { TileSetSourceID } from "../styles/sources.js"
9
+
10
+ /**
11
+ * Identifier for the Mailwoman base tileset. Matches the R2 object basename
12
+ * (`nexus-assets/tiles/basemap-v4.pmtiles`) — `tiles.sister.software/basemap-v4.json` returns its
13
+ * tilejson, `tiles.sister.software/basemap-v4/{z}/{x}/{y}.mvt` returns vector tiles.
14
+ */
15
+ export const MailwomanBaseTileSetID = TileSetSourceID("basemap-v4")
16
+
17
+ /**
18
+ * The Mailwoman theme for Protomaps via MapLibre. Keys follow `@protomaps/basemaps@5.x` which
19
+ * targets the v4 tile schema (`["==","kind","..."]` filters), matching the basemap-v4 PMTiles on
20
+ * R2.
21
+ */
22
+ export const MailwomanBaseFlavor: Flavor = {
23
+ //#region Base
24
+ background: "hsl(0deg 10% 5%)",
25
+ earth: "hsl(0deg 10% 5%)",
26
+
27
+ park_a: "hsl(120deg 45.75% 1%)",
28
+ park_b: "hsl(120deg 65.75% 10%)",
29
+
30
+ hospital: "#252424",
31
+ industrial: "#222222",
32
+ school: "#262323",
33
+ wood_a: "#202121",
34
+ wood_b: "#202121",
35
+ pedestrian: "#1e1e1e",
36
+ scrub_a: "#222323",
37
+ scrub_b: "#222323",
38
+ glacier: "#1c1c1c",
39
+ sand: "#212123",
40
+ beach: "hsl(44.24deg 100% 20% / 0.1)",
41
+ aerodrome: "#1e1e1e",
42
+ runway: "#333333",
43
+
44
+ water: "hsl(194deg 100% 10%)",
45
+
46
+ pier: "#222222",
47
+ zoo: "#222323",
48
+ military: "#242323",
49
+
50
+ //#endregion
51
+ //#region Tunnel
52
+ tunnel_other_casing: "#141414",
53
+ tunnel_minor_casing: "#141414",
54
+ tunnel_link_casing: "#141414",
55
+ tunnel_major_casing: "#141414",
56
+ tunnel_highway_casing: "#141414",
57
+ tunnel_other: "#292929",
58
+ tunnel_minor: "#292929",
59
+ tunnel_link: "#292929",
60
+ tunnel_major: "#292929",
61
+ tunnel_highway: "#292929",
62
+
63
+ //#endregion
64
+ buildings: "#111111",
65
+
66
+ //#region Casing
67
+ minor_service_casing: "#1f1f1f",
68
+ minor_casing: "#1f1f1f",
69
+ link_casing: "#1f1f1f",
70
+ major_casing_late: "#1f1f1f",
71
+ highway_casing_late: "#1f1f1f",
72
+ other: "#333333",
73
+ minor_service: "#333333",
74
+ minor_a: "#3d3d3d",
75
+ minor_b: "#333333",
76
+ link: "#3d3d3d",
77
+ major_casing_early: "#1f1f1f",
78
+ major: "#3d3d3d",
79
+ highway_casing_early: "#1f1f1f",
80
+ highway: "hsl(36deg 10% 50%)",
81
+
82
+ //#endregion
83
+ railway: "#000000",
84
+ boundaries: "hsl(240deg 100% 90%)",
85
+
86
+ //#region Bridges
87
+ bridges_other_casing: "#2b2b2b",
88
+ bridges_minor_casing: "#1f1f1f",
89
+ bridges_link_casing: "#1f1f1f",
90
+ bridges_major_casing: "#1f1f1f",
91
+ bridges_highway_casing: "#1f1f1f",
92
+ bridges_other: "#333333",
93
+ bridges_minor: "#333333",
94
+ bridges_link: "#3d3d3d",
95
+ bridges_major: "#3d3d3d",
96
+ bridges_highway: "#474747",
97
+
98
+ //#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
+ //#endregion
106
+ ocean_label: "#717784",
107
+ subplace_label: "hsl(50deg 50% 70%)",
108
+ subplace_label_halo: "hsl(40deg 100% 10%)",
109
+
110
+ city_label: "hsl(50deg 100% 90%)",
111
+ city_label_halo: "hsl(240deg 100% 10%)",
112
+
113
+ state_label: "hsl(240deg 100% 90%)",
114
+ state_label_halo: "#1f1f1f",
115
+ country_label: "hsl(240deg 50% 80% / 0.5)",
116
+ address_label: "hsl(240deg 100% 90%)",
117
+ address_label_halo: "hsl(240deg 100% 10%)",
118
+ }