fix(drone-airspace): redraw a zone when only its boundary changes

The refresh signature left out the polygons, and these rows carry no
timestamp that moves with the geometry, so an amended boundary with the same
attributes kept the old shape and card anchor until the layer was destroyed.
Add a deterministic hash of each zone's rings to the signature.
This commit is contained in:
Sameh Khamis committed 2026-11-03 14:34:28 -08:00
1 parent 8e63420f06
commit ca17412c70
2 files changed
+40 -1

No files matched your search

+20 -1
View File
@@ -18,6 +18,20 @@ export function droneAirspaceColor(typeCode) {
const ringPositions = (ring) =>
ring.map(([lon, lat]) => Cesium.Cartesian3.fromDegrees(lon, lat));
/**
* FNV-1a over a zone's rings: a short, deterministic fingerprint so a
* boundary change with unchanged attributes still alters the refresh signature.
*/
function geometryHash(polygons) {
const text = JSON.stringify(polygons);
let hash = 0x811c9dc5;
for (let i = 0; i < text.length; i++) {
hash ^= text.charCodeAt(i);
hash = Math.imul(hash, 0x01000193);
}
return (hash >>> 0).toString(36);
}
const PICK_PREFIX = 'drone-airspace:';
const CARD_HOST_OPTIONS = Object.freeze({
cohortLimit: 1,
@@ -197,9 +211,14 @@ export function createDroneAirspaceLayer({
? snapshot.fetchedAt
: Date.now();
// FAA amends boundaries in its 8-week cycle without any attribute
// changing, so the geometry is part of the signature.
const signature = JSON.stringify(
rows
.map(({ polygons, ...facts }) => facts)
.map(({ polygons, ...facts }) => ({
...facts,
geometry: geometryHash(polygons),
}))
.sort((a, b) => a.stableId.localeCompare(b.stableId)),
);
if (signature === _snapshotSignature) {
@@ -196,3 +196,23 @@ test('a failed refresh after a stale snapshot reads as the failure, not stale',
assert.equal(h.layer.getStats().error, 'FAA SUA HTTP 502');
assert.equal(layerFeedState(h.layer.getStats()), 'degraded');
});
test('a boundary change alone redraws the zone; identical geometry does not', async () => {
const moved = ring.map(([lon, lat]) => [lon + 0.5, lat]);
let rows = [row];
const h = harness({ getSnapshot: async () => rows });
await h.layer.update(h.viewer);
const [first] = h.sources[0].entities.values;
rows = [{ ...row, polygons: [[ring.map((point) => [...point])]] }];
await h.layer.update(h.viewer);
assert.equal(h.sources[0].entities.values[0], first, 'same geometry kept');
rows = [{ ...row, polygons: [[moved]] }];
await h.layer.update(h.viewer);
const [redrawn] = h.sources[0].entities.values;
assert.notEqual(redrawn, first, 'moved boundary must redraw');
assert.notDeepEqual(
redrawn.polyline.positions.getValue(),
first.polyline.positions.getValue(),
);
assert.ok(h.layer.getAnalystRecords()[0].lon > -76.6);
});