@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.js CHANGED
@@ -15940,10 +15940,13 @@ function parseMap(content) {
15940
15940
  );
15941
15941
  d.projection = value;
15942
15942
  break;
15943
- case "region-metric":
15943
+ case "region-metric": {
15944
15944
  dup(d.regionMetric);
15945
- d.regionMetric = value;
15945
+ const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
15946
+ d.regionMetric = rmLabel;
15947
+ if (rmColor) d.regionMetricColor = rmColor;
15946
15948
  break;
15949
+ }
15947
15950
  case "poi-metric":
15948
15951
  dup(d.poiMetric);
15949
15952
  d.poiMetric = value;
@@ -15992,6 +15995,12 @@ function parseMap(content) {
15992
15995
  case "no-legend":
15993
15996
  d.noLegend = true;
15994
15997
  break;
15998
+ case "no-insets":
15999
+ d.noInsets = true;
16000
+ break;
16001
+ case "relief":
16002
+ d.relief = true;
16003
+ break;
15995
16004
  case "muted":
15996
16005
  case "natural":
15997
16006
  if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
@@ -16104,6 +16113,7 @@ function parseMap(content) {
16104
16113
  };
16105
16114
  if (regionScope !== void 0) region.scope = regionScope;
16106
16115
  if (valueNum !== void 0) region.value = valueNum;
16116
+ if (split.color) region.color = split.color;
16107
16117
  regions.push(region);
16108
16118
  }
