@diagrammo/dgmo 0.47.0 → 0.48.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 (319) hide show
  1. package/dist/auto.js +1 -1
  2. package/dist/auto.mjs +1 -1
  3. package/dist/element.js +1 -1
  4. package/dist/element.mjs +1 -1
  5. package/package.json +19 -70
  6. package/dist/advanced.cjs +0 -71648
  7. package/dist/advanced.d.cts +0 -5719
  8. package/dist/auto.cjs +0 -70648
  9. package/dist/auto.d.cts +0 -54
  10. package/dist/block.cjs +0 -70208
  11. package/dist/block.d.cts +0 -115
  12. package/dist/editor.cjs +0 -526
  13. package/dist/editor.d.cts +0 -26
  14. package/dist/element.cjs +0 -70530
  15. package/dist/element.d.cts +0 -62
  16. package/dist/highlight.cjs +0 -852
  17. package/dist/highlight.d.cts +0 -49
  18. package/dist/index.cjs +0 -70491
  19. package/dist/index.d.cts +0 -160
  20. package/dist/pert.cjs +0 -325
  21. package/dist/pert.d.cts +0 -559
  22. package/dist/themes-1-CuKpeH.d.cts +0 -908
  23. package/src/advanced.ts +0 -873
  24. package/src/arc/parser.ts +0 -15
  25. package/src/arc/renderer.ts +0 -633
  26. package/src/auto/index.ts +0 -621
  27. package/src/auto/shared.ts +0 -370
  28. package/src/auto/styles.ts +0 -73
  29. package/src/block/layout.ts +0 -198
  30. package/src/block/parser.ts +0 -427
  31. package/src/block/renderer.ts +0 -484
  32. package/src/block/types.ts +0 -67
  33. package/src/boxes-and-lines/collapse.ts +0 -96
  34. package/src/boxes-and-lines/focus.ts +0 -257
  35. package/src/boxes-and-lines/label-placement.ts +0 -339
  36. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  37. package/src/boxes-and-lines/layout-layered.ts +0 -722
  38. package/src/boxes-and-lines/layout-search.ts +0 -1678
  39. package/src/boxes-and-lines/layout.ts +0 -398
  40. package/src/boxes-and-lines/parser.ts +0 -1181
  41. package/src/boxes-and-lines/renderer.ts +0 -1465
  42. package/src/boxes-and-lines/types.ts +0 -66
  43. package/src/c4/layout.ts +0 -2441
  44. package/src/c4/parser.ts +0 -1158
  45. package/src/c4/renderer.ts +0 -2008
  46. package/src/c4/types.ts +0 -89
  47. package/src/chart-type-registry.ts +0 -529
  48. package/src/chart-types.ts +0 -225
  49. package/src/chart.ts +0 -685
  50. package/src/charts-d3/bar.ts +0 -338
  51. package/src/charts-d3/chord.ts +0 -140
  52. package/src/charts-d3/function.ts +0 -147
  53. package/src/charts-d3/funnel.ts +0 -104
  54. package/src/charts-d3/heatmap.ts +0 -145
  55. package/src/charts-d3/index.ts +0 -337
  56. package/src/charts-d3/interactions.ts +0 -477
  57. package/src/charts-d3/line.ts +0 -344
  58. package/src/charts-d3/mount.ts +0 -151
  59. package/src/charts-d3/pie.ts +0 -117
  60. package/src/charts-d3/polar.ts +0 -89
  61. package/src/charts-d3/radar.ts +0 -0
  62. package/src/charts-d3/sankey.ts +0 -0
  63. package/src/charts-d3/scatter.ts +0 -185
  64. package/src/charts-d3/shared.ts +0 -208
  65. package/src/class/layout.ts +0 -326
  66. package/src/class/parser.ts +0 -635
  67. package/src/class/renderer.ts +0 -793
  68. package/src/class/types.ts +0 -58
  69. package/src/cli-banner.ts +0 -106
  70. package/src/cli.ts +0 -1080
  71. package/src/colors-diagnostics.ts +0 -35
  72. package/src/colors.ts +0 -379
  73. package/src/completion-types.ts +0 -27
  74. package/src/completion.ts +0 -2668
  75. package/src/cycle/layout.ts +0 -1026
  76. package/src/cycle/parser.ts +0 -387
  77. package/src/cycle/renderer.ts +0 -653
  78. package/src/cycle/types.ts +0 -101
  79. package/src/d3.ts +0 -1434
  80. package/src/data-chart-parser.ts +0 -1007
  81. package/src/dgmo-router.ts +0 -296
  82. package/src/diagnostics-registry.ts +0 -108
  83. package/src/diagnostics.ts +0 -0
  84. package/src/dimensions.ts +0 -20
  85. package/src/directives-registry.ts +0 -396
  86. package/src/editor/dgmo.grammar +0 -76
  87. package/src/editor/dgmo.grammar.d.ts +0 -2
  88. package/src/editor/dgmo.grammar.js +0 -18
  89. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  90. package/src/editor/dgmo.grammar.terms.js +0 -35
  91. package/src/editor/highlight-api.ts +0 -596
  92. package/src/editor/highlight.ts +0 -36
  93. package/src/editor/index.ts +0 -27
  94. package/src/editor/keywords.ts +0 -128
  95. package/src/editor/tokens.ts +0 -30
  96. package/src/element/index.ts +0 -432
  97. package/src/embed/css.ts +0 -312
  98. package/src/embed/escape.ts +0 -15
  99. package/src/embed/index.ts +0 -280
  100. package/src/er/classify.ts +0 -228
  101. package/src/er/layout.ts +0 -530
  102. package/src/er/parser.ts +0 -750
  103. package/src/er/renderer.ts +0 -725
  104. package/src/er/types.ts +0 -52
  105. package/src/error-card.ts +0 -269
  106. package/src/event-line/diagnostics.ts +0 -117
  107. package/src/event-line/parser.ts +0 -643
  108. package/src/event-line/renderer.ts +0 -1742
  109. package/src/event-line/types.ts +0 -74
  110. package/src/fonts.ts +0 -3
  111. package/src/gantt/calculator.ts +0 -979
  112. package/src/gantt/parser.ts +0 -1641
  113. package/src/gantt/renderer.ts +0 -3787
  114. package/src/gantt/resolver.ts +0 -172
  115. package/src/gantt/types.ts +0 -206
  116. package/src/graph/diagnostics.ts +0 -64
  117. package/src/graph/edge-spline.ts +0 -29
  118. package/src/graph/flowchart-parser.ts +0 -759
  119. package/src/graph/flowchart-renderer.ts +0 -817
  120. package/src/graph/layout.ts +0 -472
  121. package/src/graph/notes.ts +0 -21
  122. package/src/graph/state-collapse.ts +0 -78
  123. package/src/graph/state-parser.ts +0 -574
  124. package/src/graph/state-renderer.ts +0 -653
  125. package/src/graph/types.ts +0 -65
  126. package/src/index.ts +0 -261
  127. package/src/infra/compute.ts +0 -1385
  128. package/src/infra/layout.ts +0 -888
  129. package/src/infra/parser.ts +0 -1234
  130. package/src/infra/renderer.ts +0 -2534
  131. package/src/infra/roles.ts +0 -75
  132. package/src/infra/types.ts +0 -216
  133. package/src/infra/validation.ts +0 -201
  134. package/src/journey-map/layout.ts +0 -490
  135. package/src/journey-map/parser.ts +0 -557
  136. package/src/journey-map/renderer.ts +0 -1580
  137. package/src/journey-map/types.ts +0 -47
  138. package/src/kanban/mutations.ts +0 -175
  139. package/src/kanban/parser.ts +0 -509
  140. package/src/kanban/renderer.ts +0 -1449
  141. package/src/kanban/types.ts +0 -39
  142. package/src/label-layout.ts +0 -368
  143. package/src/map/colorize.ts +0 -54
  144. package/src/map/completion.ts +0 -316
  145. package/src/map/context-labels.ts +0 -609
  146. package/src/map/data/PROVENANCE.json +0 -1
  147. package/src/map/data/README.md +0 -40
  148. package/src/map/data/airport-collisions.json +0 -1
  149. package/src/map/data/airports.json +0 -1
  150. package/src/map/data/gazetteer.json +0 -1
  151. package/src/map/data/lakes.json +0 -1
  152. package/src/map/data/mountain-ranges.json +0 -1
  153. package/src/map/data/na-lakes.json +0 -1
  154. package/src/map/data/na-land.json +0 -1
  155. package/src/map/data/region-names.json +0 -1
  156. package/src/map/data/rivers.json +0 -1
  157. package/src/map/data/types.ts +0 -121
  158. package/src/map/data/us-states.json +0 -1
  159. package/src/map/data/water-bodies.json +0 -1
  160. package/src/map/data/world-coarse.json +0 -1
  161. package/src/map/data/world-detail.json +0 -1
  162. package/src/map/diagnostics.ts +0 -134
  163. package/src/map/dimensions.ts +0 -150
  164. package/src/map/geo-query.ts +0 -295
  165. package/src/map/geo.ts +0 -438
  166. package/src/map/invert.ts +0 -111
  167. package/src/map/layout.ts +0 -5069
  168. package/src/map/legend-band.ts +0 -127
  169. package/src/map/load-data.ts +0 -186
  170. package/src/map/parser.ts +0 -783
  171. package/src/map/renderer.ts +0 -1445
  172. package/src/map/resolved-types.ts +0 -160
  173. package/src/map/resolver.ts +0 -1146
  174. package/src/map/types.ts +0 -194
  175. package/src/mindmap/collapse.ts +0 -67
  176. package/src/mindmap/layout.ts +0 -649
  177. package/src/mindmap/parser.ts +0 -430
  178. package/src/mindmap/renderer.ts +0 -637
  179. package/src/mindmap/text-wrap.ts +0 -218
  180. package/src/mindmap/types.ts +0 -56
  181. package/src/org/collapse.ts +0 -151
  182. package/src/org/layout.ts +0 -1262
  183. package/src/org/parser.ts +0 -519
  184. package/src/org/renderer.ts +0 -866
  185. package/src/org/resolver.ts +0 -636
  186. package/src/palettes/atlas.ts +0 -77
  187. package/src/palettes/blueprint.ts +0 -73
  188. package/src/palettes/catppuccin.ts +0 -84
  189. package/src/palettes/color-utils.ts +0 -443
  190. package/src/palettes/index.ts +0 -67
  191. package/src/palettes/nord.ts +0 -67
  192. package/src/palettes/registry.ts +0 -114
  193. package/src/palettes/slate.ts +0 -73
  194. package/src/palettes/tidewater.ts +0 -73
  195. package/src/palettes/tokyo-night.ts +0 -86
  196. package/src/palettes/types.ts +0 -88
  197. package/src/pert/analyzer.ts +0 -1183
  198. package/src/pert/diagnostics.ts +0 -110
  199. package/src/pert/index.ts +0 -43
  200. package/src/pert/internal.ts +0 -252
  201. package/src/pert/layout.ts +0 -971
  202. package/src/pert/monte-carlo.ts +0 -446
  203. package/src/pert/parser.ts +0 -1606
  204. package/src/pert/renderer.ts +0 -4009
  205. package/src/pert/share-normalize.ts +0 -62
  206. package/src/pert/types.ts +0 -604
  207. package/src/pyramid/parser.ts +0 -185
  208. package/src/pyramid/renderer.ts +0 -768
  209. package/src/pyramid/types.ts +0 -28
  210. package/src/quadrant/parser.ts +0 -18
  211. package/src/quadrant/renderer.ts +0 -638
  212. package/src/raci/diagnostics.ts +0 -213
  213. package/src/raci/index.ts +0 -23
  214. package/src/raci/mutations.ts +0 -321
  215. package/src/raci/parser.ts +0 -699
  216. package/src/raci/renderer.ts +0 -1627
  217. package/src/raci/types.ts +0 -82
  218. package/src/raci/variants.ts +0 -320
  219. package/src/render.ts +0 -268
  220. package/src/ring/parser.ts +0 -212
  221. package/src/ring/renderer.ts +0 -490
  222. package/src/ring/types.ts +0 -27
  223. package/src/sequence/collapse.ts +0 -174
  224. package/src/sequence/parser.ts +0 -1596
  225. package/src/sequence/participant-inference.ts +0 -152
  226. package/src/sequence/renderer.ts +0 -3168
  227. package/src/sequence/tag-resolution.ts +0 -164
  228. package/src/sharing.ts +0 -211
  229. package/src/sitemap/collapse.ts +0 -100
  230. package/src/sitemap/layout.ts +0 -925
  231. package/src/sitemap/parser.ts +0 -755
  232. package/src/sitemap/renderer.ts +0 -828
  233. package/src/sitemap/types.ts +0 -42
  234. package/src/slope/parser.ts +0 -18
  235. package/src/slope/renderer.ts +0 -378
  236. package/src/swimlane/diagnostics.ts +0 -94
  237. package/src/swimlane/layout.ts +0 -597
  238. package/src/swimlane/parser.ts +0 -613
  239. package/src/swimlane/renderer.ts +0 -681
  240. package/src/swimlane/types.ts +0 -151
  241. package/src/tech-radar/interactive.ts +0 -1129
  242. package/src/tech-radar/layout.ts +0 -197
  243. package/src/tech-radar/parser.ts +0 -436
  244. package/src/tech-radar/renderer.ts +0 -1239
  245. package/src/tech-radar/shared.ts +0 -151
  246. package/src/tech-radar/types.ts +0 -86
  247. package/src/themes.ts +0 -22
  248. package/src/timeline/parser.ts +0 -472
  249. package/src/timeline/renderer.ts +0 -3024
  250. package/src/timeline/types.ts +0 -33
  251. package/src/timeline/viz-parser.ts +0 -21
  252. package/src/treemap/diagnostics.ts +0 -62
  253. package/src/treemap/layout-radial.ts +0 -233
  254. package/src/treemap/layout.ts +0 -189
  255. package/src/treemap/parser.ts +0 -463
  256. package/src/treemap/renderer-radial.ts +0 -438
  257. package/src/treemap/renderer.ts +0 -572
  258. package/src/treemap/treemap-shared.ts +0 -309
  259. package/src/treemap/types.ts +0 -60
  260. package/src/utils/arrow-markers.ts +0 -51
  261. package/src/utils/arrows-diagnostics.ts +0 -62
  262. package/src/utils/arrows.ts +0 -227
  263. package/src/utils/brand.ts +0 -61
  264. package/src/utils/card.ts +0 -183
  265. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  266. package/src/utils/collapse-engine/tree.ts +0 -117
  267. package/src/utils/d3-helpers.ts +0 -219
  268. package/src/utils/d3-types.ts +0 -9
  269. package/src/utils/description-helpers.ts +0 -38
  270. package/src/utils/duration.ts +0 -312
  271. package/src/utils/export-container.ts +0 -41
  272. package/src/utils/expr-eval.ts +0 -27
  273. package/src/utils/fit-canvas.ts +0 -64
  274. package/src/utils/hover-styles.ts +0 -673
  275. package/src/utils/inline-markdown.ts +0 -96
  276. package/src/utils/legend-constants.ts +0 -48
  277. package/src/utils/legend-d3.ts +0 -618
  278. package/src/utils/legend-integration.ts +0 -107
  279. package/src/utils/legend-layout.ts +0 -776
  280. package/src/utils/legend-svg.ts +0 -214
  281. package/src/utils/legend-types.ts +0 -272
  282. package/src/utils/name-normalize.ts +0 -163
  283. package/src/utils/note-box/constants.ts +0 -25
  284. package/src/utils/note-box/index.ts +0 -11
  285. package/src/utils/note-box/metrics.ts +0 -90
  286. package/src/utils/note-box/svg.ts +0 -331
  287. package/src/utils/notes/bounds.ts +0 -30
  288. package/src/utils/notes/build.ts +0 -131
  289. package/src/utils/notes/index.ts +0 -18
  290. package/src/utils/notes/model.ts +0 -19
  291. package/src/utils/notes/parse.ts +0 -131
  292. package/src/utils/notes/place.ts +0 -177
  293. package/src/utils/notes/resolve.ts +0 -88
  294. package/src/utils/number-format.ts +0 -36
  295. package/src/utils/parsing.ts +0 -953
  296. package/src/utils/reserved-key-registry.ts +0 -239
  297. package/src/utils/safe-href.ts +0 -38
  298. package/src/utils/scaling.ts +0 -97
  299. package/src/utils/svg-embed.ts +0 -251
  300. package/src/utils/tag-groups.ts +0 -607
  301. package/src/utils/text-measure.ts +0 -122
  302. package/src/utils/time-ticks.ts +0 -253
  303. package/src/utils/title-constants.ts +0 -24
  304. package/src/utils/visual-conventions.ts +0 -61
  305. package/src/utils/wrapped-desc.ts +0 -87
  306. package/src/venn/parser.ts +0 -18
  307. package/src/venn/renderer.ts +0 -1047
  308. package/src/version-control/diagnostics.ts +0 -67
  309. package/src/version-control/parser.ts +0 -544
  310. package/src/version-control/renderer.ts +0 -724
  311. package/src/version-control/types.ts +0 -100
  312. package/src/visualizations/parse.ts +0 -1560
  313. package/src/visualizations/types.ts +0 -262
  314. package/src/wireframe/layout.ts +0 -479
  315. package/src/wireframe/parser.ts +0 -1114
  316. package/src/wireframe/renderer.ts +0 -1318
  317. package/src/wireframe/types.ts +0 -110
  318. package/src/wordcloud/parser.ts +0 -18
  319. package/src/wordcloud/renderer.ts +0 -357
