@motionstudies/data 0.1.0-alpha.23 → 0.1.0-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
+ import type { AirContinent, AirContinentSource } from '@motionstudies/core/air-continents'
1
2
  import type { AirEndpoint, AirSample } from '@motionstudies/core/domain/air'
2
- export interface EndpointAirport { icao: string; iata: string; name: string; city: string; longitude: number; latitude: number; elevation: number }
3
- export function parseAirports(csv: string): EndpointAirport[]
3
+ export interface EndpointAirport { icao: string; iata: string; name: string; city: string; longitude: number; latitude: number; elevation: number; continent?: AirContinent; continentSource?: AirContinentSource }
4
+ export interface AirContinentOverride { airportIdent: string; continent: AirContinent; reason: string }
5
+ export function parseAirports(csv: string, options?: { continentOverrides?: readonly AirContinentOverride[] }): EndpointAirport[]
4
6
  export function inferAirEndpoints(samples: readonly AirSample[], airports: readonly EndpointAirport[]): { origin?: AirEndpoint; destination?: AirEndpoint }
5
7
  export function enrichAirEndpoints(options: { manifestPath: string; snapshotPaths?: string[]; heatmapDirectory: string; airportCsvPath: string; utcOffsetHours: number }): Promise<{ tracks: number; origins: number; destinations: number }>
package/air-endpoints.mjs CHANGED
@@ -2,16 +2,33 @@ import { readFile, readdir, writeFile } from 'node:fs/promises'
2
2
  import { dirname, join } from 'node:path'
3
3
  import { gunzipSync } from 'node:zlib'
4
4
  import { createHash } from 'node:crypto'
5
+ import { isAirContinent } from '@motionstudies/core/air-continents'
5
6
  import { parseCsvLine } from './gtfs.mjs'
6
7
  import { decodeAdsbHeatmap } from './adsb-heatmap.mjs'
7
8
 
8
- export function parseAirports(csv) {
9
+ export function parseAirports(csv, { continentOverrides = [] } = {}) {
9
10
  const [header, ...lines] = csv.trim().split(/\r?\n/)
10
11
  const keys = parseCsvLine(header)
11
- return lines.map(line => Object.fromEntries(parseCsvLine(line).map((value, index) => [keys[index], value])))
12
+ const rows = lines.map(line => Object.fromEntries(parseCsvLine(line).map((value, index) => [keys[index], value])))
13
+ if (!Array.isArray(continentOverrides)) throw new Error('Continent overrides must be an array')
14
+ const identifiers = new Set(rows.map(row => row.ident)), overrides = new Map()
15
+ for (const entry of continentOverrides) {
16
+ if (!entry || !identifiers.has(entry.airportIdent) || !isAirContinent(entry.continent)
17
+ || typeof entry.reason !== 'string' || !entry.reason.trim() || overrides.has(entry.airportIdent)) {
18
+ throw new Error('Each continent override needs a unique reference airportIdent, valid continent and reason')
19
+ }
20
+ overrides.set(entry.airportIdent, entry.continent)
21
+ }
22
+ const geography = row => {
23
+ const continent = overrides.get(row.ident) ?? row.continent?.trim().toUpperCase()
24
+ if (!continent) return {}
25
+ if (!isAirContinent(continent)) throw new Error(`Invalid continent for airport ${row.ident}: ${continent}`)
26
+ return { continent, continentSource: overrides.has(row.ident) ? 'override' : 'ourairports' }
27
+ }
28
+ return rows
12
29
  .filter(row => ['large_airport', 'medium_airport'].includes(row.type) && row.ident && row.elevation_ft)
13
30
  .map(row => ({ icao: row.ident, iata: row.iata_code, name: row.name, city: row.municipality,
14
- longitude: Number(row.longitude_deg), latitude: Number(row.latitude_deg), elevation: Number(row.elevation_ft) }))
31
+ longitude: Number(row.longitude_deg), latitude: Number(row.latitude_deg), elevation: Number(row.elevation_ft), ...geography(row) }))
15
32
  .filter(row => [row.longitude, row.latitude, row.elevation].every(Number.isFinite))
16
33
  }
17
34
 
@@ -38,7 +55,8 @@ export function inferAirEndpoints(samples, airports) {
38
55
  if (!atAirport(sample, airport)) break
39
56
  boundary = sample
40
57
  }
41
- return { icao: airport.icao, iata: airport.iata, name: airport.name, city: airport.city, time: boundary[0], evidence: 'observed-endpoint' }
58
+ return { icao: airport.icao, iata: airport.iata, name: airport.name, city: airport.city, time: boundary[0], evidence: 'observed-endpoint',
59
+ ...(isAirContinent(airport.continent) ? { continent: airport.continent, continentSource: airport.continentSource ?? 'ourairports' } : {}) }
42
60
  }
43
61
  const origin = endpoint(false), destination = endpoint(true)
44
62
  if (origin && destination && (origin.icao === destination.icao || origin.time >= destination.time)) return {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionstudies/data",
3
- "version": "0.1.0-alpha.23",
3
+ "version": "0.1.0-alpha.24",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Offline data preparation primitives for Motion Studies.",
@@ -101,7 +101,7 @@
101
101
  ],
102
102
  "sideEffects": false,
103
103
  "dependencies": {
104
- "@motionstudies/core": "0.1.0-alpha.23",
104
+ "@motionstudies/core": "0.1.0-alpha.24",
105
105
  "fast-xml-parser": "5.11.1"
106
106
  }
107
107
  }