16109
16119
  function handlePoi(rest, line12, indent) {
@@ -16128,6 +16138,7 @@ function parseMap(content) {
16128
16138
  const poi = { pos, tags, meta, lineNumber: line12 };
16129
16139
  if (split.alias) poi.alias = split.alias;
16130
16140
  if (label !== void 0) poi.label = label;
16141
+ if (split.color) poi.color = split.color;
16131
16142
  pois.push(poi);
16132
16143
  open.poi = { poi, indent };
16133
16144
  }
@@ -16332,6 +16343,8 @@ var init_parser12 = __esm({
16332
16343
  "default-state",
16333
16344
  "active-tag",
16334
16345
  "no-legend",
16346
+ "no-insets",
16347
+ "relief",
16335
16348
  "subtitle",
16336
16349
  "caption"
16337
16350
  ]);
@@ -45843,7 +45856,7 @@ var init_renderer15 = __esm({
45843
45856
 
45844
45857
  // src/map/geo.ts
45845
45858
  import { feature } from "topojson-client";
45846
- import { geoBounds } from "d3-geo";
45859
+ import { geoBounds, geoArea } from "d3-geo";
45847
45860
  function geomObject(topo) {
45848
45861
  const key = Object.keys(topo.objects)[0];
45849
45862
  return topo.objects[key];
@@ -45860,6 +45873,84 @@ function featureIndex(topo) {
45860
45873
  }
45861
45874
  return idx;
45862
45875
  }
45876
+ function decodeFeatures(topo) {
45877
+ return geomObject(topo).geometries.map((g) => {
45878
+ const f = feature(topo, g);
45879
+ return {
45880
+ type: "Feature",
45881
+ id: g.id,
45882
+ properties: g.properties,
45883
+ geometry: f.geometry
45884
+ };
45885
+ });
45886
+ }
45887
+ function pointInRing(lon, lat, ring) {
45888
+ let inside = false;
45889
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45890
+ const xi = ring[i][0];
45891
+ const yi = ring[i][1];
45892
+ const xj = ring[j][0];
45893
+ const yj = ring[j][1];
45894
+ const intersect = yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi;
45895
+ if (intersect) inside = !inside;
45896
+ }
45897
+ return inside;
45898
+ }
45899
+ function pointOnRingEdge(lon, lat, ring) {
45900
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45901
+ const xi = ring[i][0];
45902
+ const yi = ring[i][1];
45903
+ const xj = ring[j][0];
45904
+ const yj = ring[j][1];
45905
+ if (lon < Math.min(xi, xj) - EDGE_EPS || lon > Math.max(xi, xj) + EDGE_EPS)
45906
+ continue;
45907
+ if (lat < Math.min(yi, yj) - EDGE_EPS || lat > Math.max(yi, yj) + EDGE_EPS)
45908
+ continue;
45909
+ const cross = (xj - xi) * (lat - yi) - (yj - yi) * (lon - xi);
45910
+ if (Math.abs(cross) <= EDGE_EPS) return true;
45911
+ }
45912
+ return false;
45913
+ }
45914
+ function pointInGeometry(geometry, lon, lat) {
45915
+ const g = geometry;
45916
+ if (!g) return false;
45917
+ const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
45918
+ for (const rings of polys) {
45919
+ if (!rings.length) continue;
45920
+ if (pointOnRingEdge(lon, lat, rings[0])) return true;
45921
+ if (!pointInRing(lon, lat, rings[0])) continue;
45922
+ let inHole = false;
45923
+ for (let h = 1; h < rings.length; h++) {
45924
+ if (pointInRing(lon, lat, rings[h]) && !pointOnRingEdge(lon, lat, rings[h])) {
45925
+ inHole = true;
45926
+ break;
45927
+ }
45928
+ }
45929
+ if (!inHole) return true;
45930
+ }
45931
+ return false;
45932
+ }
45933
+ function regionAt(lonLat, countries, states) {
45934
+ const lon = lonLat[0];
45935
+ const lat = lonLat[1];
45936
+ let country = null;
45937
+ for (const f of countries) {
45938
+ if (pointInGeometry(f.geometry, lon, lat)) {
45939
+ country = { iso: f.id, name: f.properties.name };
45940
+ break;
45941
+ }
45942
+ }
45943
+ let state = null;
45944
+ if (country?.iso === "US" && states) {
45945
+ for (const f of states) {
45946
+ if (pointInGeometry(f.geometry, lon, lat)) {
45947
+ state = { iso: f.id, name: f.properties.name };
45948
+ break;
45949
+ }
45950
+ }
45951
+ }
45952
+ return { country, state };
45953
+ }
45863
45954
  function featureBbox(topo, geomId) {
45864
45955
  const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45865
45956
  if (!geom) return null;
@@ -45871,6 +45962,74 @@ function featureBbox(topo, geomId) {
45871
45962
  [b[1][0], b[1][1]]
45872
45963
  ];
45873
45964
  }
45965
+ function explodePolygons(gj) {
45966
+ const g = gj.geometry ?? gj;
45967
+ const t = g.type;
45968
+ const coords = g.coordinates;
45969
+ if (t === "Polygon") {
45970
+ return [
45971
+ { type: "Feature", geometry: { type: "Polygon", coordinates: coords } }
45972
+ ];
45973
+ }
45974
+ if (t === "MultiPolygon") {
45975
+ return coords.map((rings) => ({
45976
+ type: "Feature",
45977
+ geometry: { type: "Polygon", coordinates: rings }
45978
+ }));
45979
+ }
45980
+ return [];
45981
+ }
45982
+ function bboxGap(a, b) {
45983
+ const lonGap = Math.max(0, a[0][0] - b[1][0], b[0][0] - a[1][0]);
45984
+ const latGap = Math.max(0, a[0][1] - b[1][1], b[0][1] - a[1][1]);
45985
+ return Math.max(lonGap, latGap);
45986
+ }
45987
+ function featureBboxPrimary(topo, geomId) {
45988
+ const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45989
+ if (!geom) return null;
45990
+ const gj = feature(topo, geom);
45991
+ const parts = explodePolygons(gj);
45992
+ if (parts.length <= 1) return featureBbox(topo, geomId);
45993
+ const polys = parts.map((p) => {
45994
+ const b = geoBounds(p);
45995
+ if (!b || !Number.isFinite(b[0][0])) return null;
45996
+ const wraps = b[1][0] < b[0][0];
45997
+ const bbox = [
45998
+ [b[0][0], b[0][1]],
45999
+ [b[1][0], b[1][1]]
46000
+ ];
46001
+ return { bbox, area: geoArea(p), wraps };
46002
+ }).filter(
46003
+ (p) => p !== null
46004
+ );
46005
+ if (polys.length <= 1 || polys.some((p) => p.wraps))
46006
+ return featureBbox(topo, geomId);
46007
+ const maxArea = Math.max(...polys.map((p) => p.area));
46008
+ const anchor = polys.find((p) => p.area === maxArea);
46009
+ const cluster = [
46010
+ [anchor.bbox[0][0], anchor.bbox[0][1]],
46011
+ [anchor.bbox[1][0], anchor.bbox[1][1]]
46012
+ ];
46013
+ const remaining = polys.filter((p) => p !== anchor);
46014
+ let added = true;
46015
+ while (added) {
46016
+ added = false;
46017
+ for (let i = remaining.length - 1; i >= 0; i--) {
46018
+ const p = remaining[i];
46019
+ const near = bboxGap(p.bbox, cluster) <= DETACH_GAP_DEG;
46020
+ const large = p.area >= DETACH_AREA_FRAC * maxArea;
46021
+ if (near || large) {
46022
+ cluster[0][0] = Math.min(cluster[0][0], p.bbox[0][0]);
46023
+ cluster[0][1] = Math.min(cluster[0][1], p.bbox[0][1]);
46024
+ cluster[1][0] = Math.max(cluster[1][0], p.bbox[1][0]);
46025
+ cluster[1][1] = Math.max(cluster[1][1], p.bbox[1][1]);
46026
+ remaining.splice(i, 1);
46027
+ added = true;
46028
+ }
46029
+ }
46030
+ }
46031
+ return cluster;
46032
+ }
45874
46033
  function unionExtent(boxes, points) {
45875
46034
  const lats = [];
45876
46035
  const lons = [];
@@ -45909,11 +46068,14 @@ function unionLongitudes(lons) {
45909
46068
  }
45910
46069
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45911
46070
  }
45912
- var fold;
46071
+ var fold, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45913
46072
  var init_geo = __esm({
45914
46073
  "src/map/geo.ts"() {
45915
46074
  "use strict";
45916
46075
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46076
+ EDGE_EPS = 1e-9;
46077
+ DETACH_GAP_DEG = 10;
46078
+ DETACH_AREA_FRAC = 0.25;
45917
46079
  }
45918
46080
  });
45919
46081
 
@@ -46017,12 +46179,12 @@ function resolveMap(parsed, data) {
46017
46179
  chosen = { ...inState, layer: "us-state" };
46018
46180
  } else {
46019
46181
  chosen = { ...inCountry, layer: "country" };
46182
+ warn(
46183
+ r.lineNumber,
46184
+ `"${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}").`,
46185
+ "W_MAP_REGION_AMBIGUOUS"
46186
+ );
46020
46187
  }
46021
- warn(
46022
- r.lineNumber,
46023
- `"${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}").`,
46024
- "W_MAP_REGION_AMBIGUOUS"
46025
- );
46026
46188
  } else if (inState) {
46027
46189
  chosen = { ...inState, layer: "us-state" };
46028
46190
  } else if (inCountry) {
@@ -46046,6 +46208,7 @@ function resolveMap(parsed, data) {
46046
46208
  name: chosen.name,
46047
46209
  layer: chosen.layer,
46048
46210
  ...r.value !== void 0 && { value: r.value },
46211
+ ...r.color !== void 0 && { color: r.color },
46049
46212
  tags: r.tags,
46050
46213
  meta: r.meta,
46051
46214
  lineNumber: r.lineNumber
@@ -46187,6 +46350,7 @@ function resolveMap(parsed, data) {
46187
46350
  lat,
46188
46351
  lon,
46189
46352
  ...p.label !== void 0 && { label: p.label },
46353
+ ...p.color !== void 0 && { color: p.color },
46190
46354
  tags: p.tags,
46191
46355
  meta: p.meta,
46192
46356
  lineNumber: p.lineNumber
@@ -46329,7 +46493,7 @@ function resolveMap(parsed, data) {
46329
46493
  }
46330
46494
  for (const r of regions) {
46331
46495
  if (r.layer === "country") {
46332
- const bb = featureBbox(data.worldCoarse, r.iso);
46496
+ const bb = featureBboxPrimary(data.worldCoarse, r.iso);
46333
46497
  if (bb) regionBoxes.push(bb);
46334
46498
  }
46335
46499
  }
@@ -46343,6 +46507,7 @@ function resolveMap(parsed, data) {
46343
46507
  const lonSpan = extent2[1][0] - extent2[0][0];
46344
46508
  const latSpan = extent2[1][1] - extent2[0][1];
46345
46509
  const span = Math.max(lonSpan, latSpan);
46510
+ const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46346
46511
  const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46347
46512
  let projection;
46348
46513
  const override = parsed.directives.projection;
@@ -46350,12 +46515,10 @@ function resolveMap(parsed, data) {
46350
46515
  projection = override;
46351
46516
  } else if (usDominant) {
46352
46517
  projection = "albers-usa";
46353
- } else if (span > WORLD_SPAN) {
46518
+ } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46354
46519
  projection = "equirectangular";
46355
- } else if (span < MERCATOR_MAX_SPAN) {
46356
- projection = "mercator";
46357
46520
  } else {
46358
- projection = "equirectangular";
46521
+ projection = "mercator";
46359
46522
  }
46360
46523
  if (lonSpan >= 180) {
46361
46524
  extent2 = [
@@ -46409,14 +46572,14 @@ function firstError(diags) {
46409
46572
  const e = diags.find((d) => d.severity === "error");
46410
46573
  return e ? formatDgmoError(e) : null;
46411
46574
  }
46412
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46575
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46413
46576
  var init_resolver2 = __esm({
46414
46577
  "src/map/resolver.ts"() {
46415
46578
  "use strict";
46416
46579
  init_diagnostics();
46417
46580
  init_geo();
46418
46581
  WORLD_SPAN = 90;
46419
- MERCATOR_MAX_SPAN = 25;
46582
+ MERCATOR_MAX_LAT = 80;
46420
46583
  PAD_FRACTION = 0.05;
46421
46584
  WORLD_LAT_SOUTH = -58;
46422
46585
  WORLD_LAT_NORTH = 78;
@@ -46497,114 +46660,6 @@ var init_resolver2 = __esm({
46497
46660
  }
46498
46661
  });
46499
46662
 
46500
- // src/map/load-data.ts
46501
- var load_data_exports = {};
46502
- __export(load_data_exports, {
46503
- loadMapData: () => loadMapData
46504
- });
46505
- async function loadNodeBuiltins() {
46506
- const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
46507
- import("fs/promises"),
46508
- import("url"),
46509
- import("path")
46510
- ]);
46511
- return { readFile, fileURLToPath, dirname: dirname2, resolve };
46512
- }
46513
- async function readJson(nb, dir, name) {
46514
- return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46515
- }
46516
- async function firstExistingDir(nb, baseDir) {
46517
- for (const rel of CANDIDATE_DIRS) {
46518
- const dir = nb.resolve(baseDir, rel);
46519
- try {
46520
- await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46521
- return dir;
46522
- } catch {
46523
- }
46524
- }
46525
- throw new Error(
46526
- `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
46527
- );
46528
- }
46529
- function validate(data) {
46530
- const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
46531
- if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
46532
- throw new Error("map data assets are malformed (failed shape validation)");
46533
- }
46534
- return data;
46535
- }
46536
- function moduleBaseDir(nb) {
46537
- try {
46538
- const url = import.meta.url;
46539
- if (url) return nb.dirname(nb.fileURLToPath(url));
46540
- } catch {
46541
- }
46542
- if (typeof __dirname !== "undefined") return __dirname;
46543
- return process.cwd();
46544
- }
46545
- function loadMapData() {
46546
- cache ??= (async () => {
46547
- const nb = await loadNodeBuiltins();
46548
- const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46549
- const [
46550
- worldCoarse,
46551
- worldDetail,
46552
- usStates,
46553
- lakes,
46554
- rivers,
46555
- naLand,
46556
- naLakes,
46557
- gazetteer
46558
- ] = await Promise.all([
46559
- readJson(nb, dir, FILES.worldCoarse),
46560
- readJson(nb, dir, FILES.worldDetail),
46561
- readJson(nb, dir, FILES.usStates),
46562
- // Lakes/rivers/NA assets are optional — older bundles may predate them.
46563
- readJson(nb, dir, FILES.lakes).catch(() => void 0),
46564
- readJson(nb, dir, FILES.rivers).catch(() => void 0),
46565
- readJson(nb, dir, FILES.naLand).catch(() => void 0),
46566
- readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46567
- readJson(nb, dir, FILES.gazetteer)
46568
- ]);
46569
- return validate({
46570
- worldCoarse,
46571
- worldDetail,
46572
- usStates,
46573
- gazetteer,
46574
- ...lakes && { lakes },
46575
- ...rivers && { rivers },
46576
- ...naLand && { naLand },
46577
- ...naLakes && { naLakes }
46578
- });
46579
- })().catch((e) => {
46580
- cache = void 0;
46581
- throw e;
46582
- });
46583
- return cache;
46584
- }
46585
- var FILES, CANDIDATE_DIRS, cache;
46586
- var init_load_data = __esm({
46587
- "src/map/load-data.ts"() {
46588
- "use strict";
46589
- FILES = {
46590
- worldCoarse: "world-coarse.json",
46591
- worldDetail: "world-detail.json",
46592
- usStates: "us-states.json",
46593
- lakes: "lakes.json",
46594
- rivers: "rivers.json",
46595
- naLand: "na-land.json",
46596
- naLakes: "na-lakes.json",
46597
- gazetteer: "gazetteer.json"
46598
- };
46599
- CANDIDATE_DIRS = [
46600
- "./data",
46601
- "./map-data",
46602
- "../map-data",
46603
- "../src/map/data"
46604
- ];
46605
- }
46606
- });
46607
-
46608
46663
  // src/map/layout.ts
46609
46664
  import {
46610
46665
  geoPath,
@@ -46641,18 +46696,14 @@ function projectionFor(family) {
46641
46696
  return geoEquirectangular();
46642
46697
  }
46643
46698
  }
46644
- function mapBackgroundColor(palette, isDark = false, dataActive = false) {
46645
- if (dataActive)
46646
- return mix(
46647
- palette.colors.gray,
46648
- palette.bg,
46649
- isDark ? MUTED_WATER_DARK : MUTED_WATER_LIGHT
46650
- );
46651
- return mix(palette.colors.blue, palette.bg, WATER_TINT);
46699
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
46700
+ return mix(
46701
+ palette.colors.blue,
46702
+ palette.bg,
46703
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
46704
+ );
46652
46705
  }
46653
- function mapNeutralLandColor(palette, isDark, dataActive = false) {
46654
- if (dataActive)
46655
- return isDark ? mix(palette.colors.gray, palette.bg, MUTED_LAND_DARK) : palette.bg;
46706
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46656
46707
  return mix(
46657
46708
  palette.colors.green,
46658
46709
  palette.bg,
@@ -46684,7 +46735,7 @@ function layoutMap(resolved, data, size, opts) {
46684
46735
  const scaleOverride = resolved.directives.scale;
46685
46736
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46686
46737
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
46687
- const rampHue = palette.colors.red;
46738
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46688
46739
  const hasRamp = values.length > 0;
46689
46740
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
46690
46741
  const matchColorGroup = (v) => {
@@ -46707,6 +46758,7 @@ function layoutMap(resolved, data, size, opts) {
46707
46758
  const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
46708
46759
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46709
46760
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46761
+ const lakeStroke = mix(regionStroke, water, 45);
46710
46762
  const foreignFill = mix(
46711
46763
  palette.colors.gray,
46712
46764
  palette.bg,
@@ -46736,7 +46788,14 @@ function layoutMap(resolved, data, size, opts) {
46736
46788
  isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46737
46789
  );
46738
46790
  };
46791
+ const directFill = (name) => {
46792
+ const hex = name ? resolveColor(name, palette) : null;
46793
+ if (!hex) return null;
46794
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
46795
+ };
46739
46796
  const regionFill = (r) => {
46797
+ const direct = directFill(r.color);
46798
+ if (direct) return direct;
46740
46799
  if (activeIsScore) {
46741
46800
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46742
46801
  }
@@ -46790,6 +46849,7 @@ function layoutMap(resolved, data, size, opts) {
46790
46849
  const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46791
46850
  let path;
46792
46851
  let project;
46852
+ let stretchParams = null;
46793
46853
  if (fitIsGlobal) {
46794
46854
  const cb = geoPath(projection).bounds(fitTarget);
46795
46855
  const bx0 = cb[0][0];
@@ -46800,6 +46860,7 @@ function layoutMap(resolved, data, size, opts) {
46800
46860
  const oy = fitBox[0][1];
46801
46861
  const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46802
46862
  const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46863
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
46803
46864
  const stretch = (x, y) => [
46804
46865
  ox + (x - bx0) * sx,
46805
46866
  oy + (y - by0) * sy
@@ -46831,7 +46892,7 @@ function layoutMap(resolved, data, size, opts) {
46831
46892
  const insets = [];
46832
46893
  const insetRegions = [];
46833
46894
  const insetLabelSeeds = [];
46834
- if (resolved.projection === "albers-usa" && usLayer) {
46895
+ if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
46835
46896
  const PAD = 8;
46836
46897
  const GAP = 12;
46837
46898
  const yB = height - FIT_PAD;
@@ -46862,38 +46923,14 @@ function layoutMap(resolved, data, size, opts) {
46862
46923
  }
46863
46924
  return y;
46864
46925
  };
46865
- const coastTop = (x0, xr) => {
46926
+ const coastFloor = (x0, xr) => {
46866
46927
  const n = 24;
46867
- const pts = [];
46868
46928
  let maxY = -Infinity;
46869
46929
  for (let i = 0; i <= n; i++) {
46870
- const x = x0 + (xr - x0) * i / n;
46871
- const y = at(x);
46872
- if (y > -Infinity) {
46873
- pts.push([x, y]);
46874
- if (y > maxY) maxY = y;
46875
- }
46876
- }
46877
- if (pts.length === 0) return () => yB - height * 0.42;
46878
- let m = 0;
46879
- if (pts.length >= 2) {
46880
- let sx = 0, sy = 0, sxx = 0, sxy = 0;
46881
- for (const [x, y] of pts) {
46882
- sx += x;
46883
- sy += y;
46884
- sxx += x * x;
46885
- sxy += x * y;
46886
- }
46887
- const den = pts.length * sxx - sx * sx;
46888
- if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46889
- }
46890
- m = Math.max(-0.35, Math.min(0.35, m));
46891
- let c = -Infinity;
46892
- for (const [x, y] of pts) {
46893
- const need = y - m * x + GAP;
46894
- if (need > c) c = need;
46895
- }
46896
- return (x) => m * x + c;
46930
+ const y = at(x0 + (xr - x0) * i / n);
46931
+ if (y > maxY) maxY = y;
46932
+ }
46933
+ return maxY;
46897
46934
  };
46898
46935
  const placeInset = (iso, proj, boxX, iwReq) => {
46899
46936
  const f = usLayer.get(iso);
@@ -46902,19 +46939,15 @@ function layoutMap(resolved, data, size, opts) {
46902
46939
  const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46903
46940
  if (iw < 24) return boxX;
46904
46941
  const xr = x0 + iw + 2 * PAD;
46905
- const top = coastTop(x0, xr);
46906
- const yL = top(x0);
46907
- const yR = top(xr);
46942
+ const floor = coastFloor(x0, xr);
46943
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
46908
46944
  proj.fitWidth(iw, f);
46909
46945
  const bb = geoPath(proj).bounds(f);
46910
46946
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46911
46947
  const needH = sh + 2 * PAD;
46912
- let topFit = Math.max(yL, yR);
46948
+ let topFit = topGuess;
46913
46949
  const bottom = Math.min(topFit + needH, yB);
46914
46950
  if (bottom - topFit < needH) topFit = bottom - needH;
46915
- const lift = topFit - Math.max(yL, yR);
46916
- const topL = yL + lift;
46917
- const topR = yR + lift;
46918
46951
  proj.fitExtent(
46919
46952
  [
46920
46953
  [x0 + PAD, topFit + PAD],
@@ -46933,15 +46966,18 @@ function layoutMap(resolved, data, size, opts) {
46933
46966
  }
46934
46967
  insets.push({
46935
46968
  x: x0,
46936
- y: Math.min(topL, topR),
46969
+ y: topFit,
46937
46970
  w: xr - x0,
46938
- h: bottom - Math.min(topL, topR),
46971
+ h: bottom - topFit,
46939
46972
  points: [
46940
- [x0, topL],
46941
- [xr, topR],
46973
+ [x0, topFit],
46974
+ [xr, topFit],
46942
46975
  [xr, bottom],
46943
46976
  [x0, bottom]
46944
- ]
46977
+ ],
46978
+ // The FITTED inset projection (just fit to this box) — captured so the
46979
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
46980
+ projection: proj
46945
46981
  });
46946
46982
  insetRegions.push({
46947
46983
  id: iso,
@@ -47111,13 +47147,40 @@ function layoutMap(resolved, data, size, opts) {
47111
47147
  id: "lake",
47112
47148
  d,
47113
47149
  fill: water,
47114
- stroke: "none",
47150
+ stroke: lakeStroke,
47115
47151
  lineNumber: -1,
47116
47152
  layer: "base"
47117
47153
  });
47118
47154
  }
47119
47155
  }
47120
- const riverColor = water;
47156
+ const relief = [];
47157
+ let reliefHatch = null;
47158
+ if (resolved.directives.relief === true && data.mountainRanges) {
47159
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
47160
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
47161
+ if (!viewF) continue;
47162
+ const area2 = path.area(viewF);
47163
+ if (!Number.isFinite(area2) || area2 < RELIEF_MIN_AREA) continue;
47164
+ const box = path.bounds(viewF);
47165
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
47166
+ continue;
47167
+ const d = path(viewF) ?? "";
47168
+ if (!d) continue;
47169
+ relief.push({ d });
47170
+ }
47171
+ if (relief.length) {
47172
+ const darkTone = isDark ? palette.bg : palette.text;
47173
+ const lightTone = isDark ? palette.text : palette.bg;
47174
+ const landLum = relativeLuminance(neutralFill);
47175
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
47176
+ reliefHatch = {
47177
+ color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
47178
+ spacing: RELIEF_HATCH_SPACING,
47179
+ width: RELIEF_HATCH_WIDTH
47180
+ };
47181
+ }
47182
+ }
47183
+ const riverColor = mix(water, regionStroke, 16);
47121
47184
  const rivers = [];
47122
47185
  if (data.rivers) {
47123
47186
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -47138,6 +47201,9 @@ function layoutMap(resolved, data, size, opts) {
47138
47201
  return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
47139
47202
  };
47140
47203
  const poiFill = (p) => {
47204
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
47205
+ if (directHex)
47206
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
47141
47207
  for (const group of resolved.tagGroups) {
47142
47208
  const val = p.tags[group.name.toLowerCase()];
47143
47209
  if (!val) continue;
@@ -47553,19 +47619,24 @@ function layoutMap(resolved, data, size, opts) {
47553
47619
  ...resolved.caption !== void 0 && { caption: resolved.caption },
47554
47620
  regions,
47555
47621
  rivers,
47622
+ relief,
47623
+ reliefHatch,
47556
47624
  legs,
47557
47625
  pois,
47558
47626
  labels,
47559
47627
  legend,
47560
47628
  insets,
47561
- insetRegions
47629
+ insetRegions,
47630
+ projection,
47631
+ stretch: stretchParams
47562
47632
  };
47563
47633
  }
47564
- var 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;
47634
+ var 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;
47565
47635
  var init_layout15 = __esm({
47566
47636
  "src/map/layout.ts"() {
47567
47637
  "use strict";
47568
47638
  init_color_utils();
47639
+ init_colors();
47569
47640
  init_label_layout();
47570
47641
  init_legend_constants();
47571
47642
  init_title_constants();
@@ -47578,19 +47649,22 @@ var init_layout15 = __esm({
47578
47649
  W_MAX = 8;
47579
47650
  FONT = 11;
47580
47651
  COLO_EPS = 1.5;
47581
- LAND_TINT_LIGHT = 58;
47582
- LAND_TINT_DARK = 75;
47652
+ LAND_TINT_LIGHT = 12;
47653
+ LAND_TINT_DARK = 24;
47583
47654
  TAG_TINT_LIGHT = 60;
47584
47655
  TAG_TINT_DARK = 68;
47585
- WATER_TINT = 55;
47656
+ WATER_TINT_LIGHT = 13;
47657
+ WATER_TINT_DARK = 14;
47586
47658
  RIVER_WIDTH = 1.3;
47659
+ RELIEF_MIN_AREA = 12;
47660
+ RELIEF_MIN_DIM = 2;
47661
+ RELIEF_HATCH_SPACING = 3;
47662
+ RELIEF_HATCH_WIDTH = 0.25;
47663
+ RELIEF_HATCH_STRENGTH = 32;
47587
47664
  FOREIGN_TINT_LIGHT = 30;
47588
47665
  FOREIGN_TINT_DARK = 62;
47589
- MUTED_WATER_LIGHT = 14;
47590
- MUTED_WATER_DARK = 10;
47591
47666
  MUTED_FOREIGN_LIGHT = 28;
47592
47667
  MUTED_FOREIGN_DARK = 16;
47593
- MUTED_LAND_DARK = 24;
47594
47668
  COLO_R = 9;
47595
47669
  GOLDEN_ANGLE = 2.399963229728653;
47596
47670
  FAN_STEP = 16;
@@ -47663,6 +47737,20 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47663
47737
  }
47664
47738
  };
47665
47739
  for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47740
+ if (layout.relief.length && layout.reliefHatch) {
47741
+ const h = layout.reliefHatch;
47742
+ const rangeClipId = "dgmo-relief-clip";
47743
+ const landClipId = "dgmo-relief-land";
47744
+ const rangeClip = defs.append("clipPath").attr("id", rangeClipId);
47745
+ for (const s of layout.relief) rangeClip.append("path").attr("d", s.d);
47746
+ const landClip = defs.append("clipPath").attr("id", landClipId);
47747
+ for (const r of layout.regions)
47748
+ if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47749
+ 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");
47750
+ for (let y = h.spacing; y < height; y += h.spacing) {
47751
+ gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47752
+ }
47753
+ }
47666
47754
  if (layout.rivers.length) {
47667
47755
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47668
47756
  for (const r of layout.rivers) {
@@ -47787,7 +47875,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47787
47875
  }
47788
47876
  }
47789
47877
  if (layout.title) {
47790
- 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);
47878
+ 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);
47791
47879
  }
47792
47880
  if (layout.subtitle) {
47793
47881
  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);
@@ -47819,6 +47907,120 @@ var init_renderer16 = __esm({
47819
47907
  }
47820
47908
  });
47821
47909
 
47910
+ // src/map/load-data.ts
47911
+ var load_data_exports = {};
47912
+ __export(load_data_exports, {
47913
+ loadMapData: () => loadMapData
47914
+ });
47915
+ async function loadNodeBuiltins() {
47916
+ const [{ readFile }, { fileURLToPath }, { dirname: dirname2, resolve }] = await Promise.all([
47917
+ import("fs/promises"),
47918
+ import("url"),
47919
+ import("path")
47920
+ ]);
47921
+ return { readFile, fileURLToPath, dirname: dirname2, resolve };
47922
+ }
47923
+ async function readJson(nb, dir, name) {
47924
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
47925
+ }
47926
+ async function firstExistingDir(nb, baseDir) {
47927
+ for (const rel of CANDIDATE_DIRS) {
47928
+ const dir = nb.resolve(baseDir, rel);
47929
+ try {
47930
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
47931
+ return dir;
47932
+ } catch {
47933
+ }
47934
+ }
47935
+ throw new Error(
47936
+ `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
47937
+ );
47938
+ }
47939
+ function validate(data) {
47940
+ const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
47941
+ if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
47942
+ throw new Error("map data assets are malformed (failed shape validation)");
47943
+ }
47944
+ return data;
47945
+ }
47946
+ function moduleBaseDir(nb) {
47947
+ try {
47948
+ const url = import.meta.url;
47949
+ if (url) return nb.dirname(nb.fileURLToPath(url));
47950
+ } catch {
47951
+ }
47952
+ if (typeof __dirname !== "undefined") return __dirname;
47953
+ return process.cwd();
47954
+ }
47955
+ function loadMapData() {
47956
+ cache ??= (async () => {
47957
+ const nb = await loadNodeBuiltins();
47958
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
47959
+ const [
47960
+ worldCoarse,
47961
+ worldDetail,
47962
+ usStates,
47963
+ lakes,
47964
+ rivers,
47965
+ mountainRanges,
47966
+ naLand,
47967
+ naLakes,
47968
+ gazetteer
47969
+ ] = await Promise.all([
47970
+ readJson(nb, dir, FILES.worldCoarse),
47971
+ readJson(nb, dir, FILES.worldDetail),
47972
+ readJson(nb, dir, FILES.usStates),
47973
+ // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
47974
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
47975
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
47976
+ readJson(nb, dir, FILES.mountainRanges).catch(
47977
+ () => void 0
47978
+ ),
47979
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
47980
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
47981
+ readJson(nb, dir, FILES.gazetteer)
47982
+ ]);
47983
+ return validate({
47984
+ worldCoarse,
47985
+ worldDetail,
47986
+ usStates,
47987
+ gazetteer,
47988
+ ...lakes && { lakes },
47989
+ ...rivers && { rivers },
47990
+ ...mountainRanges && { mountainRanges },
47991
+ ...naLand && { naLand },
47992
+ ...naLakes && { naLakes }
47993
+ });
47994
+ })().catch((e) => {
47995
+ cache = void 0;
47996
+ throw e;
47997
+ });
47998
+ return cache;
47999
+ }
48000
+ var FILES, CANDIDATE_DIRS, cache;
48001
+ var init_load_data = __esm({
48002
+ "src/map/load-data.ts"() {
48003
+ "use strict";
48004
+ FILES = {
48005
+ worldCoarse: "world-coarse.json",
48006
+ worldDetail: "world-detail.json",
48007
+ usStates: "us-states.json",
48008
+ lakes: "lakes.json",
48009
+ rivers: "rivers.json",
48010
+ mountainRanges: "mountain-ranges.json",
48011
+ naLand: "na-land.json",
48012
+ naLakes: "na-lakes.json",
48013
+ gazetteer: "gazetteer.json"
48014
+ };
48015
+ CANDIDATE_DIRS = [
48016
+ "./data",
48017
+ "./map-data",
48018
+ "../map-data",
48019
+ "../src/map/data"
48020
+ ];
48021
+ }
48022
+ });
48023
+
47822
48024
  // src/pyramid/renderer.ts
47823
48025
  var renderer_exports17 = {};
47824
48026
  __export(renderer_exports17, {
@@ -56003,15 +56205,17 @@ async function renderForExport(content, theme, palette, viewState, options) {
56003
56205
  if (detectedType === "map") {
56004
56206
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
56005
56207
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
56006
- const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
56007
56208
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
56008
56209
  const effectivePalette2 = await resolveExportPalette(theme, palette);
56009
56210
  const mapParsed = parseMap2(content);
56010
- let mapData;
56011
- try {
56012
- mapData = await loadMapData2();
56013
- } catch {
56014
- return "";
56211
+ let mapData = options?.mapData;
56212
+ if (!mapData) {
56213
+ const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
56214
+ try {
56215
+ mapData = await loadMapData2();
56216
+ } catch {
56217
+ return "";
56218
+ }
56015
56219
  }
56016
56220
  const mapResolved = resolveMap2(mapParsed, mapData);
56017
56221
  const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
@@ -57305,6 +57509,160 @@ init_load_data();
57305
57509
  init_layout15();
57306
57510
  init_renderer16();
57307
57511
 
57512
+ // src/map/geo-query.ts
57513
+ init_parser12();
57514
+ init_resolver2();
57515
+ init_layout15();
57516
+ init_geo();
57517
+
57518
+ // src/map/invert.ts
57519
+ function inInsetFrame(inset, px, py) {
57520
+ return px >= inset.x && px <= inset.x + inset.w && py >= inset.y && py <= inset.y + inset.h;
57521
+ }
57522
+ function unstretch(layout, px, py) {
57523
+ const s = layout.stretch;
57524
+ return [
57525
+ s.bx0 + (s.sx !== 0 ? (px - s.ox) / s.sx : 0),
57526
+ s.by0 + (s.sy !== 0 ? (py - s.oy) / s.sy : 0)
57527
+ ];
57528
+ }
57529
+ function applyStretch(layout, x, y) {
57530
+ const s = layout.stretch;
57531
+ return [s.ox + (x - s.bx0) * s.sx, s.oy + (y - s.by0) * s.sy];
57532
+ }
57533
+ function pixelToLonLat(layout, px, py) {
57534
+ for (const inset of layout.insets) {
57535
+ if (inInsetFrame(inset, px, py)) {
57536
+ const ll2 = inset.projection.invert?.([px, py]);
57537
+ return ll2 && Number.isFinite(ll2[0]) && Number.isFinite(ll2[1]) ? [ll2[0], ll2[1]] : null;
57538
+ }
57539
+ }
57540
+ const [x, y] = layout.stretch ? unstretch(layout, px, py) : [px, py];
57541
+ const ll = layout.projection.invert?.([x, y]);
57542
+ return ll && Number.isFinite(ll[0]) && Number.isFinite(ll[1]) ? [ll[0], ll[1]] : null;
57543
+ }
57544
+ function lonLatToPixel(layout, lonLat) {
57545
+ const pt = [lonLat[0], lonLat[1]];
57546
+ const main = layout.projection(pt);
57547
+ const mainPx = main && Number.isFinite(main[0]) && Number.isFinite(main[1]) ? layout.stretch ? applyStretch(layout, main[0], main[1]) : [main[0], main[1]] : null;
57548
+ const onCanvas = !!mainPx && mainPx[0] >= 0 && mainPx[0] <= layout.width && mainPx[1] >= 0 && mainPx[1] <= layout.height;
57549
+ if (onCanvas) return mainPx;
57550
+ for (const inset of layout.insets) {
57551
+ const p = inset.projection(pt);
57552
+ if (p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && inInsetFrame(inset, p[0], p[1]))
57553
+ return [p[0], p[1]];
57554
+ }
57555
+ return mainPx;
57556
+ }
57557
+
57558
+ // src/map/geo-query.ts
57559
+ var EARTH_R_KM = 6371;
57560
+ var DEG = Math.PI / 180;
57561
+ function haversineKm(lat1, lon1, lat2, lon2) {
57562
+ const dLat = (lat2 - lat1) * DEG;
57563
+ const dLon = (lon2 - lon1) * DEG;
57564
+ const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * DEG) * Math.cos(lat2 * DEG) * Math.sin(dLon / 2) ** 2;
57565
+ return 2 * EARTH_R_KM * Math.asin(Math.min(1, Math.sqrt(a)));
57566
+ }
57567
+ var POP_PULL_KM = 12;
57568
+ function nearestCity(lonLat, gazetteer) {
57569
+ const [lon, lat] = lonLat;
57570
+ let best = null;
57571
+ const cities = gazetteer.cities;
57572
+ for (let i = 0; i < cities.length; i++) {
57573
+ const c2 = cities[i];
57574
+ const dist = haversineKm(lat, lon, c2[0], c2[1]);
57575
+ const score = dist - POP_PULL_KM * Math.log10((c2[3] || 0) + 1);
57576
+ if (!best || score < best.score) best = { score, idx: i, dist };
57577
+ }
57578
+ if (!best) return null;
57579
+ const c = cities[best.idx];
57580
+ return {
57581
+ name: c[4],
57582
+ iso: c[2],
57583
+ ...c[5] !== void 0 && { sub: c[5] },
57584
+ distanceKm: best.dist
57585
+ };
57586
+ }
57587
+ function roundCoord(n) {
57588
+ return Number(n.toFixed(2));
57589
+ }
57590
+ function buildTokens(lonLat, region, city) {
57591
+ const coordPoiLine = `poi ${roundCoord(lonLat[1])} ${roundCoord(lonLat[0])}`;
57592
+ let stateTok = null;
57593
+ if (region.state) {
57594
+ const { iso, name } = region.state;
57595
+ stateTok = { primary: `${name} ${iso}`, alternates: [iso, name] };
57596
+ }
57597
+ let countryTok = null;
57598
+ if (region.country) {
57599
+ const { iso, name } = region.country;
57600
+ countryTok = { primary: name, alternates: [iso] };
57601
+ }
57602
+ let cityTok = null;
57603
+ if (city) {
57604
+ const scope = city.sub ?? (city.iso || "");
57605
+ cityTok = scope ? { token: `poi ${city.name} ${scope}`, ambiguous: false } : { token: `poi ${city.name}`, ambiguous: true };
57606
+ }
57607
+ return { coordPoiLine, state: stateTok, country: countryTok, city: cityTok };
57608
+ }
57609
+ var MAX_CITY_DOTS = 250;
57610
+ function createMapGeoQuery(opts) {
57611
+ const { content, width, height, data, palette, isDark } = opts;
57612
+ const resolved = resolveMap(parseMap(content), data);
57613
+ const layout = layoutMap(
57614
+ resolved,
57615
+ data,
57616
+ { width, height },
57617
+ { palette, isDark }
57618
+ );
57619
+ const countries = decodeFeatures(data.worldDetail);
57620
+ const states = decodeFeatures(data.usStates);
57621
+ const gazetteer = data.gazetteer;
57622
+ const invert = (px, py) => pixelToLonLat(layout, px, py);
57623
+ const project = (lonLat) => lonLatToPixel(layout, lonLat);
57624
+ const locate = (px, py) => {
57625
+ const lonLat = invert(px, py);
57626
+ if (!lonLat) return null;
57627
+ const region = regionAt(lonLat, countries, states);
57628
+ const city = nearestCity(lonLat, gazetteer);
57629
+ return {
57630
+ lonLat,
57631
+ country: region.country,
57632
+ state: region.state,
57633
+ nearestCity: city,
57634
+ tokens: buildTokens(lonLat, region, city)
57635
+ };
57636
+ };
57637
+ const cities = (extent2) => {
57638
+ const sorted = [...gazetteer.cities].sort((a, b) => b[3] - a[3]);
57639
+ const out = [];
57640
+ for (const c of sorted) {
57641
+ const [lat, lon, iso, pop, name, sub] = c;
57642
+ if (extent2) {
57643
+ const [[w, s], [e, n]] = extent2;
57644
+ if (lon < w || lon > e || lat < s || lat > n) continue;
57645
+ }
57646
+ const p = project([lon, lat]);
57647
+ if (!p) continue;
57648
+ if (p[0] < 0 || p[0] > width || p[1] < 0 || p[1] > height) continue;
57649
+ out.push({
57650
+ name,
57651
+ iso,
57652
+ ...sub !== void 0 && { sub },
57653
+ lon,
57654
+ lat,
57655
+ px: p[0],
57656
+ py: p[1],
57657
+ pop
57658
+ });
57659
+ if (out.length >= MAX_CITY_DOTS) break;
57660
+ }
57661
+ return out;
57662
+ };
57663
+ return { invert, project, locate, cities };
57664
+ }
57665
+
57308
57666
  // src/map/completion.ts
57309
57667
  var fold2 = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
57310
57668
  var groupThousands = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -58457,6 +58815,7 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58457
58815
  "default-country": { description: "ISO scope for bare city resolution" },
58458
58816
  "default-state": { description: "ISO subdivision scope" },
58459
58817
  "no-legend": { description: "Suppress the legend" },
58818
+ relief: { description: "Subtle mountain-range relief shading" },
58460
58819
  subtitle: { description: "Subtitle line" },
58461
58820
  caption: { description: "Caption line" }
58462
58821
  })
@@ -59967,6 +60326,7 @@ export {
59967
60326
  computeTimeTicks,
59968
60327
  contrastText,
59969
60328
  controlsGroupCapsuleWidth,
60329
+ createMapGeoQuery,
59970
60330
  decodeDiagramUrl,
59971
60331
  decodeViewState,
59972
60332
  displayName,