@diagrammo/dgmo 0.21.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/advanced.cjs +556 -195
  2. package/dist/advanced.js +555 -195
  3. package/dist/auto.cjs +322 -196
  4. package/dist/auto.js +113 -113
  5. package/dist/auto.mjs +322 -196
  6. package/dist/cli.cjs +156 -156
  7. package/dist/editor.cjs +1 -0
  8. package/dist/editor.js +1 -0
  9. package/dist/highlight.cjs +1 -0
  10. package/dist/highlight.js +1 -0
  11. package/dist/index.cjs +320 -195
  12. package/dist/index.js +320 -195
  13. package/dist/internal.cjs +556 -195
  14. package/dist/internal.js +555 -195
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/mountain-ranges.json +1 -0
  17. package/docs/language-reference.md +27 -25
  18. package/gallery/fixtures/map-direct-color.dgmo +10 -0
  19. package/package.json +1 -1
  20. package/src/advanced.ts +14 -0
  21. package/src/completion.ts +1 -0
  22. package/src/d3.ts +15 -9
  23. package/src/editor/keywords.ts +1 -0
  24. package/src/map/data/PROVENANCE.json +1 -1
  25. package/src/map/data/mountain-ranges.json +1 -0
  26. package/src/map/geo-query.ts +277 -0
  27. package/src/map/geo.ts +258 -1
  28. package/src/map/invert.ts +111 -0
  29. package/src/map/layout.ts +233 -113
  30. package/src/map/load-data.ts +7 -1
  31. package/src/map/parser.ts +22 -2
  32. package/src/map/renderer.ts +44 -0
  33. package/src/map/resolved-types.ts +8 -0
  34. package/src/map/resolver.ts +40 -19
  35. package/src/map/types.ts +18 -0
  36. package/dist/advanced.d.cts +0 -5331
  37. package/dist/advanced.d.ts +0 -5331
  38. package/dist/auto.d.cts +0 -39
  39. package/dist/auto.d.ts +0 -39
  40. package/dist/index.d.cts +0 -336
  41. package/dist/index.d.ts +0 -336
  42. package/dist/internal.d.cts +0 -5331
  43. package/dist/internal.d.ts +0 -5331
package/dist/internal.cjs CHANGED
@@ -15924,10 +15924,13 @@ function parseMap(content) {
15924
15924
  );
15925
15925
  d.projection = value;
15926
15926
  break;
15927
- case "region-metric":
15927
+ case "region-metric": {
15928
15928
  dup(d.regionMetric);
15929
- d.regionMetric = value;
15929
+ const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
15930
+ d.regionMetric = rmLabel;
15931
+ if (rmColor) d.regionMetricColor = rmColor;
15930
15932
  break;
15933
+ }
15931
15934
  case "poi-metric":
15932
15935
  dup(d.poiMetric);
15933
15936
  d.poiMetric = value;
@@ -15976,6 +15979,12 @@ function parseMap(content) {
15976
15979
  case "no-legend":
15977
15980
  d.noLegend = true;
15978
15981
  break;
15982
+ case "no-insets":
15983
+ d.noInsets = true;
15984
+ break;
15985
+ case "relief":
15986
+ d.relief = true;
15987
+ break;
15979
15988
  case "muted":
15980
15989
  case "natural":
15981
15990
  if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
@@ -16088,6 +16097,7 @@ function parseMap(content) {
16088
16097
  };
16089
16098
  if (regionScope !== void 0) region.scope = regionScope;
16090
16099
  if (valueNum !== void 0) region.value = valueNum;
16100
+ if (split.color) region.color = split.color;
16091
16101
  regions.push(region);
16092
16102
  }
16093
16103
  function handlePoi(rest, line12, indent) {
@@ -16112,6 +16122,7 @@ function parseMap(content) {
16112
16122
  const poi = { pos, tags, meta, lineNumber: line12 };
16113
16123
  if (split.alias) poi.alias = split.alias;
16114
16124
  if (label !== void 0) poi.label = label;
16125
+ if (split.color) poi.color = split.color;
16115
16126
  pois.push(poi);
16116
16127
  open.poi = { poi, indent };
16117
16128
  }
@@ -16316,6 +16327,8 @@ var init_parser12 = __esm({
16316
16327
  "default-state",
16317
16328
  "active-tag",
16318
16329
  "no-legend",
16330
+ "no-insets",
16331
+ "relief",
16319
16332
  "subtitle",
16320
16333
  "caption"
16321
16334
  ]);
@@ -45842,6 +45855,84 @@ function featureIndex(topo) {
45842
45855
  }
45843
45856
  return idx;
45844
45857
  }
