@tileflow/core 0.1.0-alpha.37 → 0.1.0-alpha.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -850,8 +850,11 @@ var init_time_of_day = __esm({
850
850
  });
851
851
 
852
852
  // src/themes/visual-semantics.ts
853
+ function isTileflowVisualColorCollection(property) {
854
+ return property === "colors" || property.endsWith("Colors") || property.endsWith("-colors");
855
+ }
853
856
  function classifyTileflowVisualProperty(property, _value) {
854
- if (property === "color" || property === "colors" || property.endsWith("Color") || property.endsWith("Colors") || property.endsWith("-color") || property.endsWith("-colors")) {
857
+ if (property === "color" || property.endsWith("Color") || property.endsWith("-color") || isTileflowVisualColorCollection(property)) {
855
858
  return "color";
856
859
  }
857
860
  if (property === "font" || property === "fallbacks" || property.endsWith("-font")) return "font";
@@ -1218,15 +1221,19 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1218
1221
  }
1219
1222
  return;
1220
1223
  }
1224
+ const collectionCategory = isTileflowVisualColorCollection(lastPathKey(path)) ? category : void 0;
1221
1225
  for (const key of Object.keys(value).sort(compareCodeUnits2)) {
1222
1226
  visitAuditableValue(
1223
1227
  value[key],
1224
1228
  `${path}.${key}`,
1225
- classifyTileflowVisualProperty(key, value[key]),
1229
+ classifyTileflowVisualProperty(key, value[key]) ?? collectionCategory,
1226
1230
  state
1227
1231
  );
1228
1232
  }
1229
1233
  }
