UNPKG

flight-planner

Version:

Plan and route VFR flights

136 lines (135 loc) 5.54 kB
import { isICAO, normalizeICAO } from "./utils.js"; import { point, nearestPoint } from "@turf/turf"; import { featureCollection } from '@turf/helpers'; /** * AerodromeService class provides methods to manage and retrieve aerodrome data. * * @class AerodromeService * @property {Map<ICAO, Aerodrome>} aerodromes - A map of ICAO codes to Aerodrome objects. * @property {RepositoryBase<Aerodrome>} [repository] - Optional repository for fetching aerodrome data. * @property {WeatherService} [weatherService] - Optional weather service for fetching METAR data. */ export class AerodromeService { aerodromes; repository; weatherService; /** * Creates a new instance of the AerodromeService class. * * @param options - An object containing optional properties for initializing the service. * @returns An instance of the AerodromeService class. */ constructor(options = {}) { this.repository = options.repository; this.weatherService = options.weatherService; this.aerodromes = options.aerodromes ? new Map(options.aerodromes.map(aerodrome => [normalizeICAO(aerodrome.ICAO), aerodrome])) : new Map(); } /** * Returns an array of ICAO codes for the aerodromes. * * @returns An array of ICAO codes. */ keys() { return Array.from(this.aerodromes.keys()); } /** * Returns an array of aerodromes. * * @returns An array of Aerodrome objects. */ values() { return Array.from(this.aerodromes.values()); } /** * Adds aerodromes to the service. * * @param aerodromes - An array of Aerodrome objects or a single Aerodrome object to add. */ async add(aerodromes) { let aerodromeArray = []; if (Array.isArray(aerodromes)) { aerodromeArray = aerodromes; } else { aerodromeArray = [aerodromes]; } const aerodromeWithoutMetar = []; aerodromeArray.forEach(aerodrome => { const normalizedICAO = normalizeICAO(aerodrome.ICAO); if (isICAO(normalizedICAO)) { this.aerodromes.set(normalizedICAO, aerodrome); // TODO: calculate geohash if (!aerodrome.metarStation) { aerodromeWithoutMetar.push(aerodrome); } } }); if (this.weatherService && aerodromeWithoutMetar.length > 0) { for (const aerodrome of aerodromeWithoutMetar) { if (aerodrome.location && aerodrome.location.geometry && aerodrome.location.geometry.coordinates) { const nearestMetar = await this.weatherService.nearest(aerodrome.location.geometry.coordinates); if (nearestMetar) { aerodrome.metarStation = nearestMetar; this.aerodromes.set(normalizeICAO(aerodrome.ICAO), aerodrome); } } } } } // TODO: Check if isICAO /** * Finds an aerodrome by its ICAO code. * * @param icao - The ICAO code of the aerodrome. * @returns A promise that resolves to the aerodrome, or undefined if not found. */ async get(icao) { const normalizedIcao = normalizeICAO(icao); const aerodrome = this.aerodromes.get(normalizedIcao); if (aerodrome) { if (this.weatherService && aerodrome.metarStation) { const metar = await this.weatherService.get(aerodrome.metarStation.station); if (metar && metar.length > 0) { aerodrome.metarStation = metar[0]; } } return aerodrome; } else if (this.repository) { const result = await this.repository.fetchByICAO([normalizedIcao]); await this.add(result); } return this.aerodromes.get(normalizedIcao); } /** * Finds the nearest aerodrome to the given location. * * @param location - The geographical location to find the nearest aerodrome to. * @param radius - The search radius in kilometers (default is 100 km). * @param exclude - An optional array of ICAO codes to exclude from the search. * @returns A promise that resolves to the nearest aerodrome, or undefined if not found. * @throws Error if no aerodromes are available and the repository doesn't support radius search. */ async nearest(location, radius = 100, exclude = []) { if (!this.repository || !this.repository.fetchByRadius) { throw new Error('Repository not set or does not support fetchByRadius'); } const radiusRange = Math.min(1000, Math.max(1, radius)); const result = await this.repository.fetchByRadius(location, radiusRange); await this.add(result); if (this.aerodromes.size === 0) { return undefined; } const normalizedExclude = exclude.map(icao => normalizeICAO(icao)); const aerodromeCandidates = this.values().filter(airport => !normalizedExclude.includes(normalizeICAO(airport.ICAO))); if (aerodromeCandidates.length === 0) { return undefined; } const nearest = nearestPoint(location, featureCollection(aerodromeCandidates.map(airport => { return point(airport.location.geometry.coordinates, { icao: airport.ICAO }); }))); return this.get(nearest.properties.icao); } }