OVGJ4SOD555KPML7LEDK2HROJTQGKCHC4RHJQ4TZTIXDLBT6QC5AC interface ProximalSelectionData {id: string;startTime: number;endTime: number;description: string | null;timestamp: string;distanceMeters: number;timeOffsetMinutes: number;cluster: {id: string;name: string;location: {id: string;name: string;latitude: number;longitude: number;};};file: {id: string;fileName: string;path: string;duration: number;};filters: ProximalSelectionFilter[];species: ProximalSelectionSpecies[];}
const proximalSelections = proximalResults.map(result => {const resultLat = parseFloat(result.locationLatitude!);const resultLon = parseFloat(result.locationLongitude!);
// Group results by selection ID to handle multiple labels per selectionconst selectionMap = new Map<string, ProximalSelectionData>();proximalResults.forEach(result => {const selectionId = result.selectionId;
const distanceMeters = calculateDistance(groundZeroLat, groundZeroLon, resultLat, resultLon);const timeOffsetMs = new Date(result.fileTimestamp!).getTime() - groundZeroTimestamp.getTime();const timeOffsetMinutes = Math.round(timeOffsetMs / (1000 * 60));
if (!selectionMap.has(selectionId)) {const resultLat = parseFloat(result.locationLatitude!);const resultLon = parseFloat(result.locationLongitude!);const distanceMeters = calculateDistance(groundZeroLat, groundZeroLon, resultLat, resultLon);const timeOffsetMs = new Date(result.fileTimestamp!).getTime() - groundZeroTimestamp.getTime();const timeOffsetMinutes = Math.round(timeOffsetMs / (1000 * 60));
return {id: result.selectionId,startTime: parseFloat(result.selectionStartTime),endTime: parseFloat(result.selectionEndTime),description: result.selectionDescription,timestamp: result.fileTimestamp,distanceMeters: Math.round(distanceMeters),timeOffsetMinutes,cluster: {id: result.clusterId,name: result.clusterName,location: {id: result.locationId,name: result.locationName,latitude: resultLat,longitude: resultLon,}},file: {id: result.fileId,fileName: result.fileName,path: result.filePath,duration: result.fileDuration,}};
selectionMap.set(selectionId, {id: result.selectionId!,startTime: parseFloat(result.selectionStartTime!),endTime: parseFloat(result.selectionEndTime!),description: result.selectionDescription,timestamp: String(result.fileTimestamp!),distanceMeters: Math.round(distanceMeters),timeOffsetMinutes,cluster: {id: result.clusterId!,name: result.clusterName!,location: {id: result.locationId!,name: result.locationName!,latitude: resultLat,longitude: resultLon,}},file: {id: result.fileId!,fileName: result.fileName!,path: result.filePath!,duration: Number(result.fileDuration!),},filters: [],species: []});}// Add filter and species information if availableconst selection = selectionMap.get(selectionId);if (selection && result.filterId && !selection.filters.find((f) => f.id === result.filterId)) {selection.filters.push({id: result.filterId,name: result.filterName || ""});}if (selection && result.speciesId && !selection.species.find((s) => s.id === result.speciesId)) {selection.species.push({id: result.speciesId,name: result.speciesName || ""});}
// Sort by distance (closest first)proximalSelections.sort((a, b) => a.distanceMeters - b.distanceMeters);
const proximalSelections = Array.from(selectionMap.values());// Sort by time offset (ascending)proximalSelections.sort((a, b) => a.timeOffsetMinutes - b.timeOffsetMinutes);
};// Get unique filters from all selectionsconst getAvailableFilters = () => {if (!data) return [];const filterMap = new Map<string, string>();data.proximalSelections.forEach(selection => {selection.filters.forEach(filter => {filterMap.set(filter.id, filter.name);});});return Array.from(filterMap.entries()).map(([id, name]) => ({ id, name }));};// Get unique species from all selectionsconst getAvailableSpecies = () => {if (!data) return [];const speciesMap = new Map<string, string>();data.proximalSelections.forEach(selection => {selection.species.forEach(species => {speciesMap.set(species.id, species.name);});});return Array.from(speciesMap.entries()).map(([id, name]) => ({ id, name }));
// Filter selections based on selected filtersconst getFilteredSelections = () => {if (!data) return [];return data.proximalSelections.filter(selection => {const matchesFilter = !selectedFilterId ||selection.filters.some(filter => filter.id === selectedFilterId);const matchesSpecies = !selectedSpeciesId ||selection.species.some(species => species.id === selectedSpeciesId);return matchesFilter && matchesSpecies;});};
{/* Filter Controls */}<div className="mb-6 flex flex-wrap gap-4">{getAvailableFilters().length > 0 && (<div className="flex items-center gap-2"><label className="text-sm font-medium text-gray-700">Model:</label><selectvalue={selectedFilterId || "all"}onChange={(e) => setSelectedFilterId(e.target.value === "all" ? null : e.target.value)}className="text-sm rounded-md border border-gray-300 bg-white py-1 px-2 shadow-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"><option value="all">All Models</option>{getAvailableFilters().map((filter) => (<option key={filter.id} value={filter.id}>{filter.name}</option>))}</select></div>)}{getAvailableSpecies().length > 0 && (<div className="flex items-center gap-2"><label className="text-sm font-medium text-gray-700">Species:</label><selectvalue={selectedSpeciesId || "all"}onChange={(e) => setSelectedSpeciesId(e.target.value === "all" ? null : e.target.value)}className="text-sm rounded-md border border-gray-300 bg-white py-1 px-2 shadow-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"><option value="all">All Species</option>{getAvailableSpecies().map((species) => (<option key={species.id} value={species.id}>{species.name}</option>))}</select></div>)}</div>
<TableCell className="whitespace-normal break-words">{selection.filters.length > 0? selection.filters.map(f => f.name).join(", "): "-"}</TableCell><TableCell className="whitespace-normal break-words">{selection.species.length > 0? selection.species.map(s => s.name).join(", "): "-"}</TableCell>