fix(map): keep tracked aircraft and yacht labels in step with data filters

Tracked-flight and tracked-yacht HTML labels are rendered from the raw
store arrays, while their icons come from the worker GeoJSON that already
has the operator's data filters applied. Filtering Tracked Aircraft to a
category therefore removed the icons but left every other aircraft's name
on the map.

Derive the label subjects from the ids in the filtered feature collection
so a label is only drawn where an icon is. The id rule lives in one shared
helper used by both the worker builders and the label selector; rows with
no identifier keep the worker's positional fallback and draw no label.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
C3B2W23
2026-09-13 14:57:09 -07:00
co-authored by Claude Fable 5.1
parent a5fb1c392e
commit f95b0eccc5
5 changed files with 143 additions and 6 deletions
@@ -4,6 +4,7 @@ import { classifyAircraft } from '@/utils/aircraftClassification';
import type { Flight, Ship, SigintSignal } from '@/types/dashboard';
import type { FlightLayerConfig } from '@/components/map/geoJSONBuilders';
import { filterShipsByActiveFilters } from '@/components/map/shipFilters';
import { shipFeatureId, trackedFlightFeatureId } from '@/components/map/featureIds';
type BoundsTuple = [number, number, number, number];
type FC = GeoJSON.FeatureCollection | null;
@@ -306,7 +307,7 @@ function buildTrackedFlightsGeoJSONWorker(
features.push({
type: 'Feature',
properties: {
id: f.icao24 || i,
id: trackedFlightFeatureId(f, `tracked-${i}`),
type: 'tracked_flight',
callsign: String(displayName),
rotation,
@@ -375,7 +376,7 @@ function buildShipsGeoJSONWorker(
features.push({
type: 'Feature',
properties: {
id: s.mmsi || s.name || `ship-${i}`,
id: shipFeatureId(s, `ship-${i}`),
type: 'ship',
name: s.name,
rotation,
+18
View File
@@ -0,0 +1,18 @@
import type { Ship } from '@/types/dashboard';
/**
* Feature ids shared by the worker that builds map icons and the main-thread
* code that picks label subjects, so both sides agree on which record a
* feature represents. Records with no identifier get the worker's positional
* fallback, which cannot be matched from outside and so draw no label.
*/
export function trackedFlightFeatureId(
f: { icao24?: string; callsign?: string },
fallback = '',
): string {
return f.icao24 || f.callsign || fallback;
}
export function shipFeatureId(s: Pick<Ship, 'mmsi' | 'name'>, fallback = ''): string {
return String(s.mmsi || s.name || fallback);
}
@@ -0,0 +1,36 @@
import type { Ship, TrackedFlight } from '@/types/dashboard';
import { shipFeatureId, trackedFlightFeatureId } from '@/components/map/featureIds';
/**
* HTML labels are rendered from the raw store arrays, while the icons come
* from the worker-built GeoJSON that already has the operator's data filters
* applied. Restrict the label subjects to entities that actually have an icon
* so a filtered-out aircraft or yacht does not keep its name on the map.
*/
function featureIds(fc: GeoJSON.FeatureCollection | null | undefined): Set<string> | null {
if (!fc) return null;
const ids = new Set<string>();
for (const feature of fc.features) {
const id = feature.properties?.id;
if (id != null) ids.add(String(id));
}
return ids;
}
export function trackedFlightsWithIcons(
flights: TrackedFlight[] | undefined,
fc: GeoJSON.FeatureCollection | null | undefined,
): TrackedFlight[] {
const ids = featureIds(fc);
if (!ids || !flights?.length) return [];
return flights.filter((f) => ids.has(trackedFlightFeatureId(f)));
}
export function shipsWithIcons(
ships: Ship[] | undefined,
fc: GeoJSON.FeatureCollection | null | undefined,
): Ship[] {
const ids = featureIds(fc);
if (!ids || !ships?.length) return [];
return ships.filter((s) => ids.has(shipFeatureId(s)));
}