45858
+ function decodeFeatures(topo) {
45859
+ return geomObject(topo).geometries.map((g) => {
45860
+ const f = (0, import_topojson_client.feature)(topo, g);
45861
+ return {
45862
+ type: "Feature",
45863
+ id: g.id,
45864
+ properties: g.properties,
45865
+ geometry: f.geometry
45866
+ };
45867
+ });
45868
+ }
45869
+ function pointInRing(lon, lat, ring) {
45870
+ let inside = false;
45871
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45872
+ const xi = ring[i][0];
45873
+ const yi = ring[i][1];
45874
+ const xj = ring[j][0];
45875
+ const yj = ring[j][1];
45876
+ const intersect = yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi;
45877
+ if (intersect) inside = !inside;
45878
+ }
45879
+ return inside;
45880
+ }
45881
+ function pointOnRingEdge(lon, lat, ring) {
45882
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45883
+ const xi = ring[i][0];
45884
+ const yi = ring[i][1];
45885
+ const xj = ring[j][0];
45886
+ const yj = ring[j][1];
45887
+ if (lon < Math.min(xi, xj) - EDGE_EPS || lon > Math.max(xi, xj) + EDGE_EPS)
45888
+ continue;
45889
+ if (lat < Math.min(yi, yj) - EDGE_EPS || lat > Math.max(yi, yj) + EDGE_EPS)
45890
+ continue;
45891
+ const cross = (xj - xi) * (lat - yi) - (yj - yi) * (lon - xi);
45892
+ if (Math.abs(cross) <= EDGE_EPS) return true;
45893
+ }
45894
+ return false;
45895
+ }
45896
+ function pointInGeometry(geometry, lon, lat) {
45897
+ const g = geometry;
45898
+ if (!g) return false;
45899
+ const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
45900
+ for (const rings of polys) {
45901
+ if (!rings.length) continue;
45902
+ if (pointOnRingEdge(lon, lat, rings[0])) return true;
45903
+ if (!pointInRing(lon, lat, rings[0])) continue;
45904
+ let inHole = false;
45905
+ for (let h = 1; h < rings.length; h++) {
45906
+ if (pointInRing(lon, lat, rings[h]) && !pointOnRingEdge(lon, lat, rings[h])) {
45907
+ inHole = true;
45908
+ break;
45909
+ }
45910
+ }
45911
+ if (!inHole) return true;
45912
+ }
45913
+ return false;
45914
+ }
45915
+ function regionAt(lonLat, countries, states) {
45916
+ const lon = lonLat[0];
45917
+ const lat = lonLat[1];
45918
+ let country = null;
45919
+ for (const f of countries) {
45920
+ if (pointInGeometry(f.geometry, lon, lat)) {
45921
+ country = { iso: f.id, name: f.properties.name };
45922
+ break;
45923
+ }
45924
+ }
45925
+ let state = null;
45926
+ if (country?.iso === "US" && states) {
45927
+ for (const f of states) {
45928
+ if (pointInGeometry(f.geometry, lon, lat)) {
45929
+ state = { iso: f.id, name: f.properties.name };
45930
+ break;
45931
+ }
45932
+ }
45933
+ }
45934
+ return { country, state };
45935
+ }
45845
45936
  function featureBbox(topo, geomId) {
45846
45937
  const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45847
45938
  if (!geom) return null;
@@ -45853,6 +45944,74 @@ function featureBbox(topo, geomId) {
45853
45944
  [b[1][0], b[1][1]]
45854
45945
  ];
45855
45946
  }
45947
+ function explodePolygons(gj) {
45948
+ const g = gj.geometry ?? gj;
45949
+ const t = g.type;
45950
+ const coords = g.coordinates;
45951
+ if (t === "Polygon") {
45952
+ return [
45953
+ { type: "Feature", geometry: { type: "Polygon", coordinates: coords } }
45954
+ ];
45955
+ }
45956
+ if (t === "MultiPolygon") {
45957
+ return coords.map((rings) => ({
45958
+ type: "Feature",
45959
+ geometry: { type: "Polygon", coordinates: rings }
45960
+ }));
45961
+ }
45962
+ return [];
45963
+ }
45964
+ function bboxGap(a, b) {
45965
+ const lonGap = Math.max(0, a[0][0] - b[1][0], b[0][0] - a[1][0]);
45966
+ const latGap = Math.max(0, a[0][1] - b[1][1], b[0][1] - a[1][1]);
45967
+ return Math.max(lonGap, latGap);
45968
+ }
45969
+ function featureBboxPrimary(topo, geomId) {
45970
+ const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45971
+ if (!geom) return null;
45972
+ const gj = (0, import_topojson_client.feature)(topo, geom);
45973
+ const parts = explodePolygons(gj);
45974
+ if (parts.length <= 1) return featureBbox(topo, geomId);
45975
+ const polys = parts.map((p) => {
45976
+ const b = (0, import_d3_geo.geoBounds)(p);
45977
+ if (!b || !Number.isFinite(b[0][0])) return null;
45978
+ const wraps = b[1][0] < b[0][0];
45979
+ const bbox = [
45980
+ [b[0][0], b[0][1]],
45981
+ [b[1][0], b[1][1]]
45982
+ ];
45983
+ return { bbox, area: (0, import_d3_geo.geoArea)(p), wraps };
45984
+ }).filter(
45985
+ (p) => p !== null
45986
+ );
45987
+ if (polys.length <= 1 || polys.some((p) => p.wraps))
45988
+ return featureBbox(topo, geomId);
45989
+ const maxArea = Math.max(...polys.map((p) => p.area));
45990
+ const anchor = polys.find((p) => p.area === maxArea);
45991
+ const cluster = [
45992
+ [anchor.bbox[0][0], anchor.bbox[0][1]],
45993
+ [anchor.bbox[1][0], anchor.bbox[1][1]]
45994
+ ];
45995
+ const remaining = polys.filter((p) => p !== anchor);
45996
+ let added = true;
45997
+ while (added) {
45998
+ added = false;
45999
+ for (let i = remaining.length - 1; i >= 0; i--) {
46000
+ const p = remaining[i];
46001
+ const near = bboxGap(p.bbox, cluster) <= DETACH_GAP_DEG;
46002
+ const large = p.area >= DETACH_AREA_FRAC * maxArea;
46003
+ if (near || large) {
46004
+ cluster[0][0] = Math.min(cluster[0][0], p.bbox[0][0]);
46005
+ cluster[0][1] = Math.min(cluster[0][1], p.bbox[0][1]);
46006
+ cluster[1][0] = Math.max(cluster[1][0], p.bbox[1][0]);
46007
+ cluster[1][1] = Math.max(cluster[1][1], p.bbox[1][1]);
46008
+ remaining.splice(i, 1);
46009
+ added = true;
46010
+ }
46011
+ }
46012
+ }
46013
+ return cluster;
46014
+ }
45856
46015
  function unionExtent(boxes, points) {
45857
46016
  const lats = [];
45858
46017
  const lons = [];
@@ -45891,13 +46050,16 @@ function unionLongitudes(lons) {
45891
46050
  }
45892
46051
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45893
46052
  }
45894
- var import_topojson_client, import_d3_geo, fold;
46053
+ var import_topojson_client, import_d3_geo, fold, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45895
46054
  var init_geo = __esm({
45896
46055
  "src/map/geo.ts"() {
45897
46056
  "use strict";
45898
46057
  import_topojson_client = require("topojson-client");
45899
46058
  import_d3_geo = require("d3-geo");
45900
46059
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46060
+ EDGE_EPS = 1e-9;
46061
+ DETACH_GAP_DEG = 10;
46062
+ DETACH_AREA_FRAC = 0.25;
45901
46063
  }
45902
46064
  });
