fix: correct OpenAIP v2 airport frequency parsing (3 bugs)

The frequency panel was always empty when using OpenAIP because of three
compounding bugs in frequencies.get.ts:

1. Wrong query parameter — ?icao=EDDF performs an unfiltered full-text
   search and returns all 46 000+ airports paginated; EDDF wasn't even
   on the first page.  The correct parameter is ?search=EDDF, which
   returns exactly the one matching airport.

2. Wrong ICAO field name — the code checked airport.icao but the real
   field in the v2 API response is icaoCode.  Even on a correctly
   filtered response the match would always fail.

3. Wrong frequency field names and numeric types — each frequency item
   exposes the MHz value in a value field (not frequency / frequencyMHz),
   and the service type is a numeric code (5=Delivery, 9=Ground,
   14=Tower, 15=ATIS) rather than a string.  Added OPENAIP_TYPE_MAP to
   translate these numeric codes to the internal DEL/GND/TWR/ATIS codes
   the rest of the pipeline expects.

With these fixes EDDF now returns all 8 frequencies (Delivery 122.035,
Ground 121.805, three Tower variants, two ATIS) which are then stored in
delivery_freq / ground_freq / tower_freq / atis_freq and used for both
the frequency overview panel and the per-state frequency validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
leubeem
2026-05-20 18:16:32 +02:00
parent c21ddc1d52
commit 25de89c5f5

View File

@@ -174,11 +174,27 @@ export default defineEventHandler(async (event): Promise<FrequencyResponse> => {
console.warn('[OpenSquawk] Failed to fetch VATSIM frequencies:', err)
}
// OpenAIP v2 numeric frequency type → our internal type code.
// Derived from real API responses (EDDF: 5=Delivery, 9=Ground, 14=Tower, 15=ATIS).
const OPENAIP_TYPE_MAP: Record<number, string> = {
4: 'GND', // Ground (some older entries)
5: 'DEL', // Clearance Delivery
6: 'APP', // Approach
7: 'DEP', // Departure
8: 'CTR', // Centre / ACC
9: 'GND', // Ground
14: 'TWR', // Tower
15: 'ATIS', // ATIS
}
const { openaipApiKey } = getServerRuntimeConfig()
if (openaipApiKey) {
try {
// Must use `search` (not `icao`) — the `icao` param does a full-text search
// across all fields and returns the entire 46k-airport dataset unpaged.
// `search=<ICAO>` returns exactly the matching airport.
const openaipData: any = await $fetch('https://api.core.openaip.net/api/airports', {
query: { icao },
query: { search: icao },
headers: {
Accept: 'application/json',
'x-openaip-api-key': openaipApiKey
@@ -187,22 +203,28 @@ export default defineEventHandler(async (event): Promise<FrequencyResponse> => {
const items = Array.isArray(openaipData?.items) ? openaipData.items : []
for (const airport of items) {
if ((airport?.icao || '').toUpperCase() !== icao) continue
const freqCollections = [airport?.frequencies, airport?.radio, airport?.communication]
.filter(Array.isArray) as any[][]
// Real field is `icaoCode`, not `icao`
if ((airport?.icaoCode || '').toUpperCase() !== icao) continue
// Frequencies live under `airport.frequencies[]`; each item has:
// value (MHz string, e.g. "122.035")
// type (numeric code, e.g. 5 for Delivery)
// name (human label, e.g. "FRANKFURT DELIVERY")
const freqItems: any[] = Array.isArray(airport?.frequencies) ? airport.frequencies : []
for (const collection of freqCollections) {
for (const freqItem of collection) {
const frequency = normalizeFrequency(freqItem?.frequency ?? freqItem?.frequencyMHz ?? freqItem?.frequency_mhz)
if (!frequency) continue
const { type, label } = toTypeLabel(freqItem?.type, freqItem?.description || freqItem?.name)
addFrequencyEntry(frequencyMap, {
type,
label,
frequency,
source: 'openaip'
})
}
for (const freqItem of freqItems) {
// Real field is `value`, not `frequency` / `frequencyMHz`
const frequency = normalizeFrequency(freqItem?.value ?? freqItem?.frequency)
if (!frequency) continue
const numericType: number | undefined = typeof freqItem?.type === 'number' ? freqItem.type : undefined
const typeCode = numericType !== undefined ? (OPENAIP_TYPE_MAP[numericType] ?? 'UNK') : 'UNK'
const { type, label } = toTypeLabel(typeCode, freqItem?.name || freqItem?.description)
addFrequencyEntry(frequencyMap, {
type,
label,
frequency,
source: 'openaip'
})
}
}