@diagrammo/dgmo 0.20.3 → 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 (48) hide show
  1. package/dist/advanced.cjs +867 -286
  2. package/dist/advanced.js +866 -286
  3. package/dist/auto.cjs +635 -284
  4. package/dist/auto.js +113 -113
  5. package/dist/auto.mjs +635 -284
  6. package/dist/cli.cjs +156 -156
  7. package/dist/editor.cjs +6 -2
  8. package/dist/editor.js +6 -2
  9. package/dist/highlight.cjs +6 -2
  10. package/dist/highlight.js +6 -2
  11. package/dist/index.cjs +628 -281
  12. package/dist/index.js +628 -281
  13. package/dist/internal.cjs +867 -286
  14. package/dist/internal.js +866 -286
  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-choropleth.dgmo +7 -7
  19. package/gallery/fixtures/map-direct-color.dgmo +10 -0
  20. package/gallery/fixtures/map-pois.dgmo +4 -4
  21. package/gallery/fixtures/map-region-scope.dgmo +8 -8
  22. package/gallery/fixtures/map-route.dgmo +5 -6
  23. package/package.json +1 -1
  24. package/src/advanced.ts +14 -0
  25. package/src/completion.ts +10 -4
  26. package/src/d3.ts +15 -9
  27. package/src/editor/keywords.ts +6 -2
  28. package/src/map/data/PROVENANCE.json +1 -1
  29. package/src/map/data/mountain-ranges.json +1 -0
  30. package/src/map/geo-query.ts +277 -0
  31. package/src/map/geo.ts +258 -1
  32. package/src/map/invert.ts +111 -0
  33. package/src/map/layout.ts +333 -139
  34. package/src/map/load-data.ts +7 -1
  35. package/src/map/parser.ts +142 -33
  36. package/src/map/renderer.ts +57 -6
  37. package/src/map/resolved-types.ts +21 -2
  38. package/src/map/resolver.ts +219 -53
  39. package/src/map/types.ts +57 -14
  40. package/src/utils/reserved-key-registry.ts +7 -7
  41. package/dist/advanced.d.cts +0 -5290
  42. package/dist/advanced.d.ts +0 -5290
  43. package/dist/auto.d.cts +0 -39
  44. package/dist/auto.d.ts +0 -39
  45. package/dist/index.d.cts +0 -336
  46. package/dist/index.d.ts +0 -336
  47. package/dist/internal.d.cts +0 -5290
  48. package/dist/internal.d.ts +0 -5290
package/dist/index.js CHANGED
@@ -833,13 +833,9 @@ var init_reserved_key_registry = __esm({
833
833
  "icon"
834
834
  ]);
835
835
  MAP_REGISTRY = staticRegistry([
836
- "score",
836
+ "value",
837
837
  "label",
838
- "size",
839
- "description",
840
- "weight",
841
- "style",
842
- "date"
838
+ "style"
843
839
  ]);
