@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.cjs CHANGED
@@ -835,13 +835,9 @@ var init_reserved_key_registry = __esm({
835
835
  "icon"
836
836
  ]);
837
837
  MAP_REGISTRY = staticRegistry([
838
- "score",
838
+ "value",
839
839
  "label",
840
- "size",
841
- "description",
842
- "weight",
843
- "style",
844
- "date"
840
+ "style"
845
841
  ]);
846
842
  ORG_REGISTRY = staticRegistry([
847
843
  "color",
@@ -15854,7 +15850,8 @@ function parseMap(content) {
15854
15850
  continue;
15855
15851
  }
15856
15852
  if (open.route && indent > open.route.indent) {
15857
- open.route.route.stops.push(parseStop(trimmed, lineNumber));
15853
+ const leg = parseLeg(trimmed, lineNumber, open.route.route.style);
15854
+ open.route.route.legs.push(leg);
15858
15855
  continue;
15859
15856
  }
15860
15857
  if (open.poi && indent > open.poi.indent) {
@@ -15885,6 +15882,10 @@ function parseMap(content) {
15885
15882
  handleTag(trimmed, lineNumber);
15886
15883
  continue;
15887
15884
  }
15885
+ if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15886
+ handleDirective(firstWord, "", lineNumber);
15887
+ continue;
15888
+ }
15888
15889
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15889
15890
  handleDirective(
15890
15891
  firstWord,
@@ -15949,13 +15950,20 @@ function parseMap(content) {
15949
15950
  );
15950
15951
  d.projection = value;
15951
15952
  break;
15952
- case "metric":
15953
- dup(d.metric);
15954
- d.metric = value;
15953
+ case "region-metric": {
15954
+ dup(d.regionMetric);
15955
+ const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
15956
+ d.regionMetric = rmLabel;
15957
+ if (rmColor) d.regionMetricColor = rmColor;
15958
+ break;
15959
+ }
15960
+ case "poi-metric":
15961
+ dup(d.poiMetric);
15962
+ d.poiMetric = value;
15955
15963
  break;
15956
- case "size-metric":
15957
- dup(d.sizeMetric);
15958
- d.sizeMetric = value;
15964
+ case "flow-metric":
15965
+ dup(d.flowMetric);
15966
+ d.flowMetric = value;
15959
15967
  break;
15960
15968
  case "scale":
15961
15969
  dup(d.scale);
@@ -15997,6 +16005,21 @@ function parseMap(content) {
15997
16005
  case "no-legend":
15998
16006
  d.noLegend = true;
15999
16007
  break;
16008
+ case "no-insets":
16009
+ d.noInsets = true;
16010
+ break;
16011
+ case "relief":
16012
+ d.relief = true;
16013
+ break;
16014
+ case "muted":
16015
+ case "natural":
16016
+ if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
16017
+ pushWarning(
16018
+ line12,
16019
+ `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
16020
+ );
16021
+ d.basemapStyle = key;
16022
+ break;
16000
16023
  case "subtitle":
16001
16024
  dup(d.subtitle);
16002
16025
  d.subtitle = value;
@@ -16074,14 +16097,14 @@ function parseMap(content) {
16074
16097
  line12
16075
16098
  );
16076
16099
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16077
- let scoreNum;
16078
- const score = meta["score"];
16079
- if (score !== void 0) {
16080
- delete meta["score"];
16081
- scoreNum = Number(score);
16082
- if (!Number.isFinite(scoreNum)) {
16083
- pushError(line12, `score must be a number (got "${score}").`);
16084
- scoreNum = void 0;
16100
+ let valueNum;
16101
+ const value = meta["value"];
16102
+ if (value !== void 0) {
16103
+ delete meta["value"];
16104
+ valueNum = Number(value);
16105
+ if (!Number.isFinite(valueNum)) {
16106
+ pushError(line12, `value must be a number (got "${value}").`);
16107
+ valueNum = void 0;
16085
16108
  }
16086
16109
  }
16087
16110
  let regionName = split.name;
@@ -16099,7 +16122,8 @@ function parseMap(content) {
16099
16122
  lineNumber: line12
16100
16123
  };
16101
16124
  if (regionScope !== void 0) region.scope = regionScope;
16102
- if (scoreNum !== void 0) region.score = scoreNum;
16125
+ if (valueNum !== void 0) region.value = valueNum;
16126
+ if (split.color) region.color = split.color;
16103
16127
  regions.push(region);
16104
16128
  }
16105
16129
  function handlePoi(rest, line12, indent) {
@@ -16124,28 +16148,81 @@ function parseMap(content) {
16124
16148
  const poi = { pos, tags, meta, lineNumber: line12 };
16125
16149
  if (split.alias) poi.alias = split.alias;
16126
16150
  if (label !== void 0) poi.label = label;
16151
+ if (split.color) poi.color = split.color;
16127
16152
  pois.push(poi);
16128
16153
  open.poi = { poi, indent };
16129
16154
  }
16130
16155
  function handleRoute(rest, line12, indent) {
16131
- const meta = rest ? splitNameAndMeta(rest, registry(), aliasMap).meta : {};
16132
- const route = { stops: [], meta, lineNumber: line12 };
16156
+ const split = rest ? splitNameAndMeta(
16157
+ rest,
16158
+ registry(),
16159
+ aliasMap,
16160
+ void 0,
16161
+ diagnostics,
16162
+ line12
16163
+ ) : { name: "", meta: {}, alias: void 0 };
16164
+ const pos = parsePos(split.name, line12);
16165
+ if (!pos || pos.kind === "name" && !pos.name) {
16166
+ pushError(
16167
+ line12,
16168
+ "route requires an origin: `route <origin> [style: arc]`."
16169
+ );
16170
+ return;
16171
+ }
16172
+ const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16173
+ const originLabel = meta["label"];
16174
+ const originValue = meta["value"];
16175
+ const style = meta["style"] === "arc" ? "arc" : "straight";
16176
+ const route = {
16177
+ origin: pos,
16178
+ ...split.alias !== void 0 && { originAlias: split.alias },
16179
+ ...originLabel !== void 0 && { originLabel },
16180
+ ...originValue !== void 0 && { originValue },
16181
+ originTags: tags,
16182
+ style,
16183
+ legs: [],
16184
+ lineNumber: line12
16185
+ };
16133
16186
  routes.push(route);
16134
16187
  open.route = { route, indent };
16135
16188
  }
16136
- function parseStop(trimmed, line12) {
16137
- const split = splitNameAndMeta(trimmed, registry(), aliasMap);
16138
- const ref = parsePos(split.name, line12) ?? {
16189
+ function parseLeg(trimmed, line12, headerStyle) {
16190
+ let arrowStyle = "straight";
16191
+ let label;
16192
+ let rest = trimmed;
16193
+ const m = trimmed.match(LEG_ARROW_RE);
16194
+ if (m) {
16195
+ const arr = classifyArrow(m[1], line12);
16196
+ arrowStyle = arr.style;
16197
+ label = arr.label;
16198
+ rest = m[2];
16199
+ }
16200
+ const split = splitNameAndMeta(
16201
+ rest,
16202
+ registry(),
16203
+ aliasMap,
16204
+ void 0,
16205
+ diagnostics,
16206
+ line12
16207
+ );
16208
+ const pos = parsePos(split.name, line12) ?? {
16139
16209
  kind: "name",
16140
16210
  name: split.name
16141
16211
  };
16142
- const stop = {
16143
- ref,
16144
- meta: split.meta,
16212
+ const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
16213
+ const value = meta["value"];
16214
+ const destLabel = meta["label"];
16215
+ const style = arrowStyle === "arc" || headerStyle === "arc" ? "arc" : "straight";
16216
+ return {
16217
+ ...label !== void 0 && { label },
16218
+ style,
16219
+ ...value !== void 0 && { value },
16220
+ dest: pos,
16221
+ ...split.alias !== void 0 && { destAlias: split.alias },
16222
+ ...destLabel !== void 0 && { destLabel },
16223
+ destTags: tags,
16145
16224
  lineNumber: line12
16146
16225
  };
16147
- if (split.alias) stop.alias = split.alias;
16148
- return stop;
16149
16226
  }
16150
16227
  function handleEdges(trimmed, line12) {
16151
16228
  const parts = trimmed.split(ARROW_SPLIT);
@@ -16247,7 +16324,7 @@ function partitionMeta(meta, tagGroupNames) {
16247
16324
  function poiName(pos) {
16248
16325
  return pos.kind === "name" ? pos.name : void 0;
16249
16326
  }
16250
- var COORD_RE, NUMERIC_LEAD_RE, SCOPE_RE, ARROW_SPLIT, HUB_RE, AT_RE, DIRECTIVE_SET;
16327
+ var COORD_RE, NUMERIC_LEAD_RE, SCOPE_RE, ARROW_SPLIT, HUB_RE, LEG_ARROW_RE, AT_RE, DIRECTIVE_SET;
16251
16328
  var init_parser12 = __esm({
16252
16329
  "src/map/parser.ts"() {
16253
16330
  "use strict";
@@ -16261,12 +16338,14 @@ var init_parser12 = __esm({
16261
16338
  SCOPE_RE = /^[A-Z]{2}(?:-[A-Z0-9]{1,3})?$/;
16262
16339
  ARROW_SPLIT = /\s+(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+/;
16263
16340
  HUB_RE = /^(->|~>)\s+(.+)$/;
16341
+ LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16264
16342
  AT_RE = /(^|[\s,])at\s*:/i;
16265
16343
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16266
16344
  "region",
16267
16345
  "projection",
16268
- "metric",
16269
- "size-metric",
16346
+ "region-metric",
16347
+ "poi-metric",
16348
+ "flow-metric",
16270
16349
  "scale",
16271
16350
  "region-labels",
16272
16351
  "poi-labels",
@@ -16274,6 +16353,8 @@ var init_parser12 = __esm({
16274
16353
  "default-state",
16275
16354
  "active-tag",
16276
16355
  "no-legend",
16356
+ "no-insets",
16357
+ "relief",
16277
16358
  "subtitle",
16278
16359
  "caption"
16279
16360
  ]);
@@ -45498,6 +45579,74 @@ function featureBbox(topo, geomId) {
45498
45579
  [b[1][0], b[1][1]]
45499
45580
  ];
45500
45581
  }
45582
+ function explodePolygons(gj) {
45583
+ const g = gj.geometry ?? gj;
45584
+ const t = g.type;
45585
+ const coords = g.coordinates;
45586
+ if (t === "Polygon") {
45587
+ return [
45588
+ { type: "Feature", geometry: { type: "Polygon", coordinates: coords } }
45589
+ ];
45590
+ }
45591
+ if (t === "MultiPolygon") {
45592
+ return coords.map((rings) => ({
45593
+ type: "Feature",
45594
+ geometry: { type: "Polygon", coordinates: rings }
45595
+ }));
45596
+ }
45597
+ return [];
45598
+ }
45599
+ function bboxGap(a, b) {
45600
+ const lonGap = Math.max(0, a[0][0] - b[1][0], b[0][0] - a[1][0]);
45601
+ const latGap = Math.max(0, a[0][1] - b[1][1], b[0][1] - a[1][1]);
45602
+ return Math.max(lonGap, latGap);
45603
+ }
45604
+ function featureBboxPrimary(topo, geomId) {
45605
+ const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45606
+ if (!geom) return null;
45607
+ const gj = (0, import_topojson_client.feature)(topo, geom);
45608
+ const parts = explodePolygons(gj);
45609
+ if (parts.length <= 1) return featureBbox(topo, geomId);
45610
+ const polys = parts.map((p) => {
45611
+ const b = (0, import_d3_geo.geoBounds)(p);
45612
+ if (!b || !Number.isFinite(b[0][0])) return null;
45613
+ const wraps = b[1][0] < b[0][0];
45614
+ const bbox = [
45615
+ [b[0][0], b[0][1]],
45616
+ [b[1][0], b[1][1]]
45617
+ ];
45618
+ return { bbox, area: (0, import_d3_geo.geoArea)(p), wraps };
45619
+ }).filter(
45620
+ (p) => p !== null
45621
+ );
45622
+ if (polys.length <= 1 || polys.some((p) => p.wraps))
45623
+ return featureBbox(topo, geomId);
45624
+ const maxArea = Math.max(...polys.map((p) => p.area));
45625
+ const anchor = polys.find((p) => p.area === maxArea);
45626
+ const cluster = [
45627
+ [anchor.bbox[0][0], anchor.bbox[0][1]],
45628
+ [anchor.bbox[1][0], anchor.bbox[1][1]]
45629
+ ];
45630
+ const remaining = polys.filter((p) => p !== anchor);
45631
+ let added = true;
45632
+ while (added) {
45633
+ added = false;
45634
+ for (let i = remaining.length - 1; i >= 0; i--) {
45635
+ const p = remaining[i];
45636
+ const near = bboxGap(p.bbox, cluster) <= DETACH_GAP_DEG;
45637
+ const large = p.area >= DETACH_AREA_FRAC * maxArea;
45638
+ if (near || large) {
45639
+ cluster[0][0] = Math.min(cluster[0][0], p.bbox[0][0]);
45640
+ cluster[0][1] = Math.min(cluster[0][1], p.bbox[0][1]);
45641
+ cluster[1][0] = Math.max(cluster[1][0], p.bbox[1][0]);
45642
+ cluster[1][1] = Math.max(cluster[1][1], p.bbox[1][1]);
45643
+ remaining.splice(i, 1);
45644
+ added = true;
45645
+ }
45646
+ }
45647
+ }
45648
+ return cluster;
45649
+ }
45501
45650
  function unionExtent(boxes, points) {
45502
45651
  const lats = [];
45503
45652
  const lons = [];
@@ -45536,13 +45685,15 @@ function unionLongitudes(lons) {
45536
45685
  }
45537
45686
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45538
45687
  }
45539
- var import_topojson_client, import_d3_geo, fold;
45688
+ var import_topojson_client, import_d3_geo, fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45540
45689
  var init_geo = __esm({
45541
45690
  "src/map/geo.ts"() {
45542
45691
  "use strict";
45543
45692
  import_topojson_client = require("topojson-client");
45544
45693
  import_d3_geo = require("d3-geo");
45545
45694
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
45695
+ DETACH_GAP_DEG = 10;
45696
+ DETACH_AREA_FRAC = 0.25;
45546
45697
  }
45547
45698
  });
45548
45699
 
@@ -45551,6 +45702,11 @@ var resolver_exports = {};
45551
45702
  __export(resolver_exports, {
45552
45703
  resolveMap: () => resolveMap
45553
45704
  });
45705
+ function usStateFromBareScope(scope) {
45706
+ if (!scope) return null;
45707
+ const up = scope.toUpperCase();
45708
+ return US_STATE_POSTAL.has(up) ? `US-${up}` : null;
45709
+ }
45554
45710
  function looksUS(lat, lon) {
45555
45711
  if (lat < 15 || lat > 72) return false;
45556
45712
  return lon >= -180 && lon <= -64 || lon >= 172;
@@ -45600,9 +45756,9 @@ function resolveMap(parsed, data) {
45600
45756
  const f = fold(r.name);
45601
45757
  return usStateIndex.has(f) && !countryIndex.has(f);
45602
45758
  }) || parsed.regions.some(
45603
- (r) => r.scope === "US" || r.scope?.startsWith("US-")
45759
+ (r) => r.scope === "US" || r.scope?.startsWith("US-") || usStateFromBareScope(r.scope) !== null
45604
45760
  ) || parsed.pois.some(
45605
- (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45761
+ (p) => p.pos.kind === "name" && (p.pos.scope?.startsWith("US-") || usStateFromBareScope(p.pos.scope) !== null)
45606
45762
  );
45607
45763
  const regions = [];
45608
45764
  const seenRegion = /* @__PURE__ */ new Map();
@@ -45641,12 +45797,12 @@ function resolveMap(parsed, data) {
45641
45797
  chosen = { ...inState, layer: "us-state" };
45642
45798
  } else {
45643
45799
  chosen = { ...inCountry, layer: "country" };
45800
+ warn(
45801
+ r.lineNumber,
45802
+ `"${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}").`,
45803
+ "W_MAP_REGION_AMBIGUOUS"
45804
+ );
45644
45805
  }
45645
- warn(
45646
- r.lineNumber,
45647
- `"${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}").`,
45648
- "W_MAP_REGION_AMBIGUOUS"
45649
- );
45650
45806
  } else if (inState) {
45651
45807
  chosen = { ...inState, layer: "us-state" };
45652
45808
  } else if (inCountry) {
@@ -45669,7 +45825,8 @@ function resolveMap(parsed, data) {
45669
45825
  iso: chosen.id,
45670
45826
  name: chosen.name,
45671
45827
  layer: chosen.layer,
45672
- ...r.score !== void 0 && { score: r.score },
45828
+ ...r.value !== void 0 && { value: r.value },
45829
+ ...r.color !== void 0 && { color: r.color },
45673
45830
  tags: r.tags,
45674
45831
  meta: r.meta,
45675
45832
  lineNumber: r.lineNumber
@@ -45727,9 +45884,10 @@ function resolveMap(parsed, data) {
45727
45884
  let cands = idxs.map((i) => data.gazetteer.cities[i]);
45728
45885
  const scopeUse = scope ?? scopeHint;
45729
45886
  if (scopeUse) {
45730
- const isSub = /^[A-Za-z]{2}-/.test(scopeUse);
45887
+ const bareState = usStateFromBareScope(scopeUse);
45888
+ const subScope = /^[A-Za-z]{2}-/.test(scopeUse) ? scopeUse : bareState;
45731
45889
  const filtered = cands.filter(
45732
- (c2) => isSub ? c2[5] === scopeUse : c2[2] === scopeUse
45890
+ (c2) => subScope ? c2[5] === subScope : c2[2] === scopeUse
45733
45891
  );
45734
45892
  if (filtered.length) cands = filtered;
45735
45893
  else if (scope) {
@@ -45810,6 +45968,7 @@ function resolveMap(parsed, data) {
45810
45968
  lat,
45811
45969
  lon,
45812
45970
  ...p.label !== void 0 && { label: p.label },
45971
+ ...p.color !== void 0 && { color: p.color },
45813
45972
  tags: p.tags,
45814
45973
  meta: p.meta,
45815
45974
  lineNumber: p.lineNumber
@@ -45858,33 +46017,89 @@ function resolveMap(parsed, data) {
45858
46017
  lineNumber: e.lineNumber
45859
46018
  });
45860
46019
  }
45861
- const routes = [];
45862
- for (const rt of parsed.routes) {
45863
- const stopIds = [];
45864
- for (const stop of rt.stops) {
45865
- let id;
45866
- if (stop.ref.kind === "coords") {
45867
- id = stop.alias ? fold(stop.alias) : `@${stop.ref.lat},${stop.ref.lon}`;
45868
- if (!looksUS(stop.ref.lat, stop.ref.lon)) anyNonUsPoi = true;
45869
- if (!registry.has(id)) {
45870
- const poi = {
46020
+ const resolveStop = (pos, alias, label, tags, sizeValue, line12) => {
46021
+ const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
46022
+ if (pos.kind === "coords") {
46023
+ const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
46024
+ if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46025
+ if (!registry.has(id)) {
46026
+ registerPoi(
46027
+ id,
46028
+ {
45871
46029
  id,
45872
- ...stop.alias !== void 0 && { name: stop.alias },
45873
- lat: stop.ref.lat,
45874
- lon: stop.ref.lon,
45875
- tags: {},
45876
- meta: stop.meta,
45877
- lineNumber: stop.lineNumber,
45878
- implicit: true
45879
- };
45880
- registerPoi(id, poi, stop.lineNumber);
45881
- }
45882
- } else {
45883
- id = stop.alias && registry.has(fold(stop.alias)) ? fold(stop.alias) : resolveEndpoint2(stop.ref.name, stop.lineNumber);
46030
+ ...alias !== void 0 && { name: alias },
46031
+ lat: pos.lat,
46032
+ lon: pos.lon,
46033
+ ...label !== void 0 && { label },
46034
+ tags,
46035
+ meta,
46036
+ lineNumber: line12
46037
+ },
46038
+ line12
46039
+ );
45884
46040
  }
45885
- if (id) stopIds.push(id);
46041
+ return id;
45886
46042
  }
45887
- routes.push({ stopIds, meta: rt.meta, lineNumber: rt.lineNumber });
46043
+ const f = fold(pos.name);
46044
+ if (registry.has(f)) return f;
46045
+ const aliased = declaredByName.get(f);
46046
+ if (aliased) return aliased;
46047
+ const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46048
+ if (got.kind !== "ok") return null;
46049
+ noteCountry(got.iso);
46050
+ registerPoi(
46051
+ f,
46052
+ {
46053
+ id: f,
46054
+ name: pos.name,
46055
+ lat: got.lat,
46056
+ lon: got.lon,
46057
+ ...label !== void 0 && { label },
46058
+ tags,
46059
+ meta,
46060
+ lineNumber: line12
46061
+ },
46062
+ line12
46063
+ );
46064
+ return f;
46065
+ };
46066
+ const routes = [];
46067
+ for (const rt of parsed.routes) {
46068
+ const originId = resolveStop(
46069
+ rt.origin,
46070
+ rt.originAlias,
46071
+ rt.originLabel,
46072
+ rt.originTags,
46073
+ rt.originValue,
46074
+ rt.lineNumber
46075
+ );
46076
+ if (!originId) continue;
46077
+ const stopIds = [originId];
46078
+ const legs = [];
46079
+ let prevId = originId;
46080
+ for (const leg of rt.legs) {
46081
+ const destId = resolveStop(
46082
+ leg.dest,
46083
+ leg.destAlias,
46084
+ leg.destLabel,
46085
+ leg.destTags,
46086
+ void 0,
46087
+ // a leg's `value:` is leg thickness, not the dest's size
46088
+ leg.lineNumber
46089
+ );
46090
+ if (!destId) continue;
46091
+ legs.push({
46092
+ fromId: prevId,
46093
+ toId: destId,
46094
+ ...leg.label !== void 0 && { label: leg.label },
46095
+ style: leg.style,
46096
+ ...leg.value !== void 0 && { value: leg.value },
46097
+ lineNumber: leg.lineNumber
46098
+ });
46099
+ if (!stopIds.includes(destId)) stopIds.push(destId);
46100
+ prevId = destId;
46101
+ }
46102
+ routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
45888
46103
  }
45889
46104
  const subdivisions = [];
45890
46105
  if (usSubdivisionReferenced || parsed.directives.region === "us-states")
@@ -45896,7 +46111,7 @@ function resolveMap(parsed, data) {
45896
46111
  }
45897
46112
  for (const r of regions) {
45898
46113
  if (r.layer === "country") {
45899
- const bb = featureBbox(data.worldCoarse, r.iso);
46114
+ const bb = featureBboxPrimary(data.worldCoarse, r.iso);
45900
46115
  if (bb) regionBoxes.push(bb);
45901
46116
  }
45902
46117
  }
@@ -45910,6 +46125,7 @@ function resolveMap(parsed, data) {
45910
46125
  const lonSpan = extent2[1][0] - extent2[0][0];
45911
46126
  const latSpan = extent2[1][1] - extent2[0][1];
45912
46127
  const span = Math.max(lonSpan, latSpan);
46128
+ const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
45913
46129
  const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45914
46130
  let projection;
45915
46131
  const override = parsed.directives.projection;
@@ -45917,12 +46133,10 @@ function resolveMap(parsed, data) {
45917
46133
  projection = override;
45918
46134
  } else if (usDominant) {
45919
46135
  projection = "albers-usa";
45920
- } else if (span > WORLD_SPAN) {
46136
+ } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
45921
46137
  projection = "equirectangular";
45922
- } else if (span < MERCATOR_MAX_SPAN) {
45923
- projection = "mercator";
45924
46138
  } else {
45925
- projection = "equirectangular";
46139
+ projection = "mercator";
45926
46140
  }
45927
46141
  if (lonSpan >= 180) {
45928
46142
  extent2 = [
@@ -45976,14 +46190,14 @@ function firstError(diags) {
45976
46190
  const e = diags.find((d) => d.severity === "error");
45977
46191
  return e ? formatDgmoError(e) : null;
45978
46192
  }
45979
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
46193
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
45980
46194
  var init_resolver2 = __esm({
45981
46195
  "src/map/resolver.ts"() {
45982
46196
  "use strict";
45983
46197
  init_diagnostics();
45984
46198
  init_geo();
45985
46199
  WORLD_SPAN = 90;
45986
- MERCATOR_MAX_SPAN = 25;
46200
+ MERCATOR_MAX_LAT = 80;
45987
46201
  PAD_FRACTION = 0.05;
45988
46202
  WORLD_LAT_SOUTH = -58;
45989
46203
  WORLD_LAT_NORTH = 78;
@@ -46008,115 +46222,59 @@ var init_resolver2 = __esm({
46008
46222
  "north macedonia": "macedonia",
46009
46223
  "czech republic": "czechia"
46010
46224
  };
46011
- }
46012
- });
46013
-
46014
- // src/map/load-data.ts
46015
- var load_data_exports = {};
46016
- __export(load_data_exports, {
46017
- loadMapData: () => loadMapData
46018
- });
46019
- async function loadNodeBuiltins() {
46020
- const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
46021
- import("fs/promises"),
46022
- import("url"),
46023
- import("path")
46024
- ]);
46025
- return { readFile, fileURLToPath, dirname, resolve };
46026
- }
46027
- async function readJson(nb, dir, name) {
46028
- return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
46029
- }
46030
- async function firstExistingDir(nb, baseDir) {
46031
- for (const rel of CANDIDATE_DIRS) {
46032
- const dir = nb.resolve(baseDir, rel);
46033
- try {
46034
- await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
46035
- return dir;
46036
- } catch {
46037
- }
46038
- }
46039
- throw new Error(
46040
- `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
46041
- );
46042
- }
46043
- function validate(data) {
46044
- const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
46045
- if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
46046
- throw new Error("map data assets are malformed (failed shape validation)");
46047
- }
46048
- return data;
46049
- }
46050
- function moduleBaseDir(nb) {
46051
- try {
46052
- const url = import_meta.url;
46053
- if (url) return nb.dirname(nb.fileURLToPath(url));
46054
- } catch {
46055
- }
46056
- if (typeof __dirname !== "undefined") return __dirname;
46057
- return process.cwd();
46058
- }
46059
- function loadMapData() {
46060
- cache ??= (async () => {
46061
- const nb = await loadNodeBuiltins();
46062
- const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46063
- const [
46064
- worldCoarse,
46065
- worldDetail,
46066
- usStates,
46067
- lakes,
46068
- rivers,
46069
- naLand,
46070
- naLakes,
46071
- gazetteer
46072
- ] = await Promise.all([
46073
- readJson(nb, dir, FILES.worldCoarse),
46074
- readJson(nb, dir, FILES.worldDetail),
46075
- readJson(nb, dir, FILES.usStates),
46076
- // Lakes/rivers/NA assets are optional — older bundles may predate them.
46077
- readJson(nb, dir, FILES.lakes).catch(() => void 0),
46078
- readJson(nb, dir, FILES.rivers).catch(() => void 0),
46079
- readJson(nb, dir, FILES.naLand).catch(() => void 0),
46080
- readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46081
- readJson(nb, dir, FILES.gazetteer)
46225
+ US_STATE_POSTAL = /* @__PURE__ */ new Set([
46226
+ "AL",
46227
+ "AK",
46228
+ "AZ",
46229
+ "AR",
46230
+ "CA",
46231
+ "CO",
46232
+ "CT",
46233
+ "DE",
46234
+ "FL",
46235
+ "GA",
46236
+ "HI",
46237
+ "ID",
46238
+ "IL",
46239
+ "IN",
46240
+ "IA",
46241
+ "KS",
46242
+ "KY",
46243
+ "LA",
46244
+ "ME",
46245
+ "MD",
46246
+ "MA",
46247
+ "MI",
46248
+ "MN",
46249
+ "MS",
46250
+ "MO",
46251
+ "MT",
46252
+ "NE",
46253
+ "NV",
46254
+ "NH",
46255
+ "NJ",
46256
+ "NM",
46257
+ "NY",
46258
+ "NC",
46259
+ "ND",
46260
+ "OH",
46261
+ "OK",
46262
+ "OR",
46263
+ "PA",
46264
+ "RI",
46265
+ "SC",
46266
+ "SD",
46267
+ "TN",
46268
+ "TX",
46269
+ "UT",
46270
+ "VT",
46271
+ "VA",
46272
+ "WA",
46273
+ "WV",
46274
+ "WI",
46275
+ "WY",
46276
+ "DC"
46082
46277
  ]);
46083
- return validate({
46084
- worldCoarse,
46085
- worldDetail,
46086
- usStates,
46087
- gazetteer,
46088
- ...lakes && { lakes },
46089
- ...rivers && { rivers },
46090
- ...naLand && { naLand },
46091
- ...naLakes && { naLakes }
46092
- });
46093
- })().catch((e) => {
46094
- cache = void 0;
46095
- throw e;
46096
- });
46097
- return cache;
46098
- }
46099
- var import_meta, FILES, CANDIDATE_DIRS, cache;
46100
- var init_load_data = __esm({
46101
- "src/map/load-data.ts"() {
46102
- "use strict";
46103
- import_meta = {};
46104
- FILES = {
46105
- worldCoarse: "world-coarse.json",
46106
- worldDetail: "world-detail.json",
46107
- usStates: "us-states.json",
46108
- lakes: "lakes.json",
46109
- rivers: "rivers.json",
46110
- naLand: "na-land.json",
46111
- naLakes: "na-lakes.json",
46112
- gazetteer: "gazetteer.json"
46113
- };
46114
- CANDIDATE_DIRS = [
46115
- "./data",
46116
- "./map-data",
46117
- "../map-data",
46118
- "../src/map/data"
46119
- ];
46120
46278
  }
46121
46279
  });
46122
46280
 
@@ -46146,8 +46304,19 @@ function projectionFor(family) {
46146
46304
  return (0, import_d3_geo2.geoEquirectangular)();
46147
46305
  }
46148
46306
  }
46149
- function mapBackgroundColor(palette) {
46150
- return mix(palette.colors.blue, palette.bg, WATER_TINT);
46307
+ function mapBackgroundColor(palette, isDark = false, _dataActive = false) {
46308
+ return mix(
46309
+ palette.colors.blue,
46310
+ palette.bg,
46311
+ isDark ? WATER_TINT_DARK : WATER_TINT_LIGHT
46312
+ );
46313
+ }
46314
+ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46315
+ return mix(
46316
+ palette.colors.green,
46317
+ palette.bg,
46318
+ isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46319
+ );
46151
46320
  }
46152
46321
  function layoutMap(resolved, data, size, opts) {
46153
46322
  const { palette, isDark } = opts;
@@ -46168,28 +46337,19 @@ function layoutMap(resolved, data, size, opts) {
46168
46337
  }
46169
46338
  }
46170
46339
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46171
- const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46172
- const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46173
- const water = mapBackgroundColor(palette);
46174
46340
  const usContext = usLayer !== null;
46175
- const foreignFill = mix(
46176
- palette.colors.gray,
46177
- palette.bg,
46178
- isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46179
- );
46180
46341
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46181
- const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
46342
+ const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46182
46343
  const scaleOverride = resolved.directives.scale;
46183
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
46184
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
46185
- const rampHue = palette.colors.red;
46186
- const hasRamp = scores.length > 0;
46187
- const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46344
+ const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46345
+ const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
46346
+ const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46347
+ const hasRamp = values.length > 0;
46348
+ const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
46188
46349
  const matchColorGroup = (v) => {
46189
46350
  const lv = v.trim().toLowerCase();
46190
46351
  if (lv === "none") return null;
46191
- if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46192
- return SCORE_NAME;
46352
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
46193
46353
  const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46194
46354
  return tg ? tg.name : v;
46195
46355
  };
@@ -46200,11 +46360,20 @@ function layoutMap(resolved, data, size, opts) {
46200
46360
  } else if (resolved.directives.activeTag !== void 0) {
46201
46361
  activeGroup = matchColorGroup(resolved.directives.activeTag);
46202
46362
  } else {
46203
- activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46363
+ activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46204
46364
  }
46205
- const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46365
+ const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46366
+ const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
46367
+ const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46368
+ const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46369
+ const lakeStroke = mix(regionStroke, water, 45);
46370
+ const foreignFill = mix(
46371
+ palette.colors.gray,
46372
+ palette.bg,
46373
+ mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46374
+ );
46206
46375
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46207
- const fillForScore = (s) => {
46376
+ const fillForValue = (s) => {
46208
46377
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46209
46378
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
46210
46379
  return mix(rampHue, rampBase, pct);
@@ -46227,9 +46396,16 @@ function layoutMap(resolved, data, size, opts) {
46227
46396
  isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46228
46397
  );
46229
46398
  };
46399
+ const directFill = (name) => {
46400
+ const hex = name ? resolveColor(name, palette) : null;
46401
+ if (!hex) return null;
46402
+ return mix(hex, palette.bg, isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT);
46403
+ };
46230
46404
  const regionFill = (r) => {
46405
+ const direct = directFill(r.color);
46406
+ if (direct) return direct;
46231
46407
  if (activeIsScore) {
46232
- return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46408
+ return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46233
46409
  }
46234
46410
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46235
46411
  };
@@ -46281,6 +46457,7 @@ function layoutMap(resolved, data, size, opts) {
46281
46457
  const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46282
46458
  let path;
46283
46459
  let project;
46460
+ let stretchParams = null;
46284
46461
  if (fitIsGlobal) {
46285
46462
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46286
46463
  const bx0 = cb[0][0];
@@ -46291,6 +46468,7 @@ function layoutMap(resolved, data, size, opts) {
46291
46468
  const oy = fitBox[0][1];
46292
46469
  const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46293
46470
  const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46471
+ stretchParams = { sx, sy, ox, oy, bx0, by0 };
46294
46472
  const stretch = (x, y) => [
46295
46473
  ox + (x - bx0) * sx,
46296
46474
  oy + (y - by0) * sy
@@ -46322,7 +46500,7 @@ function layoutMap(resolved, data, size, opts) {
46322
46500
  const insets = [];
46323
46501
  const insetRegions = [];
46324
46502
  const insetLabelSeeds = [];
46325
- if (resolved.projection === "albers-usa" && usLayer) {
46503
+ if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
46326
46504
  const PAD = 8;
46327
46505
  const GAP = 12;
46328
46506
  const yB = height - FIT_PAD;
@@ -46353,38 +46531,14 @@ function layoutMap(resolved, data, size, opts) {
46353
46531
  }
46354
46532
  return y;
46355
46533
  };
46356
- const coastTop = (x0, xr) => {
46534
+ const coastFloor = (x0, xr) => {
46357
46535
  const n = 24;
46358
- const pts = [];
46359
46536
  let maxY = -Infinity;
46360
46537
  for (let i = 0; i <= n; i++) {
46361
- const x = x0 + (xr - x0) * i / n;
46362
- const y = at(x);
46363
- if (y > -Infinity) {
46364
- pts.push([x, y]);
46365
- if (y > maxY) maxY = y;
46366
- }
46367
- }
46368
- if (pts.length === 0) return () => yB - height * 0.42;
46369
- let m = 0;
46370
- if (pts.length >= 2) {
46371
- let sx = 0, sy = 0, sxx = 0, sxy = 0;
46372
- for (const [x, y] of pts) {
46373
- sx += x;
46374
- sy += y;
46375
- sxx += x * x;
46376
- sxy += x * y;
46377
- }
46378
- const den = pts.length * sxx - sx * sx;
46379
- if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46380
- }
46381
- m = Math.max(-0.35, Math.min(0.35, m));
46382
- let c = -Infinity;
46383
- for (const [x, y] of pts) {
46384
- const need = y - m * x + GAP;
46385
- if (need > c) c = need;
46386
- }
46387
- return (x) => m * x + c;
46538
+ const y = at(x0 + (xr - x0) * i / n);
46539
+ if (y > maxY) maxY = y;
46540
+ }
46541
+ return maxY;
46388
46542
  };
46389
46543
  const placeInset = (iso, proj, boxX, iwReq) => {
46390
46544
  const f = usLayer.get(iso);
@@ -46393,19 +46547,15 @@ function layoutMap(resolved, data, size, opts) {
46393
46547
  const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46394
46548
  if (iw < 24) return boxX;
46395
46549
  const xr = x0 + iw + 2 * PAD;
46396
- const top = coastTop(x0, xr);
46397
- const yL = top(x0);
46398
- const yR = top(xr);
46550
+ const floor = coastFloor(x0, xr);
46551
+ const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
46399
46552
  proj.fitWidth(iw, f);
46400
46553
  const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
46401
46554
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46402
46555
  const needH = sh + 2 * PAD;
46403
- let topFit = Math.max(yL, yR);
46556
+ let topFit = topGuess;
46404
46557
  const bottom = Math.min(topFit + needH, yB);
46405
46558
  if (bottom - topFit < needH) topFit = bottom - needH;
46406
- const lift = topFit - Math.max(yL, yR);
46407
- const topL = yL + lift;
46408
- const topR = yR + lift;
46409
46559
  proj.fitExtent(
46410
46560
  [
46411
46561
  [x0 + PAD, topFit + PAD],
@@ -46424,15 +46574,18 @@ function layoutMap(resolved, data, size, opts) {
46424
46574
  }
46425
46575
  insets.push({
46426
46576
  x: x0,
46427
- y: Math.min(topL, topR),
46577
+ y: topFit,
46428
46578
  w: xr - x0,
46429
- h: bottom - Math.min(topL, topR),
46579
+ h: bottom - topFit,
46430
46580
  points: [
46431
- [x0, topL],
46432
- [xr, topR],
46581
+ [x0, topFit],
46582
+ [xr, topFit],
46433
46583
  [xr, bottom],
46434
46584
  [x0, bottom]
46435
- ]
46585
+ ],
46586
+ // The FITTED inset projection (just fit to this box) — captured so the
46587
+ // geo-query can invert pixels inside the frame back to AK/HI coords.
46588
+ projection: proj
46436
46589
  });
46437
46590
  insetRegions.push({
46438
46591
  id: iso,
@@ -46441,7 +46594,7 @@ function layoutMap(resolved, data, size, opts) {
46441
46594
  stroke: regionStroke,
46442
46595
  lineNumber,
46443
46596
  layer: "us-state",
46444
- ...r?.score !== void 0 && { score: r.score },
46597
+ ...r?.value !== void 0 && { value: r.value },
46445
46598
  ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46446
46599
  });
46447
46600
  const ctr = (0, import_d3_geo2.geoPath)(proj).centroid(f);
@@ -46584,7 +46737,7 @@ function layoutMap(resolved, data, size, opts) {
46584
46737
  lineNumber,
46585
46738
  layer,
46586
46739
  ...label !== void 0 && { label },
46587
- ...isThisLayer && r.score !== void 0 && { score: r.score },
46740
+ ...isThisLayer && r.value !== void 0 && { value: r.value },
46588
46741
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46589
46742
  });
46590
46743
  }
@@ -46602,13 +46755,40 @@ function layoutMap(resolved, data, size, opts) {
46602
46755
  id: "lake",
46603
46756
  d,
46604
46757
  fill: water,
46605
- stroke: "none",
46758
+ stroke: lakeStroke,
46606
46759
  lineNumber: -1,
46607
46760
  layer: "base"
46608
46761
  });
46609
46762
  }
46610
46763
  }
46611
- const riverColor = water;
46764
+ const relief = [];
46765
+ let reliefHatch = null;
46766
+ if (resolved.directives.relief === true && data.mountainRanges) {
46767
+ for (const [, f] of decodeLayer(data.mountainRanges)) {
46768
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46769
+ if (!viewF) continue;
46770
+ const area2 = path.area(viewF);
46771
+ if (!Number.isFinite(area2) || area2 < RELIEF_MIN_AREA) continue;
46772
+ const box = path.bounds(viewF);
46773
+ if (box[1][0] - box[0][0] < RELIEF_MIN_DIM || box[1][1] - box[0][1] < RELIEF_MIN_DIM)
46774
+ continue;
46775
+ const d = path(viewF) ?? "";
46776
+ if (!d) continue;
46777
+ relief.push({ d });
46778
+ }
46779
+ if (relief.length) {
46780
+ const darkTone = isDark ? palette.bg : palette.text;
46781
+ const lightTone = isDark ? palette.text : palette.bg;
46782
+ const landLum = relativeLuminance(neutralFill);
46783
+ const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46784
+ reliefHatch = {
46785
+ color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
46786
+ spacing: RELIEF_HATCH_SPACING,
46787
+ width: RELIEF_HATCH_WIDTH
46788
+ };
46789
+ }
46790
+ }
46791
+ const riverColor = mix(water, regionStroke, 16);
46612
46792
  const rivers = [];
46613
46793
  if (data.rivers) {
46614
46794
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46619,16 +46799,19 @@ function layoutMap(resolved, data, size, opts) {
46619
46799
  rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46620
46800
  }
46621
46801
  }
46622
- const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46802
+ const sizeVals = resolved.pois.map((p) => Number(p.meta["value"])).filter((n) => Number.isFinite(n) && n > 0);
46623
46803
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46624
46804
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
46625
46805
  const radiusFor = (p) => {
46626
- const v = Number(p.meta["size"]);
46806
+ const v = Number(p.meta["value"]);
46627
46807
  if (!Number.isFinite(v) || v <= 0 || sizeMax <= 0) return R_DEFAULT;
46628
46808
  const t = sizeMax > sizeMin ? (Math.sqrt(v) - Math.sqrt(sizeMin)) / (Math.sqrt(sizeMax) - Math.sqrt(sizeMin)) : 1;
46629
46809
  return R_MIN + Math.max(0, Math.min(1, t)) * (R_MAX - R_MIN);
46630
46810
  };
46631
46811
  const poiFill = (p) => {
46812
+ const directHex = p.color ? resolveColor(p.color, palette) : null;
46813
+ if (directHex)
46814
+ return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
46632
46815
  for (const group of resolved.tagGroups) {
46633
46816
  const val = p.tags[group.name.toLowerCase()];
46634
46817
  if (!val) continue;
@@ -46690,7 +46873,8 @@ function layoutMap(resolved, data, size, opts) {
46690
46873
  lineNumber: e.p.lineNumber,
46691
46874
  implicit: !!e.p.implicit,
46692
46875
  isOrigin: originIds.has(e.p.id),
46693
- ...num !== void 0 && { routeNumber: num }
46876
+ ...num !== void 0 && { routeNumber: num },
46877
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
46694
46878
  });
46695
46879
  });
46696
46880
  }
@@ -46726,26 +46910,40 @@ function layoutMap(resolved, data, size, opts) {
46726
46910
  const by = b.cy - (b.cy - py) / tb * trimB;
46727
46911
  return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46728
46912
  };
46913
+ const routeLegVals = resolved.routes.flatMap((rt) => rt.legs).map((l) => Number(l.value)).filter((n) => Number.isFinite(n) && n > 0);
46914
+ const rlMin = routeLegVals.length ? Math.min(...routeLegVals) : 0;
46915
+ const rlMax = routeLegVals.length ? Math.max(...routeLegVals) : 0;
46916
+ const routeWidthFor = (v) => {
46917
+ if (!Number.isFinite(v) || v <= 0 || rlMax <= 0) return W_MIN;
46918
+ const t = rlMax > rlMin ? (v - rlMin) / (rlMax - rlMin) : 1;
46919
+ return W_MIN + t * (W_MAX - W_MIN);
46920
+ };
46729
46921
  for (const rt of resolved.routes) {
46730
- const curved = rt.meta["style"] === "arc";
46731
- for (let i = 1; i < rt.stopIds.length; i++) {
46732
- const a = poiScreen.get(rt.stopIds[i - 1]);
46733
- const b = poiScreen.get(rt.stopIds[i]);
46922
+ for (const leg of rt.legs) {
46923
+ const a = poiScreen.get(leg.fromId);
46924
+ const b = poiScreen.get(leg.toId);
46734
46925
  if (!a || !b) continue;
46926
+ const mx = (a.cx + b.cx) / 2;
46927
+ const my = (a.cy + b.cy) / 2;
46735
46928
  legs.push({
46736
- d: legPath(a, b, curved, 0),
46737
- width: W_MIN,
46929
+ d: legPath(a, b, leg.style === "arc", 0),
46930
+ width: routeWidthFor(Number(leg.value)),
46738
46931
  color: mix(palette.text, palette.bg, 72),
46739
46932
  arrow: true,
46740
- lineNumber: rt.lineNumber
46933
+ lineNumber: leg.lineNumber,
46934
+ ...leg.label !== void 0 && {
46935
+ label: leg.label,
46936
+ labelX: mx,
46937
+ labelY: my - 4
46938
+ }
46741
46939
  });
46742
46940
  }
46743
46941
  }
46744
- const weightVals = resolved.edges.map((e) => Number(e.meta["weight"])).filter((n) => Number.isFinite(n) && n > 0);
46942
+ const weightVals = resolved.edges.map((e) => Number(e.meta["value"])).filter((n) => Number.isFinite(n) && n > 0);
46745
46943
  const wMin = weightVals.length ? Math.min(...weightVals) : 0;
46746
46944
  const wMax = weightVals.length ? Math.max(...weightVals) : 0;
46747
46945
  const widthFor = (e) => {
46748
- const v = Number(e.meta["weight"]);
46946
+ const v = Number(e.meta["value"]);
46749
46947
  if (!Number.isFinite(v) || v <= 0 || wMax <= 0) return W_MIN;
46750
46948
  const t = wMax > wMin ? (v - wMin) / (wMax - wMin) : 1;
46751
46949
  return W_MIN + t * (W_MAX - W_MIN);
@@ -47008,8 +47206,8 @@ function layoutMap(resolved, data, size, opts) {
47008
47206
  activeGroup,
47009
47207
  ...hasRamp && {
47010
47208
  ramp: {
47011
- ...resolved.directives.metric !== void 0 && {
47012
- metric: resolved.directives.metric
47209
+ ...resolved.directives.regionMetric !== void 0 && {
47210
+ metric: resolved.directives.regionMetric
47013
47211
  },
47014
47212
  min: rampMin,
47015
47213
  max: rampMax,
@@ -47029,21 +47227,26 @@ function layoutMap(resolved, data, size, opts) {
47029
47227
  ...resolved.caption !== void 0 && { caption: resolved.caption },
47030
47228
  regions,
47031
47229
  rivers,
47230
+ relief,
47231
+ reliefHatch,
47032
47232
  legs,
47033
47233
  pois,
47034
47234
  labels,
47035
47235
  legend,
47036
47236
  insets,
47037
- insetRegions
47237
+ insetRegions,
47238
+ projection,
47239
+ stretch: stretchParams
47038
47240
  };
47039
47241
  }
47040
- var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47242
+ var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
47041
47243
  var init_layout15 = __esm({
47042
47244
  "src/map/layout.ts"() {
47043
47245
  "use strict";
47044
47246
  import_d3_geo2 = require("d3-geo");
47045
47247
  import_topojson_client2 = require("topojson-client");
47046
47248
  init_color_utils();
47249
+ init_colors();
47047
47250
  init_label_layout();
47048
47251
  init_legend_constants();
47049
47252
  init_title_constants();
@@ -47056,14 +47259,22 @@ var init_layout15 = __esm({
47056
47259
  W_MAX = 8;
47057
47260
  FONT = 11;
47058
47261
  COLO_EPS = 1.5;
47059
- LAND_TINT_LIGHT = 58;
47060
- LAND_TINT_DARK = 75;
47262
+ LAND_TINT_LIGHT = 12;
47263
+ LAND_TINT_DARK = 24;
47061
47264
  TAG_TINT_LIGHT = 60;
47062
47265
  TAG_TINT_DARK = 68;
47063
- WATER_TINT = 55;
47266
+ WATER_TINT_LIGHT = 13;
47267
+ WATER_TINT_DARK = 14;
47064
47268
  RIVER_WIDTH = 1.3;
47269
+ RELIEF_MIN_AREA = 12;
47270
+ RELIEF_MIN_DIM = 2;
47271
+ RELIEF_HATCH_SPACING = 3;
47272
+ RELIEF_HATCH_WIDTH = 0.25;
47273
+ RELIEF_HATCH_STRENGTH = 32;
47065
47274
  FOREIGN_TINT_LIGHT = 30;
47066
47275
  FOREIGN_TINT_DARK = 62;
47276
+ MUTED_FOREIGN_LIGHT = 28;
47277
+ MUTED_FOREIGN_DARK = 16;
47067
47278
  COLO_R = 9;
47068
47279
  GOLDEN_ANGLE = 2.399963229728653;
47069
47280
  FAN_STEP = 16;
@@ -47117,7 +47328,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47117
47328
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47118
47329
  if (r.layer !== "base") {
47119
47330
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47120
- if (r.score !== void 0) p.attr("data-score", r.score);
47331
+ if (r.value !== void 0) p.attr("data-value", r.value);
47121
47332
  if (r.tags) {
47122
47333
  for (const [group, value] of Object.entries(r.tags)) {
47123
47334
  p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47135,6 +47346,20 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47135
47346
  }
47136
47347
  };
47137
47348
  for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47349
+ if (layout.relief.length && layout.reliefHatch) {
47350
+ const h = layout.reliefHatch;
47351
+ const rangeClipId = "dgmo-relief-clip";
47352
+ const landClipId = "dgmo-relief-land";
47353
+ const rangeClip = defs.append("clipPath").attr("id", rangeClipId);
47354
+ for (const s of layout.relief) rangeClip.append("path").attr("d", s.d);
47355
+ const landClip = defs.append("clipPath").attr("id", landClipId);
47356
+ for (const r of layout.regions)
47357
+ if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47358
+ 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");
47359
+ for (let y = h.spacing; y < height; y += h.spacing) {
47360
+ gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47361
+ }
47362
+ }
47138
47363
  if (layout.rivers.length) {
47139
47364
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47140
47365
  for (const r of layout.rivers) {
@@ -47178,6 +47403,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47178
47403
  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);
47179
47404
  }
47180
47405
  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);
47406
+ if (poi.tags) {
47407
+ for (const [group, value] of Object.entries(poi.tags)) {
47408
+ c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47409
+ }
47410
+ }
47181
47411
  if (onClickItem) {
47182
47412
  c.style("cursor", "pointer").on(
47183
47413
  "click",
@@ -47227,7 +47457,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47227
47457
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
47228
47458
  const ramp = layout.legend.ramp;
47229
47459
  const scoreGroup = ramp ? {
47230
- name: ramp.metric?.trim() || "Score",
47460
+ name: ramp.metric?.trim() || "Value",
47231
47461
  entries: [],
47232
47462
  gradient: {
47233
47463
  min: ramp.min,
@@ -47254,7 +47484,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47254
47484
  }
47255
47485
  }
47256
47486
  if (layout.title) {
47257
- 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);
47487
+ 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);
47258
47488
  }
47259
47489
  if (layout.subtitle) {
47260
47490
  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);
@@ -47287,6 +47517,121 @@ var init_renderer16 = __esm({
47287
47517
  }
47288
47518
  });
47289
47519
 
47520
+ // src/map/load-data.ts
47521
+ var load_data_exports = {};
47522
+ __export(load_data_exports, {
47523
+ loadMapData: () => loadMapData
47524
+ });
47525
+ async function loadNodeBuiltins() {
47526
+ const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
47527
+ import("fs/promises"),
47528
+ import("url"),
47529
+ import("path")
47530
+ ]);
47531
+ return { readFile, fileURLToPath, dirname, resolve };
47532
+ }
47533
+ async function readJson(nb, dir, name) {
47534
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
47535
+ }
47536
+ async function firstExistingDir(nb, baseDir) {
47537
+ for (const rel of CANDIDATE_DIRS) {
47538
+ const dir = nb.resolve(baseDir, rel);
47539
+ try {
47540
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
47541
+ return dir;
47542
+ } catch {
47543
+ }
47544
+ }
47545
+ throw new Error(
47546
+ `map data assets not found near ${baseDir} (looked in ${CANDIDATE_DIRS.join(", ")}). Run \`pnpm build:map-data\` and \`pnpm build\`.`
47547
+ );
47548
+ }
47549
+ function validate(data) {
47550
+ const topoOk = (t) => !!t && t.type === "Topology" && !!t.objects;
47551
+ if (!topoOk(data.worldCoarse) || !topoOk(data.worldDetail) || !topoOk(data.usStates) || !data.gazetteer || !Array.isArray(data.gazetteer.cities) || !data.gazetteer.byName) {
47552
+ throw new Error("map data assets are malformed (failed shape validation)");
47553
+ }
47554
+ return data;
47555
+ }
47556
+ function moduleBaseDir(nb) {
47557
+ try {
47558
+ const url = import_meta.url;
47559
+ if (url) return nb.dirname(nb.fileURLToPath(url));
47560
+ } catch {
47561
+ }
47562
+ if (typeof __dirname !== "undefined") return __dirname;
47563
+ return process.cwd();
47564
+ }
47565
+ function loadMapData() {
47566
+ cache ??= (async () => {
47567
+ const nb = await loadNodeBuiltins();
47568
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
47569
+ const [
47570
+ worldCoarse,
47571
+ worldDetail,
47572
+ usStates,
47573
+ lakes,
47574
+ rivers,
47575
+ mountainRanges,
47576
+ naLand,
47577
+ naLakes,
47578
+ gazetteer
47579
+ ] = await Promise.all([
47580
+ readJson(nb, dir, FILES.worldCoarse),
47581
+ readJson(nb, dir, FILES.worldDetail),
47582
+ readJson(nb, dir, FILES.usStates),
47583
+ // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
47584
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
47585
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
47586
+ readJson(nb, dir, FILES.mountainRanges).catch(
47587
+ () => void 0
47588
+ ),
47589
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
47590
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
47591
+ readJson(nb, dir, FILES.gazetteer)
47592
+ ]);
47593
+ return validate({
47594
+ worldCoarse,
47595
+ worldDetail,
47596
+ usStates,
47597
+ gazetteer,
47598
+ ...lakes && { lakes },
47599
+ ...rivers && { rivers },
47600
+ ...mountainRanges && { mountainRanges },
47601
+ ...naLand && { naLand },
47602
+ ...naLakes && { naLakes }
47603
+ });
47604
+ })().catch((e) => {
47605
+ cache = void 0;
47606
+ throw e;
47607
+ });
47608
+ return cache;
47609
+ }
47610
+ var import_meta, FILES, CANDIDATE_DIRS, cache;
47611
+ var init_load_data = __esm({
47612
+ "src/map/load-data.ts"() {
47613
+ "use strict";
47614
+ import_meta = {};
47615
+ FILES = {
47616
+ worldCoarse: "world-coarse.json",
47617
+ worldDetail: "world-detail.json",
47618
+ usStates: "us-states.json",
47619
+ lakes: "lakes.json",
47620
+ rivers: "rivers.json",
47621
+ mountainRanges: "mountain-ranges.json",
47622
+ naLand: "na-land.json",
47623
+ naLakes: "na-lakes.json",
47624
+ gazetteer: "gazetteer.json"
47625
+ };
47626
+ CANDIDATE_DIRS = [
47627
+ "./data",
47628
+ "./map-data",
47629
+ "../map-data",
47630
+ "../src/map/data"
47631
+ ];
47632
+ }
47633
+ });
47634
+
47290
47635
  // src/pyramid/renderer.ts
47291
47636
  var renderer_exports17 = {};
47292
47637
  __export(renderer_exports17, {
@@ -55410,15 +55755,17 @@ async function renderForExport(content, theme, palette, viewState, options) {
55410
55755
  if (detectedType === "map") {
55411
55756
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55412
55757
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55413
- const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55414
55758
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
55415
55759
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55416
55760
  const mapParsed = parseMap2(content);
55417
- let mapData;
55418
- try {
55419
- mapData = await loadMapData2();
55420
- } catch {
55421
- return "";
55761
+ let mapData = options?.mapData;
55762
+ if (!mapData) {
55763
+ const { loadMapData: loadMapData2 } = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
55764
+ try {
55765
+ mapData = await loadMapData2();
55766
+ } catch {
55767
+ return "";
55768
+ }
55422
55769
  }
55423
55770
  const mapResolved = resolveMap2(mapParsed, mapData);
55424
55771
  const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);