@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.esm.js
CHANGED
|
@@ -3454,8 +3454,17 @@ const useControls = (base, areas) => {
|
|
|
3454
3454
|
/**
|
|
3455
3455
|
* Single dev-warning channel for react-map hooks (invalid data, invalid style, unhandled render errors).
|
|
3456
3456
|
* Messages are prefixed with the emitting hook, e.g. `[useHeatmap]`.
|
|
3457
|
+
*
|
|
3458
|
+
* Skipped in production builds: the warnings describe consumer mistakes a developer fixes
|
|
3459
|
+
* once, and with polled data they would otherwise log on every data change. Vitest runs
|
|
3460
|
+
* with `NODE_ENV` set to `test`, so specs can still spy on `console.warn`.
|
|
3461
|
+
*
|
|
3462
|
+
* Call it from effects, handlers and callbacks, never during render: pure normalizers
|
|
3463
|
+
* return their warnings and `useDevWarnings` emits them once per result.
|
|
3457
3464
|
*/
|
|
3458
3465
|
const consoleWarnMap = (source, message, ...details) => {
|
|
3466
|
+
if (process.env.NODE_ENV === "production")
|
|
3467
|
+
return;
|
|
3459
3468
|
// 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
|
|
3460
3469
|
console.warn(`[${source}] ${message}`, ...details);
|
|
3461
3470
|
};
|
|
@@ -5912,17 +5921,39 @@ const createLazyGetter = (compute) => {
|
|
|
5912
5921
|
};
|
|
5913
5922
|
};
|
|
5914
5923
|
|
|
5924
|
+
/** Shared empty result so hooks with nothing to report keep a stable reference. */
|
|
5925
|
+
const NO_DEV_WARNINGS = [];
|
|
5926
|
+
/**
|
|
5927
|
+
* Emits a normalizer's warnings through the react-map dev-warning channel, from an
|
|
5928
|
+
* effect rather than during render.
|
|
5929
|
+
*
|
|
5930
|
+
* Pure normalizers (`filterPositioned`, `normalizeHeatmapData`, ...) run inside render
|
|
5931
|
+
* and must not log: StrictMode and discarded concurrent renders would log them twice.
|
|
5932
|
+
* They return their warnings instead, and this hook emits each list once. The list's
|
|
5933
|
+
* identity is the dedupe key, so StrictMode's double effect run and re-renders that
|
|
5934
|
+
* keep the same normalized result add nothing; a new normalization pass (a new list)
|
|
5935
|
+
* warns again, which is what a developer feeding new invalid data expects.
|
|
5936
|
+
*/
|
|
5937
|
+
const useDevWarnings = (source, warnings) => {
|
|
5938
|
+
const emittedRef = useRef(null);
|
|
5939
|
+
useEffect(() => {
|
|
5940
|
+
if (warnings.length === 0 || emittedRef.current === warnings)
|
|
5941
|
+
return;
|
|
5942
|
+
emittedRef.current = warnings;
|
|
5943
|
+
for (const message of warnings) {
|
|
5944
|
+
consoleWarnMap(source, message);
|
|
5945
|
+
}
|
|
5946
|
+
}, [source, warnings]);
|
|
5947
|
+
};
|
|
5948
|
+
|
|
5915
5949
|
// ============================================================================
|
|
5916
5950
|
// Constants
|
|
5917
5951
|
// ============================================================================
|
|
5918
5952
|
const EMPTY_CONTROLS$3 = {};
|
|
5919
5953
|
const EMPTY_FEATURES$1 = { type: "FeatureCollection", features: [] };
|
|
5920
|
-
// ============================================================================
|
|
5921
|
-
// Internal helpers
|
|
5922
|
-
// ============================================================================
|
|
5923
5954
|
/**
|
|
5924
5955
|
* Filter waypoints to only valid GeoJSON positions. Invalid entries are dropped.
|
|
5925
|
-
*
|
|
5956
|
+
* Pure: it runs during render, so it returns one grouped warning instead of logging.
|
|
5926
5957
|
*/
|
|
5927
5958
|
const filterValidWaypoints = (waypoints) => {
|
|
5928
5959
|
const valid = [];
|
|
@@ -5932,12 +5963,11 @@ const filterValidWaypoints = (waypoints) => {
|
|
|
5932
5963
|
valid.push(result.data);
|
|
5933
5964
|
}
|
|
5934
5965
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
}
|
|
5940
|
-
return valid;
|
|
5966
|
+
const filtered = waypoints.length - valid.length;
|
|
5967
|
+
const warnings = filtered > 0
|
|
5968
|
+
? [`Filtered ${filtered} invalid waypoint(s). Expected [longitude, latitude] with latitude between -90 and 90.`]
|
|
5969
|
+
: NO_DEV_WARNINGS;
|
|
5970
|
+
return { waypoints: valid, warnings };
|
|
5941
5971
|
};
|
|
5942
5972
|
/**
|
|
5943
5973
|
* Convert waypoints to a GeoJSON FeatureCollection with a single LineString.
|
|
@@ -5985,7 +6015,9 @@ const useRoute = (options) => {
|
|
|
5985
6015
|
// ---- Data readiness tracking ----
|
|
5986
6016
|
const layerReady = useLayerReady(loading);
|
|
5987
6017
|
// ---- Filter to valid waypoints only ----
|
|
5988
|
-
const
|
|
6018
|
+
const filtered = useMemo(() => filterValidWaypoints(rawWaypoints), [rawWaypoints]);
|
|
6019
|
+
const waypoints = filtered.waypoints;
|
|
6020
|
+
useDevWarnings("useRoute", filtered.warnings);
|
|
5989
6021
|
// ---- Convert to GeoJSON ----
|
|
5990
6022
|
const features = useMemo(() => toLineStringFeatureCollection(id, waypoints), [id, waypoints]);
|
|
5991
6023
|
// ---- Lazy getBounds with cache ----
|
|
@@ -6100,8 +6132,7 @@ const useFitToContent = (api, handles, options) => {
|
|
|
6100
6132
|
const stillPending = filterHandles(handlesRef.current, "initial").filter(h => h.loadsData && h.loading);
|
|
6101
6133
|
if (stillPending.length > 0) {
|
|
6102
6134
|
const names = stillPending.map(h => h.name).join(", ");
|
|
6103
|
-
|
|
6104
|
-
console.warn(`[useFitToContent] Timeout (${timeout}ms) reached. Fitting with available data. Still loading: ${names}`);
|
|
6135
|
+
consoleWarnMap("useFitToContent", `Timeout (${timeout}ms) reached. Fitting with available data. Still loading: ${names}`);
|
|
6105
6136
|
}
|
|
6106
6137
|
performFit();
|
|
6107
6138
|
}, timeout);
|
|
@@ -6133,7 +6164,8 @@ const useFitToContent = (api, handles, options) => {
|
|
|
6133
6164
|
* Converts consumer items into weighted GeoJSON `Point` features.
|
|
6134
6165
|
*
|
|
6135
6166
|
* Null positions and zero weights are dropped silently. Invalid positions and
|
|
6136
|
-
* negative or non-finite weights are dropped
|
|
6167
|
+
* negative or non-finite weights are dropped and reported as one grouped warning
|
|
6168
|
+
* per pass in `warnings`; the hook emits it from an effect, never during render.
|
|
6137
6169
|
*/
|
|
6138
6170
|
const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
|
|
6139
6171
|
const features = [];
|
|
@@ -6163,12 +6195,15 @@ const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
|
|
|
6163
6195
|
properties: { weight },
|
|
6164
6196
|
});
|
|
6165
6197
|
}
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6198
|
+
const warnings = invalidPositionCount > 0 || invalidWeightCount > 0
|
|
6199
|
+
? [
|
|
6200
|
+
`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.`,
|
|
6201
|
+
]
|
|
6202
|
+
: NO_DEV_WARNINGS;
|
|
6169
6203
|
return {
|
|
6170
6204
|
features: { type: "FeatureCollection", features },
|
|
6171
6205
|
positions,
|
|
6206
|
+
warnings,
|
|
6172
6207
|
};
|
|
6173
6208
|
};
|
|
6174
6209
|
|
|
@@ -6222,13 +6257,15 @@ const DEFAULT_HEATMAP_COLOR_RANGE = [
|
|
|
6222
6257
|
const DEFAULT_PARSED_COLOR_RANGE = DEFAULT_HEATMAP_COLOR_RANGE.map(parseHeatmapColor).filter(nonNullable);
|
|
6223
6258
|
const resolveColorRange = (colorRange) => {
|
|
6224
6259
|
if (colorRange === undefined)
|
|
6225
|
-
return DEFAULT_PARSED_COLOR_RANGE;
|
|
6260
|
+
return { colorRange: DEFAULT_PARSED_COLOR_RANGE, warning: null };
|
|
6226
6261
|
const parsed = colorRange.map(parseHeatmapColor);
|
|
6227
6262
|
const valid = parsed.filter(nonNullable);
|
|
6228
6263
|
if (colorRange.length >= 2 && valid.length === parsed.length)
|
|
6229
|
-
return valid;
|
|
6230
|
-
|
|
6231
|
-
|
|
6264
|
+
return { colorRange: valid, warning: null };
|
|
6265
|
+
return {
|
|
6266
|
+
colorRange: DEFAULT_PARSED_COLOR_RANGE,
|
|
6267
|
+
warning: `colorRange needs at least two supported colours (#rgb, #rrggbb, #rrggbbaa, rgb(), rgba()); using the default. Received: ${JSON.stringify(colorRange)}`,
|
|
6268
|
+
};
|
|
6232
6269
|
};
|
|
6233
6270
|
const HEATMAP_STYLE_RULES = {
|
|
6234
6271
|
radiusPixels: { defaultValue: 30, min: 1, max: 100 },
|
|
@@ -6238,7 +6275,8 @@ const HEATMAP_STYLE_RULES = {
|
|
|
6238
6275
|
/**
|
|
6239
6276
|
* Applies defaults and supported ranges so both adapters receive identical style values.
|
|
6240
6277
|
*
|
|
6241
|
-
* Finite values are clamped; non-finite values fall back to the default with one
|
|
6278
|
+
* Finite values are clamped; non-finite values fall back to the default with one grouped
|
|
6279
|
+
* warning. Pure: it runs inside a render-time `useMemo`, so it returns its warnings.
|
|
6242
6280
|
*/
|
|
6243
6281
|
const resolveHeatmapStyle = (style) => {
|
|
6244
6282
|
const nonFiniteKeys = [];
|
|
@@ -6253,16 +6291,20 @@ const resolveHeatmapStyle = (style) => {
|
|
|
6253
6291
|
}
|
|
6254
6292
|
return Math.min(rule.max, Math.max(rule.min, value));
|
|
6255
6293
|
};
|
|
6294
|
+
const colorRange = resolveColorRange(style?.colorRange);
|
|
6256
6295
|
const resolved = {
|
|
6257
6296
|
radiusPixels: resolveValue("radiusPixels"),
|
|
6258
6297
|
intensity: resolveValue("intensity"),
|
|
6259
6298
|
opacity: resolveValue("opacity"),
|
|
6260
|
-
colorRange:
|
|
6299
|
+
colorRange: colorRange.colorRange,
|
|
6261
6300
|
};
|
|
6301
|
+
const warnings = [];
|
|
6262
6302
|
if (nonFiniteKeys.length > 0) {
|
|
6263
|
-
|
|
6303
|
+
warnings.push(`Non-finite style value(s) for ${nonFiniteKeys.join(", ")} replaced with defaults.`);
|
|
6264
6304
|
}
|
|
6265
|
-
|
|
6305
|
+
if (colorRange.warning !== null)
|
|
6306
|
+
warnings.push(colorRange.warning);
|
|
6307
|
+
return { style: resolved, warnings: warnings.length === 0 ? NO_DEV_WARNINGS : warnings };
|
|
6266
6308
|
};
|
|
6267
6309
|
|
|
6268
6310
|
const EMPTY_CONTROLS$2 = {};
|
|
@@ -6324,12 +6366,17 @@ const useHeatmap = (options) => {
|
|
|
6324
6366
|
if (style?.colorRange !== colorRange && !isEqual(style?.colorRange, colorRange)) {
|
|
6325
6367
|
setColorRange(style?.colorRange);
|
|
6326
6368
|
}
|
|
6327
|
-
const
|
|
6369
|
+
const resolved = useMemo(() => resolveHeatmapStyle({
|
|
6328
6370
|
radiusPixels: style?.radiusPixels,
|
|
6329
6371
|
intensity: style?.intensity,
|
|
6330
6372
|
opacity: style?.opacity,
|
|
6331
6373
|
colorRange,
|
|
6332
6374
|
}), [style?.radiusPixels, style?.intensity, style?.opacity, colorRange]);
|
|
6375
|
+
const resolvedStyle = resolved.style;
|
|
6376
|
+
// Normalization and style resolution run during render and stay pure; their
|
|
6377
|
+
// warnings are emitted once per pass from an effect (SAGA-907).
|
|
6378
|
+
useDevWarnings("useHeatmap", normalized.warnings);
|
|
6379
|
+
useDevWarnings("useHeatmap", resolved.warnings);
|
|
6333
6380
|
const getBounds = useMemo(() => createLazyGetter(() => computeBoundsFromPositions(normalized.positions)), [normalized.positions]);
|
|
6334
6381
|
const counts = useMemo(() => ({ total: data.length, points: normalized.features.features.length }), [data.length, normalized.features.features.length]);
|
|
6335
6382
|
return useMemo(() => ({
|
|
@@ -7395,7 +7442,8 @@ const EMPTY_FEATURES = { type: "FeatureCollection", features: [] };
|
|
|
7395
7442
|
const EMPTY_CONTROLS$1 = {};
|
|
7396
7443
|
/**
|
|
7397
7444
|
* Filter items to only those with valid positions and extract id + position.
|
|
7398
|
-
* Invalid positions (e.g. latitude out of range) are skipped.
|
|
7445
|
+
* Invalid positions (e.g. latitude out of range) are skipped. Pure: it runs during
|
|
7446
|
+
* render, so it returns one grouped warning instead of logging.
|
|
7399
7447
|
*/
|
|
7400
7448
|
const filterPositioned = (data, getPosition, getId) => {
|
|
7401
7449
|
const result = [];
|
|
@@ -7412,11 +7460,12 @@ const filterPositioned = (data, getPosition, getId) => {
|
|
|
7412
7460
|
}
|
|
7413
7461
|
}
|
|
7414
7462
|
}
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7463
|
+
const warnings = invalidCount > 0
|
|
7464
|
+
? [
|
|
7465
|
+
`Filtered ${invalidCount} item(s) with invalid position. Expected [longitude, latitude] with latitude between -90 and 90.`,
|
|
7466
|
+
]
|
|
7467
|
+
: NO_DEV_WARNINGS;
|
|
7468
|
+
return { items: result, warnings };
|
|
7420
7469
|
};
|
|
7421
7470
|
/**
|
|
7422
7471
|
* Convert positioned items to a GeoJSON FeatureCollection of Points.
|
|
@@ -7520,11 +7569,13 @@ const useMarkers = (options) => {
|
|
|
7520
7569
|
// structurally-identical inline arrays do NOT trigger infinite re-renders.
|
|
7521
7570
|
// The reference check short-circuits isEqual for the common stable-ref case.
|
|
7522
7571
|
const [prevData, setPrevData] = useState(data);
|
|
7523
|
-
const [
|
|
7572
|
+
const [positioned, setPositioned] = useState(() => filterPositioned(data, getPosition, getId));
|
|
7524
7573
|
if (data !== prevData && !isEqual(data, prevData)) {
|
|
7525
7574
|
setPrevData(data);
|
|
7526
|
-
|
|
7575
|
+
setPositioned(filterPositioned(data, getPosition, getId));
|
|
7527
7576
|
}
|
|
7577
|
+
const positionedItems = positioned.items;
|
|
7578
|
+
useDevWarnings("useMarkers", positioned.warnings);
|
|
7528
7579
|
// ---- Convert to GeoJSON FeatureCollection ----
|
|
7529
7580
|
const features = useMemo(() => toPointFeatureCollection(positionedItems), [positionedItems]);
|
|
7530
7581
|
// ---- Resolved server clusters (after resolveGroups entity-shape decisions) ----
|
|
@@ -9378,14 +9429,8 @@ const computeFillTiling = (input) => {
|
|
|
9378
9429
|
}
|
|
9379
9430
|
validFeatures.push(feature);
|
|
9380
9431
|
}
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
9384
|
-
.filter((id) => id !== undefined)
|
|
9385
|
-
.join(", ") || "(no feature ids)";
|
|
9386
|
-
// eslint-disable-next-line no-console -- Intentional: warn devs when schema-invalid polygonal geometry is excluded from fill tiling
|
|
9387
|
-
console.warn(`[computeFillTiling] Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${idList}`);
|
|
9388
|
-
}
|
|
9432
|
+
// Dropped features are returned, not logged: this function runs inside the hook's
|
|
9433
|
+
// recompute path and stays pure; the hook emits one dev warning per pass.
|
|
9389
9434
|
// Densify before any polygon/boundary math so clip geometry follows great-circle
|
|
9390
9435
|
// arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
|
|
9391
9436
|
// defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
|
|
@@ -9418,11 +9463,7 @@ const computeFillTiling = (input) => {
|
|
|
9418
9463
|
const featureToGroupKey = new Map();
|
|
9419
9464
|
const featureZIndex = new Map();
|
|
9420
9465
|
const { groups: overlapGroups, intersectionFailures } = buildOverlapGroups(records);
|
|
9421
|
-
|
|
9422
|
-
const pairList = intersectionFailures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9423
|
-
// 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
|
|
9424
|
-
console.warn(`[computeFillTiling] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${pairList}`);
|
|
9425
|
-
}
|
|
9466
|
+
// Failures are returned for the hook to warn about and report (SAGA-743).
|
|
9426
9467
|
for (const group of overlapGroups) {
|
|
9427
9468
|
const contended = buildContendedShapes(group);
|
|
9428
9469
|
const recordById = new Map(group.map(record => [record.id, record]));
|
|
@@ -9501,6 +9542,9 @@ const computeFillTiling = (input) => {
|
|
|
9501
9542
|
};
|
|
9502
9543
|
};
|
|
9503
9544
|
|
|
9545
|
+
/** Renders failing feature pairs as `a↔b, c↔d` for dev warnings and error reports. */
|
|
9546
|
+
const formatIntersectionFailurePairs = (failures) => failures.map(failure => `${failure.featureIdA}↔${failure.featureIdB}`).join(", ");
|
|
9547
|
+
|
|
9504
9548
|
const DEFAULT_ZOOM = 0;
|
|
9505
9549
|
const DEFAULT_TILE_SIZE = 256;
|
|
9506
9550
|
/**
|
|
@@ -9811,7 +9855,8 @@ const resolveIntersectionFailureDedupeKey = (layerHandleId, failure) => {
|
|
|
9811
9855
|
/**
|
|
9812
9856
|
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
9813
9857
|
* (SAGA-743), at most once per handle/pair per session.
|
|
9814
|
-
* Note:
|
|
9858
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
9859
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
9815
9860
|
*/
|
|
9816
9861
|
const captureFillTilingIntersectionFailures = (intersectionFailures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
9817
9862
|
for (const failure of intersectionFailures) {
|
|
@@ -11233,9 +11278,7 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
11233
11278
|
const reportOverlapIntersectionFailures = useCallback((intersectionFailures, layerHandleId) => {
|
|
11234
11279
|
if (intersectionFailures.length === 0)
|
|
11235
11280
|
return;
|
|
11236
|
-
|
|
11237
|
-
// 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
|
|
11238
|
-
console.warn(`[useShapeDecorations] polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${pairList}`);
|
|
11281
|
+
consoleWarnMap("useShapeDecorations", `polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping: ${formatIntersectionFailurePairs(intersectionFailures)}`);
|
|
11239
11282
|
if (errorHandler === null)
|
|
11240
11283
|
return;
|
|
11241
11284
|
captureFillTilingIntersectionFailures(intersectionFailures, layerHandleId, sessionReportedIntersectionKeysRef.current, payload => {
|
|
@@ -11658,7 +11701,8 @@ const captureOnce = (key, report, sessionReportedDedupeKeys, payload) => {
|
|
|
11658
11701
|
};
|
|
11659
11702
|
/**
|
|
11660
11703
|
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
11661
|
-
* Note:
|
|
11704
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
11705
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
11662
11706
|
*/
|
|
11663
11707
|
const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
11664
11708
|
for (const entry of droppedInvalidFeatures) {
|
|
@@ -11672,6 +11716,12 @@ const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHa
|
|
|
11672
11716
|
}
|
|
11673
11717
|
};
|
|
11674
11718
|
|
|
11719
|
+
/** Lists the ids of features dropped from fill tiling, or a placeholder when none carry an id. */
|
|
11720
|
+
const formatDroppedInvalidFeatureIds = (dropped) => dropped
|
|
11721
|
+
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
11722
|
+
.filter(nonNullable)
|
|
11723
|
+
.join(", ") || "(no feature ids)";
|
|
11724
|
+
|
|
11675
11725
|
// ============================================================================
|
|
11676
11726
|
// Default resolver
|
|
11677
11727
|
// ============================================================================
|
|
@@ -12108,6 +12158,14 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
12108
12158
|
layerGeodesic: handle.style.geodesic,
|
|
12109
12159
|
featureStyles: handle.featureStyles,
|
|
12110
12160
|
});
|
|
12161
|
+
// The dev warnings live here, not in computeFillTiling: recompute runs from
|
|
12162
|
+
// effects and idle callbacks, so the pure function stays silent (SAGA-907).
|
|
12163
|
+
if (droppedInvalidFeatures.length > 0) {
|
|
12164
|
+
consoleWarnMap("useShapes", `Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${formatDroppedInvalidFeatureIds(droppedInvalidFeatures)}`);
|
|
12165
|
+
}
|
|
12166
|
+
if (intersectionFailures.length > 0) {
|
|
12167
|
+
consoleWarnMap("useShapes", `polygon-clipping failed on ${intersectionFailures.length} feature pair(s); treated as non-overlapping (full fill still renders): ${formatIntersectionFailurePairs(intersectionFailures)}`);
|
|
12168
|
+
}
|
|
12111
12169
|
if (errorHandler !== null && droppedInvalidFeatures.length > 0) {
|
|
12112
12170
|
captureDroppedInvalidFillTilingFeatures(droppedInvalidFeatures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
12113
12171
|
errorHandler.captureException(new Error(`Schema-invalid polygonal geometry excluded from fill tiling: ${JSON.stringify(payload)}`), {
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.230",
|
|
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": "3.11.
|
|
11
|
-
"@trackunit/css-class-variance-utilities": "2.6.
|
|
12
|
-
"@trackunit/react-form-components": "2.13.
|
|
13
|
-
"@trackunit/react-core-hooks": "1.28.
|
|
14
|
-
"@trackunit/geo-json-utils": "1.21.
|
|
15
|
-
"@trackunit/i18n-library-translation": "2.11.
|
|
10
|
+
"@trackunit/react-components": "3.11.2",
|
|
11
|
+
"@trackunit/css-class-variance-utilities": "2.6.1",
|
|
12
|
+
"@trackunit/react-form-components": "2.13.2",
|
|
13
|
+
"@trackunit/react-core-hooks": "1.28.2",
|
|
14
|
+
"@trackunit/geo-json-utils": "1.21.1",
|
|
15
|
+
"@trackunit/i18n-library-translation": "2.11.2",
|
|
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/shared-utils": "1.22.
|
|
20
|
-
"@trackunit/ui-design-tokens": "1.21.
|
|
17
|
+
"@trackunit/react-map-adapter-shared": "0.0.168",
|
|
18
|
+
"@trackunit/react-map-color-utils": "0.0.142",
|
|
19
|
+
"@trackunit/shared-utils": "1.22.1",
|
|
20
|
+
"@trackunit/ui-design-tokens": "1.21.1",
|
|
21
21
|
"@floating-ui/react": "^0.26.25",
|
|
22
22
|
"es-toolkit": "^1.52.0",
|
|
23
23
|
"tailwind-merge": "^3.7.0",
|
package/src/draw/useDraw.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export type UseDrawOptions = Readonly<{
|
|
|
26
26
|
* engine failed to load or update, or part of a committed value could not be
|
|
27
27
|
* rendered (`value-rejected`, with the indices of the features that are not on the
|
|
28
28
|
* map). The committed `value` is never changed by an error. Without a handler the
|
|
29
|
-
* error is logged
|
|
29
|
+
* error is logged as a development warning (silent in production builds); it is
|
|
30
|
+
* reported to the app's error handler
|
|
30
31
|
* either way, when the host provides one, at most once per code per mount.
|
|
31
32
|
*/
|
|
32
33
|
onError?: (error: DrawError) => void;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single dev-warning channel for react-map hooks (invalid data, invalid style, unhandled render errors).
|
|
3
3
|
* Messages are prefixed with the emitting hook, e.g. `[useHeatmap]`.
|
|
4
|
+
*
|
|
5
|
+
* Skipped in production builds: the warnings describe consumer mistakes a developer fixes
|
|
6
|
+
* once, and with polled data they would otherwise log on every data change. Vitest runs
|
|
7
|
+
* with `NODE_ENV` set to `test`, so specs can still spy on `console.warn`.
|
|
8
|
+
*
|
|
9
|
+
* Call it from effects, handlers and callbacks, never during render: pure normalizers
|
|
10
|
+
* return their warnings and `useDevWarnings` emits them once per result.
|
|
4
11
|
*/
|
|
5
12
|
export declare const consoleWarnMap: (source: string, message: string, ...details: ReadonlyArray<unknown>) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shared empty result so hooks with nothing to report keep a stable reference. */
|
|
2
|
+
export declare const NO_DEV_WARNINGS: ReadonlyArray<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Emits a normalizer's warnings through the react-map dev-warning channel, from an
|
|
5
|
+
* effect rather than during render.
|
|
6
|
+
*
|
|
7
|
+
* Pure normalizers (`filterPositioned`, `normalizeHeatmapData`, ...) run inside render
|
|
8
|
+
* and must not log: StrictMode and discarded concurrent renders would log them twice.
|
|
9
|
+
* They return their warnings instead, and this hook emits each list once. The list's
|
|
10
|
+
* identity is the dedupe key, so StrictMode's double effect run and re-renders that
|
|
11
|
+
* keep the same normalized result add nothing; a new normalization pass (a new list)
|
|
12
|
+
* warns again, which is what a developer feeding new invalid data expects.
|
|
13
|
+
*/
|
|
14
|
+
export declare const useDevWarnings: (source: string, warnings: ReadonlyArray<string>) => void;
|
|
@@ -24,7 +24,8 @@ export type UseHeatmapOptions<TItem> = Readonly<{
|
|
|
24
24
|
style?: HeatmapStyle;
|
|
25
25
|
/**
|
|
26
26
|
* Called once per failed renderer load or update (for example when the Google deck.gl chunk fails to load).
|
|
27
|
-
* The basemap and other layers keep rendering. Without it, failures are logged
|
|
27
|
+
* The basemap and other layers keep rendering. Without it, failures are logged as a development warning
|
|
28
|
+
* (silent in production builds).
|
|
28
29
|
* Failures are reported to the app's error handler (Sentry) either way.
|
|
29
30
|
* After a renderer failure (for example WebGL context loss on Google), the heatmap stays hidden until the next map
|
|
30
31
|
* layer or interaction change triggers a retry; nothing retries on an idle map.
|
|
@@ -3,6 +3,8 @@ import type { GeoJsonFeatureCollection } from "../../types";
|
|
|
3
3
|
export type NormalizedHeatmapData = Readonly<{
|
|
4
4
|
features: GeoJsonFeatureCollection;
|
|
5
5
|
positions: ReadonlyArray<GeoJsonPosition>;
|
|
6
|
+
/** One grouped message per pass when items were dropped; emitted by `useDevWarnings`. */
|
|
7
|
+
warnings: ReadonlyArray<string>;
|
|
6
8
|
}>;
|
|
7
9
|
type NormalizeHeatmapDataOptions<TItem> = Readonly<{
|
|
8
10
|
data: ReadonlyArray<TItem>;
|
|
@@ -13,7 +15,8 @@ type NormalizeHeatmapDataOptions<TItem> = Readonly<{
|
|
|
13
15
|
* Converts consumer items into weighted GeoJSON `Point` features.
|
|
14
16
|
*
|
|
15
17
|
* Null positions and zero weights are dropped silently. Invalid positions and
|
|
16
|
-
* negative or non-finite weights are dropped
|
|
18
|
+
* negative or non-finite weights are dropped and reported as one grouped warning
|
|
19
|
+
* per pass in `warnings`; the hook emits it from an effect, never during render.
|
|
17
20
|
*/
|
|
18
21
|
export declare const normalizeHeatmapData: <TItem>({ data, getPosition, getWeight, }: NormalizeHeatmapDataOptions<TItem>) => NormalizedHeatmapData;
|
|
19
22
|
export {};
|
|
@@ -7,10 +7,16 @@ type HeatmapStyleInput = Readonly<{
|
|
|
7
7
|
}>;
|
|
8
8
|
/** Movement page gradient (legacy Google heatmap), low to high density. */
|
|
9
9
|
export declare const DEFAULT_HEATMAP_COLOR_RANGE: ReadonlyArray<string>;
|
|
10
|
+
export type ResolvedHeatmapStyle = Readonly<{
|
|
11
|
+
style: HeatmapRenderStyle;
|
|
12
|
+
/** Grouped messages for values that were replaced; emitted by `useDevWarnings`, never here. */
|
|
13
|
+
warnings: ReadonlyArray<string>;
|
|
14
|
+
}>;
|
|
10
15
|
/**
|
|
11
16
|
* Applies defaults and supported ranges so both adapters receive identical style values.
|
|
12
17
|
*
|
|
13
|
-
* Finite values are clamped; non-finite values fall back to the default with one
|
|
18
|
+
* Finite values are clamped; non-finite values fall back to the default with one grouped
|
|
19
|
+
* warning. Pure: it runs inside a render-time `useMemo`, so it returns its warnings.
|
|
14
20
|
*/
|
|
15
|
-
export declare const resolveHeatmapStyle: (style: HeatmapStyleInput | undefined) =>
|
|
21
|
+
export declare const resolveHeatmapStyle: (style: HeatmapStyleInput | undefined) => ResolvedHeatmapStyle;
|
|
16
22
|
export {};
|
|
@@ -2,7 +2,8 @@ import type { DroppedInvalidFillTilingFeature } from "./shapeFillTiling";
|
|
|
2
2
|
type ReportDroppedInvalidFillTilingEvent = (payload: Record<string, unknown>) => void;
|
|
3
3
|
/**
|
|
4
4
|
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
5
|
-
* Note:
|
|
5
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
6
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
6
7
|
*/
|
|
7
8
|
export declare const captureDroppedInvalidFillTilingFeatures: (droppedInvalidFeatures: ReadonlyArray<DroppedInvalidFillTilingFeature>, layerHandleId: string, sessionReportedDedupeKeys: Set<string>, report: ReportDroppedInvalidFillTilingEvent) => void;
|
|
8
9
|
export {};
|
|
@@ -3,7 +3,8 @@ type ReportFillTilingIntersectionFailureEvent = (payload: Record<string, unknown
|
|
|
3
3
|
/**
|
|
4
4
|
* Report to Sentry each feature pair whose overlap test threw inside `polygon-clipping`
|
|
5
5
|
* (SAGA-743), at most once per handle/pair per session.
|
|
6
|
-
* Note:
|
|
6
|
+
* Note: the dev warning for this case is emitted by `useShapeFillTiling` from its recompute
|
|
7
|
+
* path (`computeFillTiling` itself is pure and logs nothing).
|
|
7
8
|
*/
|
|
8
9
|
export declare const captureFillTilingIntersectionFailures: (intersectionFailures: ReadonlyArray<FillTilingIntersectionFailure>, layerHandleId: string, sessionReportedDedupeKeys: Set<string>, report: ReportFillTilingIntersectionFailureEvent) => void;
|
|
9
10
|
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DroppedInvalidFillTilingFeature } from "../shapeFillTiling";
|
|
2
|
+
/** Lists the ids of features dropped from fill tiling, or a placeholder when none carry an id. */
|
|
3
|
+
export declare const formatDroppedInvalidFeatureIds: (dropped: ReadonlyArray<DroppedInvalidFillTilingFeature>) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FillTilingIntersectionFailure } from "../shapeFillTiling";
|
|
2
|
+
/** Renders failing feature pairs as `a↔b, c↔d` for dev warnings and error reports. */
|
|
3
|
+
export declare const formatIntersectionFailurePairs: (failures: ReadonlyArray<FillTilingIntersectionFailure>) => string;
|
package/translation.cjs.js
CHANGED
|
@@ -14,6 +14,7 @@ var translation = {
|
|
|
14
14
|
"controls.stepper.increase": "Vergrößern",
|
|
15
15
|
"controls.zoom": "Zoom",
|
|
16
16
|
"draw.clear": "Clear drawing",
|
|
17
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
17
18
|
"draw.polygon": "Draw polygon",
|
|
18
19
|
"draw.select": "Edit polygon",
|
|
19
20
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -22,6 +23,7 @@ var translation = {
|
|
|
22
23
|
"map.loadingErrorMessage": "Die Karte konnte nicht geladen werden. Bitte versuchen Sie es später erneut.",
|
|
23
24
|
"multiGeometry.badge.tooltip": "Teil eines zusammengesetzten Objekts",
|
|
24
25
|
"settings.mapStyle.dark": "Dunkel",
|
|
26
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
25
27
|
"settings.mapStyle.light": "Hell",
|
|
26
28
|
"settings.mapStyle.roadmap": "Straßenkarte",
|
|
27
29
|
"settings.mapStyle.roads": "Straßen",
|
package/translation.esm.js
CHANGED
|
@@ -12,6 +12,7 @@ var translation = {
|
|
|
12
12
|
"controls.stepper.increase": "Vergrößern",
|
|
13
13
|
"controls.zoom": "Zoom",
|
|
14
14
|
"draw.clear": "Clear drawing",
|
|
15
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
15
16
|
"draw.polygon": "Draw polygon",
|
|
16
17
|
"draw.select": "Edit polygon",
|
|
17
18
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -20,6 +21,7 @@ var translation = {
|
|
|
20
21
|
"map.loadingErrorMessage": "Die Karte konnte nicht geladen werden. Bitte versuchen Sie es später erneut.",
|
|
21
22
|
"multiGeometry.badge.tooltip": "Teil eines zusammengesetzten Objekts",
|
|
22
23
|
"settings.mapStyle.dark": "Dunkel",
|
|
24
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
23
25
|
"settings.mapStyle.light": "Hell",
|
|
24
26
|
"settings.mapStyle.roadmap": "Straßenkarte",
|
|
25
27
|
"settings.mapStyle.roads": "Straßen",
|
package/translation10.cjs.js
CHANGED
|
@@ -14,6 +14,7 @@ var translation = {
|
|
|
14
14
|
"controls.stepper.increase": "Zwiększ",
|
|
15
15
|
"controls.zoom": "Powiększ",
|
|
16
16
|
"draw.clear": "Clear drawing",
|
|
17
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
17
18
|
"draw.polygon": "Draw polygon",
|
|
18
19
|
"draw.select": "Edit polygon",
|
|
19
20
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -22,6 +23,7 @@ var translation = {
|
|
|
22
23
|
"map.loadingErrorMessage": "Nie można załadować mapy. Spróbuj ponownie później.",
|
|
23
24
|
"multiGeometry.badge.tooltip": "Część kształtu wieloczęściowego",
|
|
24
25
|
"settings.mapStyle.dark": "Ciemny",
|
|
26
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
25
27
|
"settings.mapStyle.light": "Jasny",
|
|
26
28
|
"settings.mapStyle.roadmap": "Mapa drogowa",
|
|
27
29
|
"settings.mapStyle.roads": "Drogi",
|
package/translation10.esm.js
CHANGED
|
@@ -12,6 +12,7 @@ var translation = {
|
|
|
12
12
|
"controls.stepper.increase": "Zwiększ",
|
|
13
13
|
"controls.zoom": "Powiększ",
|
|
14
14
|
"draw.clear": "Clear drawing",
|
|
15
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
15
16
|
"draw.polygon": "Draw polygon",
|
|
16
17
|
"draw.select": "Edit polygon",
|
|
17
18
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -20,6 +21,7 @@ var translation = {
|
|
|
20
21
|
"map.loadingErrorMessage": "Nie można załadować mapy. Spróbuj ponownie później.",
|
|
21
22
|
"multiGeometry.badge.tooltip": "Część kształtu wieloczęściowego",
|
|
22
23
|
"settings.mapStyle.dark": "Ciemny",
|
|
24
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
23
25
|
"settings.mapStyle.light": "Jasny",
|
|
24
26
|
"settings.mapStyle.roadmap": "Mapa drogowa",
|
|
25
27
|
"settings.mapStyle.roads": "Drogi",
|
package/translation11.cjs.js
CHANGED
|
@@ -14,6 +14,7 @@ var translation = {
|
|
|
14
14
|
"controls.stepper.increase": "Aumentar",
|
|
15
15
|
"controls.zoom": "Ampliar",
|
|
16
16
|
"draw.clear": "Clear drawing",
|
|
17
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
17
18
|
"draw.polygon": "Draw polygon",
|
|
18
19
|
"draw.select": "Edit polygon",
|
|
19
20
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -22,6 +23,7 @@ var translation = {
|
|
|
22
23
|
"map.loadingErrorMessage": "Não foi possível carregar o mapa. Tente novamente mais tarde.",
|
|
23
24
|
"multiGeometry.badge.tooltip": "Parte de uma forma de várias partes",
|
|
24
25
|
"settings.mapStyle.dark": "Escuro",
|
|
26
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
25
27
|
"settings.mapStyle.light": "Claro",
|
|
26
28
|
"settings.mapStyle.roadmap": "Mapa de estrada",
|
|
27
29
|
"settings.mapStyle.roads": "Estradas",
|
package/translation11.esm.js
CHANGED
|
@@ -12,6 +12,7 @@ var translation = {
|
|
|
12
12
|
"controls.stepper.increase": "Aumentar",
|
|
13
13
|
"controls.zoom": "Ampliar",
|
|
14
14
|
"draw.clear": "Clear drawing",
|
|
15
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
15
16
|
"draw.polygon": "Draw polygon",
|
|
16
17
|
"draw.select": "Edit polygon",
|
|
17
18
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -20,6 +21,7 @@ var translation = {
|
|
|
20
21
|
"map.loadingErrorMessage": "Não foi possível carregar o mapa. Tente novamente mais tarde.",
|
|
21
22
|
"multiGeometry.badge.tooltip": "Parte de uma forma de várias partes",
|
|
22
23
|
"settings.mapStyle.dark": "Escuro",
|
|
24
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
23
25
|
"settings.mapStyle.light": "Claro",
|
|
24
26
|
"settings.mapStyle.roadmap": "Mapa de estrada",
|
|
25
27
|
"settings.mapStyle.roads": "Estradas",
|
package/translation12.cjs.js
CHANGED
|
@@ -14,6 +14,7 @@ var translation = {
|
|
|
14
14
|
"controls.stepper.increase": "Увеличить",
|
|
15
15
|
"controls.zoom": "Масштаб",
|
|
16
16
|
"draw.clear": "Clear drawing",
|
|
17
|
+
"draw.limitReached": "Polygon limit reached. Clear the drawing to draw again",
|
|
17
18
|
"draw.polygon": "Draw polygon",
|
|
18
19
|
"draw.select": "Edit polygon",
|
|
19
20
|
"draw.selectDisabled": "Draw a polygon first",
|
|
@@ -22,6 +23,7 @@ var translation = {
|
|
|
22
23
|
"map.loadingErrorMessage": "Не удается загрузить карту. Повторите попытку позже.",
|
|
23
24
|
"multiGeometry.badge.tooltip": "Часть составной фигуры",
|
|
24
25
|
"settings.mapStyle.dark": "Темная",
|
|
26
|
+
"settings.mapStyle.disabledWhileDrawing": "Finish drawing to change the map style",
|
|
25
27
|
"settings.mapStyle.light": "Светлая",
|
|
26
28
|
"settings.mapStyle.roadmap": "Карта маршрута",
|
|
27
29
|
"settings.mapStyle.roads": "Дороги",
|