844
840
  ORG_REGISTRY = staticRegistry([
845
841
  "color",
@@ -15870,7 +15866,8 @@ function parseMap(content) {
15870
15866
  continue;
15871
15867
  }
15872
15868
  if (open.route && indent > open.route.indent) {
15873
- open.route.route.stops.push(parseStop(trimmed, lineNumber));
15869
+ const leg = parseLeg(trimmed, lineNumber, open.route.route.style);
15870
+ open.route.route.legs.push(leg);
15874
15871
  continue;
15875
15872
  }
15876
15873
  if (open.poi && indent > open.poi.indent) {
@@ -15901,6 +15898,10 @@ function parseMap(content) {
15901
15898
  handleTag(trimmed, lineNumber);
15902
15899
  continue;
15903
15900
  }
15901
+ if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15902
+ handleDirective(firstWord, "", lineNumber);
15903
+ continue;
15904
+ }
15904
15905
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15905
15906
  handleDirective(
15906
15907
  firstWord,
@@ -15965,13 +15966,20 @@ function parseMap(content) {
15965
15966
  );
15966
15967
  d.projection = value;
15967
15968
  break;
15968
- case "metric":
15969
- dup(d.metric);
15970
- d.metric = value;
15969
+ case "region-metric": {
15970
+ dup(d.regionMetric);
15971
+ const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
15972
+ d.regionMetric = rmLabel;
15973
+ if (rmColor) d.regionMetricColor = rmColor;
15974
+ break;
15975
+ }
15976
+ case "poi-metric":
15977
+ dup(d.poiMetric);
15978
+ d.poiMetric = value;
15971
15979
  break;
15972
- case "size-metric":
15973
- dup(d.sizeMetric);
15974
- d.sizeMetric = value;
15980
+ case "flow-metric":
15981
+ dup(d.flowMetric);
15982
+ d.flowMetric = value;
15975
15983
  break;
15976
15984
  case "scale":
15977
15985
  dup(d.scale);
@@ -16013,6 +16021,21 @@ function parseMap(content) {
16013
16021
  case "no-legend":
16014
16022
  d.noLegend = true;
16015
16023
  break;
16024
+ case "no-insets":
16025
+ d.noInsets = true;
16026
+ break;
16027
+ case "relief":
16028
+ d.relief = true;
16029
+ break;
16030
+ case "muted":
16031
+ case "natural":
16032
+ if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
16033
+ pushWarning(
16034
+ line12,
16035
+ `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
16036
+ );
16037
+ d.basemapStyle = key;
16038
+ break;
16016
16039
  case "subtitle":
16017
16040
  dup(d.subtitle);
16018
16041
  d.subtitle = value;
@@ -16090,14 +16113,14 @@ function parseMap(content) {
16090
16113
  line12
16091
16114
  );
16092
16115
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16093
- let scoreNum;
16094
- const score = meta["score"];
16095
- if (score !== void 0) {
16096
- delete meta["score"];
16097
- scoreNum = Number(score);
16098
- if (!Number.isFinite(scoreNum)) {
16099
- pushError(line12, `score must be a number (got "${score}").`);
16100
- scoreNum = void 0;
16116
+ let valueNum;
16117
+ const value = meta["value"];
16118
+ if (value !== void 0) {
16119
+ delete meta["value"];
16120
+ valueNum = Number(value);
16121
+ if (!Number.isFinite(valueNum)) {
16122
+ pushError(line12, `value must be a number (got "${value}").`);
16123
+ valueNum = void 0;
16101
16124
  }
16102
16125
  }
16103
16126
  let regionName = split.name;
@@ -16115,7 +16138,8 @@ function parseMap(content) {
16115
16138
  lineNumber: line12
16116
16139
  };
16117
16140
  if (regionScope !== void 0) region.scope = regionScope;
16118
- if (scoreNum !== void 0) region.score = scoreNum;
16141
+ if (valueNum !== void 0) region.value = valueNum;
16142
+ if (split.color) region.color = split.color;
16119
16143
  regions.push(region);
16120
16144
  }
16121
16145
  function handlePoi(rest, line12, indent) {
@@ -16140,28 +16164,81 @@ function parseMap(content) {
16140
16164
  const poi = { pos, tags, meta, lineNumber: line12 };
16141
16165
  if (split.alias) poi.alias = split.alias;
16142
16166
  if (label !== void 0) poi.label = label;
16167
+ if (split.color) poi.color = split.color;
16143
16168
  pois.push(poi);
16144
16169
  open.poi = { poi, indent };
16145
16170
  }
16146
16171
  function handleRoute(rest, line12, indent) {
16147
- const meta = rest ? splitNameAndMeta(rest, registry(), aliasMap).meta : {};
16148
- const route = { stops: [], meta, lineNumber: line12 };
16172
+ const split = rest ? splitNameAndMeta(
16173
+ rest,
16174
+ registry(),
16175
+ aliasMap,
16176
+ void 0,
16177
+ diagnostics,
16178
+ line12
16179
+ ) : { name: "", meta: {}, alias: void 0 };
16180
+ const pos = parsePos(split.name, line12);
16181
+ if (!pos || pos.kind === "name" && !pos.name) {
16182
+ pushError(
16183
+ line12,
16184
+ "route requires an origin: `route <origin> [style: arc]`."
16185
+ );
16186
+ return;
16187
+ }
16188
+ const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16189
+ const originLabel = meta["label"];
16190
+ const originValue = meta["value"];
16191
+ const style = meta["style"] === "arc" ? "arc" : "straight";
16192
+ const route = {
16193
+ origin: pos,
16194
+ ...split.alias !== void 0 && { originAlias: split.alias },
16195
+ ...originLabel !== void 0 && { originLabel },
16196
+ ...originValue !== void 0 && { originValue },
16197
+ originTags: tags,
16198
+ style,
16199
+ legs: [],
16200
+ lineNumber: line12
16201
+ };
16149
16202
  routes.push(route);
16150
16203
  open.route = { route, indent };
16151
16204
  }
16152
- function parseStop(trimmed, line12) {
16153
- const split = splitNameAndMeta(trimmed, registry(), aliasMap);
16154
- const ref = parsePos(split.name, line12) ?? {
16205
+ function parseLeg(trimmed, line12, headerStyle) {
16206
+ let arrowStyle = "straight";
16207
+ let label;
16208
+ let rest = trimmed;
16209
+ const m = trimmed.match(LEG_ARROW_RE);
16210
+ if (m) {
16211
+ const arr = classifyArrow(m[1], line12);
16212
+ arrowStyle = arr.style;
16213
+ label = arr.label;
16214
+ rest = m[2];
16215
+ }
16216
+ const split = splitNameAndMeta(
16217
+ rest,
16218
+ registry(),
16219
+ aliasMap,
16220
+ void 0,
16221
+ diagnostics,
16222
+ line12
16223
+ );
16224
+ const pos = parsePos(split.name, line12) ?? {
16155
16225
  kind: "name",
16156
16226
  name: split.name
16157
16227
  };
16158
- const stop = {
16159
- ref,
16160
- meta: split.meta,
16228
+ const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16229
+ const value = meta["value"];
16230
+ const destLabel = meta["label"];
16231
+ const style = arrowStyle === "arc" || headerStyle === "arc" ? "arc" : "straight";
16232
+ return {
16233
+ ...label !== void 0 && { label },
16234
+ style,
16235
+ ...value !== void 0 && { value },
16236
+ dest: pos,
16237
+ ...split.alias !== void 0 && { destAlias: split.alias },
16238
+ ...destLabel !== void 0 && { destLabel },
16239
+ destTags: tags,
16161
16240
  lineNumber: line12
16162
16241
  };
16163
- if (split.alias) stop.alias = split.alias;
16164
- return stop;
16165
16242
  }
16166
16243
  function handleEdges(trimmed, line12) {
16167
16244
  const parts = trimmed.split(ARROW_SPLIT);
@@ -16263,7 +16340,7 @@ function partitionMeta(meta, tagGroupNames) {
16263
16340
  function poiName(pos) {
16264
16341
  return pos.kind === "name" ? pos.name : void 0;
16265
16342
  }
16266
- var COORD_RE, NUMERIC_LEAD_RE, SCOPE_RE, ARROW_SPLIT, HUB_RE, AT_RE, DIRECTIVE_SET;
16343
+ var COORD_RE, NUMERIC_LEAD_RE, SCOPE_RE, ARROW_SPLIT, HUB_RE, LEG_ARROW_RE, AT_RE, DIRECTIVE_SET;
16267
16344
  var init_parser12 = __esm({
16268
16345
  "src/map/parser.ts"() {
16269
16346
  "use strict";
@@ -16277,12 +16354,14 @@ var init_parser12 = __esm({
16277
16354
  SCOPE_RE = /^[A-Z]{2}(?:-[A-Z0-9]{1,3})?$/;
16278
16355
  ARROW_SPLIT = /\s+(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+/;
16279
16356
  HUB_RE = /^(->|~>)\s+(.+)$/;
16357
+ LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16280
16358
  AT_RE = /(^|[\s,])at\s*:/i;
16281
16359
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16282
16360
  "region",
16283
16361
  "projection",
16284
- "metric",
16285
- "size-metric",
16362
+ "region-metric",
16363
+ "poi-metric",
16364
+ "flow-metric",
16286
16365
  "scale",
16287
16366
  "region-labels",
16288
16367
  "poi-labels",
@@ -16290,6 +16369,8 @@ var init_parser12 = __esm({
16290
16369
  "default-state",
16291
16370
  "active-tag",
16292
16371
  "no-legend",
16372
+ "no-insets",
16373
+ "relief",
16293
16374
  "subtitle",
16294
16375
  "caption"
16295
16376
  ]);
@@ -45488,7 +45569,7 @@ var init_renderer15 = __esm({
45488
45569
 
45489
45570
  // src/map/geo.ts
45490
45571
  import { feature } from "topojson-client";
45491
- import { geoBounds } from "d3-geo";
45572
+ import { geoBounds, geoArea } from "d3-geo";
45492
45573
  function geomObject(topo) {
45493
45574
  const key = Object.keys(topo.objects)[0];
45494
45575
  return topo.objects[key];
@@ -45516,6 +45597,74 @@ function featureBbox(topo, geomId) {
45516
45597
  [b[1][0], b[1][1]]
45517
45598
  ];
45518
45599
  }
45600
+ function explodePolygons(gj) {
45601
+ const g = gj.geometry ?? gj;
45602
+ const t = g.type;
45603
+ const coords = g.coordinates;
45604
+ if (t === "Polygon") {
45605
+ return [
45606
+ { type: "Feature", geometry: { type: "Polygon", coordinates: coords } }
45607
+ ];
45608
+ }
45609
+ if (t === "MultiPolygon") {
45610
+ return coords.map((rings) => ({
45611
+ type: "Feature",
45612
+ geometry: { type: "Polygon", coordinates: rings }
45613
+ }));
45614
+ }
45615
+ return [];
45616
+ }
45617
+ function bboxGap(a, b) {
45618
+ const lonGap = Math.max(0, a[0][0] - b[1][0], b[0][0] - a[1][0]);
45619
+ const latGap = Math.max(0, a[0][1] - b[1][1], b[0][1] - a[1][1]);
45620
+ return Math.max(lonGap, latGap);
45621
+ }
45622
+ function featureBboxPrimary(topo, geomId) {
45623
+ const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45624
+ if (!geom) return null;
45625
+ const gj = feature(topo, geom);
45626
+ const parts = explodePolygons(gj);
45627
+ if (parts.length <= 1) return featureBbox(topo, geomId);
45628
+ const polys = parts.map((p) => {
45629
+ const b = geoBounds(p);
45630
+ if (!b || !Number.isFinite(b[0][0])) return null;
45631
+ const wraps = b[1][0] < b[0][0];
45632
+ const bbox = [
45633
+ [b[0][0], b[0][1]],
45634
+ [b[1][0], b[1][1]]
45635
+ ];
45636
+ return { bbox, area: geoArea(p), wraps };
45637
+ }).filter(
45638
+ (p) => p !== null
45639
+ );
45640
+ if (polys.length <= 1 || polys.some((p) => p.wraps))
45641
+ return featureBbox(topo, geomId);
45642
+ const maxArea = Math.max(...polys.map((p) => p.area));
45643
+ const anchor = polys.find((p) => p.area === maxArea);
45644
+ const cluster = [
45645
+ [anchor.bbox[0][0], anchor.bbox[0][1]],
45646
+ [anchor.bbox[1][0], anchor.bbox[1][1]]
45647
+ ];
45648
+ const remaining = polys.filter((p) => p !== anchor);
45649
+ let added = true;
45650
+ while (added) {
45651
+ added = false;
45652
+ for (let i = remaining.length - 1; i >= 0; i--) {
45653
+ const p = remaining[i];
45654
+ const near = bboxGap(p.bbox, cluster) <= DETACH_GAP_DEG;
45655
+ const large = p.area >= DETACH_AREA_FRAC * maxArea;
45656
+ if (near || large) {
45657
+ cluster[0][0] = Math.min(cluster[0][0], p.bbox[0][0]);
45658
+ cluster[0][1] = Math.min(cluster[0][1], p.bbox[0][1]);
45659
+ cluster[1][0] = Math.max(cluster[1][0], p.bbox[1][0]);
45660
+ cluster[1][1] = Math.max(cluster[1][1], p.bbox[1][1]);
45661
+ remaining.splice(i, 1);
45662
+ added = true;
45663
+ }
45664
+ }
45665
+ }
45666
+ return cluster;
45667
+ }
45519
45668
  function unionExtent(boxes, points) {
45520
45669
  const lats = [];
45521
45670
  const lons = [];
@@ -45554,11 +45703,13 @@ function unionLongitudes(lons) {
45554
45703
  }
45555
45704
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45556
45705
  }
45557
- var fold;
45706
+ var fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45558
45707
  var init_geo = __esm({
45559
45708
  "src/map/geo.ts"() {
45560
45709
  "use strict";
45561
45710
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
45711
+ DETACH_GAP_DEG = 10;
45712
+ DETACH_AREA_FRAC = 0.25;
45562
45713
  }
45563
45714
  });
45564
45715
 
@@ -45567,6 +45718,11 @@ var resolver_exports = {};
45567
45718
  __export(resolver_exports, {
45568
45719
  resolveMap: () => resolveMap
45569
45720
  });
45721
+ function usStateFromBareScope(scope) {
45722
+ if (!scope) return null;
45723
+ const up = scope.toUpperCase();
45724
+ return US_STATE_POSTAL.has(up) ? `US-${up}` : null;
45725
+ }
45570
45726
  function looksUS(lat, lon) {
45571
45727
  if (lat < 15 || lat > 72) return false;
45572
45728
  return lon >= -180 && lon <= -64 || lon >= 172;
@@ -45616,9 +45772,9 @@ function resolveMap(parsed, data) {
45616
45772
  const f = fold(r.name);
45617
45773
  return usStateIndex.has(f) && !countryIndex.has(f);
45618
45774
  }) || parsed.regions.some(
45619
- (r) => r.scope === "US" || r.scope?.startsWith("US-")
45775
+ (r) => r.scope === "US" || r.scope?.startsWith("US-") || usStateFromBareScope(r.scope) !== null
45620
45776
  ) || parsed.pois.some(
45621
- (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45777
+ (p) => p.pos.kind === "name" && (p.pos.scope?.startsWith("US-") || usStateFromBareScope(p.pos.scope) !== null)
45622
45778
  );
45623
45779
  const regions = [];
45624
45780
  const seenRegion = /* @__PURE__ */ new Map();
@@ -45657,12 +45813,12 @@ function resolveMap(parsed, data) {
45657
45813
  chosen = { ...inState, layer: "us-state" };
45658
45814
  } else {
45659
45815
  chosen = { ...inCountry, layer: "country" };
45816
+ warn(
45817
+ r.lineNumber,
45818
+ `"${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}").`,
45819
+ "W_MAP_REGION_AMBIGUOUS"
45820
+ );
45660
45821
  }
45661
- warn(
45662
- r.lineNumber,
45663
- `"${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}").`,
45664
- "W_MAP_REGION_AMBIGUOUS"
45665
- );
45666
45822
  } else if (inState) {
45667
45823
  chosen = { ...inState, layer: "us-state" };
45668
45824
  } else if (inCountry) {
@@ -45685,7 +45841,8 @@ function resolveMap(parsed, data) {
45685
45841
  iso: chosen.id,
45686
45842
  name: chosen.name,
45687
45843
  layer: chosen.layer,
45688
- ...r.score !== void 0 && { score: r.score },
45844
+ ...r.value !== void 0 && { value: r.value },
45845
+ ...r.color !== void 0 && { color: r.color },
45689
45846
  tags: r.tags,
45690
45847
  meta: r.meta,
45691
45848
  lineNumber: r.lineNumber
@@ -45743,9 +45900,10 @@ function resolveMap(parsed, data) {
45743
45900
  let cands = idxs.map((i) => data.gazetteer.cities[i]);
45744
45901
  const scopeUse = scope ?? scopeHint;
45745
45902
  if (scopeUse) {
45746
- const isSub = /^[A-Za-z]{2}-/.test(scopeUse);
45903
+ const bareState = usStateFromBareScope(scopeUse);
45904
+ const subScope = /^[A-Za-z]{2}-/.test(scopeUse) ? scopeUse : bareState;
45747
45905
  const filtered = cands.filter(
45748
- (c2) => isSub ? c2[5] === scopeUse : c2[2] === scopeUse
45906
+ (c2) => subScope ? c2[5] === subScope : c2[2] === scopeUse
45749
45907
  );
45750
45908
  if (filtered.length) cands = filtered;
45751
45909
  else if (scope) {
@@ -45826,6 +45984,7 @@ function resolveMap(parsed, data) {
45826
45984
  lat,
45827
45985
  lon,
45828
45986
  ...p.label !== void 0 && { label: p.label },
45987
+ ...p.color !== void 0 && { color: p.color },
45829
45988
  tags: p.tags,
45830
45989
  meta: p.meta,
45831
45990
  lineNumber: p.lineNumber
@@ -45874,33 +46033,89 @@ function resolveMap(parsed, data) {
45874
46033
  lineNumber: e.lineNumber
45875
46034
  });
45876
46035
  }
45877
- const routes = [];
45878
- for (const rt of parsed.routes) {
45879
- const stopIds = [];
45880
- for (const stop of rt.stops) {
45881
- let id;
45882
- if (stop.ref.kind === "coords") {
45883
- id = stop.alias ? fold(stop.alias) : `@${stop.ref.lat},${stop.ref.lon}`;
45884
- if (!looksUS(stop.ref.lat, stop.ref.lon)) anyNonUsPoi = true;
45885
- if (!registry.has(id)) {
45886
- const poi = {
46036
+ const resolveStop = (pos, alias, label, tags, sizeValue, line12) => {
46037
+ const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
46038
+ if (pos.kind === "coords") {
46039
+ const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
46040
+ if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46041
+ if (!registry.has(id)) {
46042
+ registerPoi(
46043
+ id,
46044
+ {
45887
46045
  id,
45888
- ...stop.alias !== void 0 && { name: stop.alias },
45889
- lat: stop.ref.lat,
45890
- lon: stop.ref.lon,
45891
- tags: {},
45892
- meta: stop.meta,
45893
- lineNumber: stop.lineNumber,
45894
- implicit: true
45895
- };
45896
- registerPoi(id, poi, stop.lineNumber);
45897
- }
45898
- } else {
45899
- id = stop.alias && registry.has(fold(stop.alias)) ? fold(stop.alias) : resolveEndpoint2(stop.ref.name, stop.lineNumber);
46046
+ ...alias !== void 0 && { name: alias },
46047
+ lat: pos.lat,
46048
+ lon: pos.lon,
46049
+ ...label !== void 0 && { label },
46050
+ tags,
46051
+ meta,
46052
+ lineNumber: line12
46053
+ },
46054
+ line12
46055
+ );
45900
46056
  }
45901
- if (id) stopIds.push(id);
46057
+ return id;
46058
+ }
46059
+ const f = fold(pos.name);
46060
+ if (registry.has(f)) return f;
46061
+ const aliased = declaredByName.get(f);
46062
+ if (aliased) return aliased;
46063
+ const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46064
+ if (got.kind !== "ok") return null;
46065
+ noteCountry(got.iso);
46066
+ registerPoi(
46067
+ f,
46068
+ {
46069
+ id: f,
46070
+ name: pos.name,
46071
+ lat: got.lat,
46072
+ lon: got.lon,
46073
+ ...label !== void 0 && { label },
46074
+ tags,
46075
+ meta,
46076
+ lineNumber: line12
46077
+ },
46078
+ line12
46079
+ );
46080
+ return f;
46081
+ };
46082
+ const routes = [];
46083
+ for (const rt of parsed.routes) {
46084
+ const originId = resolveStop(
46085
+ rt.origin,
46086
+ rt.originAlias,
46087
+ rt.originLabel,
46088
+ rt.originTags,
46089
+ rt.originValue,
46090
+ rt.lineNumber
46091
+ );
46092
+ if (!originId) continue;
46093
+ const stopIds = [originId];
46094
+ const legs = [];
46095
+ let prevId = originId;
46096
+ for (const leg of rt.legs) {
46097
+ const destId = resolveStop(
46098
+ leg.dest,
46099
+ leg.destAlias,
46100
+ leg.destLabel,
46101
+ leg.destTags,
46102
+ void 0,
46103
+ // a leg's `value:` is leg thickness, not the dest's size
46104
+ leg.lineNumber
46105
+ );
46106
+ if (!destId) continue;
46107
+ legs.push({
46108
+ fromId: prevId,
46109
+ toId: destId,
46110
+ ...leg.label !== void 0 && { label: leg.label },
46111
+ style: leg.style,
46112
+ ...leg.value !== void 0 && { value: leg.value },
46113
+ lineNumber: leg.lineNumber
46114
+ });
46115
+ if (!stopIds.includes(destId)) stopIds.push(destId);
46116
+ prevId = destId;
45902
46117
  }
45903
- routes.push({ stopIds, meta: rt.meta, lineNumber: rt.lineNumber });
46118
+ routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
45904
46119
  }
45905
46120
  const subdivisions = [];
45906
46121
  if (usSubdivisionReferenced || parsed.directives.region === "us-states")
@@ -45912,7 +46127,7 @@ function resolveMap(parsed, data) {
45912
46127
  }
45913
46128
  for (const r of regions) {
45914
46129
  if (r.layer === "country") {
45915
- const bb = featureBbox(data.worldCoarse, r.iso);
46130
+ const bb = featureBboxPrimary(data.worldCoarse, r.iso);
45916
46131
  if (bb) regionBoxes.push(bb);
45917
46132
  }
45918
46133
  }
@@ -45926,6 +46141,7 @@ function resolveMap(parsed, data) {
45926
46141
  const lonSpan = extent2[1][0] - extent2[0][0];
45927
46142
  const latSpan = extent2[1][1] - extent2[0][1];
45928
46143
  const span = Math.max(lonSpan, latSpan);
46144
+ const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
45929
46145
  const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45930
46146
  let projection;
45931
46147
  const override = parsed.directives.projection;
@@ -45933,12 +46149,10 @@ function resolveMap(parsed, data) {
45933
46149
  projection = override;
45934
46150
  } else if (usDominant) {
45935
46151
  projection = "albers-usa";
45936
- } else if (span > WORLD_SPAN) {
46152
+ } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
45937
46153
  projection = "equirectangular";
45938
- } else if (span < MERCATOR_MAX_SPAN) {
45939
- projection = "mercator";
45940
46154
  } else {
45941
- projection = "equirectangular";
46155
+ projection = "mercator";
45942
46156
  }
45943
46157
  if (lonSpan >= 180) {
45944
46158
  extent2 = [
@@ -45992,14 +46206,14 @@ function firstError(diags) {
45992
46206
  const e = diags.find((d) => d.severity === "error");
45993
46207
  return e ? formatDgmoError(e) : null;
45994
46208
  }
45995
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
46209
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
45996
46210
  var init_resolver2 = __esm({
45997
46211
  "src/map/resolver.ts"() {
45998
46212
  "use strict";
45999
46213
  init_diagnostics();
46000
46214
  init_geo();
46001
46215
  WORLD_SPAN = 90;
46002
- MERCATOR_MAX_SPAN = 25;
46216
+ MERCATOR_MAX_LAT = 80;
46003
46217
  PAD_FRACTION = 0.05;
46004
46218
  WORLD_LAT_SOUTH = -58;
46005
46219
  WORLD_LAT_NORTH = 78;
@@ -46024,114 +46238,59 @@ var init_resolver2 = __esm({
46024
46238
  "north macedonia": "macedonia",
46025
46239
  "czech republic": "czechia"
46026
46240
  };
46027
- }
46028
- });
46029
-
46030
- // src/map/load-data.ts
46031
- var load_data_exports = {};
46032
- __export(load_data_exports, {
46033
- loadMapData: () => loadMapData
46034
- });
46035
- async function loadNodeBuiltins() {
46036
- const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
46037
- import("fs/promises"),
46038
- import("url"),
46039
- import("path")
46040
- ]);
46041
- return { readFile, fileURLToPath, dirname, resolve };
46042
- }
46043
- async function readJson(nb, dir, name) {
46044
- return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46045
- }
46046
- async function firstExistingDir(nb, baseDir) {
46047
- for (const rel of CANDIDATE_DIRS) {
46048
- const dir = nb.resolve(baseDir, rel);
46049
- try {
46050
- await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46051
- return dir;
46052
- } catch {
46053
- }
46054
- }
46055
- throw new Error(
46056
- `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
46057
- );
46058
- }
46059
- function validate(data) {
46060
- const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
46061
- if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
46062
- throw new Error("map data assets are malformed (failed shape validation)");
46063
- }
46064
- return data;
46065
- }
46066
- function moduleBaseDir(nb) {
46067
- try {
46068
- const url = import.meta.url;
46069
- if (url) return nb.dirname(nb.fileURLToPath(url));
46070
- } catch {
46071
- }
46072
- if (typeof __dirname !== "undefined") return __dirname;
46073
- return process.cwd();
46074
- }
46075
- function loadMapData() {
46076
- cache ??= (async () => {
46077
- const nb = await loadNodeBuiltins();
46078
- const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46079
- const [
46080
- worldCoarse,
46081
- worldDetail,
46082
- usStates,
46083
- lakes,
46084
- rivers,
46085
- naLand,
46086
- naLakes,
46087
- gazetteer
46088
- ] = await Promise.all([
46089
- readJson(nb, dir, FILES.worldCoarse),
46090
- readJson(nb, dir, FILES.worldDetail),
46091
- readJson(nb, dir, FILES.usStates),
46092
- // Lakes/rivers/NA assets are optional — older bundles may predate them.
46093
- readJson(nb, dir, FILES.lakes).catch(() => void 0),
46094
- readJson(nb, dir, FILES.rivers).catch(() => void 0),
46095
- readJson(nb, dir, FILES.naLand).catch(() => void 0),
46096
- readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46097
- readJson(nb, dir, FILES.gazetteer)
46241
+ US_STATE_POSTAL = /* @__PURE__ */ new Set([
46242
+ "AL",
46243
+ "AK",
46244
+ "AZ",
46245
+ "AR",
46246
+ "CA",
46247
+ "CO",
46248
+ "CT",
46249
+ "DE",
46250
+ "FL",
46251
+ "GA",
46252
+ "HI",
46253
+ "ID",
46254
+ "IL",
46255
+ "IN",
46256
+ "IA",
46257
+ "KS",
46258
+ "KY",
46259
+ "LA",
46260
+ "ME",
46261
+ "MD",
46262
+ "MA",
46263
+ "MI",
46264
+ "MN",
46265
+ "MS",
46266
+ "MO",
46267
+ "MT",
46268
+ "NE",
46269
+ "NV",
46270
+ "NH",
46271
+ "NJ",
46272
+ "NM",
46273
+ "NY",
46274
+ "NC",
46275
+ "ND",
46276
+ "OH",
46277
+ "OK",
46278
+ "OR",
46279
+ "PA",
46280
+ "RI",
46281
+ "SC",
46282
+ "SD",
46283
+ "TN",
46284
+ "TX",
46285
+ "UT",
46286
+ "VT",
46287
+ "VA",
46288
+ "WA",
46289
+ "WV",
46290
+ "WI",
46291
+ "WY",
46292
+ "DC"
46098
46293
  ]);
46099
- return validate({
46100
- worldCoarse,
46101
- worldDetail,
46102
- usStates,
46103
- gazetteer,
46104
- ...lakes && { lakes },
46105
- ...rivers && { rivers },
46106
- ...naLand && { naLand },
46107
- ...naLakes && { naLakes }
46108
- });
46109
- })().catch((e) => {
46110
- cache = void 0;
46111
- throw e;
46112
- });
46113
- return cache;
46114
- }
46115
- var FILES, CANDIDATE_DIRS, cache;
46116
- var init_load_data = __esm({
46117
- "src/map/load-data.ts"() {
46118
- "use strict";
46119
- FILES = {
46120
- worldCoarse: "world-coarse.json",
46121
- worldDetail: "world-detail.json",
46122
- usStates: "us-states.json",
46123
- lakes: "lakes.json",
46124
- rivers: "rivers.json",
46125
- naLand: "na-land.json",
46126
- naLakes: "na-lakes.json",
46127
- gazetteer: "gazetteer.json"
46128
- };
46129
- CANDIDATE_DIRS = [
46130
- "./data",
46131
- "./map-data",
46132
- "../map-data",
46133
- "../src/map/data"
46134
- ];
46135
46294
  }
46136
46295
  });
46137
46296
 
@@ -46171,8 +46330,19 @@ function projectionFor(family) {
46171
46330
  return geoEquirectangular();
46172
46331
  }
46173
46332
  }
46174
- function mapBackgroundColor(palette) {
46175
- return mix(palette.colors.blue, palette.bg, WATER_TINT);
46333
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
46334
+ return mix(
46335
+ palette.colors.blue,
46336
+ palette.bg,
46337
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
46338
+ );
46339
+ }
46340
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46341
+ return mix(
46342
+ palette.colors.green,
46343
+ palette.bg,
46344
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46345
+ );
46176
46346
  }