45903
46065
 
@@ -46001,12 +46163,12 @@ function resolveMap(parsed, data) {
46001
46163
  chosen = { ...inState, layer: "us-state" };
46002
46164
  } else {
46003
46165
  chosen = { ...inCountry, layer: "country" };
46166
+ warn(
46167
+ r.lineNumber,
46168
+ `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}). Pin it with an ISO code (${inState.id} / ${inCountry.id}) or name + scope ("${r.name} US" / "${r.name} ${inCountry.id}").`,
46169
+ "W_MAP_REGION_AMBIGUOUS"
46170
+ );
46004
46171
  }
46005
- warn(
46006
- r.lineNumber,
46007
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}). Pin it with an ISO code (${inState.id} / ${inCountry.id}) or name + scope ("${r.name} US" / "${r.name} ${inCountry.id}").`,
46008
- "W_MAP_REGION_AMBIGUOUS"
46009
- );
46010
46172
  } else if (inState) {
46011
46173
  chosen = { ...inState, layer: "us-state" };
46012
46174
  } else if (inCountry) {
@@ -46030,6 +46192,7 @@ function resolveMap(parsed, data) {
46030
46192
  name: chosen.name,
46031
46193
  layer: chosen.layer,
46032
46194
  ...r.value !== void 0 && { value: r.value },
46195
+ ...r.color !== void 0 && { color: r.color },
46033
46196
  tags: r.tags,
46034
46197
  meta: r.meta,
46035
46198
  lineNumber: r.lineNumber
@@ -46171,6 +46334,7 @@ function resolveMap(parsed, data) {
46171
46334
  lat,
46172
46335
  lon,
46173
46336
  ...p.label !== void 0 && { label: p.label },
46337
+ ...p.color !== void 0 && { color: p.color },
46174
46338
  tags: p.tags,
46175
46339
  meta: p.meta,
46176
46340
  lineNumber: p.lineNumber
@@ -46313,7 +46477,7 @@ function resolveMap(parsed, data) {
46313
46477
  }
46314
46478
  for (const r of regions) {
46315
46479
  if (r.layer === "country") {
46316
- const bb = featureBbox(data.worldCoarse, r.iso);
46480
+ const bb = featureBboxPrimary(data.worldCoarse, r.iso);
46317
46481
  if (bb) regionBoxes.push(bb);
46318
46482
  }
46319
46483
  }
@@ -46327,6 +46491,7 @@ function resolveMap(parsed, data) {
46327
46491
  const lonSpan = extent2[1][0] - extent2[0][0];
46328
46492
  const latSpan = extent2[1][1] - extent2[0][1];
46329
46493
  const span = Math.max(lonSpan, latSpan);
46494
+ const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46330
46495
  const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46331
46496
  let projection;
46332
46497
  const override = parsed.directives.projection;
@@ -46334,12 +46499,10 @@ function resolveMap(parsed, data) {
46334
46499
  projection = override;
46335
46500
  } else if (usDominant) {
46336
46501
  projection = "albers-usa";
46337
- } else if (span > WORLD_SPAN) {
46502
+ } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46338
46503
  projection = "equirectangular";
46339
- } else if (span < MERCATOR_MAX_SPAN) {
46340
- projection = "mercator";
46341
46504
  } else {
46342
- projection = "equirectangular";
46505
+ projection = "mercator";
46343
46506
  }
46344
46507
  if (lonSpan >= 180) {
46345
46508
  extent2 = [
@@ -46393,14 +46556,14 @@ function firstError(diags) {
46393
46556
  const e = diags.find((d) => d.severity === "error");
46394
46557
  return e ? formatDgmoError(e) : null;
46395
46558
  }
46396
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46559
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46397
46560
  var init_resolver2 = __esm({
46398
46561
  "src/map/resolver.ts"() {
46399
46562
  "use strict";
46400
46563
  init_diagnostics();
46401
46564
  init_geo();
46402
46565
  WORLD_SPAN = 90;
46403
- MERCATOR_MAX_SPAN = 25;
46566
+ MERCATOR_MAX_LAT = 80;
46404
46567
  PAD_FRACTION = 0.05;
46405
46568
  WORLD_LAT_SOUTH = -58;
46406
46569
  WORLD_LAT_NORTH = 78;
@@ -46481,115 +46644,6 @@ var init_resolver2 = __esm({
46481
46644
  }
46482
46645
  });
46483
46646
 
46484
- // src/map/load-data.ts
46485
- var load_data_exports = {};
46486
- __export(load_data_exports, {
46487
- loadMapData: () => loadMapData
46488
- });
46489
- async function loadNodeBuiltins() {
46490
- const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
46491
- import("fs/promises"),
46492
- import("url"),
46493
- import("path")
46494
- ]);
46495
- return { readFile, fileURLToPath, dirname: dirname2, resolve };
46496
- }
46497
- async function readJson(nb, dir, name) {
46498
- return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46499
- }
46500
- async function firstExistingDir(nb, baseDir) {
46501
- for (const rel of CANDIDATE_DIRS) {
46502
- const dir = nb.resolve(baseDir, rel);
46503
- try {
46504
- await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46505
- return dir;
46506
- } catch {
46507
- }
46508
- }
46509
- throw new Error(
46510
- `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
46511
- );
46512
- }
46513
- function validate(data) {
46514
- const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
46515
- if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
46516
- throw new Error("map data assets are malformed (failed shape validation)");
46517
- }
46518
- return data;
46519
- }
46520
- function moduleBaseDir(nb) {
46521
- try {
46522
- const url = import_meta.url;
46523
- if (url) return nb.dirname(nb.fileURLToPath(url));
46524
- } catch {
46525
- }
46526
- if (typeof __dirname !== "undefined") return __dirname;
46527
- return process.cwd();
46528
- }
46529
- function loadMapData() {
46530
- cache ??= (async () => {
46531
- const nb = await loadNodeBuiltins();
46532
- const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46533
- const [
46534
- worldCoarse,
46535
- worldDetail,
46536
- usStates,
46537
- lakes,
46538
- rivers,
46539
- naLand,
46540
- naLakes,
46541
- gazetteer
46542
- ] = await Promise.all([
46543
- readJson(nb, dir, FILES.worldCoarse),
46544
- readJson(nb, dir, FILES.worldDetail),
46545
- readJson(nb, dir, FILES.usStates),
46546
- // Lakes/rivers/NA assets are optional — older bundles may predate them.
46547
- readJson(nb, dir, FILES.lakes).catch(() => void 0),
46548
- readJson(nb, dir, FILES.rivers).catch(() => void 0),
46549
- readJson(nb, dir, FILES.naLand).catch(() => void 0),
46550
- readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46551
- readJson(nb, dir, FILES.gazetteer)
46552
- ]);
46553
- return validate({
46554
- worldCoarse,
46555
- worldDetail,
46556
- usStates,
46557
- gazetteer,
46558
- ...lakes && { lakes },
46559
- ...rivers && { rivers },
46560
- ...naLand && { naLand },
46561
- ...naLakes && { naLakes }
46562
- });
46563
- })().catch((e) => {
46564
- cache = void 0;
46565
- throw e;
46566
- });
46567
- return cache;
46568
- }
46569
- var import_meta, FILES, CANDIDATE_DIRS, cache;
46570
- var init_load_data = __esm({
46571
- "src/map/load-data.ts"() {
46572
- "use strict";
46573
- import_meta = {};
46574
- FILES = {
46575
- worldCoarse: "world-coarse.json",
46576
- worldDetail: "world-detail.json",
46577
- usStates: "us-states.json",
46578
- lakes: "lakes.json",
46579
- rivers: "rivers.json",
46580
- naLand: "na-land.json",
46581
- naLakes: "na-lakes.json",
46582
- gazetteer: "gazetteer.json"
46583
- };
46584
- CANDIDATE_DIRS = [
46585
- "./data",
46586
- "./map-data",
46587
- "../map-data",
46588
- "../src/map/data"
46589
- ];
46590
- }
46591
- });
46592
-
46593
46647
  // src/map/layout.ts
