@trackunit/react-map 0.2.227 → 0.2.230
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/index.cjs.js +110 -52
- package/index.esm.js +110 -52
- package/package.json +11 -11
- package/src/draw/useDraw.d.ts +2 -1
- package/src/layers/shared/consoleWarnMap.d.ts +7 -0
- package/src/layers/shared/useDevWarnings.d.ts +14 -0
- package/src/layers/useHeatmap/useHeatmap.d.ts +2 -1
- package/src/layers/useHeatmap/utils/normalizeHeatmapData.d.ts +4 -1
- package/src/layers/useHeatmap/utils/resolveHeatmapStyle.d.ts +8 -2
- package/src/layers/useShapes/captureDroppedInvalidFillTilingFeatures.d.ts +2 -1
- package/src/layers/useShapes/captureFillTilingIntersectionFailures.d.ts +2 -1
- package/src/layers/useShapes/utils/formatDroppedInvalidFeatureIds.d.ts +3 -0
- package/src/layers/useShapes/utils/formatIntersectionFailurePairs.d.ts +3 -0
- package/translation.cjs.js +2 -0
- package/translation.esm.js +2 -0
- package/translation10.cjs.js +2 -0
- package/translation10.esm.js +2 -0
- package/translation11.cjs.js +2 -0
- package/translation11.esm.js +2 -0
- package/translation12.cjs.js +2 -0
- package/translation12.esm.js +2 -0
- package/translation13.cjs.js +2 -0
- package/translation13.esm.js +2 -0
- package/translation14.cjs.js +2 -0
- package/translation14.esm.js +2 -0
- package/translation15.cjs.js +2 -0
- package/translation15.esm.js +2 -0
- package/translation16.cjs.js +2 -0
- package/translation16.esm.js +2 -0
- package/translation17.cjs.js +2 -0
- package/translation17.esm.js +2 -0
- package/translation2.cjs.js +2 -0
- package/translation2.esm.js +2 -0
- package/translation3.cjs.js +2 -0
- package/translation3.esm.js +2 -0
- package/translation4.cjs.js +2 -0
- package/translation4.esm.js +2 -0
- package/translation5.cjs.js +2 -0
- package/translation5.esm.js +2 -0
- package/translation6.cjs.js +2 -0
- package/translation6.esm.js +2 -0
- package/translation7.cjs.js +2 -0
- package/translation7.esm.js +2 -0
- package/translation8.cjs.js +2 -0
- package/translation8.esm.js +2 -0
- package/translation9.cjs.js +2 -0
- package/translation9.esm.js +2 -0
- package/migrations/entry.js.map +0 -1
package/index.cjs.js
CHANGED
|
@@ -3455,8 +3455,17 @@ const useControls = (base, areas) => {
|
|
|
3455
3455
|
/**
|
|
3456
3456
|
* Single dev-warning channel for react-map hooks (invalid data, invalid style, unhandled render errors).
|
|
3457
3457
|
* Messages are prefixed with the emitting hook, e.g. `[useHeatmap]`.
|
|
3458
|
+
*
|
|
3459
|
+
* Skipped in production builds: the warnings describe consumer mistakes a developer fixes
|
|
3460
|
+
* once, and with polled data they would otherwise log on every data change. Vitest runs
|
|
3461
|
+
* with `NODE_ENV` set to `test`, so specs can still spy on `console.warn`.
|
|
3462
|
+
*
|
|
3463
|
+
* Call it from effects, handlers and callbacks, never during render: pure normalizers
|
|
3464
|
+
* return their warnings and `useDevWarnings` emits them once per result.
|
|
3458
3465
|
*/
|
|
3459
3466
|
const consoleWarnMap = (source, message, ...details) => {
|
|
3467
|
+
if (process.env.NODE_ENV === "production")
|
|
3468
|
+
return;
|
|
3460
3469
|
// eslint-disable-next-line no-console -- Intentional: the one react-map warning channel; invalid input is dropped or defaulted and render errors without onError must stay visible
|
|
3461
3470
|
console.warn(`[${source}] ${message}`, ...details);
|
|
3462
3471
|
};
|
|
@@ -5913,17 +5922,39 @@ const createLazyGetter = (compute) => {
|
|
|
5913
5922
|
};
|
|
5914
5923
|
};
|
|
5915
5924
|
|
|
5925
|
+
/** Shared empty result so hooks with nothing to report keep a stable reference. */
|
|
5926
|
+
const NO_DEV_WARNINGS = [];
|
|
5927
|
+
/**
|
|
5928
|
+
* Emits a normalizer's warnings through the react-map dev-warning channel, from an
|
|
5929
|
+
* effect rather than during render.
|
|
5930
|
+
*
|
|
5931
|
+
* Pure normalizers (`filterPositioned`, `normalizeHeatmapData`, ...) run inside render
|
|
5932
|
+
* and must not log: StrictMode and discarded concurrent renders would log them twice.
|
|
5933
|
+
* They return their warnings instead, and this hook emits each list once. The list's
|
|
5934
|
+
* identity is the dedupe key, so StrictMode's double effect run and re-renders that
|
|
5935
|
+
* keep the same normalized result add nothing; a new normalization pass (a new list)
|
|
5936
|
+
* warns again, which is what a developer feeding new invalid data expects.
|
|
5937
|
+
*/
|
|
5938
|
+
const useDevWarnings = (source, warnings) => {
|
|
5939
|
+
const emittedRef = react.useRef(null);
|
|
5940
|
+
react.useEffect(() => {
|
|
5941
|
+
if (warnings.length === 0 || emittedRef.current === warnings)
|
|
5942
|
+
return;
|
|
5943
|
+
emittedRef.current = warnings;
|
|
5944
|
+
for (const message of warnings) {
|
|
5945
|
+
consoleWarnMap(source, message);
|
|
5946
|
+
}
|
|
5947
|
+
}, [source, warnings]);
|
|
5948
|
+
};
|
|
5949
|
+
|
|
5916
5950
|
// ============================================================================
|
|
5917
5951
|
// Constants
|
|
5918
5952
|
// ============================================================================
|
|
5919
5953
|
const EMPTY_CONTROLS$3 = {};
|
|
5920
5954
|
const EMPTY_FEATURES$1 = { type: "FeatureCollection", features: [] };
|
|
5921
|
-
// ============================================================================
|
|
5922
|
-
// Internal helpers
|
|
5923
|
-
// ============================================================================
|
|
5924
5955
|
/**
|
|
5925
5956
|
* Filter waypoints to only valid GeoJSON positions. Invalid entries are dropped.
|
|
5926
|
-
*
|
|
5957
|
+
* Pure: it runs during render, so it returns one grouped warning instead of logging.
|
|
5927
5958
|
*/
|
|
5928
5959
|
const filterValidWaypoints = (waypoints) => {
|
|
5929
5960
|
const valid = [];
|
|
@@ -5933,12 +5964,11 @@ const filterValidWaypoints = (waypoints) => {
|
|
|
5933
5964
|
valid.push(result.data);
|
|
5934
5965
|
}
|
|
5935
5966
|
}
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
}
|
|
5941
|
-
return valid;
|
|
5967
|
+
const filtered = waypoints.length - valid.length;
|
|
5968
|
+
const warnings = filtered > 0
|
|
5969
|
+
? [`Filtered ${filtered} invalid waypoint(s). Expected [longitude, latitude] with latitude between -90 and 90.`]
|
|
5970
|
+
: NO_DEV_WARNINGS;
|
|
5971
|
+
return { waypoints: valid, warnings };
|
|
5942
5972
|
};
|
|
5943
5973
|
/**
|
|
5944
5974
|
* Convert waypoints to a GeoJSON FeatureCollection with a single LineString.
|
|
@@ -5986,7 +6016,9 @@ const useRoute = (options) => {
|
|
|
5986
6016
|
// ---- Data readiness tracking ----
|
|
5987
6017
|
const layerReady = useLayerReady(loading);
|
|
5988
6018
|
// ---- Filter to valid waypoints only ----
|
|
5989
|
-
const
|
|
6019
|
+
const filtered = react.useMemo(() => filterValidWaypoints(rawWaypoints), [rawWaypoints]);
|
|
6020
|
+
const waypoints = filtered.waypoints;
|
|
6021
|
+
useDevWarnings("useRoute", filtered.warnings);
|
|
5990
6022
|
// ---- Convert to GeoJSON ----
|
|
5991
6023
|
const features = react.useMemo(() => toLineStringFeatureCollection(id, waypoints), [id, waypoints]);
|
|
5992
6024
|
// ---- Lazy getBounds with cache ----
|
|
@@ -6101,8 +6133,7 @@ const useFitToContent = (api, handles, options) => {
|
|
|
6101
6133
|
const stillPending = filterHandles(handlesRef.current, "initial").filter(h => h.loadsData && h.loading);
|
|
6102
6134
|
if (stillPending.length > 0) {
|
|
6103
6135
|
const names = stillPending.map(h => h.name).join(", ");
|
|
6104
|
-
|
|
6105
|
-
console.warn(`[useFitToContent] Timeout (${timeout}ms) reached. Fitting with available data. Still loading: ${names}`);
|
|
6136
|
+
consoleWarnMap("useFitToContent", `Timeout (${timeout}ms) reached. Fitting with available data. Still loading: ${names}`);
|
|
6106
6137
|
}
|
|
6107
6138
|
performFit();
|
|
6108
6139
|
}, timeout);
|
|
@@ -6134,7 +6165,8 @@ const useFitToContent = (api, handles, options) => {
|
|
|
6134
6165
|
* Converts consumer items into weighted GeoJSON `Point` features.
|
|
6135
6166
|
*
|
|
6136
6167
|
* Null positions and zero weights are dropped silently. Invalid positions and
|
|
6137
|
-
* negative or non-finite weights are dropped
|
|
6168
|
+
* negative or non-finite weights are dropped and reported as one grouped warning
|
|
6169
|
+
* per pass in `warnings`; the hook emits it from an effect, never during render.
|
|
6138
6170
|
*/
|
|
6139
6171
|
const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
|
|
6140
6172
|
const features = [];
|
|
@@ -6164,12 +6196,15 @@ const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
|
|
|
6164
6196
|
properties: { weight },
|
|
6165
6197
|
});
|
|
6166
6198
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6199
|
+
const warnings = invalidPositionCount > 0 || invalidWeightCount > 0
|
|
6200
|
+
? [
|
|
6201
|
+
`Filtered ${invalidPositionCount} item(s) with invalid position and ${invalidWeightCount} item(s) with invalid weight. Expected [longitude, latitude] with longitude between -180 and 180, latitude between -90 and 90, and a finite weight >= 0.`,
|
|
6202
|
+
]
|
|
6203
|
+
: NO_DEV_WARNINGS;
|
|
6170
6204
|
return {
|
|
6171
6205
|
features: { type: "FeatureCollection", features },
|
|
6172
6206
|
positions,
|
|
6207
|
+
warnings,
|
|
6173
6208
|
};
|
|
6174
6209
|
};
|
|
6175
6210
|
|
|
@@ -6223,13 +6258,15 @@ const DEFAULT_HEATMAP_COLOR_RANGE = [
|
|
|
6223
6258
|
const DEFAULT_PARSED_COLOR_RANGE = DEFAULT_HEATMAP_COLOR_RANGE.map(parseHeatmapColor).filter(sharedUtils.nonNullable);
|
|
6224
6259
|
const resolveColorRange = (colorRange) => {
|
|
6225
6260
|
if (colorRange === undefined)
|
|
6226
|
-
return DEFAULT_PARSED_COLOR_RANGE;
|
|
6261
|
+
return { colorRange: DEFAULT_PARSED_COLOR_RANGE, warning: null };
|
|
6227
6262
|
const parsed = colorRange.map(parseHeatmapColor);
|
|
6228
6263
|
const valid = parsed.filter(sharedUtils.nonNullable);
|
|
6229
6264
|
if (colorRange.length >= 2 && valid.length === parsed.length)
|
|
6230
|
-
return valid;
|
|
6231
|
-
|
|
6232
|
-
|
|
6265
|
+
return { colorRange: valid, warning: null };
|
|
6266
|
+
return {
|
|
6267
|
+
colorRange: DEFAULT_PARSED_COLOR_RANGE,
|
|
6268
|
+
warning: `colorRange needs at least two supported colours (#rgb, #rrggbb, #rrggbbaa, rgb(), rgba()); using the default. Received: ${JSON.stringify(colorRange)}`,
|
|
6269
|
+
};
|
|
6233
6270
|
};
|
|
6234
6271
|
const HEATMAP_STYLE_RULES = {
|
|
6235
6272
|
radiusPixels: { defaultValue: 30, min: 1, max: 100 },
|
|
@@ -6239,7 +6276,8 @@ const HEATMAP_STYLE_RULES = {
|
|
|
6239
6276
|
/**
|
|
6240
6277
|
* Applies defaults and supported ranges so both adapters receive identical style values.
|
|
6241
6278
|
*
|
|
6242
|
-
* Finite values are clamped; non-finite values fall back to the default with one
|
|
6279
|
+
* Finite values are clamped; non-finite values fall back to the default with one grouped
|
|
6280
|
+
* warning. Pure: it runs inside a render-time `useMemo`, so it returns its warnings.
|
|
6243
6281
|
*/
|
|
6244
6282
|
const resolveHeatmapStyle = (style) => {
|
|
6245
6283
|
const nonFiniteKeys = [];
|
|
@@ -6254,16 +6292,20 @@ const resolveHeatmapStyle = (style) => {
|
|
|
6254
6292
|
}
|
|
6255
6293
|
return Math.min(rule.max, Math.max(rule.min, value));
|
|
6256
6294
|
};
|
|
6295
|
+
const colorRange = resolveColorRange(style?.colorRange);
|
|
6257
6296
|
const resolved = {
|
|
6258
6297
|
radiusPixels: resolveValue("radiusPixels"),
|
|
6259
6298
|
intensity: resolveValue("intensity"),
|
|
6260
6299
|
opacity: resolveValue("opacity"),
|
|
6261
|
-
colorRange:
|
|
6300
|
+
colorRange: colorRange.colorRange,
|
|
6262
6301
|
};
|
|
6302
|
+
const warnings = [];
|
|
6263
6303
|
if (nonFiniteKeys.length > 0) {
|
|
6264
|
-
|
|
6304
|
+
warnings.push(`Non-finite style value(s) for ${nonFiniteKeys.join(", ")} replaced with defaults.`);
|
|
6265
6305
|
}
|
|
6266
|
-
|
|
6306
|
+
if (colorRange.warning !== null)
|
|
6307
|
+
warnings.push(colorRange.warning);
|
|
6308
|
+
return { style: resolved, warnings: warnings.length === 0 ? NO_DEV_WARNINGS : warnings };
|
|
6267
6309
|
};
|
|
6268
6310
|
|
|
6269
6311
|
const EMPTY_CONTROLS$2 = {};
|
|
@@ -6325,12 +6367,17 @@ const useHeatmap = (options) => {
|
|
|
6325
6367
|
if (style?.colorRange !== colorRange && !esToolkit.isEqual(style?.colorRange, colorRange)) {
|
|
6326
6368
|
setColorRange(style?.colorRange);
|
|
6327
6369
|
}
|
|
6328
|
-
const
|
|
6370
|
+
const resolved = react.useMemo(() => resolveHeatmapStyle({
|
|
6329
6371
|
radiusPixels: style?.radiusPixels,
|
|
6330
6372
|
intensity: style?.intensity,
|
|
6331
6373
|
opacity: style?.opacity,
|
|
6332
6374
|
colorRange,
|
|
6333
6375
|
}), [style?.radiusPixels, style?.intensity, style?.opacity, colorRange]);
|
|
6376
|
+
const resolvedStyle = resolved.style;
|
|
6377
|
+
// Normalization and style resolution run during render and stay pure; their
|
|
6378
|
+
// warnings are emitted once per pass from an effect (SAGA-907).
|
|
6379
|
+
useDevWarnings("useHeatmap", normalized.warnings);
|
|
6380
|
+
useDevWarnings("useHeatmap", resolved.warnings);
|
|
6334
6381
|
const getBounds = react.useMemo(() => createLazyGetter(() => computeBoundsFromPositions(normalized.positions)), [normalized.positions]);
|
|
6335
6382
|
const counts = react.useMemo(() => ({ total: data.length, points: normalized.features.features.length }), [data.length, normalized.features.features.length]);
|
|
6336
6383
|
return react.useMemo(() => ({
|
|
@@ -7396,7 +7443,8 @@ const EMPTY_FEATURES = { type: "FeatureCollection", features: [] };
|
|
|
7396
7443
|
const EMPTY_CONTROLS$1 = {};
|
|
7397
7444
|
/**
|
|
7398
7445
|
* Filter items to only those with valid positions and extract id + position.
|
|
7399
|
-
* Invalid positions (e.g. latitude out of range) are skipped.
|
|
7446
|
+
* Invalid positions (e.g. latitude out of range) are skipped. Pure: it runs during
|
|
7447
|
+
* render, so it returns one grouped warning instead of logging.
|
|
7400
7448
|
*/
|
|
7401
7449
|
const filterPositioned = (data, getPosition, getId) => {
|
|
7402
7450
|
const result = [];
|
|
@@ -7413,11 +7461,12 @@ const filterPositioned = (data, getPosition, getId) => {
|
|
|
7413
7461
|
}
|
|
7414
7462
|
}
|
|
7415
7463
|
}
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7464
|
+
const warnings = invalidCount > 0
|
|
7465
|
+
? [
|
|
7466
|
+
`Filtered ${invalidCount} item(s) with invalid position. Expected [longitude, latitude] with latitude between -90 and 90.`,
|
|
7467
|
+
]
|
|
7468
|
+
: NO_DEV_WARNINGS;
|
|
7469
|
+
return { items: result, warnings };
|
|
7421
7470
|
};
|
|
7422
7471
|
/**
|
|
7423
7472
|
* Convert positioned items to a GeoJSON FeatureCollection of Points.
|
|
@@ -7521,11 +7570,13 @@ const useMarkers = (options) => {
|
|
|
7521
7570
|
// structurally-identical inline arrays do NOT trigger infinite re-renders.
|
|
7522
7571
|
// The reference check short-circuits isEqual for the common stable-ref case.
|
|
7523
7572
|
const [prevData, setPrevData] = react.useState(data);
|
|
7524
|
-
const [
|
|
7573
|
+
const [positioned, setPositioned] = react.useState(() => filterPositioned(data, getPosition, getId));
|
|
7525
7574
|
if (data !== prevData && !esToolkit.isEqual(data, prevData)) {
|
|
7526
7575
|
setPrevData(data);
|
|
7527
|
-
|
|
7576
|
+
setPositioned(filterPositioned(data, getPosition, getId));
|
|
7528
7577
|
}
|
|
7578
|
+
const positionedItems = positioned.items;
|
|
7579
|
+
useDevWarnings("useMarkers", positioned.warnings);
|
|
7529
7580
|
// ---- Convert to GeoJSON FeatureCollection ----
|
|
7530
7581
|
const features = react.useMemo(() => toPointFeatureCollection(positionedItems), [positionedItems]);
|
|
7531
7582
|
// ---- Resolved server clusters (after resolveGroups entity-shape decisions) ----
|
|
@@ -9379,14 +9430,8 @@ const computeFillTiling = (input) => {
|
|
|
9379
9430
|
}
|
|
9380
9431
|
validFeatures.push(feature);
|
|
9381
9432
|
}
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
9385
|
-
.filter((id) => id !== undefined)
|
|
9386
|
-
.join(", ") || "(no feature ids)";
|
|
9387
|
-
// eslint-disable-next-line no-console -- Intentional: warn devs when schema-invalid polygonal geometry is excluded from fill tiling
|
|
9388
|
-
console.warn(`[computeFillTiling] Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${idList}`);
|
|
9389
|
-
}
|
|
9433
|
+
// Dropped features are returned, not logged: this function runs inside the hook's
|
|
9434
|
+
// recompute path and stays pure; the hook emits one dev warning per pass.
|
|
9390
9435
|
// Densify before any polygon/boundary math so clip geometry follows great-circle
|
|
9391
9436
|
// arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
|
|
9392
9437
|
// defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
|
|
@@ -9419,11 +9464,7 @@ const computeFillTiling = (input) => {
|
|
|
9419
9464
|
const featureToGroupKey = new Map();
|
|
9420
9465
|
const featureZIndex = new Map();
|
|
9421
9466
|
const { groups: overlapGroups, intersectionFailures } = buildOverlapGroups(records);
|
|
9422
|
-
|
|
9423
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9424
|
-
// eslint-disable-next-line no-console -- Intentional: warn devs when polygon-clipping fails on schema-valid geometry (SAGA-743); the pair is treated as non-overlapping and full fills still render
|
|
9425
|
-
console.warn(`[computeFillTiling] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${pairList}`);
|
|
9426
|
-
}
|
|
9467
|
+
// Failures are returned for the hook to warn about and report (SAGA-743).
|
|
9427
9468
|
for (const group of overlapGroups) {
|
|
9428
9469
|
const contended = buildContendedShapes(group);
|
|
9429
9470
|
const recordById = new Map(group.map(record => [record.id, record]));
|
|
@@ -9502,6 +9543,9 @@ const computeFillTiling = (input) => {
|
|
|
9502
9543
|
};
|
|
9503
9544
|
};
|
|
9504
9545
|
|
|
9546
|
+
/** Renders failing feature pairs as `a↔b, c↔d` for dev warnings and error reports. */
|
|
9547
|
+
const formatIntersectionFailurePairs = (failures) => failures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9548
|
+
|
|
9505
9549
|
const DEFAULT_ZOOM = 0;
|
|
9506
9550
|
const DEFAULT_TILE_SIZE = 256;
|
|
9507
9551
|
/**
|
|
@@ -9812,7 +9856,8 @@ const resolveIntersectionFailureDedupeKey = (layerHandleId, failure) => {
|
|
|
9812
9856
|
/**
|
|
9813
9857
|
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
9814
9858
|
* (SAGA-743), at most once per handle/pair per session.
|
|
9815
|
-
* Note:
|
|
9859
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
9860
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
9816
9861
|
*/
|
|
9817
9862
|
const captureFillTilingIntersectionFailures = (intersectionFailures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
9818
9863
|
for (const failure of intersectionFailures) {
|
|
@@ -11234,9 +11279,7 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
11234
11279
|
const reportOverlapIntersectionFailures = react.useCallback((intersectionFailures, layerHandleId) => {
|
|
11235
11280
|
if (intersectionFailures.length === 0)
|
|
11236
11281
|
return;
|
|
11237
|
-
|
|
11238
|
-
// eslint-disable-next-line no-console -- Intentional: warn devs when polygon-clipping fails on schema-valid geometry (SAGA-743); the pair is treated as non-overlapping and decorations still render
|
|
11239
|
-
console.warn(`[useShapeDecorations] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${pairList}`);
|
|
11282
|
+
consoleWarnMap("useShapeDecorations", `polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${formatIntersectionFailurePairs(intersectionFailures)}`);
|
|
11240
11283
|
if (errorHandler === null)
|
|
11241
11284
|
return;
|
|
11242
11285
|
captureFillTilingIntersectionFailures(intersectionFailures, layerHandleId, sessionReportedIntersectionKeysRef.current, payload => {
|
|
@@ -11659,7 +11702,8 @@ const captureOnce = (key, report, sessionReportedDedupeKeys, payload) => {
|
|
|
11659
11702
|
};
|
|
11660
11703
|
/**
|
|
11661
11704
|
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
11662
|
-
* Note:
|
|
11705
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
11706
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
11663
11707
|
*/
|
|
11664
11708
|
const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
11665
11709
|
for (const entry of droppedInvalidFeatures) {
|
|
@@ -11673,6 +11717,12 @@ const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHa
|
|
|
11673
11717
|
}
|
|
11674
11718
|
};
|
|
11675
11719
|
|
|
11720
|
+
/** Lists the ids of features dropped from fill tiling, or a placeholder when none carry an id. */
|
|
11721
|
+
const formatDroppedInvalidFeatureIds = (dropped) => dropped
|
|
11722
|
+
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
11723
|
+
.filter(sharedUtils.nonNullable)
|
|
11724
|
+
.join(", ") || "(no feature ids)";
|
|
11725
|
+
|
|
11676
11726
|
// ============================================================================
|
|
11677
11727
|
// Default resolver
|
|
11678
11728
|
// ============================================================================
|
|
@@ -12109,6 +12159,14 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
12109
12159
|
layerGeodesic: handle.style.geodesic,
|
|
12110
12160
|
featureStyles: handle.featureStyles,
|
|
12111
12161
|
});
|
|
12162
|
+
// The dev warnings live here, not in computeFillTiling: recompute runs from
|
|
12163
|
+
// effects and idle callbacks, so the pure function stays silent (SAGA-907).
|
|
12164
|
+
if (droppedInvalidFeatures.length > 0) {
|
|
12165
|
+
consoleWarnMap("useShapes", `Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${formatDroppedInvalidFeatureIds(droppedInvalidFeatures)}`);
|
|
12166
|
+
}
|
|
12167
|
+
if (intersectionFailures.length > 0) {
|
|
12168
|
+
consoleWarnMap("useShapes", `polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${formatIntersectionFailurePairs(intersectionFailures)}`);
|
|
12169
|
+
}
|
|
12112
12170
|
if (errorHandler !== null && droppedInvalidFeatures.length > 0) {
|
|
12113
12171
|
captureDroppedInvalidFillTilingFeatures(droppedInvalidFeatures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
12114
12172
|
errorHandler.captureException(new Error(`Schema-invalid polygonal geometry excluded from fill tiling: ${JSON.stringify(payload)}`), {
|