@motionstudies/data 0.1.0-alpha.23 → 0.1.0-alpha.25
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.
- package/air-endpoints.d.mts +4 -2
- package/air-endpoints.mjs +22 -4
- package/package.json +2 -2
package/air-endpoints.d.mts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.0-alpha.25",
|
|
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.
|
|
104
|
+
"@motionstudies/core": "0.1.0-alpha.25",
|
|
105
105
|
"fast-xml-parser": "5.11.1"
|
|
106
106
|
}
|
|
107
107
|
}
|