@trackunit/react-map 0.2.92 → 0.2.93-alpha-62031fc7c43.0
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
CHANGED
|
@@ -7757,16 +7757,9 @@ const shapesUnderCursor = (position, features, options) => {
|
|
|
7757
7757
|
/**
|
|
7758
7758
|
* Connected components of features that share fill area. Bbox prefilter then
|
|
7759
7759
|
* exact polygon intersection, mirroring `countOverlappingShapes`.
|
|
7760
|
-
*
|
|
7761
|
-
* `getGeoJsonPolygonIntersection` delegates to `polygon-clipping`, which can throw
|
|
7762
|
-
* `"Unable to complete output ring"` for degenerate but schema-valid geometry
|
|
7763
|
-
* (SAGA-743). A throw here is treated as "no overlap" — the safe degradation for
|
|
7764
|
-
* grouping — and recorded in `intersectionFailures` so the hook can report it
|
|
7765
|
-
* (mirrors how `droppedInvalidFeatures` surfaces the sibling failure mode).
|
|
7766
7760
|
*/
|
|
7767
7761
|
const buildOverlapGroups = (records) => {
|
|
7768
7762
|
const parent = records.map((_, index) => index);
|
|
7769
|
-
const intersectionFailures = [];
|
|
7770
7763
|
const find = (index) => {
|
|
7771
7764
|
let root = index;
|
|
7772
7765
|
while (parent[root] !== root)
|
|
@@ -7790,17 +7783,7 @@ const buildOverlapGroups = (records) => {
|
|
|
7790
7783
|
continue;
|
|
7791
7784
|
if (!bboxesIntersect$1(a.bbox, b.bbox))
|
|
7792
7785
|
continue;
|
|
7793
|
-
|
|
7794
|
-
try {
|
|
7795
|
-
overlaps = geoJsonUtils.getGeoJsonPolygonIntersection(a.geometry, b.geometry) !== null;
|
|
7796
|
-
}
|
|
7797
|
-
catch (error) {
|
|
7798
|
-
// polygon-clipping failed on this pair. Treat as no overlap (do not union)
|
|
7799
|
-
// so tiling degrades to full fills rather than crashing the map.
|
|
7800
|
-
intersectionFailures.push({ featureIdA: a.id, featureIdB: b.id, error });
|
|
7801
|
-
continue;
|
|
7802
|
-
}
|
|
7803
|
-
if (overlaps) {
|
|
7786
|
+
if (geoJsonUtils.getGeoJsonPolygonIntersection(a.geometry, b.geometry) !== null) {
|
|
7804
7787
|
union(i, j);
|
|
7805
7788
|
}
|
|
7806
7789
|
}
|
|
@@ -7816,7 +7799,7 @@ const buildOverlapGroups = (records) => {
|
|
|
7816
7799
|
group.push(record);
|
|
7817
7800
|
}
|
|
7818
7801
|
});
|
|
7819
|
-
return
|
|
7802
|
+
return [...groups.values()].filter(group => group.length >= 2);
|
|
7820
7803
|
};
|
|
7821
7804
|
const buildContendedShapes = (group) => group.map(record => {
|
|
7822
7805
|
const containedIn = [];
|
|
@@ -7960,12 +7943,7 @@ const computeFillTiling = (input) => {
|
|
|
7960
7943
|
const fillGeometries = new Map();
|
|
7961
7944
|
const featureToGroupKey = new Map();
|
|
7962
7945
|
const featureZIndex = new Map();
|
|
7963
|
-
const
|
|
7964
|
-
if (intersectionFailures.length > 0) {
|
|
7965
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
7966
|
-
// 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
|
|
7967
|
-
console.warn(`[computeFillTiling] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${pairList}`);
|
|
7968
|
-
}
|
|
7946
|
+
const overlapGroups = buildOverlapGroups(records);
|
|
7969
7947
|
for (const group of overlapGroups) {
|
|
7970
7948
|
const contended = buildContendedShapes(group);
|
|
7971
7949
|
const recordById = new Map(group.map(record => [record.id, record]));
|
|
@@ -8040,7 +8018,6 @@ const computeFillTiling = (input) => {
|
|
|
8040
8018
|
featureToGroupKey,
|
|
8041
8019
|
featureZIndex,
|
|
8042
8020
|
droppedInvalidFeatures,
|
|
8043
|
-
intersectionFailures,
|
|
8044
8021
|
};
|
|
8045
8022
|
};
|
|
8046
8023
|
|
|
@@ -8346,31 +8323,6 @@ const buildShapeLabelResolutionContext = (feature, args) => {
|
|
|
8346
8323
|
};
|
|
8347
8324
|
};
|
|
8348
8325
|
|
|
8349
|
-
/** Stable, order-independent key so the pair (a,b) dedupes to the same entry as (b,a). */
|
|
8350
|
-
const resolveIntersectionFailureDedupeKey = (layerHandleId, failure) => {
|
|
8351
|
-
const [first, second] = [failure.featureIdA, failure.featureIdB].sort();
|
|
8352
|
-
return `${layerHandleId}:${first}|${second}`;
|
|
8353
|
-
};
|
|
8354
|
-
/**
|
|
8355
|
-
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
8356
|
-
* (SAGA-743), at most once per handle/pair per session.
|
|
8357
|
-
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
8358
|
-
*/
|
|
8359
|
-
const captureFillTilingIntersectionFailures = (intersectionFailures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
8360
|
-
for (const failure of intersectionFailures) {
|
|
8361
|
-
const key = resolveIntersectionFailureDedupeKey(layerHandleId, failure);
|
|
8362
|
-
if (sessionReportedDedupeKeys.has(key))
|
|
8363
|
-
continue;
|
|
8364
|
-
sessionReportedDedupeKeys.add(key);
|
|
8365
|
-
report({
|
|
8366
|
-
layerHandleId,
|
|
8367
|
-
featureIdA: failure.featureIdA,
|
|
8368
|
-
featureIdB: failure.featureIdB,
|
|
8369
|
-
message: failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
8370
|
-
});
|
|
8371
|
-
}
|
|
8372
|
-
};
|
|
8373
|
-
|
|
8374
8326
|
/**
|
|
8375
8327
|
* Extract ALL vertex coordinates from a geometry, walking every part of
|
|
8376
8328
|
* multi-geometries. Returns a flat array of [lng, lat] positions.
|
|
@@ -9625,7 +9577,6 @@ const bboxesIntersect = (a, b) => a[2] >= b[0] && a[0] <= b[2] && a[3] >= b[1] &
|
|
|
9625
9577
|
*/
|
|
9626
9578
|
const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds) => {
|
|
9627
9579
|
const counts = new Map();
|
|
9628
|
-
const intersectionFailures = [];
|
|
9629
9580
|
for (const feature of features) {
|
|
9630
9581
|
counts.set(feature, 0);
|
|
9631
9582
|
}
|
|
@@ -9654,21 +9605,7 @@ const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds)
|
|
|
9654
9605
|
const geomB = featureB.geometry;
|
|
9655
9606
|
const isPolygonalB = geomB !== null && (geomB.type === "Polygon" || geomB.type === "MultiPolygon");
|
|
9656
9607
|
if (isPolygonalA && isPolygonalB) {
|
|
9657
|
-
|
|
9658
|
-
try {
|
|
9659
|
-
overlaps = geoJsonUtils.getGeoJsonPolygonIntersection(geomA, geomB) !== null;
|
|
9660
|
-
}
|
|
9661
|
-
catch (error) {
|
|
9662
|
-
// polygon-clipping failed on this pair (SAGA-743). Treat as no overlap so
|
|
9663
|
-
// decoration counts degrade gracefully instead of crashing the map.
|
|
9664
|
-
intersectionFailures.push({
|
|
9665
|
-
featureIdA: featureA.id !== undefined ? String(featureA.id) : `index:${i}`,
|
|
9666
|
-
featureIdB: featureB.id !== undefined ? String(featureB.id) : `index:${j}`,
|
|
9667
|
-
error,
|
|
9668
|
-
});
|
|
9669
|
-
continue;
|
|
9670
|
-
}
|
|
9671
|
-
if (overlaps) {
|
|
9608
|
+
if (geoJsonUtils.getGeoJsonPolygonIntersection(geomA, geomB) !== null) {
|
|
9672
9609
|
if (geoJsonUtils.isFullyContainedInGeoJsonGeometry(geomA, geomB) !== true) {
|
|
9673
9610
|
counts.set(featureA, (counts.get(featureA) ?? 0) + 1);
|
|
9674
9611
|
}
|
|
@@ -9683,7 +9620,7 @@ const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds)
|
|
|
9683
9620
|
}
|
|
9684
9621
|
}
|
|
9685
9622
|
}
|
|
9686
|
-
return
|
|
9623
|
+
return counts;
|
|
9687
9624
|
};
|
|
9688
9625
|
/**
|
|
9689
9626
|
* Value-based equality for the nested style overrides maps. Used to skip
|
|
@@ -9769,25 +9706,6 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9769
9706
|
const previousLayoutSides = react.useRef(new Map());
|
|
9770
9707
|
const prevStyleOverridesRef = react.useRef(new Map());
|
|
9771
9708
|
const prevDecorationLayersRef = react.useRef([]);
|
|
9772
|
-
const errorHandler = reactCoreHooks.useErrorHandlerOrNull();
|
|
9773
|
-
// Session-level dedupe so a recurring polygon-clipping failure (SAGA-743) reports
|
|
9774
|
-
// to Sentry at most once per handle/pair per session, not on every pan frame.
|
|
9775
|
-
const sessionReportedIntersectionKeysRef = react.useRef(new Set());
|
|
9776
|
-
const reportOverlapIntersectionFailures = react.useCallback((intersectionFailures, layerHandleId) => {
|
|
9777
|
-
if (intersectionFailures.length === 0)
|
|
9778
|
-
return;
|
|
9779
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9780
|
-
// 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
|
|
9781
|
-
console.warn(`[useShapeDecorations] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${pairList}`);
|
|
9782
|
-
if (errorHandler === null)
|
|
9783
|
-
return;
|
|
9784
|
-
captureFillTilingIntersectionFailures(intersectionFailures, layerHandleId, sessionReportedIntersectionKeysRef.current, payload => {
|
|
9785
|
-
errorHandler.captureException(new Error(`polygon-clipping failed during decoration overlap counting: ${JSON.stringify(payload)}`), {
|
|
9786
|
-
level: "warning",
|
|
9787
|
-
fingerprint: ["react-map", "fill-tiling", "intersection-failure"],
|
|
9788
|
-
});
|
|
9789
|
-
});
|
|
9790
|
-
}, [errorHandler]);
|
|
9791
9709
|
/**
|
|
9792
9710
|
* Labels that just transitioned from absent (hidden) to placed. While in
|
|
9793
9711
|
* this set, edge identity is NOT stored — the algorithm gets one extra
|
|
@@ -9847,9 +9765,7 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9847
9765
|
let overlappingCountsCache = null;
|
|
9848
9766
|
const getOverlappingCount = (feature) => {
|
|
9849
9767
|
if (overlappingCountsCache === null) {
|
|
9850
|
-
|
|
9851
|
-
overlappingCountsCache = counts;
|
|
9852
|
-
reportOverlapIntersectionFailures(intersectionFailures, handle.id);
|
|
9768
|
+
overlappingCountsCache = buildOverlappingShapesCountMap(handle.features.features, featureBboxes, bounds);
|
|
9853
9769
|
}
|
|
9854
9770
|
return overlappingCountsCache.get(feature) ?? 0;
|
|
9855
9771
|
};
|
|
@@ -10110,7 +10026,6 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
10110
10026
|
hasSourceReadiness,
|
|
10111
10027
|
renderedSourceIds,
|
|
10112
10028
|
isReady,
|
|
10113
|
-
reportOverlapIntersectionFailures,
|
|
10114
10029
|
]);
|
|
10115
10030
|
const updateDecorationsRef = react.useRef(updateDecorations);
|
|
10116
10031
|
react.useEffect(() => {
|
|
@@ -10643,7 +10558,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10643
10558
|
const nextRestingFills = new Map();
|
|
10644
10559
|
const nextRestingZIndices = new Map();
|
|
10645
10560
|
for (const handle of currentHandles) {
|
|
10646
|
-
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures
|
|
10561
|
+
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures } = computeFillTiling({
|
|
10647
10562
|
features: handle.features.features,
|
|
10648
10563
|
viewportBounds: GLOBAL_BOUNDS,
|
|
10649
10564
|
resolveStackOrder: handle.overlap?.resolveStackOrder ?? defaultShapeStackOrder,
|
|
@@ -10659,14 +10574,6 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10659
10574
|
});
|
|
10660
10575
|
});
|
|
10661
10576
|
}
|
|
10662
|
-
if (errorHandler !== null && intersectionFailures.length > 0) {
|
|
10663
|
-
captureFillTilingIntersectionFailures(intersectionFailures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
10664
|
-
errorHandler.captureException(new Error(`polygon-clipping failed during fill-tiling overlap grouping: ${JSON.stringify(payload)}`), {
|
|
10665
|
-
level: "warning",
|
|
10666
|
-
fingerprint: ["react-map", "fill-tiling", "intersection-failure"],
|
|
10667
|
-
});
|
|
10668
|
-
});
|
|
10669
|
-
}
|
|
10670
10577
|
featureToGroupKeyByHandleRef.current.set(handle.id, new Map(featureToGroupKey));
|
|
10671
10578
|
if (fillGeometries.size > 0) {
|
|
10672
10579
|
nextRestingFills.set(handle.id, fillGeometries);
|
package/index.esm.js
CHANGED
|
@@ -7756,16 +7756,9 @@ const shapesUnderCursor = (position, features, options) => {
|
|
|
7756
7756
|
/**
|
|
7757
7757
|
* Connected components of features that share fill area. Bbox prefilter then
|
|
7758
7758
|
* exact polygon intersection, mirroring `countOverlappingShapes`.
|
|
7759
|
-
*
|
|
7760
|
-
* `getGeoJsonPolygonIntersection` delegates to `polygon-clipping`, which can throw
|
|
7761
|
-
* `"Unable to complete output ring"` for degenerate but schema-valid geometry
|
|
7762
|
-
* (SAGA-743). A throw here is treated as "no overlap" — the safe degradation for
|
|
7763
|
-
* grouping — and recorded in `intersectionFailures` so the hook can report it
|
|
7764
|
-
* (mirrors how `droppedInvalidFeatures` surfaces the sibling failure mode).
|
|
7765
7759
|
*/
|
|
7766
7760
|
const buildOverlapGroups = (records) => {
|
|
7767
7761
|
const parent = records.map((_, index) => index);
|
|
7768
|
-
const intersectionFailures = [];
|
|
7769
7762
|
const find = (index) => {
|
|
7770
7763
|
let root = index;
|
|
7771
7764
|
while (parent[root] !== root)
|
|
@@ -7789,17 +7782,7 @@ const buildOverlapGroups = (records) => {
|
|
|
7789
7782
|
continue;
|
|
7790
7783
|
if (!bboxesIntersect$1(a.bbox, b.bbox))
|
|
7791
7784
|
continue;
|
|
7792
|
-
|
|
7793
|
-
try {
|
|
7794
|
-
overlaps = getGeoJsonPolygonIntersection(a.geometry, b.geometry) !== null;
|
|
7795
|
-
}
|
|
7796
|
-
catch (error) {
|
|
7797
|
-
// polygon-clipping failed on this pair. Treat as no overlap (do not union)
|
|
7798
|
-
// so tiling degrades to full fills rather than crashing the map.
|
|
7799
|
-
intersectionFailures.push({ featureIdA: a.id, featureIdB: b.id, error });
|
|
7800
|
-
continue;
|
|
7801
|
-
}
|
|
7802
|
-
if (overlaps) {
|
|
7785
|
+
if (getGeoJsonPolygonIntersection(a.geometry, b.geometry) !== null) {
|
|
7803
7786
|
union(i, j);
|
|
7804
7787
|
}
|
|
7805
7788
|
}
|
|
@@ -7815,7 +7798,7 @@ const buildOverlapGroups = (records) => {
|
|
|
7815
7798
|
group.push(record);
|
|
7816
7799
|
}
|
|
7817
7800
|
});
|
|
7818
|
-
return
|
|
7801
|
+
return [...groups.values()].filter(group => group.length >= 2);
|
|
7819
7802
|
};
|
|
7820
7803
|
const buildContendedShapes = (group) => group.map(record => {
|
|
7821
7804
|
const containedIn = [];
|
|
@@ -7959,12 +7942,7 @@ const computeFillTiling = (input) => {
|
|
|
7959
7942
|
const fillGeometries = new Map();
|
|
7960
7943
|
const featureToGroupKey = new Map();
|
|
7961
7944
|
const featureZIndex = new Map();
|
|
7962
|
-
const
|
|
7963
|
-
if (intersectionFailures.length > 0) {
|
|
7964
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
7965
|
-
// 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
|
|
7966
|
-
console.warn(`[computeFillTiling] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${pairList}`);
|
|
7967
|
-
}
|
|
7945
|
+
const overlapGroups = buildOverlapGroups(records);
|
|
7968
7946
|
for (const group of overlapGroups) {
|
|
7969
7947
|
const contended = buildContendedShapes(group);
|
|
7970
7948
|
const recordById = new Map(group.map(record => [record.id, record]));
|
|
@@ -8039,7 +8017,6 @@ const computeFillTiling = (input) => {
|
|
|
8039
8017
|
featureToGroupKey,
|
|
8040
8018
|
featureZIndex,
|
|
8041
8019
|
droppedInvalidFeatures,
|
|
8042
|
-
intersectionFailures,
|
|
8043
8020
|
};
|
|
8044
8021
|
};
|
|
8045
8022
|
|
|
@@ -8345,31 +8322,6 @@ const buildShapeLabelResolutionContext = (feature, args) => {
|
|
|
8345
8322
|
};
|
|
8346
8323
|
};
|
|
8347
8324
|
|
|
8348
|
-
/** Stable, order-independent key so the pair (a,b) dedupes to the same entry as (b,a). */
|
|
8349
|
-
const resolveIntersectionFailureDedupeKey = (layerHandleId, failure) => {
|
|
8350
|
-
const [first, second] = [failure.featureIdA, failure.featureIdB].sort();
|
|
8351
|
-
return `${layerHandleId}:${first}|${second}`;
|
|
8352
|
-
};
|
|
8353
|
-
/**
|
|
8354
|
-
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
8355
|
-
* (SAGA-743), at most once per handle/pair per session.
|
|
8356
|
-
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
8357
|
-
*/
|
|
8358
|
-
const captureFillTilingIntersectionFailures = (intersectionFailures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
8359
|
-
for (const failure of intersectionFailures) {
|
|
8360
|
-
const key = resolveIntersectionFailureDedupeKey(layerHandleId, failure);
|
|
8361
|
-
if (sessionReportedDedupeKeys.has(key))
|
|
8362
|
-
continue;
|
|
8363
|
-
sessionReportedDedupeKeys.add(key);
|
|
8364
|
-
report({
|
|
8365
|
-
layerHandleId,
|
|
8366
|
-
featureIdA: failure.featureIdA,
|
|
8367
|
-
featureIdB: failure.featureIdB,
|
|
8368
|
-
message: failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
8369
|
-
});
|
|
8370
|
-
}
|
|
8371
|
-
};
|
|
8372
|
-
|
|
8373
8325
|
/**
|
|
8374
8326
|
* Extract ALL vertex coordinates from a geometry, walking every part of
|
|
8375
8327
|
* multi-geometries. Returns a flat array of [lng, lat] positions.
|
|
@@ -9624,7 +9576,6 @@ const bboxesIntersect = (a, b) => a[2] >= b[0] && a[0] <= b[2] && a[3] >= b[1] &
|
|
|
9624
9576
|
*/
|
|
9625
9577
|
const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds) => {
|
|
9626
9578
|
const counts = new Map();
|
|
9627
|
-
const intersectionFailures = [];
|
|
9628
9579
|
for (const feature of features) {
|
|
9629
9580
|
counts.set(feature, 0);
|
|
9630
9581
|
}
|
|
@@ -9653,21 +9604,7 @@ const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds)
|
|
|
9653
9604
|
const geomB = featureB.geometry;
|
|
9654
9605
|
const isPolygonalB = geomB !== null && (geomB.type === "Polygon" || geomB.type === "MultiPolygon");
|
|
9655
9606
|
if (isPolygonalA && isPolygonalB) {
|
|
9656
|
-
|
|
9657
|
-
try {
|
|
9658
|
-
overlaps = getGeoJsonPolygonIntersection(geomA, geomB) !== null;
|
|
9659
|
-
}
|
|
9660
|
-
catch (error) {
|
|
9661
|
-
// polygon-clipping failed on this pair (SAGA-743). Treat as no overlap so
|
|
9662
|
-
// decoration counts degrade gracefully instead of crashing the map.
|
|
9663
|
-
intersectionFailures.push({
|
|
9664
|
-
featureIdA: featureA.id !== undefined ? String(featureA.id) : `index:${i}`,
|
|
9665
|
-
featureIdB: featureB.id !== undefined ? String(featureB.id) : `index:${j}`,
|
|
9666
|
-
error,
|
|
9667
|
-
});
|
|
9668
|
-
continue;
|
|
9669
|
-
}
|
|
9670
|
-
if (overlaps) {
|
|
9607
|
+
if (getGeoJsonPolygonIntersection(geomA, geomB) !== null) {
|
|
9671
9608
|
if (isFullyContainedInGeoJsonGeometry(geomA, geomB) !== true) {
|
|
9672
9609
|
counts.set(featureA, (counts.get(featureA) ?? 0) + 1);
|
|
9673
9610
|
}
|
|
@@ -9682,7 +9619,7 @@ const buildOverlappingShapesCountMap = (features, featureBboxes, viewportBounds)
|
|
|
9682
9619
|
}
|
|
9683
9620
|
}
|
|
9684
9621
|
}
|
|
9685
|
-
return
|
|
9622
|
+
return counts;
|
|
9686
9623
|
};
|
|
9687
9624
|
/**
|
|
9688
9625
|
* Value-based equality for the nested style overrides maps. Used to skip
|
|
@@ -9768,25 +9705,6 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9768
9705
|
const previousLayoutSides = useRef(new Map());
|
|
9769
9706
|
const prevStyleOverridesRef = useRef(new Map());
|
|
9770
9707
|
const prevDecorationLayersRef = useRef([]);
|
|
9771
|
-
const errorHandler = useErrorHandlerOrNull();
|
|
9772
|
-
// Session-level dedupe so a recurring polygon-clipping failure (SAGA-743) reports
|
|
9773
|
-
// to Sentry at most once per handle/pair per session, not on every pan frame.
|
|
9774
|
-
const sessionReportedIntersectionKeysRef = useRef(new Set());
|
|
9775
|
-
const reportOverlapIntersectionFailures = useCallback((intersectionFailures, layerHandleId) => {
|
|
9776
|
-
if (intersectionFailures.length === 0)
|
|
9777
|
-
return;
|
|
9778
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9779
|
-
// 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
|
|
9780
|
-
console.warn(`[useShapeDecorations] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${pairList}`);
|
|
9781
|
-
if (errorHandler === null)
|
|
9782
|
-
return;
|
|
9783
|
-
captureFillTilingIntersectionFailures(intersectionFailures, layerHandleId, sessionReportedIntersectionKeysRef.current, payload => {
|
|
9784
|
-
errorHandler.captureException(new Error(`polygon-clipping failed during decoration overlap counting: ${JSON.stringify(payload)}`), {
|
|
9785
|
-
level: "warning",
|
|
9786
|
-
fingerprint: ["react-map", "fill-tiling", "intersection-failure"],
|
|
9787
|
-
});
|
|
9788
|
-
});
|
|
9789
|
-
}, [errorHandler]);
|
|
9790
9708
|
/**
|
|
9791
9709
|
* Labels that just transitioned from absent (hidden) to placed. While in
|
|
9792
9710
|
* this set, edge identity is NOT stored — the algorithm gets one extra
|
|
@@ -9846,9 +9764,7 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9846
9764
|
let overlappingCountsCache = null;
|
|
9847
9765
|
const getOverlappingCount = (feature) => {
|
|
9848
9766
|
if (overlappingCountsCache === null) {
|
|
9849
|
-
|
|
9850
|
-
overlappingCountsCache = counts;
|
|
9851
|
-
reportOverlapIntersectionFailures(intersectionFailures, handle.id);
|
|
9767
|
+
overlappingCountsCache = buildOverlappingShapesCountMap(handle.features.features, featureBboxes, bounds);
|
|
9852
9768
|
}
|
|
9853
9769
|
return overlappingCountsCache.get(feature) ?? 0;
|
|
9854
9770
|
};
|
|
@@ -10109,7 +10025,6 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
10109
10025
|
hasSourceReadiness,
|
|
10110
10026
|
renderedSourceIds,
|
|
10111
10027
|
isReady,
|
|
10112
|
-
reportOverlapIntersectionFailures,
|
|
10113
10028
|
]);
|
|
10114
10029
|
const updateDecorationsRef = useRef(updateDecorations);
|
|
10115
10030
|
useEffect(() => {
|
|
@@ -10642,7 +10557,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10642
10557
|
const nextRestingFills = new Map();
|
|
10643
10558
|
const nextRestingZIndices = new Map();
|
|
10644
10559
|
for (const handle of currentHandles) {
|
|
10645
|
-
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures
|
|
10560
|
+
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures } = computeFillTiling({
|
|
10646
10561
|
features: handle.features.features,
|
|
10647
10562
|
viewportBounds: GLOBAL_BOUNDS,
|
|
10648
10563
|
resolveStackOrder: handle.overlap?.resolveStackOrder ?? defaultShapeStackOrder,
|
|
@@ -10658,14 +10573,6 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10658
10573
|
});
|
|
10659
10574
|
});
|
|
10660
10575
|
}
|
|
10661
|
-
if (errorHandler !== null && intersectionFailures.length > 0) {
|
|
10662
|
-
captureFillTilingIntersectionFailures(intersectionFailures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
10663
|
-
errorHandler.captureException(new Error(`polygon-clipping failed during fill-tiling overlap grouping: ${JSON.stringify(payload)}`), {
|
|
10664
|
-
level: "warning",
|
|
10665
|
-
fingerprint: ["react-map", "fill-tiling", "intersection-failure"],
|
|
10666
|
-
});
|
|
10667
|
-
});
|
|
10668
|
-
}
|
|
10669
10576
|
featureToGroupKeyByHandleRef.current.set(handle.id, new Map(featureToGroupKey));
|
|
10670
10577
|
if (fillGeometries.size > 0) {
|
|
10671
10578
|
nextRestingFills.set(handle.id, fillGeometries);
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.93-alpha-62031fc7c43.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=24.x"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@trackunit/react-components": "2.10.
|
|
11
|
-
"@trackunit/css-class-variance-utilities": "1.14.
|
|
12
|
-
"@trackunit/react-form-components": "2.6.
|
|
13
|
-
"@trackunit/react-core-hooks": "1.21.
|
|
14
|
-
"@trackunit/geo-json-utils": "1.15.
|
|
15
|
-
"@trackunit/i18n-library-translation": "2.4.
|
|
10
|
+
"@trackunit/react-components": "2.10.12-alpha-62031fc7c43.0",
|
|
11
|
+
"@trackunit/css-class-variance-utilities": "1.14.19-alpha-62031fc7c43.0",
|
|
12
|
+
"@trackunit/react-form-components": "2.6.18-alpha-62031fc7c43.0",
|
|
13
|
+
"@trackunit/react-core-hooks": "1.21.18-alpha-62031fc7c43.0",
|
|
14
|
+
"@trackunit/geo-json-utils": "1.15.21-alpha-62031fc7c43.0",
|
|
15
|
+
"@trackunit/i18n-library-translation": "2.4.18-alpha-62031fc7c43.0",
|
|
16
16
|
"react-minimal-pie-chart": "^8.4.0",
|
|
17
|
-
"@trackunit/react-map-adapter-shared": "0.0.
|
|
18
|
-
"@trackunit/react-map-color-utils": "0.0.
|
|
19
|
-
"@trackunit/ui-design-tokens": "1.15.
|
|
17
|
+
"@trackunit/react-map-adapter-shared": "0.0.101-alpha-62031fc7c43.0",
|
|
18
|
+
"@trackunit/react-map-color-utils": "0.0.82-alpha-62031fc7c43.0",
|
|
19
|
+
"@trackunit/ui-design-tokens": "1.15.9-alpha-62031fc7c43.0",
|
|
20
20
|
"@floating-ui/react": "^0.26.25",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
22
|
"tailwind-merge": "^2.0.0",
|
|
@@ -70,25 +70,12 @@ export type FillTilingResult = Readonly<{
|
|
|
70
70
|
featureZIndex: ReadonlyMap<string, number>;
|
|
71
71
|
/** Features excluded from fill tiling when boundary schema validation fails. */
|
|
72
72
|
droppedInvalidFeatures: ReadonlyArray<DroppedInvalidFillTilingFeature>;
|
|
73
|
-
/**
|
|
74
|
-
* Feature pairs whose overlap test threw inside `polygon-clipping` (SAGA-743).
|
|
75
|
-
* The pair was treated as non-overlapping; surfaced here so the hook can report
|
|
76
|
-
* it to Sentry, since the geometry is schema-valid and never hits
|
|
77
|
-
* `droppedInvalidFeatures`.
|
|
78
|
-
*/
|
|
79
|
-
intersectionFailures: ReadonlyArray<FillTilingIntersectionFailure>;
|
|
80
73
|
}>;
|
|
81
74
|
export type DroppedInvalidFillTilingFeature = Readonly<{
|
|
82
75
|
feature: GeoJsonFeature;
|
|
83
76
|
/** `safeParse` failure details (`parsed.error.format()`). */
|
|
84
77
|
validationError: unknown;
|
|
85
78
|
}>;
|
|
86
|
-
export type FillTilingIntersectionFailure = Readonly<{
|
|
87
|
-
featureIdA: string;
|
|
88
|
-
featureIdB: string;
|
|
89
|
-
/** The error thrown by `polygon-clipping` (typically "Unable to complete output ring"). */
|
|
90
|
-
error: unknown;
|
|
91
|
-
}>;
|
|
92
79
|
/**
|
|
93
80
|
* Planar area of any GeoJSON geometry (outer rings minus holes). Returns 0 for
|
|
94
81
|
* non-polygonal types (Point, LineString, …). Relative magnitude only — not
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { FillTilingIntersectionFailure } from "./shapeFillTiling";
|
|
2
|
-
type ReportFillTilingIntersectionFailureEvent = (payload: Record<string, unknown>) => void;
|
|
3
|
-
/**
|
|
4
|
-
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
5
|
-
* (SAGA-743), at most once per handle/pair per session.
|
|
6
|
-
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
7
|
-
*/
|
|
8
|
-
export declare const captureFillTilingIntersectionFailures: (intersectionFailures: ReadonlyArray<FillTilingIntersectionFailure>, layerHandleId: string, sessionReportedDedupeKeys: Set<string>, report: ReportFillTilingIntersectionFailureEvent) => void;
|
|
9
|
-
export {};
|