1234
+ function lastPathKey(path) {
1235
+ return path.endsWith("]") ? "" : path.slice(path.lastIndexOf(".") + 1);
1236
+ }
1230
1237
  function visitAuditableArray(value, path, category, state, forceExpression = false, intentRequired = false) {
1231
1238
  const operator = typeof value[0] === "string" ? value[0] : void 0;
1232
1239
  const isExpression = forceExpression || isMapLibreExpressionOperator(operator);
@@ -3655,6 +3662,7 @@ function readTileflowBuildings3D(metadata) {
3655
3662
  ...floodLight ? { floodLight } : {},
3656
3663
  ...modelLighting ? { modelLighting } : {},
3657
3664
  windows: value.windows === true,
3665
+ ...readTileflowBuildingsEntrance(value.entrance),
3658
3666
  shadows: typeof value.shadows === "boolean" ? value.shadows : defaults4.shadows,
3659
3667
  occlusion: typeof value.occlusion === "boolean" ? value.occlusion : defaults4.occlusion,
3660
3668
  modelOcclusion: within(value.modelOcclusion, 1, defaults4.modelOcclusion),
@@ -3663,6 +3671,13 @@ function readTileflowBuildings3D(metadata) {
3663
3671
  ...landmarksOf(value.landmarks)
3664
3672
  };
3665
3673
  }
3674
+ function readTileflowBuildingsEntrance(value) {
3675
+ if (!isRecord(value)) return {};
3676
+ const band = (entry) => Array.isArray(entry) && entry.length === 2 && entry.every((zoom3) => typeof zoom3 === "number" && zoom3 >= 0 && zoom3 <= 24) && entry[0] < entry[1] ? [entry[0], entry[1]] : void 0;
3677
+ const buildings2 = band(value.buildings);
3678
+ const models = band(value.models);
3679
+ return buildings2 || models ? { entrance: { ...buildings2 ? { buildings: buildings2 } : {}, ...models ? { models } : {} } } : {};
3680
+ }
3666
3681
  var modelId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
3667
3682
  var tileflowBuildingModelPlacementLimit = 256;
3668
3683
  var teamModelPrefix = "tileflow://team-models/";
@@ -3872,6 +3887,7 @@ function tileflowWorldV1Schema(options = {}) {
3872
3887
  sidewalk: "sidewalk",
3873
3888
  streetFurniture: "street_furniture",
3874
3889
  trafficControl: "traffic_control",
3890
+ transportationOutline: "transportation_outline",
3875
3891
  ...options.layers
3876
3892
  },
3877
3893
  semantics: { ...options.semantics, parkLayer: "protected-only" }
@@ -3891,6 +3907,11 @@ function validateTileflowWorldV1Tilejson(tilejson, schema = tileflowWorldV1Schem
3891
3907
  const roadShield = requireTilejsonLayer(layers2, schema.layers.roadShield, issues);
3892
3908
  const canopy = optionalTilejsonLayer(layers2, schema.layers.canopy, issues);
3893
3909
  const trafficControl = optionalTilejsonLayer(layers2, schema.layers.trafficControl, issues);
3910
+ const transportationOutline = requireTilejsonLayer(
3911
+ layers2,
3912
+ schema.layers.transportationOutline,
3913
+ issues
3914
+ );
3894
3915
  if (bathymetry2) {
3895
3916
  if (bathymetry2.minzoom !== 0 || bathymetry2.maxzoom !== 9) {
3896
3917
  issues.push(`Tileflow World V1 ${schema.layers.bathymetry} must declare z0-z9.`);
@@ -4081,6 +4102,27 @@ function validateTileflowWorldV1Tilejson(tilejson, schema = tileflowWorldV1Schem
4081
4102
  requireTilejsonField(canopy, schema.layers.canopy, field2, ["Number"], issues);
4082
4103
  }
4083
4104
  }
4105
+ if (transportationOutline) {
4106
+ if (transportationOutline.minzoom !== 13 || transportationOutline.maxzoom !== 15) {
4107
+ issues.push(
4108
+ `Tileflow World V1 ${schema.layers.transportationOutline} must declare native z13-z15.`
4109
+ );
4110
+ }
4111
+ requireTilejsonField(
4112
+ transportationOutline,
4113
+ schema.layers.transportationOutline,
4114
+ schema.fields.class,
4115
+ ["String"],
4116
+ issues
4117
+ );
4118
+ requireTilejsonField(
4119
+ transportationOutline,
4120
+ schema.layers.transportationOutline,
4121
+ schema.fields.subclass,
4122
+ ["String"],
4123
+ issues
4124
+ );
4125
+ }
4084
4126
  return issues;
4085
4127
  }
4086
4128
  function requireTilejsonLayer(layers2, id2, issues) {
@@ -5045,7 +5087,7 @@ function applyLineStyle(layer, style) {
5045
5087
  const layout = { ...asRecord2(layer.layout) };
5046
5088
  setPaint(paint, "line-blur", style.blur);
5047
5089
  setPaint(paint, "line-color", style.color);
5048
- setPaint(paint, "line-dasharray", style.dash);
5090
+ if (!isSolidDash(style.dash)) setPaint(paint, "line-dasharray", style.dash);
5049
5091
  setPaint(paint, "line-gap-width", style.gapWidth);
5050
5092
  setPaint(paint, "line-offset", style.offset);
5051
5093
  setPaint(paint, "line-opacity", style.opacity);
@@ -5289,6 +5331,12 @@ function withoutGovernedAllowOverlap(layout) {
5289
5331
  }
5290
5332
  return result;
5291
5333
  }
5334
+ function isSolidDash(value) {
5335
+ if (value === void 0) return false;
5336
+ const dash = toMapLibreStyleValue(value);
5337
+ const array = Array.isArray(dash) && dash[0] === "literal" ? dash[1] : dash;
5338
+ return Array.isArray(array) && array.length === 2 && array[0] === 1 && array[1] === 0;
5339
+ }
5292
5340
  function setPaint(paint, property, value) {
5293
5341
  if (value !== void 0) paint[property] = toMapLibreStyleValue(value);
5294
5342
  }
@@ -5816,6 +5864,21 @@ function compileBuildings(request, context) {
5816
5864
  }
5817
5865
  return contributions;
5818
5866
  }
5867
+ var buildingTargetsByMode = {
5868
+ flat: ["buildings.flat.fill", "buildings.flat.outline"],
5869
+ "3d": ["buildings.extrusion"]
5870
+ };
5871
+ function buildingTargetsOfOtherModes(request) {
5872
+ const activeMode = request?.mode ?? "flat";
5873
+ const targets = /* @__PURE__ */ new Map();
5874
+ for (const [mode, modeTargets] of Object.entries(buildingTargetsByMode)) {
5875
+ if (mode === activeMode) continue;
5876
+ for (const target of modeTargets) {
5877
+ targets.set(target, { activeMode, mode, owner: "buildings" });
5878
+ }
5879
+ }
5880
+ return targets;
5881
+ }
5819
5882
  function buildingColorExpression(context) {
5820
5883
  const fields2 = context.data.schema.fields;
5821
5884
  const colors = context.colors.buildings;
@@ -5933,29 +5996,23 @@ function isTileflowPathRoadClass(roadClass) {
5933
5996
  return tileflowPathRoadClasses.includes(roadClass);
5934
5997
  }
5935
5998
  function tileflowRoadClassFilter(fields2, roadClass) {
5999
+ const classes = tileflowRoadClassValues(roadClass);
5936
6000
  if (isTileflowPathRoadClass(roadClass)) {
5937
6001
  return [
5938
6002
  "all",
5939
- ["match", ["get", fields2.class], ["path", ...constructionClasses[roadClass]], true, false],
6003
+ ["match", ["get", fields2.class], classes, true, false],
5940
6004
  ["match", ["get", fields2.subclass], pathSubclasses[roadClass], true, false]
5941
6005
  ];
5942
6006
  }
5943
- const classes = roadClass === "minor" ? [
5944
- "minor",
5945
- "residential",
5946
- "unclassified",
5947
- ...compatibleRoadClasses[roadClass] ?? [],
5948
- ...constructionClasses[roadClass]
5949
- ] : roadClass === "service" ? [
5950
- "service",
5951
- ...compatibleRoadClasses[roadClass] ?? [],
5952
- ...constructionClasses[roadClass]
5953
- ] : [
5954
- roadClass,
6007
+ return ["match", ["get", fields2.class], classes, true, false];
6008
+ }
6009
+ function tileflowRoadClassValues(roadClass) {
6010
+ if (isTileflowPathRoadClass(roadClass)) return ["path", ...constructionClasses[roadClass]];
6011
+ return [
6012
+ ...roadClass === "minor" ? ["minor", "residential", "unclassified"] : [roadClass],
5955
6013
  ...compatibleRoadClasses[roadClass] ?? [],
5956
6014
  ...constructionClasses[roadClass]
5957
6015
  ];
5958
- return ["match", ["get", fields2.class], classes, true, false];
5959
6016
  }
5960
6017
 
5961
6018
  // src/modules/roads/index.ts
@@ -9689,35 +9746,37 @@ function compileRoundabouts(request, surfaces, context, contributions) {
9689
9746
  }),
9690
9747
  0
9691
9748
  ) : atZoom(borderWidth, level);
9692
- const geometry = (casing, width) => expression([
9693
- "interpolate",
9694
- ["exponential", 2],
9695
- ["zoom"],
9696
- ...geometryLevels.flatMap((level) => {
9697
- const centerline = ["*", radiusAt15, 2 ** (level - 15)];
9698
- const fallback = fallbackWidth === void 0 ? select(
9699
- widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
9700
- 0
9701
- ) : atZoom(fallbackWidth, level);
9702
- const inner = [
9703
- "case",
9704
- physical,
9705
- ["*", centerline, ["/", innerMeters, radiusMeters]],
9706
- ["-", centerline, ["/", fallback, 2]]
9707
- ];
9708
- const fillWidth = [
9709
- "case",
9710
- physical,
9711
- ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
9712
- fallback
9713
- ];
9714
- const border = casing ? borderAt(level) : 0;
9715
- return [
9716
- level,
9717
- width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
9718
- ];
9719
- })
9720
- ]);
9749
+ const geometry = (casing, width) => expression(
9750
+ bindRepeated([
9751
+ "interpolate",
9752
+ ["exponential", 2],
9753
+ ["zoom"],
9754
+ ...geometryLevels.flatMap((level) => {
9755
+ const centerline = ["*", radiusAt15, 2 ** (level - 15)];
9756
+ const fallback = fallbackWidth === void 0 ? select(
9757
+ widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
9758
+ 0
9759
+ ) : atZoom(fallbackWidth, level);
9760
+ const inner = [
9761
+ "case",
9762
+ physical,
9763
+ ["*", centerline, ["/", innerMeters, radiusMeters]],
9764
+ ["-", centerline, ["/", fallback, 2]]
9765
+ ];
9766
+ const fillWidth = [
9767
+ "case",
9768
+ physical,
9769
+ ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
9770
+ fallback
9771
+ ];
9772
+ const border = casing ? borderAt(level) : 0;
9773
+ return [
9774
+ level,
9775
+ width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
9776
+ ];
9777
+ })
9778
+ ])
9779
+ );
9721
9780
  const phaseStyle = (phase) => ({
9722
9781
  color: "rgba(0, 0, 0, 0)",
9723
9782
  minZoom: 15,
@@ -9774,6 +9833,63 @@ function compileRoundabouts(request, surfaces, context, contributions) {
9774
9833
  });
9775
9834
  }
9776
9835
  }
9836
+ var bindable = /* @__PURE__ */ new Set([
9837
+ "case",
9838
+ "match",
9839
+ "all",
9840
+ "any",
9841
+ "!",
9842
+ "==",
9843
+ "!=",
9844
+ "<",
9845
+ "<=",
9846
+ ">",
9847
+ ">=",
9848
+ "has",
9849
+ "get",
9850
+ "to-number",
9851
+ "coalesce",
9852
+ "+",
9853
+ "-",
9854
+ "*",
9855
+ "/",
9856
+ "max",
9857
+ "min"
9858
+ ]);
9859
+ function bindRepeated(curve) {
9860
+ let tree = curve;
9861
+ for (let index = 0; index < 2; index += 1) {
9862
+ const json = /* @__PURE__ */ new Map();
9863
+ const found = /* @__PURE__ */ new Map();
9864
+ const visit = (value) => {
9865
+ if (!Array.isArray(value) || value[0] === "literal")
9866
+ return { text: JSON.stringify(value), free: true };
9867
+ const parts = value.map(visit);
9868
+ const text = `[${parts.map((part) => part.text).join(",")}]`;
9869
+ json.set(value, text);
9870
+ const free = value[0] !== "zoom" && value[0] !== "var" && parts.every((part) => part.free);
9871
+ if (free && typeof value[0] === "string" && bindable.has(value[0]))
9872
+ found.set(text, (found.get(text) ?? 0) + 1);
9873
+ return { text, free };
9874
+ };
9875
+ visit(tree);
9876
+ let best;
9877
+ let saving = 256;
9878
+ for (const [text, count] of found) {
9879
+ const saved = (count - 1) * text.length;
9880
+ if (count > 1 && saved > saving) [best, saving] = [text, saved];
9881
+ }
9882
+ if (best === void 0) break;
9883
+ const name = `tileflow_${index}`;
9884
+ const replace = (value) => {
9885
+ if (!Array.isArray(value) || value[0] === "literal") return value;
9886
+ if (json.get(value) === best) return ["var", name];
9887
+ return value.map(replace);
9888
+ };
9889
+ tree = ["let", name, JSON.parse(best), replace(tree)];
9890
+ }
9891
+ return tree;
9892
+ }
9777
9893
  function select(values, fallback) {
9778
9894
  if (!values.length) return fallback;
9779
9895
  const serialized = JSON.stringify(values[0].value);
@@ -9885,13 +10001,49 @@ function atZoom(value, level) {
9885
10001
  stops.push({ level: value[index], value: value[index + 1] });
9886
10002
  }
9887
10003
  const exact = stops.find((stop) => stop.level === level);
9888
- if (exact) return exact.value;
10004
+ if (exact) return foldConstants(exact.value);
9889
10005
  const lower = [...stops].reverse().find((stop) => stop.level < level);
9890
10006
  const upper = stops.find((stop) => stop.level > level);
9891
- if (value[0] === "step") return lower?.value ?? value[2];
9892
- if (!lower) return upper?.value ?? 0;
9893
- if (!upper) return lower.value;
9894
- return ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
10007
+ if (value[0] === "step") return foldConstants(lower?.value ?? value[2]);
10008
+ if (!lower) return foldConstants(upper?.value ?? 0);
10009
+ if (!upper) return foldConstants(lower.value);
10010
+ const at = interpolationFactor2(
10011
+ value[1],
10012
+ level - lower.level,
10013
+ upper.level - lower.level
10014
+ );
10015
+ const folded = Number.isFinite(at) ? interpolated(foldConstants(lower.value), foldConstants(upper.value), at) : void 0;
10016
+ return folded ?? ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
10017
+ }
10018
+ function foldConstants(value) {
10019
+ if (!Array.isArray(value) || value[0] === "literal") return value;
10020
+ const folded = value.map(foldConstants);
10021
+ const [operator, ...operands] = folded;
10022
+ if (!operands.length || !operands.every((operand) => typeof operand === "number")) return folded;
10023
+ const numbers = operands;
10024
+ const result = operator === "*" ? numbers.reduce((product, number) => product * number, 1) : operator === "+" ? numbers.reduce((sum, number) => sum + number, 0) : operator === "-" ? numbers.length === 1 ? -numbers[0] : numbers[0] - numbers[1] : operator === "/" && numbers.length === 2 ? numbers[0] / numbers[1] : operator === "max" ? Math.max(...numbers) : operator === "min" ? Math.min(...numbers) : void 0;
10025
+ return result !== void 0 && Number.isFinite(result) ? result : folded;
10026
+ }
10027
+ function interpolated(lower, upper, at) {
10028
+ if (typeof lower === "number" && typeof upper === "number") return lower + (upper - lower) * at;
10029
+ if (JSON.stringify(lower) === JSON.stringify(upper)) return lower;
10030
+ if (!Array.isArray(lower) || !Array.isArray(upper) || lower.length !== upper.length || lower[0] !== "case" && lower[0] !== "match" || lower[0] !== upper[0])
10031
+ return void 0;
10032
+ const first = lower[0] === "case" ? 1 : 2;
10033
+ const result = lower.slice(0, first);
10034
+ if (JSON.stringify(result) !== JSON.stringify(upper.slice(0, first))) return void 0;
10035
+ for (let index = first; index < lower.length; index += 1) {
10036
+ const output = (index - first) % 2 === 1 || index === lower.length - 1;
10037
+ if (!output) {
10038
+ if (JSON.stringify(lower[index]) !== JSON.stringify(upper[index])) return void 0;
10039
+ result.push(lower[index]);
10040
+ continue;
10041
+ }
10042
+ const value = interpolated(lower[index], upper[index], at);
10043
+ if (value === void 0) return void 0;
10044
+ result.push(value);
10045
+ }
10046
+ return result;
9895
10047
  }
9896
10048
 
9897
10049
  // src/modules/roads/compiler.ts
@@ -9962,6 +10114,10 @@ function compileRoads(request, context) {
9962
10114
  request?.classes?.[roadClass]
9963
10115
  );
9964
10116
  const treatments = resolveRoadTreatments(request, schema.fields, roadClass);
10117
+ const drawn = {
10118
+ field: schema.fields.class,
10119
+ classes: new Set(tileflowRoadClassValues(roadClass))
10120
+ };
9965
10121
  const roundaboutSurface = { roadClass };
9966
10122
  roundaboutSurfaces.push(roundaboutSurface);
9967
10123
  for (const structure of ["tunnel", "surface", "bridge"]) {
@@ -9984,7 +10140,8 @@ function compileRoads(request, context) {
9984
10140
  treatments,
9985
10141
  structure,
9986
10142
  phase,
9987
- phaseDefaults.color
10143
+ phaseDefaults.color,
10144
+ drawn
9988
10145
  );
9989
10146
  const contribution = {
9990
10147
  family: roadLinePhysicalFamily(roadClass, structure, phase),
@@ -10047,7 +10204,8 @@ function compileRoads(request, context) {
10047
10204
  treatments,
10048
10205
  structure,
10049
10206
  "fill",
10050
- roadColor(context, roadClass)
10207
+ roadColor(context, roadClass),
10208
+ drawn
10051
10209
  );
10052
10210
  const size = hatch.size === void 0 ? scaleStyleValue(treatedFill.width ?? 1, 0.68) : toMapLibreStyleValue(hatch.size);
10053
10211
  const minZoom = hatch.minZoom ?? treatedFill.minZoom;
@@ -10168,31 +10326,24 @@ function compileRoads(request, context) {
10168
10326
  const areaTargets = [
10169
10327
  {
10170
10328
  id: "tileflow-road-area",
10171
- filter: ["==", ["geometry-type"], "Polygon"],
10329
+ kind: void 0,
10172
10330
  name: "road",
10173
10331
  style: areaStyles.road
10174
10332
  },
10175
10333
  {
10176
10334
  id: "tileflow-road-pedestrian-area",
10177
- filter: [
10178
- "all",
10179
- ["==", ["geometry-type"], "Polygon"],
10180
- tileflowRoadClassFilter(schema.fields, "pedestrian")
10181
- ],
10335
+ kind: tileflowRoadClassFilter(schema.fields, "pedestrian"),
10182
10336
  name: "pedestrian",
10183
10337
  style: areaStyles.pedestrian
10184
10338
  },
10185
10339
  {
10186
10340
  id: "tileflow-road-pier-area",
10187
- filter: [
10188
- "all",
10189
- ["==", ["geometry-type"], "Polygon"],
10190
- ["==", ["get", schema.fields.class], "pier"]
10191
- ],
10341
+ kind: ["==", ["get", schema.fields.class], "pier"],
10192
10342
  name: "pier",
10193
10343
  style: areaStyles.pier
10194
10344
  }
10195
10345
  ];
10346
+ const polygons2 = ["==", ["geometry-type"], "Polygon"];
10196
10347
  let areaOrder = 900;
10197
10348
  for (const target of areaTargets) {
10198
10349
  if (!target.style) continue;
@@ -10202,13 +10353,19 @@ function compileRoads(request, context) {
10202
10353
  type: "fill",
10203
10354
  source,
10204
10355
  "source-layer": schema.layers.road,
10205
- filter: target.filter
10356
+ filter: target.kind ? ["all", polygons2, target.kind] : polygons2
10206
10357
  },
10207
10358
  target.style
10208
10359
  )) {
10360
+ const { filter: _polygonFilter, ...unfiltered } = area.layer;
10361
+ const outline = area.phase === "outline" && schema.layers.transportationOutline ? {
10362
+ ...unfiltered,
10363
+ "source-layer": schema.layers.transportationOutline,
10364
+ ...target.kind ? { filter: target.kind } : {}
10365
+ } : area.layer;
10209
10366
  contributions.push({
10210
10367
  kind: "layer",
10211
- layer: area.layer,
10368
+ layer: outline,
10212
10369
  localOrder: areaOrder++,
10213
10370
  owner: "roads",
10214
10371
  slot: "transport-areas",
@@ -10485,9 +10642,10 @@ function resolveRoadTreatments(request, fields2, roadClass) {
10485
10642
  add2(request?.modifiers?.unpaved, ["==", ["get", fields2.surface], "unpaved"]);
10486
10643
  add2(request?.modifiers?.indoor, flagFilter(fields2.indoor));
10487
10644
  add2(request?.modifiers?.official, flagFilter(fields2.official));
10488
- for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
10489
- add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
10490
- }
10645
+ if (isTileflowPathRoadClass(roadClass) || roadClass === "track")
10646
+ for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
10647
+ add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
10648
+ }
10491
10649
  if (roadClass === "service") {
10492
10650
  for (const [serviceType, value] of Object.entries(serviceTypeValues)) {
10493
10651
  add2(request?.serviceTypes?.[serviceType], [
@@ -10499,7 +10657,7 @@ function resolveRoadTreatments(request, fields2, roadClass) {
10499
10657
  }
10500
10658
  return entries;
10501
10659
  }
10502
- function applyRoadTreatments(base, treatments, structure, phase, fallbackColor) {
10660
+ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor, drawn) {
10503
10661
  const result = { ...base };
10504
10662
  const paintProperties = [
10505
10663
  "blur",
@@ -10516,12 +10674,12 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
10516
10674
  return value === void 0 ? [] : [
10517
10675
  {
10518
10676
  condition,
10519
- resolve: () => expressionOutput(toMapLibreStyleValue(value))
10677
+ resolve: () => expressionOutput(withinClasses(toMapLibreStyleValue(value), drawn))
10520
10678
  }
10521
10679
  ];
10522
10680
  });
10523
10681
  if (branches.length > 0) {
10524
- if (property === "dash" && isSolidRoadDash(result[property] ?? treatmentPaintDefaults.dash) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
10682
+ if (property === "dash" && isSolidRoadDash(withinClasses(result[property] ?? treatmentPaintDefaults.dash, drawn)) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
10525
10683
  delete result[property];
10526
10684
  continue;
10527
10685
  }
@@ -10551,6 +10709,49 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
10551
10709
  }
10552
10710
  return result;
10553
10711
  }
10712
+ function withinClasses(value, drawn) {
10713
+ const decided = (condition) => {
10714
+ if (!Array.isArray(condition)) return typeof condition === "boolean" ? condition : void 0;
10715
+ const [operator, ...operands] = condition;
10716
+ const isClass = (operand) => Array.isArray(operand) && operand.length === 2 && operand[0] === "get" && (operand[1] === drawn.field || typeof operand[1] === "object" && operand[1] !== null && operand[1].kind === "tileflow-data-field" && operand[1].name === "class");
10717
+ const among = (labels2) => {
10718
+ const hits = [...drawn.classes].filter((value2) => labels2.includes(value2)).length;
10719
+ return hits === 0 ? false : hits === drawn.classes.size ? true : void 0;
10720
+ };
10721
+ if ((operator === "==" || operator === "!=") && operands.length === 2) {
10722
+ const label = isClass(operands[0]) ? operands[1] : isClass(operands[1]) ? operands[0] : null;
10723
+ if (typeof label !== "string") return void 0;
10724
+ const equal = among([label]);
10725
+ return equal === void 0 ? void 0 : operator === "==" ? equal : !equal;
10726
+ }
10727
+ if (operator === "match" && operands.length === 4 && isClass(operands[0]) && typeof operands[2] === "boolean" && typeof operands[3] === "boolean") {
10728
+ const labels2 = Array.isArray(operands[1]) ? operands[1] : [operands[1]];
10729
+ const matched = among(labels2);
10730
+ return matched === void 0 ? void 0 : matched ? operands[2] : operands[3];
10731
+ }
10732
+ if (operator === "!" && operands.length === 1) {
10733
+ const inner = decided(operands[0]);
10734
+ return inner === void 0 ? void 0 : !inner;
10735
+ }
10736
+ if (operator === "all" || operator === "any") {
10737
+ const results = operands.map(decided);
10738
+ if (results.includes(operator === "all" ? false : true)) return operator !== "all";
10739
+ return results.every((result) => result !== void 0) ? operator === "all" : void 0;
10740
+ }
10741
+ return void 0;
10742
+ };
10743
+ if (!Array.isArray(value) || value[0] !== "case") return value;
10744
+ const branches = [];
10745
+ for (let index = 1; index < value.length - 1; index += 2) {
10746
+ const holds = decided(value[index]);
10747
+ if (holds === false) continue;
10748
+ const output = withinClasses(value[index + 1], drawn);
10749
+ if (holds === true) return branches.length ? ["case", ...branches, output] : output;
10750
+ branches.push(value[index], output);
10751
+ }
10752
+ const fallback = withinClasses(value.at(-1), drawn);
10753
+ return branches.length ? ["case", ...branches, fallback] : fallback;
10754
+ }
10554
10755
  function isSolidRoadDash(value) {
10555
10756
  const output = expressionOutput(value);
10556
10757
  const dash = Array.isArray(output) && output[0] === "literal" ? output[1] : output;
@@ -14286,7 +14487,7 @@ function compileRenderSelector(selector, data) {
14286
14487
  assertRenderSelectorConstraints(selector);
14287
14488
  return lowerSelector(selector, data);
14288
14489
  }
14289
- function applyCompiledRenderStacks(input, operations2) {
14490
+ function applyCompiledRenderStacks(input, operations2, targetsOfOtherModes = /* @__PURE__ */ new Map()) {
14290
14491
  let layers2 = input.map(cloneJson21);
14291
14492
  const passes = operations2.filter(
14292
14493
  (operation) => operation.kind === "layer"
@@ -14317,9 +14518,10 @@ function applyCompiledRenderStacks(input, operations2) {
14317
14518
  for (const refinement of activeRefinements) {
14318
14519
  const index = findTargetIndex(layers2, refinement.target);
14319
14520
  if (index < 0 && !passTargets.has(refinement.target)) {
14521
+ const other = targetsOfOtherModes.get(refinement.target);
14320
14522
  throw renderStackError(
14321
14523
  "unknown-refinement-target",
14322
- `Render refinement targets unknown semantic contribution: ${refinement.target}.`
14524
+ other === void 0 ? `Render refinement targets unknown semantic contribution: ${refinement.target}.` : `Render refinement targets ${refinement.target}, ${describeOtherMode(other)}`
14323
14525
  );
14324
14526
  }
14325
14527
  if (refinement.placement !== void 0 && placementAnchor(refinement.placement) === refinement.target) {
@@ -14341,9 +14543,10 @@ function applyCompiledRenderStacks(input, operations2) {
14341
14543
  for (const operation of pending) {
14342
14544
  const anchor = placementAnchor(operation.placement);
14343
14545
  if (anchor !== void 0 && !knownTargets.has(anchor)) {
14546
+ const other = targetsOfOtherModes.get(anchor);
14344
14547
  throw renderStackError(
14345
14548
  "unknown-or-circular-anchor",
14346
- `${operation.target} is placed beside unknown semantic target ${anchor}.`
14549
+ other === void 0 ? `${operation.target} is placed beside unknown semantic target ${anchor}.` : `${operation.target} is placed beside ${anchor}, ${describeOtherMode(other)}`
14347
14550
  );
14348
14551
  }
14349
14552
  }
@@ -15016,6 +15219,9 @@ function assertOnlyKeys(input, allowed, description) {
15016
15219
  );
15017
15220
  }
15018
15221
  }
15222
+ function describeOtherMode({ activeMode, mode, owner }) {
15223
+ return `which ${owner} draws only in mode '${mode}'; this map draws ${owner} in mode '${activeMode}'. Place the operation beside a target drawn in mode '${activeMode}' or at a band edge, or remove it; refine({mode: '${activeMode}', renderStack: reset()}) removes an inherited ${owner} render stack.`;
15224
+ }
15019
15225
  function renderStackError(code, message) {
15020
15226
  return Object.assign(new Error(message), { code, name: "TileflowRenderStackError" });
15021
15227
  }
@@ -16106,6 +16312,7 @@ function boundedThemeNumberSchema(min, max) {
16106
16312
  }).strict()
16107
16313
  ]);
16108
16314
  }
16315
+ var entranceBandSchema = z7.tuple([z7.number().finite().min(0).max(24), z7.number().finite().min(0).max(24)]).refine(([from, to]) => from < to, "A band starts at a lower zoom than it ends.");
16109
16316
  var modelRoleLightingSchema = z7.object({
16110
16317
  emissiveStrength: boundedThemeNumberSchema(0, 5).optional(),
16111
16318
  emissiveVariation: boundedThemeNumberSchema(0, 1).optional(),
@@ -16161,6 +16368,7 @@ var buildingsModuleSchema = z7.object({
16161
16368
  lamp: modelRoleLightingSchema.optional(),
16162
16369
  logo: modelRoleLightingSchema.optional()
16163
16370
  }).strict().optional(),
16371
+ entrance: z7.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().optional(),
16164
16372
  occlusion: z7.boolean().optional(),
16165
16373
  modelOcclusion: boundedThemeNumberSchema(0, 1).optional(),
16166
16374
  shadows: z7.boolean().optional(),
@@ -16540,6 +16748,7 @@ var layerBindingsSchema = z7.object({
16540
16748
  streetFurniture: z7.string().trim().min(1).optional(),
16541
16749
  trafficControl: z7.string().trim().min(1).optional(),
16542
16750
  tree: z7.string().trim().min(1).optional(),
16751
+ transportationOutline: z7.string().trim().min(1).optional(),
16543
16752
  water: z7.string().trim().min(1),
16544
16753
  waterName: z7.string().trim().min(1),
16545
16754
  waterway: z7.string().trim().min(1)
@@ -18415,6 +18624,8 @@ function mergeRoadLineGroup(key, layers2) {
18415
18624
  const paintKeys = new Set(layers2.flatMap((layer) => Object.keys(layer.style.appearance ?? {})));
18416
18625
  for (const key2 of paintKeys) {
18417
18626
  const values = layers2.map((layer) => layer.style.appearance?.[key2]);
18627
+ if (key2 === "line-dasharray" && values.every((value) => isSolidDash(value ?? linePaintDefaults[key2])))
18628
+ continue;
18418
18629
  const combined = combineConditionalValues(
18419
18630
  values.map((value, index) => ({
18420
18631
  condition: conditions[index],
@@ -19208,6 +19419,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19208
19419
  )
19209
19420
  } : {},
19210
19421
  windows: style.windows === true,
19422
+ ...readTileflowBuildingsEntrance(style.entrance),
19211
19423
  shadows: typeof style.shadows === "boolean" ? style.shadows : defaults4.shadows,
19212
19424
  occlusion: typeof style.occlusion === "boolean" ? style.occlusion : defaults4.occlusion,
19213
19425
  modelOcclusion: number(style.modelOcclusion, defaults4.modelOcclusion),
@@ -19491,7 +19703,11 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19491
19703
  const requestedRenderOperations = domainIR.renderOperations;
19492
19704
  const stackedFamilies = runCompilationPhase(
19493
19705
  "render-stack",
19494
- () => applyCompiledRenderStacks(assembledFamilies, requestedRenderOperations)
19706
+ () => applyCompiledRenderStacks(
19707
+ assembledFamilies,
19708
+ requestedRenderOperations,
19709
+ buildingTargetsOfOtherModes(modules.buildings)
19710
+ )
19495
19711
  );
19496
19712
  const plannedFamilies = runCompilationPhase(
19497
19713
  "physical-planner",
@@ -20296,6 +20512,9 @@ function applyModuleOperation(resolved, moduleName, operation, mapId, path) {
20296
20512
  assertNoReset(patch, mapId, `${path}/patches/${index}`, true);
20297
20513
  next = applyModulePatch(next, patch, mapId, `${path}/patches/${index}`);
20298
20514
  }
20515
+ if (isPlainRecord4(next.renderStack) && Object.keys(next.renderStack).length === 0) {
20516
+ delete next.renderStack;
20517
+ }
20299
20518
  assertModuleOwner(next, moduleName, mapId, path);
20300
20519
  resolved[moduleName] = next;
20301
20520
  return;