@diagrammo/dgmo 0.25.5 → 0.27.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.
- package/README.md +3 -3
- package/dist/advanced.cjs +4255 -2756
- package/dist/advanced.d.cts +285 -59
- package/dist/advanced.d.ts +285 -59
- package/dist/advanced.js +4253 -2750
- package/dist/auto.cjs +4051 -2589
- package/dist/auto.js +124 -122
- package/dist/auto.mjs +4051 -2589
- package/dist/cli.cjs +172 -170
- package/dist/editor.cjs +4 -0
- package/dist/editor.js +4 -0
- package/dist/highlight.cjs +4 -0
- package/dist/highlight.js +4 -0
- package/dist/index.cjs +4076 -2591
- package/dist/index.d.cts +33 -8
- package/dist/index.d.ts +33 -8
- package/dist/index.js +4076 -2591
- package/dist/internal.cjs +4255 -2756
- package/dist/internal.d.cts +285 -59
- package/dist/internal.d.ts +285 -59
- package/dist/internal.js +4253 -2750
- package/dist/map-data/PROVENANCE.json +1 -1
- package/dist/map-data/airport-collisions.json +1 -0
- package/dist/map-data/airports.json +1 -0
- package/docs/language-reference.md +68 -18
- package/gallery/fixtures/boxes-and-lines-diverging.dgmo +15 -0
- package/gallery/fixtures/map-choropleth-diverging.dgmo +9 -0
- package/gallery/fixtures/map-region-values.dgmo +13 -0
- package/gallery/fixtures/map-subnational-zoom.dgmo +12 -0
- package/gallery/fixtures/map-tagged-legs.dgmo +16 -0
- package/gallery/fixtures/map-undirected-edges.dgmo +12 -0
- package/package.json +1 -1
- package/src/advanced.ts +3 -6
- package/src/auto/index.ts +1 -1
- package/src/boxes-and-lines/layout.ts +146 -26
- package/src/boxes-and-lines/parser.ts +43 -8
- package/src/boxes-and-lines/renderer.ts +223 -96
- package/src/boxes-and-lines/types.ts +9 -2
- package/src/c4/layout.ts +14 -32
- package/src/c4/parser.ts +9 -5
- package/src/c4/renderer.ts +34 -39
- package/src/class/layout.ts +118 -18
- package/src/class/parser.ts +35 -1
- package/src/class/renderer.ts +58 -2
- package/src/class/types.ts +3 -0
- package/src/cli.ts +4 -4
- package/src/completion-types.ts +0 -1
- package/src/completion.ts +106 -51
- package/src/cycle/layout.ts +55 -72
- package/src/cycle/renderer.ts +11 -6
- package/src/d3.ts +78 -117
- package/src/diagnostics.ts +16 -0
- package/src/echarts.ts +46 -33
- package/src/editor/keywords.ts +4 -0
- package/src/er/layout.ts +114 -22
- package/src/er/parser.ts +28 -1
- package/src/er/renderer.ts +55 -2
- package/src/er/types.ts +3 -0
- package/src/gantt/renderer.ts +46 -38
- package/src/gantt/resolver.ts +9 -2
- package/src/graph/edge-spline.ts +29 -0
- package/src/graph/flowchart-parser.ts +35 -2
- package/src/graph/flowchart-renderer.ts +80 -52
- package/src/graph/layout.ts +206 -23
- package/src/graph/notes.ts +21 -0
- package/src/graph/state-parser.ts +26 -1
- package/src/graph/state-renderer.ts +80 -52
- package/src/graph/types.ts +13 -0
- package/src/index.ts +1 -1
- package/src/infra/layout.ts +46 -26
- package/src/infra/parser.ts +1 -1
- package/src/infra/renderer.ts +16 -7
- package/src/journey-map/layout.ts +38 -49
- package/src/journey-map/renderer.ts +22 -45
- package/src/kanban/renderer.ts +15 -6
- package/src/label-layout.ts +3 -3
- package/src/map/completion.ts +77 -22
- package/src/map/context-labels.ts +57 -12
- package/src/map/data/PROVENANCE.json +1 -1
- package/src/map/data/airport-collisions.json +1 -0
- package/src/map/data/airports.json +1 -0
- package/src/map/data/types.ts +19 -0
- package/src/map/layout.ts +1196 -90
- package/src/map/legend-band.ts +2 -2
- package/src/map/load-data.ts +10 -1
- package/src/map/parser.ts +61 -32
- package/src/map/renderer.ts +284 -12
- package/src/map/resolved-types.ts +15 -1
- package/src/map/resolver.ts +132 -12
- package/src/map/types.ts +28 -8
- package/src/migrate/embedded.ts +9 -7
- package/src/mindmap/text-wrap.ts +13 -14
- package/src/org/layout.ts +19 -17
- package/src/org/renderer.ts +11 -4
- package/src/palettes/color-utils.ts +82 -21
- package/src/palettes/index.ts +0 -19
- package/src/palettes/registry.ts +1 -1
- package/src/palettes/types.ts +2 -2
- package/src/pert/layout.ts +48 -40
- package/src/pert/parser.ts +0 -14
- package/src/pert/renderer.ts +30 -43
- package/src/pyramid/renderer.ts +4 -5
- package/src/raci/renderer.ts +42 -70
- package/src/render.ts +1 -1
- package/src/ring/renderer.ts +1 -2
- package/src/sequence/parser.ts +100 -22
- package/src/sequence/renderer.ts +75 -50
- package/src/sitemap/layout.ts +27 -19
- package/src/sitemap/renderer.ts +12 -5
- package/src/tech-radar/renderer.ts +11 -35
- package/src/utils/arrow-markers.ts +51 -0
- package/src/utils/fit-canvas.ts +64 -0
- package/src/utils/legend-constants.ts +8 -54
- package/src/utils/legend-d3.ts +10 -7
- package/src/utils/legend-layout.ts +7 -4
- package/src/utils/legend-types.ts +10 -4
- package/src/utils/note-box/constants.ts +25 -0
- package/src/utils/note-box/index.ts +11 -0
- package/src/utils/note-box/metrics.ts +90 -0
- package/src/utils/note-box/svg.ts +331 -0
- package/src/utils/notes/bounds.ts +30 -0
- package/src/utils/notes/build.ts +131 -0
- package/src/utils/notes/index.ts +18 -0
- package/src/utils/notes/model.ts +19 -0
- package/src/utils/notes/parse.ts +131 -0
- package/src/utils/notes/place.ts +177 -0
- package/src/utils/notes/resolve.ts +88 -0
- package/src/utils/number-format.ts +36 -0
- package/src/utils/parsing.ts +41 -0
- package/src/utils/reserved-key-registry.ts +4 -0
- package/src/utils/text-measure.ts +122 -0
- package/src/wireframe/layout.ts +4 -2
- package/src/wireframe/renderer.ts +8 -6
- package/src/palettes/dracula.ts +0 -68
- package/src/palettes/gruvbox.ts +0 -85
- package/src/palettes/monokai.ts +0 -68
- package/src/palettes/one-dark.ts +0 -70
- package/src/palettes/rose-pine.ts +0 -84
- package/src/palettes/solarized.ts +0 -77
|
@@ -7,6 +7,7 @@ import { parseJourneyMap } from './parser';
|
|
|
7
7
|
import {
|
|
8
8
|
layoutJourneyMap,
|
|
9
9
|
scoreToColor,
|
|
10
|
+
scoreToCurveY,
|
|
10
11
|
TAG_STRIP_HEIGHT,
|
|
11
12
|
type CurvePoint,
|
|
12
13
|
type StepLayout,
|
|
@@ -21,6 +22,11 @@ import { renderLegendD3 } from '../utils/legend-d3';
|
|
|
21
22
|
import type { LegendConfig, LegendState } from '../utils/legend-types';
|
|
22
23
|
import { resolveActiveTagGroup } from '../utils/tag-groups';
|
|
23
24
|
import { ScaleContext } from '../utils/scaling';
|
|
25
|
+
import {
|
|
26
|
+
measureText,
|
|
27
|
+
wrapTextToWidth,
|
|
28
|
+
truncateText as truncateToWidth,
|
|
29
|
+
} from '../utils/text-measure';
|
|
24
30
|
|
|
25
31
|
// ============================================================
|
|
26
32
|
// Interactive Options
|
|
@@ -66,7 +72,6 @@ const CURVE_STROKE_WIDTH = 2.5;
|
|
|
66
72
|
const FACE_RADIUS = 14;
|
|
67
73
|
const DIM_HOVER = 0.25;
|
|
68
74
|
const TITLE_LINE_HEIGHT = 16;
|
|
69
|
-
const TITLE_CHAR_WIDTH = 6.5;
|
|
70
75
|
|
|
71
76
|
// ============================================================
|
|
72
77
|
// Renderer
|
|
@@ -415,10 +420,7 @@ export function renderJourneyMap(
|
|
|
415
420
|
|
|
416
421
|
// Grid lines at score levels 1-5
|
|
417
422
|
for (let score = 1; score <= 5; score++) {
|
|
418
|
-
const y =
|
|
419
|
-
layout.curveAreaBottom -
|
|
420
|
-
((score - 1) / 4) * (layout.curveAreaBottom - layout.curveAreaTop - 120) -
|
|
421
|
-
10;
|
|
423
|
+
const y = scoreToCurveY(score, layout.curveAreaBottom);
|
|
422
424
|
|
|
423
425
|
curveG
|
|
424
426
|
.append('line')
|
|
@@ -621,7 +623,11 @@ export function renderJourneyMap(
|
|
|
621
623
|
.attr('fill', onHeaderText)
|
|
622
624
|
.text(
|
|
623
625
|
isCollapsed
|
|
624
|
-
? truncateText(
|
|
626
|
+
? truncateText(
|
|
627
|
+
pl.phase.name,
|
|
628
|
+
pl.width - COLUMN_PADDING * 2,
|
|
629
|
+
FONT_SIZE_PHASE
|
|
630
|
+
)
|
|
625
631
|
: pl.phase.name
|
|
626
632
|
);
|
|
627
633
|
|
|
@@ -728,7 +734,7 @@ export function renderJourneyMap(
|
|
|
728
734
|
.attr('y', itemY + COLLAPSED_CARD_H / 2 + FONT_SIZE_META / 2 - 1)
|
|
729
735
|
.attr('font-size', FONT_SIZE_META)
|
|
730
736
|
.attr('fill', palette.text)
|
|
731
|
-
.text(truncateText(step.title, maxTextW));
|
|
737
|
+
.text(truncateText(step.title, maxTextW, FONT_SIZE_META));
|
|
732
738
|
|
|
733
739
|
if (onNavigateToLine) {
|
|
734
740
|
itemG.style('cursor', 'pointer').on('click', (event: Event) => {
|
|
@@ -893,7 +899,7 @@ export function renderJourneyMap(
|
|
|
893
899
|
const lines = wrapText(thoughtText, THOUGHT_MAX_W, THOUGHT_FONT);
|
|
894
900
|
const textW = Math.min(
|
|
895
901
|
THOUGHT_MAX_W,
|
|
896
|
-
Math.max(...lines.map((l) => l
|
|
902
|
+
Math.max(...lines.map((l) => measureText(l, THOUGHT_FONT)))
|
|
897
903
|
);
|
|
898
904
|
const bw = textW + THOUGHT_PAD_X * 2;
|
|
899
905
|
const bh = lines.length * THOUGHT_LINE_H + THOUGHT_PAD_Y * 2;
|
|
@@ -1170,20 +1176,7 @@ function renderStepCard(
|
|
|
1170
1176
|
|
|
1171
1177
|
// Title (wrapped)
|
|
1172
1178
|
const titleMaxW = sl.width - CARD_PADDING_X * 2;
|
|
1173
|
-
const
|
|
1174
|
-
const titleWords = sl.step.title.split(/\s+/);
|
|
1175
|
-
const titleLines: string[] = [];
|
|
1176
|
-
let titleCur = '';
|
|
1177
|
-
for (const w of titleWords) {
|
|
1178
|
-
const candidate = titleCur ? `${titleCur} ${w}` : w;
|
|
1179
|
-
if (candidate.length > titleMaxChars && titleCur) {
|
|
1180
|
-
titleLines.push(titleCur);
|
|
1181
|
-
titleCur = w;
|
|
1182
|
-
} else {
|
|
1183
|
-
titleCur = candidate;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
if (titleCur) titleLines.push(titleCur);
|
|
1179
|
+
const titleLines = wrapTextToWidth(sl.step.title, FONT_SIZE_STEP, titleMaxW);
|
|
1187
1180
|
|
|
1188
1181
|
for (let i = 0; i < titleLines.length; i++) {
|
|
1189
1182
|
stepG
|
|
@@ -1459,31 +1452,15 @@ function renderScoreFace(
|
|
|
1459
1452
|
}
|
|
1460
1453
|
|
|
1461
1454
|
function wrapText(text: string, maxWidth: number, fontSize: number): string[] {
|
|
1462
|
-
|
|
1463
|
-
const maxChars = Math.floor(maxWidth / charWidth);
|
|
1464
|
-
if (maxChars <= 0) return [text];
|
|
1465
|
-
|
|
1466
|
-
const words = text.split(/\s+/);
|
|
1467
|
-
const lines: string[] = [];
|
|
1468
|
-
let current = '';
|
|
1469
|
-
|
|
1470
|
-
for (const word of words) {
|
|
1471
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
1472
|
-
if (candidate.length > maxChars && current) {
|
|
1473
|
-
lines.push(current);
|
|
1474
|
-
current = word;
|
|
1475
|
-
} else {
|
|
1476
|
-
current = candidate;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (current) lines.push(current);
|
|
1480
|
-
return lines;
|
|
1455
|
+
return wrapTextToWidth(text, fontSize, maxWidth);
|
|
1481
1456
|
}
|
|
1482
1457
|
|
|
1483
|
-
function truncateText(
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1458
|
+
function truncateText(
|
|
1459
|
+
text: string,
|
|
1460
|
+
maxWidth: number,
|
|
1461
|
+
fontSize: number
|
|
1462
|
+
): string {
|
|
1463
|
+
return truncateToWidth(text, fontSize, maxWidth);
|
|
1487
1464
|
}
|
|
1488
1465
|
|
|
1489
1466
|
function annotationColor(
|
package/src/kanban/renderer.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
D3Sel,
|
|
26
26
|
} from '../utils/legend-types';
|
|
27
27
|
import { ScaleContext } from '../utils/scaling';
|
|
28
|
+
import { measureText } from '../utils/text-measure';
|
|
28
29
|
|
|
29
30
|
// ============================================================
|
|
30
31
|
// Public options object
|
|
@@ -163,7 +164,6 @@ function computeLayout(
|
|
|
163
164
|
const headerHeight = showTitle ? sTitleHeight + 8 : 0;
|
|
164
165
|
const startY = sDiagramPadding + headerHeight;
|
|
165
166
|
|
|
166
|
-
const charWidth = sCardTitleFontSize * 0.6;
|
|
167
167
|
const columnLayouts: ColumnLayout[] = [];
|
|
168
168
|
|
|
169
169
|
let maxColumnHeight = 0;
|
|
@@ -185,13 +185,13 @@ function computeLayout(
|
|
|
185
185
|
continue;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
let maxCardTextWidth = col.name
|
|
188
|
+
let maxCardTextWidth = measureText(col.name, sColumnHeaderFontSize);
|
|
189
189
|
|
|
190
190
|
const cardLayouts: CardLayout[] = [];
|
|
191
191
|
let cardY = sColumnHeaderHeight + sColumnPadding;
|
|
192
192
|
|
|
193
193
|
for (const card of col.cards) {
|
|
194
|
-
const titleWidth = card.title
|
|
194
|
+
const titleWidth = measureText(card.title, sCardTitleFontSize);
|
|
195
195
|
maxCardTextWidth = Math.max(
|
|
196
196
|
maxCardTextWidth,
|
|
197
197
|
titleWidth + sCardPaddingX * 2
|
|
@@ -214,7 +214,7 @@ function computeLayout(
|
|
|
214
214
|
|
|
215
215
|
for (const m of tagMeta) {
|
|
216
216
|
const metaW =
|
|
217
|
-
(m.label
|
|
217
|
+
measureText(`${m.label}: ${m.value}`, sCardMetaFontSize) +
|
|
218
218
|
sCardPaddingX * 2;
|
|
219
219
|
maxCardTextWidth = Math.max(maxCardTextWidth, metaW);
|
|
220
220
|
}
|
|
@@ -697,7 +697,7 @@ export function renderKanban(
|
|
|
697
697
|
.attr('fill', onCardText)
|
|
698
698
|
.text(`${meta.label}: `);
|
|
699
699
|
|
|
700
|
-
const labelWidth = (meta.label
|
|
700
|
+
const labelWidth = measureText(`${meta.label}: `, sCardMetaFontSize);
|
|
701
701
|
cg.append('text')
|
|
702
702
|
.attr('x', cx + sCardPaddingX + labelWidth)
|
|
703
703
|
.attr('y', metaY)
|
|
@@ -768,6 +768,15 @@ function drawSwimlaneIcon(
|
|
|
768
768
|
.attr('class', 'kanban-swimlane-icon')
|
|
769
769
|
.attr('transform', `translate(${x}, ${y})`);
|
|
770
770
|
|
|
771
|
+
// Transparent hit area so the whole icon (not just the 2px bars) is clickable
|
|
772
|
+
iconG
|
|
773
|
+
.append('rect')
|
|
774
|
+
.attr('x', -5)
|
|
775
|
+
.attr('y', -5)
|
|
776
|
+
.attr('width', 22)
|
|
777
|
+
.attr('height', 18)
|
|
778
|
+
.attr('fill', 'transparent');
|
|
779
|
+
|
|
771
780
|
const color = isActive ? palette.primary : palette.textMuted;
|
|
772
781
|
const opacity = isActive ? 1 : 0.35;
|
|
773
782
|
const barWidths = [8, 12, 6];
|
|
@@ -1413,7 +1422,7 @@ function renderSwimlaneCard(
|
|
|
1413
1422
|
.attr('font-size', sCardMetaFontSize)
|
|
1414
1423
|
.attr('fill', palette.textMuted)
|
|
1415
1424
|
.text(`${meta.label}: `);
|
|
1416
|
-
const labelWidth = (meta.label
|
|
1425
|
+
const labelWidth = measureText(`${meta.label}: `, sCardMetaFontSize);
|
|
1417
1426
|
cg.append('text')
|
|
1418
1427
|
.attr('x', cx + sCardPaddingX + labelWidth)
|
|
1419
1428
|
.attr('y', metaY)
|
package/src/label-layout.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Shared label collision detection and placement utilities
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
import { measureText } from './utils/text-measure';
|
|
6
|
+
|
|
5
7
|
export interface LabelRect {
|
|
6
8
|
x: number;
|
|
7
9
|
y: number;
|
|
@@ -74,8 +76,6 @@ export function segmentRectOverlap(
|
|
|
74
76
|
// Quadrant chart point label placement
|
|
75
77
|
// ---------------------------------------------------------------------------
|
|
76
78
|
|
|
77
|
-
const CHAR_WIDTH_RATIO = 0.6;
|
|
78
|
-
|
|
79
79
|
export interface QuadrantLabelPoint {
|
|
80
80
|
label: string;
|
|
81
81
|
cx: number; // pixel x
|
|
@@ -120,7 +120,7 @@ export function computeQuadrantPointLabels(
|
|
|
120
120
|
|
|
121
121
|
for (let i = 0; i < points.length; i++) {
|
|
122
122
|
const pt = points[i]!; // In-bounds by loop guard.
|
|
123
|
-
const labelWidth = pt.label
|
|
123
|
+
const labelWidth = measureText(pt.label, fontSize) + 8;
|
|
124
124
|
|
|
125
125
|
// Try 4 directions: above, below, left, right
|
|
126
126
|
// Each direction generates candidate (labelX, labelY, anchor)
|
package/src/map/completion.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// ./geo) so this module — exported from the main index — stays free of the
|
|
9
9
|
// d3-geo / topojson imports that geo.ts pulls in. Keep it byte-identical to the
|
|
10
10
|
// resolver/geo folding so matches agree.
|
|
11
|
-
import type { Gazetteer, RegionName } from './data/types';
|
|
11
|
+
import type { Gazetteer, RegionName, AirportData } from './data/types';
|
|
12
12
|
|
|
13
13
|
const fold = (s: string): string =>
|
|
14
14
|
s
|
|
@@ -34,11 +34,24 @@ export interface MapPlaceCompletion {
|
|
|
34
34
|
readonly iso: string;
|
|
35
35
|
readonly sub?: string;
|
|
36
36
|
readonly pop: number;
|
|
37
|
+
/** `'airport'` for IATA-code entries (icon/grouping affordance); absent or
|
|
38
|
+
* `'city'` for gazetteer cities. Cities rank above airports for a shared
|
|
39
|
+
* prefix so ~1500 codes never bury city names (ADR-5). */
|
|
40
|
+
readonly kind?: 'city' | 'airport';
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export interface MapCompletionOptions {
|
|
40
44
|
/** Max results (default 12). */
|
|
41
45
|
readonly limit?: number;
|
|
46
|
+
/** IATA-coded airports (`airports.json`). When supplied, airport codes
|
|
47
|
+
* matching the prefix are offered as a second (post-city) group. Optional —
|
|
48
|
+
* absent (old DI bundles / no asset) just yields city-only completions. */
|
|
49
|
+
readonly airports?: AirportData;
|
|
50
|
+
/** Resolver-inferred map scope (country `US` or subdivision `US-CA`). Biases
|
|
51
|
+
* airport ranking so in-region airports sort above out-of-region same-prefix
|
|
52
|
+
* ones (ADR-6). Pure rank, never a filter — cross-region airports still
|
|
53
|
+
* appear. App passes the document's inferred scope in (Slice 2). */
|
|
54
|
+
readonly scopeISO?: string;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
/**
|
|
@@ -65,35 +78,77 @@ export function completeMapPlaces(
|
|
|
65
78
|
for (const [key, idx] of Object.entries(gazetteer.alt)) {
|
|
66
79
|
if (key.startsWith(q)) matched.add(idx);
|
|
67
80
|
}
|
|
68
|
-
if (matched.size === 0) return [];
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
// Airport prefix matches (optional asset; guarded for absent bundles, F5/#8).
|
|
83
|
+
// Folded IATA code → index into `airports`. Scope-match (current map's country)
|
|
84
|
+
// then code-alpha — NOT pop (always 0) and NOT type (the tuple has no type).
|
|
85
|
+
const airports = opts?.airports;
|
|
86
|
+
const scopeCountry = opts?.scopeISO ? opts.scopeISO.slice(0, 2) : undefined;
|
|
87
|
+
const airportHits: Array<{ code: string; name: string; iso: string }> = [];
|
|
88
|
+
if (airports?.airportIata) {
|
|
89
|
+
for (const [key, idx] of Object.entries(airports.airportIata)) {
|
|
90
|
+
if (!key.startsWith(q)) continue;
|
|
91
|
+
const a = airports.airports[idx];
|
|
92
|
+
if (a) airportHits.push({ code: key, name: a[4], iso: a[2] });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Relaxed early-return: only when BOTH groups are empty (an airport-only prefix
|
|
97
|
+
// must still complete — the old city-only `matched.size === 0` blocked it).
|
|
98
|
+
if (matched.size === 0 && airportHits.length === 0) return [];
|
|
99
|
+
|
|
100
|
+
// Cities first (pop desc), then airports — cities fill the limit slots first.
|
|
101
|
+
// Slice the sorted index list to `limit` BEFORE building items (a short prefix
|
|
102
|
+
// matches thousands of cities; mapping them all per keystroke is wasted work —
|
|
103
|
+
// airports are appended after, so they never starve cities).
|
|
104
|
+
const cityItems: MapPlaceCompletion[] = [...matched]
|
|
71
105
|
.filter((i) => gazetteer.cities[i] !== undefined)
|
|
72
106
|
.sort((a, b) => {
|
|
73
107
|
const pa = gazetteer.cities[a]![3];
|
|
74
108
|
const pb = gazetteer.cities[b]![3];
|
|
75
109
|
return pb - pa || a - b; // pop desc, then deterministic index
|
|
76
110
|
})
|
|
77
|
-
.slice(0, limit)
|
|
111
|
+
.slice(0, limit)
|
|
112
|
+
.map((i) => {
|
|
113
|
+
const c = gazetteer.cities[i]!;
|
|
114
|
+
const [, , iso, pop, name, sub] = c;
|
|
115
|
+
const ambiguous = (gazetteer.byName[fold(name)]?.length ?? 0) > 1;
|
|
116
|
+
const qualifier = sub ?? iso;
|
|
117
|
+
const insert = ambiguous ? `${name} ${qualifier}` : name;
|
|
118
|
+
const label = ambiguous ? `${name} — ${qualifier}` : name;
|
|
119
|
+
const detail = `${qualifier} · ${groupThousands(pop)}`;
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
insert,
|
|
123
|
+
label,
|
|
124
|
+
detail,
|
|
125
|
+
iso,
|
|
126
|
+
pop,
|
|
127
|
+
kind: 'city' as const,
|
|
128
|
+
...(sub !== undefined && { sub }),
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const airportItems: MapPlaceCompletion[] = airportHits
|
|
133
|
+
.sort((a, b) => {
|
|
134
|
+
const sa = scopeCountry && a.iso === scopeCountry ? 0 : 1;
|
|
135
|
+
const sb = scopeCountry && b.iso === scopeCountry ? 0 : 1;
|
|
136
|
+
return sa - sb || (a.code < b.code ? -1 : a.code > b.code ? 1 : 0);
|
|
137
|
+
})
|
|
138
|
+
.map(({ code, name, iso }) => {
|
|
139
|
+
const upper = code.toUpperCase();
|
|
140
|
+
return {
|
|
141
|
+
name,
|
|
142
|
+
insert: upper,
|
|
143
|
+
label: upper,
|
|
144
|
+
detail: `Airport · ${name}`,
|
|
145
|
+
iso,
|
|
146
|
+
pop: 0,
|
|
147
|
+
kind: 'airport' as const,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
78
150
|
|
|
79
|
-
return
|
|
80
|
-
const c = gazetteer.cities[i]!;
|
|
81
|
-
const [, , iso, pop, name, sub] = c;
|
|
82
|
-
const ambiguous = (gazetteer.byName[fold(name)]?.length ?? 0) > 1;
|
|
83
|
-
const qualifier = sub ?? iso;
|
|
84
|
-
const insert = ambiguous ? `${name} ${qualifier}` : name;
|
|
85
|
-
const label = ambiguous ? `${name} — ${qualifier}` : name;
|
|
86
|
-
const detail = `${qualifier} · ${groupThousands(pop)}`;
|
|
87
|
-
return {
|
|
88
|
-
name,
|
|
89
|
-
insert,
|
|
90
|
-
label,
|
|
91
|
-
detail,
|
|
92
|
-
iso,
|
|
93
|
-
pop,
|
|
94
|
-
...(sub !== undefined && { sub }),
|
|
95
|
-
};
|
|
96
|
-
});
|
|
151
|
+
return [...cityItems, ...airportItems].slice(0, limit);
|
|
97
152
|
}
|
|
98
153
|
|
|
99
154
|
export interface MapRegionCompletion {
|
|
@@ -60,6 +60,17 @@ const CONTEXT_PAD = 4; // extra gap enforced between two context labels
|
|
|
60
60
|
const EDGE_CLAMP_MARGIN = 8; // px inset for edge-clamped ocean labels
|
|
61
61
|
const EDGE_CLAMP_OVERSHOOT = 0.35; // max off-frame overshoot (× dim) to still clamp
|
|
62
62
|
|
|
63
|
+
// Country labels scale with their projected footprint: a big landmass (Canada,
|
|
64
|
+
// Mexico on a US view) reads as a large, FADED backdrop name; a small one stays
|
|
65
|
+
// at the base font, fully muted. Size metric is the footprint's linear extent
|
|
66
|
+
// (√bbox-area) as a fraction of the canvas's linear extent (√canvas-area), so
|
|
67
|
+
// the ramp is resolution-independent. Below MIN ⇒ base font / no extra fade;
|
|
68
|
+
// at/above MAX ⇒ max font / max fade; linear between.
|
|
69
|
+
const COUNTRY_FONT_MAX = 22; // px ceiling for the largest footprint
|
|
70
|
+
const COUNTRY_SIZE_FRAC_MIN = 0.06; // footprint linear-frac at base font
|
|
71
|
+
const COUNTRY_SIZE_FRAC_MAX = 0.32; // footprint linear-frac at max font
|
|
72
|
+
const COUNTRY_FADE_MAX = 45; // % blend toward bg at max font (subdue big names)
|
|
73
|
+
|
|
63
74
|
// Water-kind priority within a tier (oceans first, then seas, then the rest) so
|
|
64
75
|
// a thin budget always spends on the highest-orientation-value names.
|
|
65
76
|
const KIND_ORDER: Record<WaterKind, number> = {
|
|
@@ -136,10 +147,14 @@ function insideViewport(
|
|
|
136
147
|
* the per-gap `letter-spacing` the renderer applies to water names, so without
|
|
137
148
|
* this the fit/clamp math under-measures by ~`(len-1)*spacing` and the label
|
|
138
149
|
* clips at the canvas edge. */
|
|
139
|
-
export function labelWidth(
|
|
150
|
+
export function labelWidth(
|
|
151
|
+
text: string,
|
|
152
|
+
letterSpacing: number,
|
|
153
|
+
font: number = FONT
|
|
154
|
+
): number {
|
|
140
155
|
const spacing =
|
|
141
156
|
letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
|
|
142
|
-
return measureLegendText(text,
|
|
157
|
+
return measureLegendText(text, font) + spacing + 2 * PADX;
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
/** Wrap a multi-word name into balanced lines, biased to wrap READILY — water
|
|
@@ -188,10 +203,12 @@ function rectAround(
|
|
|
188
203
|
cx: number,
|
|
189
204
|
cy: number,
|
|
190
205
|
lines: readonly string[],
|
|
191
|
-
letterSpacing: number
|
|
206
|
+
letterSpacing: number,
|
|
207
|
+
font: number = FONT
|
|
192
208
|
): LabelRect {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
209
|
+
const lineHeight = font + 2; // MUST match the renderer's per-line stride
|
|
210
|
+
const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing, font)));
|
|
211
|
+
const h = (lines.length - 1) * lineHeight + font + 2 * PADY;
|
|
195
212
|
return { x: cx - w / 2, y: cy - h / 2, w, h };
|
|
196
213
|
}
|
|
197
214
|
|
|
@@ -252,6 +269,7 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
252
269
|
italic: boolean;
|
|
253
270
|
letterSpacing: number;
|
|
254
271
|
color: string;
|
|
272
|
+
fontSize: number;
|
|
255
273
|
sort: number; // priority key (lower first)
|
|
256
274
|
};
|
|
257
275
|
const candidates: Candidate[] = [];
|
|
@@ -328,6 +346,7 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
328
346
|
italic: true,
|
|
329
347
|
letterSpacing: WATER_LETTER_SPACING,
|
|
330
348
|
color: waterColor,
|
|
349
|
+
fontSize: FONT, // water names keep the base font (no footprint to scale on)
|
|
331
350
|
// Water before any country (×1000), then by tier, then kind, then name.
|
|
332
351
|
sort: tier * 10 + KIND_ORDER[kind],
|
|
333
352
|
});
|
|
@@ -345,22 +364,40 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
345
364
|
})
|
|
346
365
|
.filter((r) => Number.isFinite(r.area) && r.area > 0)
|
|
347
366
|
.sort((a, b) => b.area - a.area);
|
|
367
|
+
// Canvas linear extent — the denominator for the footprint size ramp below.
|
|
368
|
+
const canvasLinear = Math.sqrt(Math.max(1, width * height));
|
|
348
369
|
let ci = 0;
|
|
349
370
|
for (const r of ranked) {
|
|
350
|
-
const { c, w, h } = r;
|
|
371
|
+
const { c, w, h, area } = r;
|
|
351
372
|
// F2: an antimeridian-crossing / global-smear country yields a near-full-
|
|
352
373
|
// canvas bbox while its real landmass is split — the `path.centroid` anchor
|
|
353
374
|
// is then unreliable (mid-map, wrong basin). Drop such over-wide candidates
|
|
354
375
|
// rather than spend a top-priority slot on a mispositioned name.
|
|
355
376
|
if (w > width * 0.66 || h > height * 0.66) continue;
|
|
356
377
|
if (!insideViewport(c.anchor, width, height)) continue;
|
|
378
|
+
// Footprint-driven scale (Decision: big landmass = large, faded backdrop
|
|
379
|
+
// name). t∈[0,1] over the [MIN,MAX] linear-fraction band; font ramps up and
|
|
380
|
+
// colour fades toward bg in lockstep so a bigger name is also a quieter one.
|
|
381
|
+
const sizeFrac = Math.sqrt(area) / canvasLinear;
|
|
382
|
+
const t = Math.min(
|
|
383
|
+
1,
|
|
384
|
+
Math.max(
|
|
385
|
+
0,
|
|
386
|
+
(sizeFrac - COUNTRY_SIZE_FRAC_MIN) /
|
|
387
|
+
(COUNTRY_SIZE_FRAC_MAX - COUNTRY_SIZE_FRAC_MIN)
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
|
|
391
|
+
const fade = Math.round(t * COUNTRY_FADE_MAX);
|
|
392
|
+
const color = fade > 0 ? mix(countryColor, palette.bg, fade) : countryColor;
|
|
357
393
|
// Always the full country name — never an ISO abbreviation. If the name
|
|
358
394
|
// doesn't fit the footprint, drop the label rather than abbreviate.
|
|
359
395
|
const text = c.name;
|
|
360
|
-
const tw = labelWidth(text, 0);
|
|
361
|
-
// Approximate fit (Decision 4): name fits inside the footprint
|
|
362
|
-
// true point-in-polygon — cartographic labels routinely overrun
|
|
363
|
-
|
|
396
|
+
const tw = labelWidth(text, 0, fontSize);
|
|
397
|
+
// Approximate fit (Decision 4): the (scaled) name fits inside the footprint
|
|
398
|
+
// bbox. NOT true point-in-polygon — cartographic labels routinely overrun
|
|
399
|
+
// coastlines. The bigger the font, the bigger the box it must clear.
|
|
400
|
+
if (tw > w || fontSize + 2 * PADY > h) continue;
|
|
364
401
|
candidates.push({
|
|
365
402
|
text,
|
|
366
403
|
lines: [text],
|
|
@@ -368,7 +405,8 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
368
405
|
cy: c.anchor[1],
|
|
369
406
|
italic: false,
|
|
370
407
|
letterSpacing: 0,
|
|
371
|
-
color
|
|
408
|
+
color,
|
|
409
|
+
fontSize,
|
|
372
410
|
// Always after every water body (+1e6); larger area = earlier.
|
|
373
411
|
sort: 1_000_000 + ci++,
|
|
374
412
|
});
|
|
@@ -380,7 +418,13 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
380
418
|
const placedRects: LabelRect[] = [];
|
|
381
419
|
for (const cand of candidates) {
|
|
382
420
|
if (placed.length >= budget) break;
|
|
383
|
-
const rect = rectAround(
|
|
421
|
+
const rect = rectAround(
|
|
422
|
+
cand.cx,
|
|
423
|
+
cand.cy,
|
|
424
|
+
cand.lines,
|
|
425
|
+
cand.letterSpacing,
|
|
426
|
+
cand.fontSize
|
|
427
|
+
);
|
|
384
428
|
if (!rectFits(rect, width, height)) continue;
|
|
385
429
|
// Water labels must sit over OPEN WATER and NEVER touch land — sample a grid
|
|
386
430
|
// over every wrapped line (each line's own horizontal extent at five points);
|
|
@@ -419,6 +463,7 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
419
463
|
// cleanly on the basemap without one.
|
|
420
464
|
halo: false,
|
|
421
465
|
haloColor,
|
|
466
|
+
fontSize: cand.fontSize,
|
|
422
467
|
italic: cand.italic,
|
|
423
468
|
letterSpacing: cand.letterSpacing,
|
|
424
469
|
...(cand.lines.length > 1 ? { lines: cand.lines } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"assets":{"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":{"countries":175,"gazetteerAliases":8,"gazetteerCities":2119,"mountainRanges":205,"usStates":56,"waterBodies":113},"generatedBy":"scripts/build-map-data.mjs","sourceHashes":{"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)"},"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
|
+
{"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"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"collisions":["aba","ufa"]}
|