46177
46347
  function layoutMap(resolved, data, size, opts) {
46178
46348
  const { palette, isDark } = opts;
@@ -46193,28 +46363,19 @@ function layoutMap(resolved, data, size, opts) {
46193
46363
  }
46194
46364
  }
46195
46365
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46196
- const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46197
- const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46198
- const water = mapBackgroundColor(palette);
46199
46366
  const usContext = usLayer !== null;
46200
- const foreignFill = mix(
46201
- palette.colors.gray,
46202
- palette.bg,
46203
- isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46204
- );
46205
46367
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46206
- const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46368
+ const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46207
46369
  const scaleOverride = resolved.directives.scale;
46208
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46209
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46210
- const rampHue = palette.colors.red;
46211
- const hasRamp = scores.length > 0;
46212
- const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46370
+ const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46371
+ const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
46372
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46373
+ const hasRamp = values.length > 0;
46374
+ const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
46213
46375
  const matchColorGroup = (v) => {
46214
46376
  const lv = v.trim().toLowerCase();
46215
46377
  if (lv === "none") return null;
46216
- if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46217
- return SCORE_NAME;
46378
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
46218
46379
  const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46219
46380
  return tg ? tg.name : v;
46220
46381
  };
@@ -46225,11 +46386,20 @@ function layoutMap(resolved, data, size, opts) {
46225
46386
  } else if (resolved.directives.activeTag !== void 0) {
46226
46387
  activeGroup = matchColorGroup(resolved.directives.activeTag);
46227
46388
  } else {
46228
- activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46389
+ activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46229
46390
  }