@@ -1,609 +0,0 @@
1
- // Context-label placement (step 4, part of layout). Produces a sparse,
2
- // density-thinned ORIENTATION layer — water-body names + unreferenced notable
3
- // country names — distinct from `region-labels`. PURE + SYNC + DETERMINISTIC.
4
- //
5
- // Design (tech-spec §map-context-labels): a deliberately LOW per-view label
6
- // BUDGET is the primary noise lever; a span-derived TIER BAND orders candidates
7
- // into it; each candidate is committed only if it survives COLLISION against
8
- // every already-placed data/region/POI/route label (the `collides` closure) AND
9
- // the other context labels. Context labels place DEAD LAST and never displace
10
- // data — they only fill leftover space, degrading gracefully to zero. See
11
- // Decisions 6 (budget), 7 (dead-last), 8 (viewport/projection guards).
12
- import { mix } from '../palettes/color-utils';
13
- import type { PaletteColors } from '../palettes/types';
14
- import type { LabelRect } from '../label-layout';
15
- import { measureLegendText } from '../utils/legend-constants';
16
- import type { ProjectionFamily } from './resolved-types';
17
- import type { WaterBodies, WaterKind } from './data/types';
18
- import type { PlacedLabel } from './layout';
19
-
20
- /** A view span band → priority ordering (NOT a hard zoom cutoff, Decision 6). */
21
- export type TierBand = 'world' | 'continental' | 'regional' | 'local';
22
-
23
- /** An unreferenced country, pre-projected by layout (geo work stays in layout;
24
- * area-rank + name-fit + collision live here so the module is unit-testable). */
25
- export interface CountryCandidate {
26
- readonly name: string;
27
- /** Projected screen bbox `[x0, y0, x1, y1]` (from `path.bounds`). */
28
- readonly bbox: readonly [number, number, number, number];
29
- /** Projected screen anchor `[x, y]` (mainland anchor or `path.centroid`), or
30
- * null when the feature doesn't project to a finite point. */
31
- readonly anchor: readonly [number, number] | null;
32
- /** True when `anchor` came from a curated WORLD_LABEL_ANCHORS entry (a trusted
33
- * mainland point) rather than the area-weighted centroid. Such a country
34
- * bypasses the both-axes smear gate (its full-canvas bbox is an antimeridian
35
- * artifact, not a real footprint, so the unreliable-centroid concern is moot). */
36
- readonly curatedAnchor?: boolean;
37
- /** True for a sovereign NATION touching the viewport (Canada, Mexico) — as
38
- * opposed to a state/subdivision. Nations are labeled UNCONDITIONALLY and
39
- * OUTSIDE the budget so orientation is symmetric (a US view shows Canada AND
40
- * Mexico together, never one-arbitrarily). States/subdivisions leave this
41
- * absent and compete for the rationed budget. */
42
- readonly bordering?: boolean;
43
- /** Ordered interior placement positions (screen coords), best-first: the commit
44
- * loop tries each in turn and places at the first that clears all collisions, so
45
- * a country can dodge a data cluster onto open ground on its own land
46
- * (map-context-neighbor-labels, D7/D12). INVARIANT: either ABSENT or NON-EMPTY,
47
- * and `anchor === positions[0]` always (an empty array is illegal — F7).
48
- * Absent ⇒ single-anchor behaviour (`positions ?? [anchor]`). */
49
- readonly positions?: readonly (readonly [number, number])[];
50
- }
51
-
52
- export interface ContextLabelArgs {
53
- readonly projection: ProjectionFamily;
54
- readonly dLonSpan: number;
55
- readonly dLatSpan: number;
56
- readonly width: number;
57
- readonly height: number;
58
- readonly waterBodies?: WaterBodies | undefined;
59
- readonly countries: readonly CountryCandidate[];
60
- /** Muted, contrast-floored land tone for country labels — computed in layout
61
- * against the neutral basemap and shared with the orientation backdrop so a
62
- * context country and an orientation region read identically. Absent ⇒ the
63
- * legacy `palette.textMuted`. */
64
- readonly countryTone?: string | undefined;
65
- /** Muted, contrast-floored tone for water labels (against the water fill).
66
- * Absent ⇒ the legacy `mix(blue, textMuted, 50)`. */
67
- readonly waterTone?: string | undefined;
68
- readonly palette: PaletteColors;
69
- readonly project: (lon: number, lat: number) => [number, number] | null;
70
- /** Collision test against every committed data/region/POI/route obstacle. */
71
- readonly collides: (rect: LabelRect) => boolean;
72
- /** True when the screen point sits over LAND (a country/state fill) rather than
73
- * open water. WATER labels are rejected when their footprint touches land — an
74
- * ocean name belongs over the ocean (they're optional orientation aids, so drop
75
- * rather than misplace). Country labels are exempt (they label land). Optional
76
- * for unit tests; absent ⇒ no land rejection. */
77
- readonly overLand?: (x: number, y: number) => boolean;
78
- }
79
-
80
- const FONT = 11; // matches layout's on-map label font
81
- const LINE_HEIGHT = FONT + 2; // px per wrapped line — MUST match the renderer's
82
- const PADX = 4; // half-padding around a context label rect
83
- const PADY = 3;
84
- const WATER_LETTER_SPACING = 1.5; // px — cartographic spread for water names
85
- const CONTEXT_PAD = 4; // extra gap enforced between two context labels
86
- const EDGE_CLAMP_MARGIN = 8; // px inset for edge-clamped ocean labels
87
- const EDGE_CLAMP_OVERSHOOT = 0.35; // max off-frame overshoot (× dim) to still clamp
88
-
89
- // Country labels scale with their projected footprint: a big landmass (Canada,
90
- // Mexico on a US view) reads as a larger backdrop name; a small one stays at the
91
- // base font. Size metric is the footprint's linear extent (√bbox-area) as a
92
- // fraction of the canvas's linear extent (√canvas-area), so the ramp is
93
- // resolution-independent. Below MIN ⇒ base font; at/above MAX ⇒ max font; linear
94
- // between. Colour does NOT change with size — subordination is by muting (the
95
- // contrast-floored tone) + size only, never by fading toward bg (Phase 1).
96
- const COUNTRY_FONT_MAX = 22; // px ceiling for the largest footprint
97
- const COUNTRY_SIZE_FRAC_MIN = 0.06; // footprint linear-frac at base font
98
- const COUNTRY_SIZE_FRAC_MAX = 0.32; // footprint linear-frac at max font
99
-
100
- // Multi-position country dodging (map-context-neighbor-labels). A country gets
101
- // several interior on-its-own-land positions so its label can dodge a colliding
102
- // data cluster into open space instead of being dropped. Layout generates the
103
- // positions (geo work stays in layout); the commit loop below walks them
104
- // best-first and places at the first that clears all collisions.
105
- export const MAX_COUNTRY_POSITIONS = 4; // ordered positions per country (cap; D7/D15)
106
- export const COUNTRY_POS_GRID = 5; // lon/lat sampling grid resolution N (N×N cells; D9)
107
- export const COUNTRY_POS_TOPN_MARGIN = 3; // generate positions for top budget+margin (D15)
108
-
109
- // Water-kind priority within a tier (oceans first, then seas, then the rest) so
110
- // a thin budget always spends on the highest-orientation-value names.
111
- const KIND_ORDER: Record<WaterKind, number> = {
112
- ocean: 0,
113
- sea: 1,
114
- gulf: 2,
115
- bay: 3,
116
- strait: 4,
117
- channel: 5,
118
- sound: 6,
119
- };
120
-
121
- /** Span band from the larger of the two view spans (Decision 6 — priority, not
122
- * a hard gate). */
123
- export function tierBand(maxSpanDeg: number): TierBand {
124
- if (maxSpanDeg >= 90) return 'world';
125
- if (maxSpanDeg >= 20) return 'continental';
126
- if (maxSpanDeg >= 5) return 'regional';
127
- return 'local';
128
- }
129
-
130
- /** Deliberately-LOW combined label budget = f(canvas area, band). Floors to ~1
131
- * on a thumbnail and 0 on a tiny canvas (Decision 6, ADR-3; AC9). Caps the
132
- * TOTAL context labels (water + country), so `relief`/data don't get extra
133
- * headroom (Decision 13). */
134
- export function labelBudget(
135
- width: number,
136
- height: number,
137
- band: TierBand
138
- ): number {
139
- const bandCap: Record<TierBand, number> = {
140
- world: 10,
141
- continental: 9,
142
- regional: 7,
143
- local: 6,
144
- };
145
- // Area divisor lowered 150→105 (map-context-neighbor-labels, budget knob): the
146
- // old budget was so thin a regional view spent every slot on a couple of giant
147
- // landmasses, leaving the countries that ring the story unlabeled. The lower
148
- // divisor + raised band caps roughly +50% the slots so the proximity rank has
149
- // room to surface the neighbourhood. Still floors to ~1 on a thumbnail / 0 on a
150
- // tiny canvas (AC9).
151
- const area = Math.floor(Math.sqrt(Math.max(0, width * height)) / 105);
152
- return Math.max(0, Math.min(area, bandCap[band]));
153
- }
154
-
155
- /** Which water tiers/kinds are eligible at a band. World view is oceans + major
156
- * seas ONLY (never bays/sounds/minor gulfs, AC3); broader views progressively
157
- * admit smaller features by `scalerank` (AC4). */
158
- function waterEligible(tier: number, kind: WaterKind, band: TierBand): boolean {
159
- switch (band) {
160
- case 'world':
161
- return tier <= 1 && (kind === 'ocean' || kind === 'sea');
162
- case 'continental':
163
- return tier <= 2;
164
- case 'regional':
165
- return tier <= 3;
166
- case 'local':
167
- return tier <= 4;
168
- }
169
- }
170
-
171
- function insideViewport(
172
- p: readonly [number, number] | null,
173
- width: number,
174
- height: number
175
- ): p is [number, number] {
176
- return (
177
- !!p &&
178
- Number.isFinite(p[0]) &&
179
- Number.isFinite(p[1]) &&
180
- p[0] >= 0 &&
181
- p[0] <= width &&
182
- p[1] >= 0 &&
183
- p[1] <= height
184
- );
185
- }
186
-
187
- /** Rendered label width INCLUDING letter-spacing — `measureLegendText` ignores
188
- * the per-gap `letter-spacing` the renderer applies to water names, so without
189
- * this the fit/clamp math under-measures by ~`(len-1)*spacing` and the label
190
- * clips at the canvas edge. */
191
- export function labelWidth(
192
- text: string,
193
- letterSpacing: number,
194
- font: number = FONT
195
- ): number {
196
- const spacing =
197
- letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
198
- return measureLegendText(text, font) + spacing + 2 * PADX;
199
- }
200
-
201
- /** Wrap a multi-word name into balanced lines, biased to wrap READILY — water
202
- * names ("North Pacific Ocean") read better stacked, and a narrower footprint
203
- * is far likelier to clear surrounding land (the hard rule below). Of every
204
- * contiguous split into ≤`maxLines`, picks the one minimising the widest line
205
- * (so it shrinks horizontally whenever a break helps); ties break to fewer
206
- * lines, then top-heavy ("North Pacific" / "Ocean", not "North" / "Pacific
207
- * Ocean"). Single-word names pass through unwrapped. */
208
- export function wrapLabel(text: string, letterSpacing: number): string[] {
209
- const words = text.split(/\s+/).filter(Boolean);
210
- if (words.length <= 1) return [text];
211
- const maxLines = words.length >= 4 ? 3 : 2;
212
- const n = words.length;
213
- type Split = { lines: string[]; cost: number; head: number };
214
- let best: Split | null = null;
215
- for (let mask = 0; mask < 1 << (n - 1); mask++) {
216
- const lines: string[] = [];
217
- let cur = [words[0]!];
218
- for (let i = 1; i < n; i++) {
219
- if (mask & (1 << (i - 1))) {
220
- lines.push(cur.join(' '));
221
- cur = [words[i]!];
222
- } else cur.push(words[i]!);
223
- }
224
- lines.push(cur.join(' '));
225
- if (lines.length > maxLines) continue;
226
- const cost = Math.round(
227
- Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
228
- );
229
- const head = labelWidth(lines[0]!, letterSpacing);
230
- if (
231
- !best ||
232
- cost < best.cost ||
233
- (cost === best.cost && lines.length < best.lines.length) ||
234
- (cost === best.cost &&
235
- lines.length === best.lines.length &&
236
- head > best.head)
237
- )
238
- best = { lines, cost, head };
239
- }
240
- return best?.lines ?? [text];
241
- }
242
-
243
- function rectAround(
244
- cx: number,
245
- cy: number,
246
- lines: readonly string[],
247
- letterSpacing: number,
248
- font: number = FONT
249
- ): LabelRect {
250
- const lineHeight = font + 2; // MUST match the renderer's per-line stride
251
- const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing, font)));
252
- const h = (lines.length - 1) * lineHeight + font + 2 * PADY;
253
- return { x: cx - w / 2, y: cy - h / 2, w, h };
254
- }
255
-
256
- function rectFits(r: LabelRect, width: number, height: number): boolean {
257
- return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
258
- }
259
-
260
- function overlapsPadded(a: LabelRect, b: LabelRect, pad: number): boolean {
261
- return (
262
- a.x - pad < b.x + b.w &&
263
- a.x + a.w + pad > b.x &&
264
- a.y - pad < b.y + b.h &&
265
- a.y + a.h + pad > b.y
266
- );
267
- }
268
-
269
- /** Place the orientation backdrop. Returns committed labels in priority order;
270
- * caller pushes them onto `labels` LAST so they never displace data. */
271
- export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
272
- const {
273
- projection,
274
- dLonSpan,
275
- dLatSpan,
276
- width,
277
- height,
278
- waterBodies,
279
- countries,
280
- palette,
281
- countryTone,
282
- waterTone,
283
- project,
284
- collides,
285
- overLand,
286
- } = args;
287
-
288
- // albers-usa is supported: the CONUS conic projects CONUS-area water (Gulf of
289
- // America, the Pacific/Atlantic margins) correctly, and the viewport-visibility
290
- // check below drops the off-frame anchors (Gulf of Alaska, mid-Pacific) that the
291
- // AK/HI inset relocation would otherwise mislabel. The caller additionally feeds
292
- // the AK/HI inset frames into `collides` so a label never lands on an inset box.
293
- // (Supersedes the original blanket albers-usa disable — the US map is the
294
- // flagship orientation case.)
295
- void projection;
296
-
297
- const band = tierBand(Math.max(dLonSpan, dLatSpan));
298
- const budget = labelBudget(width, height, band);
299
- if (budget <= 0) return [];
300
-
301
- // Subordinate cartographic colours. Both are muted but CONTRAST-FLOORED in the
302
- // caller (layout) against the substrate they sit on, and shared with the
303
- // orientation backdrop so a context country and an orientation region match.
304
- // Water stays the blue-gray italic convention; country = the muted land tone.
305
- // Fallbacks keep the pre-floor behaviour for unit tests that omit the tones.
306
- const waterColor =
307
- waterTone ?? mix(palette.colors.blue, palette.textMuted, 50);
308
- const countryColor = countryTone ?? palette.textMuted;
309
- const haloColor = palette.bg;
310
-
311
- type Candidate = {
312
- text: string;
313
- lines: string[];
314
- cx: number;
315
- cy: number;
316
- italic: boolean;
317
- letterSpacing: number;
318
- color: string;
319
- fontSize: number;
320
- sort: number; // priority key (lower first)
321
- // A sovereign nation (Canada/Mexico): committed unconditionally, outside the
322
- // budget (Option A symmetric orientation). Absent on water + states.
323
- bordering?: boolean;
324
- // Ordered dodge positions (screen coords), best-first. Absent on water (single
325
- // anchor); on a country, mirrors CountryCandidate.positions (anchor === [0]).
326
- positions?: readonly (readonly [number, number])[];
327
- };
328
- const candidates: Candidate[] = [];
329
-
330
- // -- Water candidates (priority core: oceans → seas → minor water) --
331
- const center: [number, number] = [width / 2, height / 2];
332
- for (const e of waterBodies?.entries ?? []) {
333
- const [lat, lon, name, tier, kind, alt] = e;
334
- if (!waterEligible(tier, kind, band)) continue;
335
- // Wrap eagerly (Decision: water names stack readily) so the clamp/fit math
336
- // below sees the real, narrower wrapped footprint, not the one-line width.
337
- const wlines = wrapLabel(name, WATER_LETTER_SPACING);
338
- // Multi-anchor (Decision 5 / ADR-4): of the anchors that project inside the
339
- // viewport, pick the one nearest the viewport centre.
340
- const anchorsLngLat: Array<[number, number]> = [[lon, lat]];
341
- for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
342
- let best: [number, number] | null = null;
343
- let bestD = Infinity;
344
- let nearestProj: [number, number] | null = null; // best finite proj (any side)
345
- let nearestProjD = Infinity;
346
- for (const [aLon, aLat] of anchorsLngLat) {
347
- const p = project(aLon, aLat);
348
- if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
349
- const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
350
- if (d < nearestProjD) {
351
- nearestProjD = d;
352
- nearestProj = p;
353
- }
354
- if (!insideViewport(p, width, height)) continue;
355
- if (d < bestD) {
356
- bestD = d;
357
- best = p;
358
- }
359
- }
360
- // Oceans (tier 0) are large enough that a frame-edge label still reads
361
- // correctly, so when their anchor falls off-screen on a zoomed-in view
362
- // (e.g. the mid-Atlantic/Pacific centroid on a US map) we CLAMP it to the
363
- // viewport margin rather than drop it — the standard cartographic "ocean
364
- // name hugs the edge" behaviour. Smaller basins keep the strict drop-if-
365
- // off-screen rule (AC10) to avoid mislabelling an adjacent basin.
366
- if (!best && tier === 0 && nearestProj) {
367
- // Only clamp an ocean ADJACENT to the frame: if its centroid overshoots
368
- // the viewport by more than ~half a dimension it's a distant ocean (e.g.
369
- // the South Atlantic / Arctic relative to a US view) and edge-clamping it
370
- // would mislabel that margin — drop instead. The surviving oceans are the
371
- // ones the frame actually borders (Pacific to the west, Atlantic east).
372
- const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
373
- const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
374
- if (
375
- overX <= width * EDGE_CLAMP_OVERSHOOT &&
376
- overY <= height * EDGE_CLAMP_OVERSHOOT
377
- ) {
378
- // Clamp the CENTRE inward by half the label so the whole (centre-
379
- // anchored) rect stays on-canvas — clamping to the bare margin would
380
- // overflow a wide name like "North Atlantic Ocean" off the edge.
381
- // letter-spacing IS counted (labelWidth) so the clamp matches render.
382
- const halfW =
383
- Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) /
384
- 2;
385
- const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
386
- const m = EDGE_CLAMP_MARGIN;
387
- best = [
388
- Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
389
- Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m),
390
- ];
391
- }
392
- }
393
- if (!best) continue;
394
- candidates.push({
395
- text: name,
396
- lines: wlines,
397
- cx: best[0],
398
- cy: best[1],
399
- italic: true,
400
- letterSpacing: WATER_LETTER_SPACING,
401
- color: waterColor,
402
- fontSize: FONT, // water names keep the base font (no footprint to scale on)
403
- // Orientation-value bands (lower = earlier): MAJOR water (oceans + major
404
- // seas, tier ≤ 1) leads at `tier*10+kind` (0..~16); MINOR water (tier ≥ 2:
405
- // smaller seas, bays, gulfs, straits) drops to band 2 (2000+) — BELOW the
406
- // country band (1000+), so a big country outranks a minor basin.
407
- sort: (tier <= 1 ? 0 : 2000) + tier * 10 + KIND_ORDER[kind],
408
- });
409
- }
410
-
411
- // -- Country candidates (unreferenced) --
412
- // Rank by biggest-area-first — a deterministic, position-independent order (no
413
- // proximity-to-POI weighting, which read as arbitrary: near-identical neighbours
414
- // like Wyoming vs Texas won or lost a slot on a hair of distance). Nations are
415
- // pulled out of the budget entirely at commit; among the budgeted states, the
416
- // largest in-frame footprints win the rationed slots. Area also drives the
417
- // font/fade ramp and the smear gate below.
418
- const ranked = countries
419
- .map((c) => {
420
- const [x0, y0, x1, y1] = c.bbox;
421
- const w = x1 - x0;
422
- const h = y1 - y0;
423
- return { c, w, h, area: w * h };
424
- })
425
- .filter((r) => Number.isFinite(r.area) && r.area > 0)
426
- .sort((a, b) => b.area - a.area);
427
- // Canvas linear extent — the denominator for the footprint size ramp below.
428
- const canvasLinear = Math.sqrt(Math.max(1, width * height));
429
- let ci = 0;
430
- for (const r of ranked) {
431
- const { c, w, h, area } = r;
432
- // F2: an antimeridian-crossing / global-smear country fills the canvas in
433
- // BOTH dimensions while its real landmass is split — the `path.centroid`
434
- // anchor is then unreliable (mid-map, wrong basin). Reject only that
435
- // both-axes canvas-smear: a country that is merely wide-but-short (Canada
436
- // hugging the top of a US frame) or tall-but-narrow (Chile) is a legitimate
437
- // landmass whose clipExtent-clipped centroid still lands over its own ground,
438
- // so it must NOT be dropped for footprint shape alone. A `curatedAnchor`
439
- // (WORLD_LABEL_ANCHORS, e.g. Russia → European Russia) is trustworthy by
440
- // construction — the smear concern is moot, so it bypasses this gate (the
441
- // `insideViewport` check below still drops an anchor that projects off-frame).
442
- if (!c.curatedAnchor && w > width * 0.66 && h > height * 0.66) continue;
443
- if (!insideViewport(c.anchor, width, height)) continue;
444
- // Footprint-driven scale (Decision: big landmass = large, faded backdrop
445
- // name). t∈[0,1] over the [MIN,MAX] linear-fraction band; font ramps up and
446
- // ink rises in lockstep, so a bigger name reads as a large, softly-inked
447
- // backdrop. A curated-anchor giant (Russia) keeps the standard big-country
448
- // styling — its full-canvas bbox lands at the top of the ramp (font/ink like
449
- // any large in-frame country), which is the intended subdued-backdrop look.
450
- const sizeFrac = Math.sqrt(area) / canvasLinear;
451
- const t = Math.min(
452
- 1,
453
- Math.max(
454
- 0,
455
- (sizeFrac - COUNTRY_SIZE_FRAC_MIN) /
456
- (COUNTRY_SIZE_FRAC_MAX - COUNTRY_SIZE_FRAC_MIN)
457
- )
458
- );
459
- const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
460
- // No fade. Subordination is by MUTING (the floored tone) + smaller size, not
461
- // by fading toward bg — fading a floored tone toward the background would
462
- // re-break the contrast it was floored to guarantee (the old behaviour washed
463
- // big names like Canada toward illegibility). Footprint still drives font size.
464
- const color = countryColor;
465
- // Always the full country name — never an ISO abbreviation. If the name
466
- // doesn't fit the footprint, drop the label rather than abbreviate.
467
- const text = c.name;
468
- const tw = labelWidth(text, 0, fontSize);
469
- // Approximate fit (Decision 4): the (scaled) name fits inside the footprint
470
- // bbox. NOT true point-in-polygon — cartographic labels routinely overrun
471
- // coastlines. The bigger the font, the bigger the box it must clear.
472
- if (tw > w || fontSize + 2 * PADY > h) continue;
473
- candidates.push({
474
- text,
475
- lines: [text],
476
- cx: c.anchor[0],
477
- cy: c.anchor[1],
478
- italic: false,
479
- letterSpacing: 0,
480
- color,
481
- fontSize,
482
- ...(c.bordering ? { bordering: true } : {}),
483
- // Multi-position dodging: carry the ordered interior positions through to the
484
- // commit loop. Invariant anchor === positions[0], so `cx/cy` is positions[0].
485
- ...(c.positions ? { positions: c.positions } : {}),
486
- // Band 1 (orientation-value ranking): above MINOR water (band 2, 2000+) but
487
- // below MAJOR water — oceans + major seas (band 0, ≤~16). So a big country
488
- // (US, Canada, Russia) outranks a minor sea/bay (Sargasso, Bahía de
489
- // Campeche) yet never displaces an ocean. Larger area = earlier within the
490
- // band (`ci` is the area-desc rank index).
491
- sort: 1000 + ci++,
492
- });
493
- }
494
-
495
- // -- Commit dead-last, highest-priority-first, into leftover space only --
496
- candidates.sort((a, b) => a.sort - b.sort);
497
- const placed: PlacedLabel[] = [];
498
- const placedRects: LabelRect[] = [];
499
- // Guarantee state room: water can otherwise monopolise a small budget (a coastal
500
- // view borders many oceans/seas), so reserve up to 2 slots for STATES whenever
501
- // any state candidate exists. Nations don't draw from the budget (committed in
502
- // pass 1 below), so they're excluded from this count. No effect on pure-water
503
- // views (`stateCount` 0 ⇒ cap = budget).
504
- const stateCount = candidates.reduce(
505
- (n, c) => n + (!c.italic && !c.bordering ? 1 : 0),
506
- 0
507
- );
508
- const waterCap = budget - Math.min(2, stateCount);
509
- let waterPlaced = 0;
510
- let budgetUsed = 0;
511
- // Test one trial position for a candidate against every gate (fit, water-on-land,
512
- // committed-obstacle collision, context-overlap). Returns the placed rect when the
513
- // position clears, else null. Country candidates carry several ordered positions
514
- // (map-context-neighbor-labels); we walk them best-first and commit the first that
515
- // clears, so a label dodges a colliding cluster instead of being dropped. Water
516
- // candidates have a single position, so behaviour is unchanged for them.
517
- const gateAt = (
518
- cx: number,
519
- cy: number,
520
- cand: Candidate
521
- ): LabelRect | null => {
522
- const rect = rectAround(
523
- cx,
524
- cy,
525
- cand.lines,
526
- cand.letterSpacing,
527
- cand.fontSize
528
- );
529
- if (!rectFits(rect, width, height)) return null;
530
- // Water labels must sit over OPEN WATER and NEVER touch land — sample a grid
531
- // over every wrapped line (each line's own horizontal extent at five points);
532
- // drop the whole label if ANY sample hits land (Decision: optional orientation
533
- // aids, so exclude rather than misplace over a coastline). Country labels are
534
- // exempt — they belong on their country.
535
- if (cand.italic && overLand) {
536
- const inset = 2;
537
- const top = cy - ((cand.lines.length - 1) / 2) * LINE_HEIGHT;
538
- const touchesLand = cand.lines.some((line, li) => {
539
- const lw = labelWidth(line, cand.letterSpacing);
540
- const x0 = cx - lw / 2 + inset;
541
- const x1 = cx + lw / 2 - inset;
542
- const xs = [x0, (x0 + cx) / 2, cx, (cx + x1) / 2, x1];
543
- const base = top + li * LINE_HEIGHT;
544
- // Sample the glyph body top→baseline (text rises above the baseline) so a
545
- // label whose ascenders clip a coastline is rejected, not just one whose
546
- // baseline sits on land.
547
- return [base, base - FONT * 0.4, base - FONT * 0.8].some((y) =>
548
- xs.some((x) => overLand(x, y))
549
- );
550
- });
551
- if (touchesLand) return null;
552
- }
553
- if (collides(rect)) return null;
554
- if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD)))
555
- return null;
556
- return rect;
557
- };
558
- // Walk a candidate's ordered positions best-first; commit at the first that
559
- // clears every gate. F8: `positions ?? [[cx,cy]]` keeps single-anchor (water +
560
- // non-top-N country) behaviour identical. If none clears → drop (no halo, no
561
- // overlap — D16). Returns true when the label was placed.
562
- const tryPlace = (cand: Candidate): boolean => {
563
- const positions = cand.positions ?? [[cand.cx, cand.cy]];
564
- for (const [px, py] of positions) {
565
- const rect = gateAt(px!, py!, cand);
566
- if (!rect) continue;
567
- placedRects.push(rect);
568
- placed.push({
569
- x: px!,
570
- y: py!,
571
- text: cand.text,
572
- anchor: 'middle',
573
- color: cand.color,
574
- // No halo: the bg-coloured outline reads as a ghost box behind the text
575
- // over the tinted water/land. Context labels are muted enough to sit
576
- // cleanly on the basemap without one.
577
- halo: false,
578
- haloColor,
579
- fontSize: cand.fontSize,
580
- italic: cand.italic,
581
- letterSpacing: cand.letterSpacing,
582
- ...(cand.lines.length > 1 ? { lines: cand.lines } : {}),
583
- lineNumber: 0,
584
- });
585
- return true;
586
- }
587
- return false;
588
- };
589
-
590
- // Pass 1 — sovereign NATIONS touching the viewport, labeled UNCONDITIONALLY and
591
- // OUTSIDE the budget (Option A: symmetric orientation — a US view shows Canada
592
- // AND Mexico together, never one arbitrarily). Still gated on fit/collision, so a
593
- // nation label never lands on data or off-canvas; it just isn't rationed. Placed
594
- // FIRST so nations claim their ground before states/water fill the leftover.
595
- for (const cand of candidates) if (cand.bordering) tryPlace(cand);
596
-
597
- // Pass 2 — water + states, rationed by the budget (the nations placed above do
598
- // not count against it).
599
- for (const cand of candidates) {
600
- if (cand.bordering) continue;
601
- if (budgetUsed >= budget) break;
602
- if (cand.italic && waterPlaced >= waterCap) continue;
603
- if (tryPlace(cand)) {
604
- budgetUsed++;
605
- if (cand.italic) waterPlaced++;
606
- }
607
- }
608
- return placed;
609
- }
@@ -1 +0,0 @@
1
- {"assets":{"airport-collisions.json":{"bytes":29,"gzBytes":49,"sha256":"a7d0107c5fcc6d787e010ea100ce1946bb724f7ae1a79eb1b862a8cf822ed1d8"},"airports.json":{"bytes":104083,"gzBytes":38159,"sha256":"c17294d4e3fd259e02c63c49c36c02d9ff34f4462103ff54516d29e93667cd76"},"gazetteer.json":{"bytes":130767,"gzBytes":56261,"sha256":"5ad56e5ba0b3a4f9a6dc8bd3bf8b0fda0e7b86cbe4d85d231114f5dd967d65f7"},"lakes.json":{"bytes":6315,"gzBytes":1487,"sha256":"5840ffd49b8dbf30183a9534a72adf80b6e77ceec224665393fa94e956220323"},"mountain-ranges.json":{"bytes":90845,"gzBytes":26493,"sha256":"a698b3f296e61712fb39b3d8d42ec7c4699f8aadecb549367feb7d09f7785580"},"na-lakes.json":{"bytes":39387,"gzBytes":11281,"sha256":"2a41c04969209380d544a09efe354277e12d704458af95955201eb4f698d16c6"},"na-land.json":{"bytes":114082,"gzBytes":32375,"sha256":"7b94c9bb4e809c22813da5ae939e1ff6a781fd77a04d9c1585a9a82d2a195388"},"region-names.json":{"bytes":11667,"gzBytes":2235,"sha256":"059662d30b6ee8572c5943096905e05218e5f337e6973a9d43d6b41b7313a9ac"},"rivers.json":{"bytes":6707,"gzBytes":2158,"sha256":"3912508469099b1c37360c5505ea033c4ffa30ce95f7428e668e9d824cb81407"},"us-states.json":{"bytes":23313,"gzBytes":7413,"sha256":"0fe3a8937bc7566192662439f29a7866e8823d687290bcb003433ad5edd86567"},"water-bodies.json":{"bytes":4854,"gzBytes":2123,"sha256":"6d1a407a376c63518329c52189e2887053c4b61062af0597e060050ae8469635"},"world-coarse.json":{"bytes":55436,"gzBytes":18397,"sha256":"5cb42e3c8975dde56504ca5c68ece0a1e71d0929680b5fc8cdab758c8666dbf8"},"world-detail.json":{"bytes":163562,"gzBytes":46767,"sha256":"39f1736eaabe9e21190972be3157822be22ee84fdc41751237f2b516f09a7586"}},"counts":{"airportCollisions":2,"airports":1565,"countries":175,"gazetteerAliases":8,"gazetteerCities":2119,"mountainRanges":205,"usStates":56,"waterBodies":113},"generatedBy":"scripts/build-map-data.mjs","sourceHashes":{"file:scripts/airports-snapshot.csv":{"bytes":308536,"sha256":"58b6e45bb91045c2897f1e7e14430aecc3b5eca911bcb389855495ff66ab9599"},"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/10m/physical/ne_10m_lakes.json":{"bytes":6648697,"sha256":"93c8fdf0e591e113f449d0d466e15c7a9841b9b6571c7afe41f95ba51b322452"},"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/110m/physical/ne_110m_lakes.json":{"bytes":27711,"sha256":"6f315b60488e0cf5da9c360e3ce593babf64c2f44cc21e2820c536f7a2aff606"},"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/110m/physical/ne_110m_rivers_lake_centerlines.json":{"bytes":54146,"sha256":"959e13128e4eb5a6ee530b8270c5017bcee9149ce48a97f6fe7fee1fce600b5d"},"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_10m_admin_0_countries.geojson":{"bytes":13287234,"sha256":"239eec57ac17f100a11e2536cffc56752c318b50ae765b0918ff7aab4ce8f255"},"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_10m_geography_regions_polys.geojson":{"bytes":5583870,"sha256":"b7b26e50ea917d3696aec87f932def2bf5f890f5770e441d59c162c6f4c92a77"},"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_110m_geography_marine_polys.geojson":{"bytes":534055,"sha256":"b9c3f7f557d0ff5217906adc82b66ecdac14aa7438df7e518cf6675d037bceb8"},"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_50m_geography_marine_polys.geojson":{"bytes":1163418,"sha256":"6fe58083e0cc5c7fad9e396970e28a8580bbd8770cfa4d1d7b5a34423e912f97"},"https://cdn.jsdelivr.net/npm/us-atlas@3.0.1/states-10m.json":{"bytes":114554,"sha256":"d76b391ccfa8bff601d51e3e3da5d43a89fa46cd5caca72ce731b383be5596d0"},"https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json":{"bytes":107761,"sha256":"2516c915867c7baf18ddec727aec46c315541a07cfb3d79a6559b05d5e94eee8"},"https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-50m.json":{"bytes":756420,"sha256":"04342cdc1e3016bcd7db1630de95684d67b79fe3c8c460321e87aef469502394"},"https://download.geonames.org/export/dump/cities5000.zip":{"bytes":5549002,"sha256":"d20e28b2f610da34c21fd82ff6a8e4d24ebe67eba2dccf65bd2c4332ff0f380a"}},"sources":{"geonames":{"citiesUrl":"https://download.geonames.org/export/dump/cities5000.zip","license":"CC BY 4.0 — https://creativecommons.org/licenses/by/4.0/","modificationDateRange":"2006-01-17..2026-06-02 (filtered subset)"},"lakes":{"url":"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/110m/physical/ne_110m_lakes.json","version":"natural-earth 110m (martynafford snapshot)"},"marineCoarse":{"url":"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_110m_geography_marine_polys.geojson","version":"natural-earth 110m (nvkelso vector snapshot)"},"marineDetail":{"url":"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_50m_geography_marine_polys.geojson","version":"natural-earth 50m (nvkelso vector snapshot)"},"mountainRanges":{"url":"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_10m_geography_regions_polys.geojson","version":"natural-earth 10m (nvkelso vector snapshot)"},"naLakes":{"url":"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/10m/physical/ne_10m_lakes.json","version":"natural-earth 10m (martynafford snapshot)"},"naLand":{"url":"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@master/geojson/ne_10m_admin_0_countries.geojson","version":"natural-earth 10m (nvkelso vector snapshot)"},"ourairports":{"license":"Public Domain — https://ourairports.com/data/","note":"Lean committed slice (scheduled_service=yes + 3-letter iata_code). Regenerate is a deliberate, reviewed bump (ADR-4).","snapshot":"scripts/airports-snapshot.csv","source":"https://davidmegginson.github.io/ourairports-data/airports.csv"},"rivers":{"url":"https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson@master/110m/physical/ne_110m_rivers_lake_centerlines.json","version":"natural-earth 110m (martynafford snapshot)"},"usAtlas":{"url":"https://cdn.jsdelivr.net/npm/us-atlas@3.0.1/states-10m.json","version":"3.0.1"},"worldCoarse":{"url":"https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json","version":"2.0.2"},"worldDetail":{"url":"https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-50m.json","version":"2.0.2"}},"tooling":{"mapshaper":"0.7.22"}}
@@ -1,40 +0,0 @@
1
- # map chart-type data assets
2
-
3
- Generated by `scripts/build-map-data.mjs` (run `pnpm build:map-data`). These
4
- committed JSON files are the runtime artifacts for the `map` chart type. Do not
5
- hand-edit — regenerate from source.
6
-
7
- ## Files
8
- - `world-coarse.json` / `world-detail.json` — world country boundaries (TopoJSON),
9
- keyed by ISO 3166-1 alpha-2. Coarse = world-scale; detail = regional/zoom.
10
- - `us-states.json` — US states + DC + territories (TopoJSON), keyed by ISO 3166-2.
11
- - `lakes.json` — major lakes (Natural Earth 110m, TopoJSON), drawn as water over land.
12
- - `rivers.json` — major river centerlines (Natural Earth 110m, TopoJSON), drawn as thin water lines.
13
- - `na-land.json` — NA-clipped 10m country land (TopoJSON, ISO-keyed): crisp neighbour context under the albers-usa US view.
14
- - `na-lakes.json` — NA-clipped 10m major lakes (TopoJSON): the lakes counterpart to `na-land.json` for the US view.
15
- - `mountain-ranges.json` — notable mountain ranges (Natural Earth 50m geography regions, FEATURECLA "Range/mtn", TopoJSON), drawn as a subtle gradient relief cue when the `relief` directive is on. Optional; single tier (no elevation).
16
- - `water-bodies.json` — water-body orientation labels (`{ entries: [lat, lon, name, tier, kind] }`) from Natural Earth 110m+50m geography marine polys (oceans/seas/gulfs/bays/straits/channels/sounds; rivers + reefs excluded). Anchors are mapshaper inner points; `tier` is the NE scalerank. Drawn only when the `context-labels` directive is on. Optional.
17
- - `gazetteer.json` — `{ cities, byName, alt }` city index (see `types.ts`).
18
- `byName`/`alt` reference `cities` by array index (normalized).
19
- - `PROVENANCE.json` — source versions + per-asset sha256/sizes + GeoNames date range.
20
- - `types.ts` — the typed data contract consumed by the parser/resolver/renderer.
21
-
22
- ## Sources & attribution
23
- - **Country boundaries:** Natural Earth via `world-atlas@2.0.2` (public domain).
24
- - **US states:** US Census via `us-atlas@3.0.1` (public domain).
25
- - **Mountain ranges:** Natural Earth 50m `geography_regions_polys` via `nvkelso/natural-earth-vector` (public domain).
26
- - **Water bodies:** Natural Earth 110m+50m `geography_marine_polys` via `nvkelso/natural-earth-vector` (public domain). One editorial override applied (`Gulf of Mexico` → `Gulf of America`).
27
- - **Cities:** Data © **GeoNames**, licensed under **CC BY 4.0**
28
- (https://creativecommons.org/licenses/by/4.0/) — https://www.geonames.org/.
29
-
30
- ## Determinism & GeoNames drift
31
- Geometry assets are byte-reproducible from the pinned atlas versions (same machine
32
- + `mapshaper@0.7.22`). The GeoNames dump is **rebuilt daily and has no pinnable
33
- snapshot**, so the committed `gazetteer.json` is the source of truth; a regenerate
34
- may pick up upstream city changes. `PROVENANCE.json` is the audit trail. The
35
- modification-date range it reports is over the *filtered* (kept) city subset.
36
-
37
- ## Regenerate
38
- ```
39
- pnpm build:map-data
40
- ```
@@ -1 +0,0 @@
1
- {"collisions":["aba","ufa"]}