46594
46648
  function geomObject2(topo) {
46595
46649
  const key = Object.keys(topo.objects)[0];
@@ -46616,18 +46670,14 @@ function projectionFor(family) {
46616
46670
  return (0, import_d3_geo2.geoEquirectangular)();
46617
46671
  }
46618
46672
  }
46619
- function mapBackgroundColor(palette, isDark = false, dataActive = false) {
46620
- if (dataActive)
46621
- return mix(
46622
- palette.colors.gray,
46623
- palette.bg,
46624
- isDark ? MUTED_WATER_DARK : MUTED_WATER_LIGHT
46625
- );
46626
- return mix(palette.colors.blue, palette.bg, WATER_TINT);
46673
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
46674
+ return mix(
46675
+ palette.colors.blue,
46676
+ palette.bg,
46677
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
46678
+ );
46627
46679
  }
46628
- function mapNeutralLandColor(palette, isDark, dataActive = false) {
46629
- if (dataActive)
46630
- return isDark ? mix(palette.colors.gray, palette.bg, MUTED_LAND_DARK) : palette.bg;
46680
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46631
46681
  return mix(
46632
46682
  palette.colors.green,
46633
46683
  palette.bg,
@@ -46659,7 +46709,7 @@ function layoutMap(resolved, data, size, opts) {
46659
46709
  const scaleOverride = resolved.directives.scale;
46660
46710
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46661
46711
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
46662
- const rampHue = palette.colors.red;
46712
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46663
46713
  const hasRamp = values.length > 0;
46664
46714
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
46665
46715
  const matchColorGroup = (v) => {
@@ -46682,6 +46732,7 @@ function layoutMap(resolved, data, size, opts) {
46682
46732
  const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
46683
46733
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46684
46734
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46735
+ const lakeStroke = mix(regionStroke, water, 45);
46685
46736
  const foreignFill = mix(
46686
46737
  palette.colors.gray,
46687
46738
  palette.bg,
@@ -46711,7 +46762,14 @@ function layoutMap(resolved, data, size, opts) {
46711
46762
  isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46712
46763
  );
46713
46764
  };
46765
+ const directFill = (name) => {
46766
+ const hex = name ? resolveColor(name, palette) : null;
46767
+ if (!hex) return null;
46768
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
46769
+ };
46714
46770
  const regionFill = (r) => {
46771
+ const direct = directFill(r.color);
46772
+ if (direct) return direct;
46715
46773
  if (activeIsScore) {
46716
46774
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46717
46775
  }
@@ -46765,6 +46823,7 @@ function layoutMap(resolved, data, size, opts) {
46765
46823
  const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46766
46824
  let path;
46767
46825
  let project;
46826
+ let stretchParams = null;
46768
46827
  if (fitIsGlobal) {
46769
46828
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46770
46829
  const bx0 = cb[0][0];
@@ -46775,6 +46834,7 @@ function layoutMap(resolved, data, size, opts) {
46775
46834
  const oy = fitBox[0][1];
46776
46835
  const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46777
46836
  const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46837
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
46778
46838
  const stretch = (x, y) => [
46779
46839
  ox + (x - bx0) * sx,
46780
46840
  oy + (y - by0) * sy
@@ -46806,7 +46866,7 @@ function layoutMap(resolved, data, size, opts) {
46806
46866
  const insets = [];
46807
46867
  const insetRegions = [];
46808
46868
  const insetLabelSeeds = [];
46809
- if (resolved.projection === "albers-usa" && usLayer) {
46869
+ if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
46810
46870
  const PAD = 8;
46811
46871
  const GAP = 12;
46812
46872
  const yB = height - FIT_PAD;
@@ -46837,38 +46897,14 @@ function layoutMap(resolved, data, size, opts) {
46837
46897
  }
46838
46898
  return y;
46839
46899
  };
46840
- const coastTop = (x0, xr) => {
46900
+ const coastFloor = (x0, xr) => {
46841
46901
  const n = 24;
46842
- const pts = [];
46843
46902
  let maxY = -Infinity;
46844
46903
  for (let i = 0; i <= n; i++) {
46845
- const x = x0 + (xr - x0) * i / n;
46846
- const y = at(x);
46847
- if (y > -Infinity) {
46848
- pts.push([x, y]);
46849
- if (y > maxY) maxY = y;
46850
- }
46851
- }
46852
- if (pts.length === 0) return () => yB - height * 0.42;
46853
- let m = 0;
46854
- if (pts.length >= 2) {
46855
- let sx = 0, sy = 0, sxx = 0, sxy = 0;
46856
- for (const [x, y] of pts) {
46857
- sx += x;
46858
- sy += y;
46859
- sxx += x * x;
46860
- sxy += x * y;
46861
- }
46862
- const den = pts.length * sxx - sx * sx;
46863
- if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46864
- }
46865
- m = Math.max(-0.35, Math.min(0.35, m));
46866
- let c = -Infinity;
46867
- for (const [x, y] of pts) {
46868
- const need = y - m * x + GAP;
46869
- if (need > c) c = need;
46870
- }
46871
- return (x) => m * x + c;
46904
+ const y = at(x0 + (xr - x0) * i / n);
46905
+ if (y > maxY) maxY = y;
46906
+ }
46907
+ return maxY;
46872
46908
  };
46873
46909
  const placeInset = (iso, proj, boxX, iwReq) => {
46874
46910
  const f = usLayer.get(iso);
@@ -46877,19 +46913,15 @@ function layoutMap(resolved, data, size, opts) {
46877
46913
  const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46878
46914
  if (iw < 24) return boxX;
46879
46915
  const xr = x0 + iw + 2 * PAD;
46880
- const top = coastTop(x0, xr);
46881
- const yL = top(x0);
46882
- const yR = top(xr);
46916
+ const floor = coastFloor(x0, xr);
46917
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
46883
46918
  proj.fitWidth(iw, f);
46884
46919
  const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
46885
46920
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46886
46921
  const needH = sh + 2 * PAD;
46887
- let topFit = Math.max(yL, yR);
46922
+ let topFit = topGuess;
46888
46923
  const bottom = Math.min(topFit + needH, yB);
46889
46924
  if (bottom - topFit < needH) topFit = bottom - needH;
46890
- const lift = topFit - Math.max(yL, yR);
46891
- const topL = yL + lift;
46892
- const topR = yR + lift;
46893
46925
  proj.fitExtent(
46894
46926
  [
46895
46927
  [x0 + PAD, topFit + PAD],
@@ -46908,15 +46940,18 @@ function layoutMap(resolved, data, size, opts) {
46908
46940
  }
46909
46941
  insets.push({
46910
46942
  x: x0,
46911
- y: Math.min(topL, topR),
46943
+ y: topFit,
46912
46944
  w: xr - x0,
46913
- h: bottom - Math.min(topL, topR),
46945
+ h: bottom - topFit,
46914
46946
  points: [
46915
- [x0, topL],
46916
- [xr, topR],
46947
+ [x0, topFit],
46948
+ [xr, topFit],
46917
46949
  [xr, bottom],
46918
46950
  [x0, bottom]
46919
- ]
46951
+ ],
46952
+ // The FITTED inset projection (just fit to this box) — captured so the
46953
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
46954
+ projection: proj
46920
46955
  });
46921
46956
  insetRegions.push({
46922
46957
  id: iso,
@@ -47086,13 +47121,40 @@ function layoutMap(resolved, data, size, opts) {
47086
47121
  id: "lake",
47087
47122
  d,
47088
47123
  fill: water,
47089
- stroke: "none",
47124
+ stroke: lakeStroke,
47090
47125
  lineNumber: -1,
47091
47126
  layer: "base"
47092
47127
  });
47093
47128
  }
47094
47129
  }
47095
- const riverColor = water;
47130
+ const relief = [];
47131
+ let reliefHatch = null;
47132
+ if (resolved.directives.relief === true && data.mountainRanges) {
47133
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
47134
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
47135
+ if (!viewF) continue;
47136
+ const area2 = path.area(viewF);
47137
+ if (!Number.isFinite(area2) || area2 < RELIEF_MIN_AREA) continue;
47138
+ const box = path.bounds(viewF);
47139
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
47140
+ continue;
47141
+ const d = path(viewF) ?? "";
47142
+ if (!d) continue;
47143
+ relief.push({ d });
47144
+ }
47145
+ if (relief.length) {
47146
+ const darkTone = isDark ? palette.bg : palette.text;
47147
+ const lightTone = isDark ? palette.text : palette.bg;
47148
+ const landLum = relativeLuminance(neutralFill);
47149
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
47150
+ reliefHatch = {
47151
+ color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
47152
+ spacing: RELIEF_HATCH_SPACING,
47153
+ width: RELIEF_HATCH_WIDTH
47154
+ };
47155
+ }
47156
+ }
47157
+ const riverColor = mix(water, regionStroke, 16);
47096
47158
  const rivers = [];
47097
47159
  if (data.rivers) {
47098
47160
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -47113,6 +47175,9 @@ function layoutMap(resolved, data, size, opts) {
47113
47175
  return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
47114
47176
  };
47115
47177
  const poiFill = (p) => {
47178
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
47179
+ if (directHex)
47180
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
47116
47181
  for (const group of resolved.tagGroups) {
47117
47182
  const val = p.tags[group.name.toLowerCase()];
47118
47183
  if (!val) continue;
@@ -47528,21 +47593,26 @@ function layoutMap(resolved, data, size, opts) {
47528
47593
  ...resolved.caption !== void 0 && { caption: resolved.caption },
47529
47594
  regions,
47530
47595
  rivers,
47596
+ relief,
47597
+ reliefHatch,
47531
47598
  legs,
47532
47599
  pois,
47533
47600
  labels,
47534
47601
  legend,
47535
47602
  insets,
47536
- insetRegions
47603
+ insetRegions,
47604
+ projection,
47605
+ stretch: stretchParams
47537
47606
  };
47538
47607
  }
47539
- var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_WATER_LIGHT, MUTED_WATER_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, MUTED_LAND_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47608
+ var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47540
47609
  var init_layout15 = __esm({
47541
47610
  "src/map/layout.ts"() {
47542
47611
  "use strict";
47543
47612
  import_d3_geo2 = require("d3-geo");
47544
47613
  import_topojson_client2 = require("topojson-client");
47545
47614
  init_color_utils();
47615
+ init_colors();
47546
47616
  init_label_layout();
47547
47617
  init_legend_constants();
47548
47618
  init_title_constants();
@@ -47555,19 +47625,22 @@ var init_layout15 = __esm({
47555
47625
  W_MAX = 8;
47556
47626
  FONT = 11;
47557
47627
  COLO_EPS = 1.5;
47558
- LAND_TINT_LIGHT = 58;
47559
- LAND_TINT_DARK = 75;
47628
+ LAND_TINT_LIGHT = 12;
47629
+ LAND_TINT_DARK = 24;
47560
47630
  TAG_TINT_LIGHT = 60;
47561
47631
  TAG_TINT_DARK = 68;
47562
- WATER_TINT = 55;
47632
+ WATER_TINT_LIGHT = 13;
47633
+ WATER_TINT_DARK = 14;
47563
47634
  RIVER_WIDTH = 1.3;
47635
+ RELIEF_MIN_AREA = 12;
47636
+ RELIEF_MIN_DIM = 2;
47637
+ RELIEF_HATCH_SPACING = 3;
47638
+ RELIEF_HATCH_WIDTH = 0.25;
47639
+ RELIEF_HATCH_STRENGTH = 32;
47564
47640
  FOREIGN_TINT_LIGHT = 30;
47565
47641
  FOREIGN_TINT_DARK = 62;
47566
- MUTED_WATER_LIGHT = 14;
47567
- MUTED_WATER_DARK = 10;
47568
47642
  MUTED_FOREIGN_LIGHT = 28;
47569
47643
  MUTED_FOREIGN_DARK = 16;
47570
- MUTED_LAND_DARK = 24;
47571
47644
  COLO_R = 9;
47572
47645
  GOLDEN_ANGLE = 2.399963229728653;
47573
47646
  FAN_STEP = 16;
@@ -47639,6 +47712,20 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47639
47712
  }
47640
47713
  };
47641
47714
  for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47715
+ if (layout.relief.length && layout.reliefHatch) {
47716
+ const h = layout.reliefHatch;
47717
+ const rangeClipId = "dgmo-relief-clip";
47718
+ const landClipId = "dgmo-relief-land";
47719
+ const rangeClip = defs.append("clipPath").attr("id", rangeClipId);
47720
+ for (const s of layout.relief) rangeClip.append("path").attr("d", s.d);
47721
+ const landClip = defs.append("clipPath").attr("id", landClipId);
47722
+ for (const r of layout.regions)
47723
+ if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47724
+ const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
47725
+ for (let y = h.spacing; y < height; y += h.spacing) {
47726
+ gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47727
+ }
47728
+ }
47642
47729
  if (layout.rivers.length) {
47643
47730
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47644
47731
  for (const r of layout.rivers) {
@@ -47763,7 +47850,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47763
47850
  }
47764
47851
  }
47765
47852
  if (layout.title) {
47766
- svg.append("text").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.title);
47853
+ svg.append("text").attr("class", "dgmo-map-title").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.title);
47767
47854
  }
47768
47855
  if (layout.subtitle) {
47769
47856
  svg.append("text").attr("x", width / 2).attr("y", TITLE_Y + TITLE_FONT_SIZE).attr("text-anchor", "middle").attr("font-size", LABEL_FONT + 1).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.subtitle);
@@ -47796,6 +47883,121 @@ var init_renderer16 = __esm({
47796
47883
  }
47797
47884
  });
47798
47885
 
47886
+ // src/map/load-data.ts
47887
+ var load_data_exports = {};
47888
+ __export(load_data_exports, {
47889
+ loadMapData: () => loadMapData
47890
+ });
47891
+ async function loadNodeBuiltins() {
47892
+ const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
47893
+ import("fs/promises"),
47894
+ import("url"),
47895
+ import("path")
47896
+ ]);
47897
+ return { readFile, fileURLToPath, dirname: dirname2, resolve };
47898
+ }
47899
+ async function readJson(nb, dir, name) {
47900
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
47901
+ }
47902
+ async function firstExistingDir(nb, baseDir) {
47903
+ for (const rel of CANDIDATE_DIRS) {
47904
+ const dir = nb.resolve(baseDir, rel);
47905
+ try {
47906
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
47907
+ return dir;
47908
+ } catch {
47909
+ }
47910
+ }
47911
+ throw new Error(
47912
+ `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
47913
+ );
47914
+ }
47915
+ function validate(data) {
47916
+ const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
47917
+ if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
47918
+ throw new Error("map data assets are malformed (failed shape validation)");
47919
+ }
47920
+ return data;
47921
+ }
47922
+ function moduleBaseDir(nb) {
47923
+ try {
47924
+ const url = import_meta.url;
47925
+ if (url) return nb.dirname(nb.fileURLToPath(url));
47926
+ } catch {
47927
+ }
47928
+ if (typeof __dirname !== "undefined") return __dirname;
47929
+ return process.cwd();
47930
+ }
47931
+ function loadMapData() {
47932
+ cache ??= (async () => {
47933
+ const nb = await loadNodeBuiltins();
47934
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
47935
+ const [
47936
+ worldCoarse,
47937
+ worldDetail,
47938
+ usStates,
47939
+ lakes,
47940
+ rivers,
47941
+ mountainRanges,
47942
+ naLand,
47943
+ naLakes,
47944
+ gazetteer
47945
+ ] = await Promise.all([
47946
+ readJson(nb, dir, FILES.worldCoarse),
47947
+ readJson(nb, dir, FILES.worldDetail),
47948
+ readJson(nb, dir, FILES.usStates),
47949
+ // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
47950
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
47951
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
47952
+ readJson(nb, dir, FILES.mountainRanges).catch(
47953
+ () => void 0
47954
+ ),
47955
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
47956
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
47957
+ readJson(nb, dir, FILES.gazetteer)
47958
+ ]);
47959
+ return validate({
47960
+ worldCoarse,
47961
+ worldDetail,
47962
+ usStates,
47963
+ gazetteer,
47964
+ ...lakes && { lakes },
47965
+ ...rivers && { rivers },
47966
+ ...mountainRanges && { mountainRanges },
47967
+ ...naLand && { naLand },
47968
+ ...naLakes && { naLakes }
47969
+ });
47970
+ })().catch((e) => {
47971
+ cache = void 0;
47972
+ throw e;
47973
+ });
47974
+ return cache;
47975
+ }
47976
+ var import_meta, FILES, CANDIDATE_DIRS, cache;
47977
+ var init_load_data = __esm({
47978
+ "src/map/load-data.ts"() {
47979
+ "use strict";
47980
+ import_meta = {};
47981
+ FILES = {
47982
+ worldCoarse: "world-coarse.json",
47983
+ worldDetail: "world-detail.json",
47984
+ usStates: "us-states.json",
47985
+ lakes: "lakes.json",
47986
+ rivers: "rivers.json",
47987
+ mountainRanges: "mountain-ranges.json",
47988
+ naLand: "na-land.json",
47989
+ naLakes: "na-lakes.json",
47990
+ gazetteer: "gazetteer.json"
47991
+ };
47992
+ CANDIDATE_DIRS = [
47993
+ "./data",
47994
+ "./map-data",
47995
+ "../map-data",
47996
+ "../src/map/data"
47997
+ ];
47998
+ }
47999
+ });
48000
+
47799
48001
  // src/pyramid/renderer.ts
47800
48002
  var renderer_exports17 = {};
47801
48003
  __export(renderer_exports17, {
@@ -55975,15 +56177,17 @@ async function renderForExport(content, theme, palette, viewState, options) {
55975
56177
  if (detectedType === "map") {
55976
56178
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55977
56179
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55978
- const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55979
56180
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
55980
56181
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55981
56182
  const mapParsed = parseMap2(content);
55982
- let mapData;
55983
- try {
55984
- mapData = await loadMapData2();
55985
- } catch {
55986
- return "";
56183
+ let mapData = options?.mapData;
56184
+ if (!mapData) {
56185
+ const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
56186
+ try {
56187
+ mapData = await loadMapData2();
56188
+ } catch {
56189
+ return "";
56190
+ }
55987
56191
  }
55988
56192
  const mapResolved = resolveMap2(mapParsed, mapData);
55989
56193
  const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
@@ -56834,6 +57038,7 @@ __export(internal_exports, {
56834
57038
  computeTimeTicks: () => computeTimeTicks,
56835
57039
  contrastText: () => contrastText,
56836
57040
  controlsGroupCapsuleWidth: () => controlsGroupCapsuleWidth,
57041
+ createMapGeoQuery: () => createMapGeoQuery,
56837
57042
  decodeDiagramUrl: () => decodeDiagramUrl,
56838
57043
  decodeViewState: () => decodeViewState,
56839
57044
  displayName: () => displayName,
@@ -57562,6 +57767,160 @@ init_load_data();
57562
57767
  init_layout15();
57563
57768
  init_renderer16();
57564
57769
 
57770
+ // src/map/geo-query.ts
57771
+ init_parser12();
57772
+ init_resolver2();
57773
+ init_layout15();
57774
+ init_geo();
57775
+
57776
+ // src/map/invert.ts
57777
+ function inInsetFrame(inset, px, py) {
57778
+ return px >= inset.x && px <= inset.x + inset.w && py >= inset.y && py <= inset.y + inset.h;
57779
+ }
57780
+ function unstretch(layout, px, py) {
57781
+ const s = layout.stretch;
57782
+ return [
57783
+ s.bx0 + (s.sx !== 0 ? (px - s.ox) / s.sx : 0),
57784
+ s.by0 + (s.sy !== 0 ? (py - s.oy) / s.sy : 0)
57785
+ ];
57786
+ }
57787
+ function applyStretch(layout, x, y) {
57788
+ const s = layout.stretch;
57789
+ return [s.ox + (x - s.bx0) * s.sx, s.oy + (y - s.by0) * s.sy];
57790
+ }
57791
+ function pixelToLonLat(layout, px, py) {
57792
+ for (const inset of layout.insets) {
57793
+ if (inInsetFrame(inset, px, py)) {
57794
+ const ll2 = inset.projection.invert?.([px, py]);
57795
+ return ll2 && Number.isFinite(ll2[0]) && Number.isFinite(ll2[1]) ? [ll2[0], ll2[1]] : null;
57796
+ }
57797
+ }
57798
+ const [x, y] = layout.stretch ? unstretch(layout, px, py) : [px, py];
57799
+ const ll = layout.projection.invert?.([x, y]);
57800
+ return ll && Number.isFinite(ll[0]) && Number.isFinite(ll[1]) ? [ll[0], ll[1]] : null;
57801
+ }
57802
+ function lonLatToPixel(layout, lonLat) {
57803
+ const pt = [lonLat[0], lonLat[1]];
57804
+ const main = layout.projection(pt);
57805
+ const mainPx = main && Number.isFinite(main[0]) && Number.isFinite(main[1]) ? layout.stretch ? applyStretch(layout, main[0], main[1]) : [main[0], main[1]] : null;
57806
+ const onCanvas = !!mainPx && mainPx[0] >= 0 && mainPx[0] <= layout.width && mainPx[1] >= 0 && mainPx[1] <= layout.height;
57807
+ if (onCanvas) return mainPx;
57808
+ for (const inset of layout.insets) {
57809
+ const p = inset.projection(pt);
57810
+ if (p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && inInsetFrame(inset, p[0], p[1]))
57811
+ return [p[0], p[1]];
57812
+ }
57813
+ return mainPx;
57814
+ }
57815
+
57816
+ // src/map/geo-query.ts
57817
+ var EARTH_R_KM = 6371;
57818
+ var DEG = Math.PI / 180;
57819
+ function haversineKm(lat1, lon1, lat2, lon2) {
57820
+ const dLat = (lat2 - lat1) * DEG;
57821
+ const dLon = (lon2 - lon1) * DEG;
57822
+ const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * DEG) * Math.cos(lat2 * DEG) * Math.sin(dLon / 2) ** 2;
57823
+ return 2 * EARTH_R_KM * Math.asin(Math.min(1, Math.sqrt(a)));
57824
+ }
57825
+ var POP_PULL_KM = 12;
57826
+ function nearestCity(lonLat, gazetteer) {
57827
+ const [lon, lat] = lonLat;
57828
+ let best = null;
57829
+ const cities = gazetteer.cities;
57830
+ for (let i = 0; i < cities.length; i++) {
57831
+ const c2 = cities[i];
57832
+ const dist = haversineKm(lat, lon, c2[0], c2[1]);
57833
+ const score = dist - POP_PULL_KM * Math.log10((c2[3] || 0) + 1);
57834
+ if (!best || score < best.score) best = { score, idx: i, dist };
57835
+ }
57836
+ if (!best) return null;
57837
+ const c = cities[best.idx];
57838
+ return {
57839
+ name: c[4],
57840
+ iso: c[2],
57841
+ ...c[5] !== void 0 && { sub: c[5] },
57842
+ distanceKm: best.dist
57843
+ };
57844
+ }
57845
+ function roundCoord(n) {
57846
+ return Number(n.toFixed(2));
57847
+ }
57848
+ function buildTokens(lonLat, region, city) {
57849
+ const coordPoiLine = `poi ${roundCoord(lonLat[1])} ${roundCoord(lonLat[0])}`;
57850
+ let stateTok = null;
57851
+ if (region.state) {
57852
+ const { iso, name } = region.state;
57853
+ stateTok = { primary: `${name} ${iso}`, alternates: [iso, name] };
57854
+ }
57855
+ let countryTok = null;
57856
+ if (region.country) {
57857
+ const { iso, name } = region.country;
57858
+ countryTok = { primary: name, alternates: [iso] };
57859
+ }
57860
+ let cityTok = null;
57861
+ if (city) {
57862
+ const scope = city.sub ?? (city.iso || "");
57863
+ cityTok = scope ? { token: `poi ${city.name} ${scope}`, ambiguous: false } : { token: `poi ${city.name}`, ambiguous: true };
57864
+ }
57865
+ return { coordPoiLine, state: stateTok, country: countryTok, city: cityTok };
57866
+ }
57867
+ var MAX_CITY_DOTS = 250;
57868
+ function createMapGeoQuery(opts) {
57869
+ const { content, width, height, data, palette, isDark } = opts;
57870
+ const resolved = resolveMap(parseMap(content), data);
57871
+ const layout = layoutMap(
57872
+ resolved,
57873
+ data,
57874
+ { width, height },
57875
+ { palette, isDark }
57876
+ );
57877
+ const countries = decodeFeatures(data.worldDetail);
57878
+ const states = decodeFeatures(data.usStates);
57879
+ const gazetteer = data.gazetteer;
57880
+ const invert = (px, py) => pixelToLonLat(layout, px, py);
57881
+ const project = (lonLat) => lonLatToPixel(layout, lonLat);
57882
+ const locate = (px, py) => {
57883
+ const lonLat = invert(px, py);
57884
+ if (!lonLat) return null;
57885
+ const region = regionAt(lonLat, countries, states);
57886
+ const city = nearestCity(lonLat, gazetteer);
57887
+ return {
57888
+ lonLat,
57889
+ country: region.country,
57890
+ state: region.state,
57891
+ nearestCity: city,
57892
+ tokens: buildTokens(lonLat, region, city)
57893
+ };
57894
+ };
57895
+ const cities = (extent2) => {
57896
+ const sorted = [...gazetteer.cities].sort((a, b) => b[3] - a[3]);
57897
+ const out = [];
57898
+ for (const c of sorted) {
57899
+ const [lat, lon, iso, pop, name, sub] = c;
57900
+ if (extent2) {
57901
+ const [[w, s], [e, n]] = extent2;
57902
+ if (lon < w || lon > e || lat < s || lat > n) continue;
57903
+ }
57904
+ const p = project([lon, lat]);
57905
+ if (!p) continue;
57906
+ if (p[0] < 0 || p[0] > width || p[1] < 0 || p[1] > height) continue;
57907
+ out.push({
57908
+ name,
57909
+ iso,
57910
+ ...sub !== void 0 && { sub },
57911
+ lon,
57912
+ lat,
57913
+ px: p[0],
57914
+ py: p[1],
57915
+ pop
57916
+ });
57917
+ if (out.length >= MAX_CITY_DOTS) break;
57918
+ }
57919
+ return out;
57920
+ };
57921
+ return { invert, project, locate, cities };
57922
+ }
57923
+
57565
57924
  // src/map/completion.ts
57566
57925
  var fold2 = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
57567
57926
  var groupThousands = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -58714,6 +59073,7 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58714
59073
  "default-country": { description: "ISO scope for bare city resolution" },
58715
59074
  "default-state": { description: "ISO subdivision scope" },
58716
59075
  "no-legend": { description: "Suppress the legend" },
59076
+ relief: { description: "Subtle mountain-range relief shading" },
58717
59077
  subtitle: { description: "Subtitle line" },
58718
59078
  caption: { description: "Caption line" }
58719
59079
  })
@@ -60219,6 +60579,7 @@ function formatLineDiff(path, original, migrated) {
60219
60579
  computeTimeTicks,
60220
60580
  contrastText,
60221
60581
  controlsGroupCapsuleWidth,
60582
+ createMapGeoQuery,
60222
60583
  decodeDiagramUrl,
60223
60584
  decodeViewState,
60224
60585
  displayName,