46230
- const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46391
+ const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46392
+ const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
46393
+ const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46394
+ const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46395
+ const lakeStroke = mix(regionStroke, water, 45);
46396
+ const foreignFill = mix(
46397
+ palette.colors.gray,
46398
+ palette.bg,
46399
+ mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46400
+ );
46231
46401
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46232
- const fillForScore = (s) => {
46402
+ const fillForValue = (s) => {
46233
46403
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46234
46404
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46235
46405
  return mix(rampHue, rampBase, pct);
@@ -46252,9 +46422,16 @@ function layoutMap(resolved, data, size, opts) {
46252
46422
  isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46253
46423
  );
46254
46424
  };
46425
+ const directFill = (name) => {
46426
+ const hex = name ? resolveColor(name, palette) : null;
46427
+ if (!hex) return null;
46428
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
46429
+ };
46255
46430
  const regionFill = (r) => {
46431
+ const direct = directFill(r.color);
46432
+ if (direct) return direct;
46256
46433
  if (activeIsScore) {
46257
- return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46434
+ return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46258
46435
  }
46259
46436
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46260
46437
  };
@@ -46306,6 +46483,7 @@ function layoutMap(resolved, data, size, opts) {
46306
46483
  const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46307
46484
  let path;
46308
46485
  let project;
46486
+ let stretchParams = null;
46309
46487
  if (fitIsGlobal) {
46310
46488
  const cb = geoPath(projection).bounds(fitTarget);
46311
46489
  const bx0 = cb[0][0];
@@ -46316,6 +46494,7 @@ function layoutMap(resolved, data, size, opts) {
46316
46494
  const oy = fitBox[0][1];
46317
46495
  const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46318
46496
  const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46497
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
46319
46498
  const stretch = (x, y) => [
46320
46499
  ox + (x - bx0) * sx,
46321
46500
  oy + (y - by0) * sy
@@ -46347,7 +46526,7 @@ function layoutMap(resolved, data, size, opts) {
46347
46526
  const insets = [];
46348
46527
  const insetRegions = [];
46349
46528
  const insetLabelSeeds = [];
46350
- if (resolved.projection === "albers-usa" && usLayer) {
46529
+ if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
46351
46530
  const PAD = 8;
46352
46531
  const GAP = 12;
46353
46532
  const yB = height - FIT_PAD;
@@ -46378,38 +46557,14 @@ function layoutMap(resolved, data, size, opts) {
46378
46557
  }
46379
46558
  return y;
46380
46559
  };
46381
- const coastTop = (x0, xr) => {
46560
+ const coastFloor = (x0, xr) => {
46382
46561
  const n = 24;
46383
- const pts = [];
46384
46562
  let maxY = -Infinity;
46385
46563
  for (let i = 0; i <= n; i++) {
46386
- const x = x0 + (xr - x0) * i / n;
46387
- const y = at(x);
46388
- if (y > -Infinity) {
46389
- pts.push([x, y]);
46390
- if (y > maxY) maxY = y;
46391
- }
46392
- }
46393
- if (pts.length === 0) return () => yB - height * 0.42;
46394
- let m = 0;
46395
- if (pts.length >= 2) {
46396
- let sx = 0, sy = 0, sxx = 0, sxy = 0;
46397
- for (const [x, y] of pts) {
46398
- sx += x;
46399
- sy += y;
46400
- sxx += x * x;
46401
- sxy += x * y;
46402
- }
46403
- const den = pts.length * sxx - sx * sx;
46404
- if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46405
- }
46406
- m = Math.max(-0.35, Math.min(0.35, m));
46407
- let c = -Infinity;
46408
- for (const [x, y] of pts) {
46409
- const need = y - m * x + GAP;
46410
- if (need > c) c = need;
46411
- }
46412
- return (x) => m * x + c;
46564
+ const y = at(x0 + (xr - x0) * i / n);
46565
+ if (y > maxY) maxY = y;
46566
+ }
46567
+ return maxY;
46413
46568
  };
46414
46569
  const placeInset = (iso, proj, boxX, iwReq) => {
46415
46570
  const f = usLayer.get(iso);
@@ -46418,19 +46573,15 @@ function layoutMap(resolved, data, size, opts) {
46418
46573
  const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46419
46574
  if (iw < 24) return boxX;
46420
46575
  const xr = x0 + iw + 2 * PAD;
46421
- const top = coastTop(x0, xr);
46422
- const yL = top(x0);
46423
- const yR = top(xr);
46576
+ const floor = coastFloor(x0, xr);
46577
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
46424
46578
  proj.fitWidth(iw, f);
46425
46579
  const bb = geoPath(proj).bounds(f);
46426
46580
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46427
46581
  const needH = sh + 2 * PAD;
46428
- let topFit = Math.max(yL, yR);
46582
+ let topFit = topGuess;
46429
46583
  const bottom = Math.min(topFit + needH, yB);
46430
46584
  if (bottom - topFit < needH) topFit = bottom - needH;
46431
- const lift = topFit - Math.max(yL, yR);
46432
- const topL = yL + lift;
46433
- const topR = yR + lift;
46434
46585
  proj.fitExtent(
46435
46586
  [
46436
46587
  [x0 + PAD, topFit + PAD],
@@ -46449,15 +46600,18 @@ function layoutMap(resolved, data, size, opts) {
46449
46600
  }
46450
46601
  insets.push({
46451
46602
  x: x0,
46452
- y: Math.min(topL, topR),
46603
+ y: topFit,
46453
46604
  w: xr - x0,
46454
- h: bottom - Math.min(topL, topR),
46605
+ h: bottom - topFit,
46455
46606
  points: [
46456
- [x0, topL],
46457
- [xr, topR],
46607
+ [x0, topFit],
46608
+ [xr, topFit],
46458
46609
  [xr, bottom],
46459
46610
  [x0, bottom]
46460
- ]
46611
+ ],
46612
+ // The FITTED inset projection (just fit to this box) — captured so the
46613
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
46614
+ projection: proj
46461
46615
  });
46462
46616
  insetRegions.push({
46463
46617
  id: iso,
@@ -46466,7 +46620,7 @@ function layoutMap(resolved, data, size, opts) {
46466
46620
  stroke: regionStroke,
46467
46621
  lineNumber,
46468
46622
  layer: "us-state",
46469
- ...r?.score !== void 0 && { score: r.score },
46623
+ ...r?.value !== void 0 && { value: r.value },
46470
46624
  ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46471
46625
  });
46472
46626
  const ctr = geoPath(proj).centroid(f);
@@ -46609,7 +46763,7 @@ function layoutMap(resolved, data, size, opts) {
46609
46763
  lineNumber,
46610
46764
  layer,
46611
46765
  ...label !== void 0 && { label },
46612
- ...isThisLayer && r.score !== void 0 && { score: r.score },
46766
+ ...isThisLayer && r.value !== void 0 && { value: r.value },
46613
46767
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46614
46768
  });
46615
46769
  }
@@ -46627,13 +46781,40 @@ function layoutMap(resolved, data, size, opts) {
46627
46781
  id: "lake",
46628
46782
  d,
46629
46783
  fill: water,
46630
- stroke: "none",
46784
+ stroke: lakeStroke,
46631
46785
  lineNumber: -1,
46632
46786
  layer: "base"
46633
46787
  });
46634
46788
  }
46635
46789
  }
46636
- const riverColor = water;
46790
+ const relief = [];
46791
+ let reliefHatch = null;
46792
+ if (resolved.directives.relief === true && data.mountainRanges) {
46793
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
46794
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46795
+ if (!viewF) continue;
46796
+ const area2 = path.area(viewF);
46797
+ if (!Number.isFinite(area2) || area2 < RELIEF_MIN_AREA) continue;
46798
+ const box = path.bounds(viewF);
46799
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
46800
+ continue;
46801
+ const d = path(viewF) ?? "";
46802
+ if (!d) continue;
46803
+ relief.push({ d });
46804
+ }
46805
+ if (relief.length) {
46806
+ const darkTone = isDark ? palette.bg : palette.text;
46807
+ const lightTone = isDark ? palette.text : palette.bg;
46808
+ const landLum = relativeLuminance(neutralFill);
46809
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46810
+ reliefHatch = {
46811
+ color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
46812
+ spacing: RELIEF_HATCH_SPACING,
46813
+ width: RELIEF_HATCH_WIDTH
46814
+ };
46815
+ }
46816
+ }
46817
+ const riverColor = mix(water, regionStroke, 16);
46637
46818
  const rivers = [];
46638
46819
  if (data.rivers) {
46639
46820
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46644,16 +46825,19 @@ function layoutMap(resolved, data, size, opts) {
46644
46825
  rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46645
46826
  }
46646
46827
  }
46647
- const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46828
+ const sizeVals = resolved.pois.map((p) => Number(p.meta["value"])).filter((n) => Number.isFinite(n) && n > 0);
46648
46829
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46649
46830
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
46650
46831
  const radiusFor = (p) => {
46651
- const v = Number(p.meta["size"]);
46832
+ const v = Number(p.meta["value"]);
46652
46833
  if (!Number.isFinite(v) || v <= 0 || sizeMax <= 0) return R_DEFAULT;
46653
46834
  const t = sizeMax > sizeMin ? (Math.sqrt(v) - Math.sqrt(sizeMin)) / (Math.sqrt(sizeMax) - Math.sqrt(sizeMin)) : 1;
46654
46835
  return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
46655
46836
  };
46656
46837
  const poiFill = (p) => {
46838
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
46839
+ if (directHex)
46840
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
46657
46841
  for (const group of resolved.tagGroups) {
46658
46842
  const val = p.tags[group.name.toLowerCase()];
46659
46843
  if (!val) continue;
@@ -46715,7 +46899,8 @@ function layoutMap(resolved, data, size, opts) {
46715
46899
  lineNumber: e.p.lineNumber,
46716
46900
  implicit: !!e.p.implicit,
46717
46901
  isOrigin: originIds.has(e.p.id),
46718
- ...num !== void 0 && { routeNumber: num }
46902
+ ...num !== void 0 && { routeNumber: num },
46903
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
46719
46904
  });
46720
46905
  });
46721
46906
  }
@@ -46751,26 +46936,40 @@ function layoutMap(resolved, data, size, opts) {
46751
46936
  const by = b.cy - (b.cy - py) / tb * trimB;
46752
46937
  return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46753
46938
  };
46939
+ const routeLegVals = resolved.routes.flatMap((rt) => rt.legs).map((l) => Number(l.value)).filter((n) => Number.isFinite(n) && n > 0);
46940
+ const rlMin = routeLegVals.length ? Math.min(...routeLegVals) : 0;
46941
+ const rlMax = routeLegVals.length ? Math.max(...routeLegVals) : 0;
46942
+ const routeWidthFor = (v) => {
46943
+ if (!Number.isFinite(v) || v <= 0 || rlMax <= 0) return W_MIN;
46944
+ const t = rlMax > rlMin ? (v - rlMin) / (rlMax - rlMin) : 1;
46945
+ return W_MIN + t * (W_MAX - W_MIN);
46946
+ };
46754
46947
  for (const rt of resolved.routes) {
46755
- const curved = rt.meta["style"] === "arc";
46756
- for (let i = 1; i < rt.stopIds.length; i++) {
46757
- const a = poiScreen.get(rt.stopIds[i - 1]);
46758
- const b = poiScreen.get(rt.stopIds[i]);
46948
+ for (const leg of rt.legs) {
46949
+ const a = poiScreen.get(leg.fromId);
46950
+ const b = poiScreen.get(leg.toId);
46759
46951
  if (!a || !b) continue;
46952
+ const mx = (a.cx + b.cx) / 2;
46953
+ const my = (a.cy + b.cy) / 2;
46760
46954
  legs.push({
46761
- d: legPath(a, b, curved, 0),
46762
- width: W_MIN,
46955
+ d: legPath(a, b, leg.style === "arc", 0),
46956
+ width: routeWidthFor(Number(leg.value)),
46763
46957
  color: mix(palette.text, palette.bg, 72),
46764
46958
  arrow: true,
46765
- lineNumber: rt.lineNumber
46959
+ lineNumber: leg.lineNumber,
46960
+ ...leg.label !== void 0 && {
46961
+ label: leg.label,
46962
+ labelX: mx,
46963
+ labelY: my - 4
46964
+ }
46766
46965
  });
46767
46966
  }
46768
46967
  }
46769
- const weightVals = resolved.edges.map((e) => Number(e.meta["weight"])).filter((n) => Number.isFinite(n) && n > 0);
46968
+ const weightVals = resolved.edges.map((e) => Number(e.meta["value"])).filter((n) => Number.isFinite(n) && n > 0);
46770
46969
  const wMin = weightVals.length ? Math.min(...weightVals) : 0;
46771
46970
  const wMax = weightVals.length ? Math.max(...weightVals) : 0;
46772
46971
  const widthFor = (e) => {
46773
- const v = Number(e.meta["weight"]);
46972
+ const v = Number(e.meta["value"]);
46774
46973
  if (!Number.isFinite(v) || v <= 0 || wMax <= 0) return W_MIN;
46775
46974
  const t = wMax > wMin ? (v - wMin) / (wMax - wMin) : 1;
46776
46975
  return W_MIN + t * (W_MAX - W_MIN);
@@ -47033,8 +47232,8 @@ function layoutMap(resolved, data, size, opts) {
47033
47232
  activeGroup,
47034
47233
  ...hasRamp && {
47035
47234
  ramp: {
47036
- ...resolved.directives.metric !== void 0 && {
47037
- metric: resolved.directives.metric
47235
+ ...resolved.directives.regionMetric !== void 0 && {
47236
+ metric: resolved.directives.regionMetric
47038
47237
  },
47039
47238
  min: rampMin,
47040
47239
  max: rampMax,
@@ -47054,19 +47253,24 @@ function layoutMap(resolved, data, size, opts) {
47054
47253
  ...resolved.caption !== void 0 && { caption: resolved.caption },
47055
47254
  regions,
47056
47255
  rivers,
47256
+ relief,
47257
+ reliefHatch,
47057
47258
  legs,
47058
47259
  pois,
47059
47260
  labels,
47060
47261
  legend,
47061
47262
  insets,
47062
- insetRegions
47263
+ insetRegions,
47264
+ projection,
47265
+ stretch: stretchParams
47063
47266
  };
47064
47267
  }
47065
- 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, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47268
+ 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;
47066
47269
  var init_layout15 = __esm({
47067
47270
  "src/map/layout.ts"() {
47068
47271
  "use strict";
47069
47272
  init_color_utils();
47273
+ init_colors();
47070
47274
  init_label_layout();
47071
47275
  init_legend_constants();
47072
47276
  init_title_constants();
@@ -47079,14 +47283,22 @@ var init_layout15 = __esm({
47079
47283
  W_MAX = 8;
47080
47284
  FONT = 11;
47081
47285
  COLO_EPS = 1.5;
47082
- LAND_TINT_LIGHT = 58;
47083
- LAND_TINT_DARK = 75;
47286
+ LAND_TINT_LIGHT = 12;
47287
+ LAND_TINT_DARK = 24;
47084
47288
  TAG_TINT_LIGHT = 60;
47085
47289
  TAG_TINT_DARK = 68;
47086
- WATER_TINT = 55;
47290
+ WATER_TINT_LIGHT = 13;
47291
+ WATER_TINT_DARK = 14;
47087
47292
  RIVER_WIDTH = 1.3;
47293
+ RELIEF_MIN_AREA = 12;
47294
+ RELIEF_MIN_DIM = 2;
47295
+ RELIEF_HATCH_SPACING = 3;
47296
+ RELIEF_HATCH_WIDTH = 0.25;
47297
+ RELIEF_HATCH_STRENGTH = 32;
47088
47298
  FOREIGN_TINT_LIGHT = 30;
47089
47299
  FOREIGN_TINT_DARK = 62;
47300
+ MUTED_FOREIGN_LIGHT = 28;
47301
+ MUTED_FOREIGN_DARK = 16;
47090
47302
  COLO_R = 9;
47091
47303
  GOLDEN_ANGLE = 2.399963229728653;
47092
47304
  FAN_STEP = 16;
@@ -47141,7 +47353,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47141
47353
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47142
47354
  if (r.layer !== "base") {
47143
47355
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47144
- if (r.score !== void 0) p.attr("data-score", r.score);
47356
+ if (r.value !== void 0) p.attr("data-value", r.value);
47145
47357
  if (r.tags) {
47146
47358
  for (const [group, value] of Object.entries(r.tags)) {
47147
47359
  p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47159,6 +47371,20 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47159
47371
  }
47160
47372
  };
47161
47373
  for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47374
+ if (layout.relief.length && layout.reliefHatch) {
47375
+ const h = layout.reliefHatch;
47376
+ const rangeClipId = "dgmo-relief-clip";
47377
+ const landClipId = "dgmo-relief-land";
47378
+ const rangeClip = defs.append("clipPath").attr("id", rangeClipId);
47379
+ for (const s of layout.relief) rangeClip.append("path").attr("d", s.d);
47380
+ const landClip = defs.append("clipPath").attr("id", landClipId);
47381
+ for (const r of layout.regions)
47382
+ if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47383
+ 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");
47384
+ for (let y = h.spacing; y < height; y += h.spacing) {
47385
+ gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47386
+ }
47387
+ }
47162
47388
  if (layout.rivers.length) {
47163
47389
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47164
47390
  for (const r of layout.rivers) {
@@ -47202,6 +47428,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47202
47428
  gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r + 3).attr("fill", "none").attr("stroke", poi.stroke).attr("stroke-width", 1.5);
47203
47429
  }
47204
47430
  const c = gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r).attr("fill", poi.fill).attr("stroke", poi.stroke).attr("stroke-width", 1).attr("data-line-number", poi.lineNumber).attr("data-poi", poi.id);
47431
+ if (poi.tags) {
47432
+ for (const [group, value] of Object.entries(poi.tags)) {
47433
+ c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47434
+ }
47435
+ }
47205
47436
  if (onClickItem) {
47206
47437
  c.style("cursor", "pointer").on(
47207
47438
  "click",
@@ -47251,7 +47482,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47251
47482
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
47252
47483
  const ramp = layout.legend.ramp;
47253
47484
  const scoreGroup = ramp ? {
47254
- name: ramp.metric?.trim() || "Score",
47485
+ name: ramp.metric?.trim() || "Value",
47255
47486
  entries: [],
47256
47487
  gradient: {
47257
47488
  min: ramp.min,
@@ -47278,7 +47509,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47278
47509
  }
47279
47510
  }
47280
47511
  if (layout.title) {
47281
- 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);
47512
+ 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);
47282
47513
  }
47283
47514
  if (layout.subtitle) {
47284
47515
  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);
@@ -47310,6 +47541,120 @@ var init_renderer16 = __esm({
47310
47541
  }
47311
47542
  });
47312
47543
 
47544
+ // src/map/load-data.ts
47545
+ var load_data_exports = {};
47546
+ __export(load_data_exports, {
47547
+ loadMapData: () => loadMapData
47548
+ });
47549
+ async function loadNodeBuiltins() {
47550
+ const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
47551
+ import("fs/promises"),
47552
+ import("url"),
47553
+ import("path")
47554
+ ]);
47555
+ return { readFile, fileURLToPath, dirname, resolve };
47556
+ }
47557
+ async function readJson(nb, dir, name) {
47558
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
47559
+ }
47560
+ async function firstExistingDir(nb, baseDir) {
47561
+ for (const rel of CANDIDATE_DIRS) {
47562
+ const dir = nb.resolve(baseDir, rel);
47563
+ try {
47564
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
47565
+ return dir;
47566
+ } catch {
47567
+ }
47568
+ }
47569
+ throw new Error(
47570
+ `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
47571
+ );
47572
+ }
47573
+ function validate(data) {
47574
+ const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
47575
+ if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
47576
+ throw new Error("map data assets are malformed (failed shape validation)");
47577
+ }
47578
+ return data;
47579
+ }
47580
+ function moduleBaseDir(nb) {
47581
+ try {
47582
+ const url = import.meta.url;
47583
+ if (url) return nb.dirname(nb.fileURLToPath(url));
47584
+ } catch {
47585
+ }
47586
+ if (typeof __dirname !== "undefined") return __dirname;
47587
+ return process.cwd();
47588
+ }
47589
+ function loadMapData() {
47590
+ cache ??= (async () => {
47591
+ const nb = await loadNodeBuiltins();
47592
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
47593
+ const [
47594
+ worldCoarse,
47595
+ worldDetail,
47596
+ usStates,
47597
+ lakes,
47598
+ rivers,
47599
+ mountainRanges,
47600
+ naLand,
47601
+ naLakes,
47602
+ gazetteer
47603
+ ] = await Promise.all([
47604
+ readJson(nb, dir, FILES.worldCoarse),
47605
+ readJson(nb, dir, FILES.worldDetail),
47606
+ readJson(nb, dir, FILES.usStates),
47607
+ // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
47608
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
47609
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
47610
+ readJson(nb, dir, FILES.mountainRanges).catch(
47611
+ () => void 0
47612
+ ),
47613
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
47614
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
47615
+ readJson(nb, dir, FILES.gazetteer)
47616
+ ]);
47617
+ return validate({
47618
+ worldCoarse,
47619
+ worldDetail,
47620
+ usStates,
47621
+ gazetteer,
47622
+ ...lakes && { lakes },
47623
+ ...rivers && { rivers },
47624
+ ...mountainRanges && { mountainRanges },
47625
+ ...naLand && { naLand },
47626
+ ...naLakes && { naLakes }
47627
+ });
47628
+ })().catch((e) => {
47629
+ cache = void 0;
47630
+ throw e;
47631
+ });
47632
+ return cache;
47633
+ }
47634
+ var FILES, CANDIDATE_DIRS, cache;
47635
+ var init_load_data = __esm({
47636
+ "src/map/load-data.ts"() {
47637
+ "use strict";
47638
+ FILES = {
47639
+ worldCoarse: "world-coarse.json",
47640
+ worldDetail: "world-detail.json",
47641
+ usStates: "us-states.json",
47642
+ lakes: "lakes.json",
47643
+ rivers: "rivers.json",
47644
+ mountainRanges: "mountain-ranges.json",
47645
+ naLand: "na-land.json",
47646
+ naLakes: "na-lakes.json",
47647
+ gazetteer: "gazetteer.json"
47648
+ };
47649
+ CANDIDATE_DIRS = [
47650
+ "./data",
47651
+ "./map-data",
47652
+ "../map-data",
47653
+ "../src/map/data"
47654
+ ];
47655
+ }
47656
+ });
47657
+
47313
47658
  // src/pyramid/renderer.ts
47314
47659
  var renderer_exports17 = {};
47315
47660
  __export(renderer_exports17, {
@@ -55438,15 +55783,17 @@ async function renderForExport(content, theme, palette, viewState, options) {
55438
55783
  if (detectedType === "map") {
55439
55784
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55440
55785
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55441
- const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55442
55786
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
55443
55787
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55444
55788
  const mapParsed = parseMap2(content);
55445
- let mapData;
55446
- try {
55447
- mapData = await loadMapData2();
55448
- } catch {
55449
- return "";
55789
+ let mapData = options?.mapData;
55790
+ if (!mapData) {
55791
+ const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55792
+ try {
55793
+ mapData = await loadMapData2();
55794
+ } catch {
55795
+ return "";
55796
+ }
55450
55797
  }
55451
55798
  const mapResolved = resolveMap2(mapParsed, mapData);
55452
55799
  const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);