@motionstudies/data 0.1.0-alpha.22 → 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.
- package/README.md +2 -0
- package/air-endpoints.d.mts +4 -2
- package/air-endpoints.mjs +22 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -265,3 +265,5 @@ The lab's **Fields** specimen renders a synthetic archipelago of seven instrumen
|
|
|
265
265
|
`@motionstudies/core/timeline` supplies validated interval geometry, lookup, time mapping and service-time formatting. `@motionstudies/web/components/StudyTimeline` renders a controlled activity chart and scrubber; import `@motionstudies/web/study-timeline.css`. Pass `bins: {start, end, value}[]`, the time window, current `time` and `onSeek`. A `null` value means missing evidence; zero is a measured zero. `variant="bars"` (default) or `"line"` shares the same time axis. `maximum` optionally fixes the scale for comparisons. Playback and loading remain consumer-owned. See [the full contract](https://github.com/emmettl/motionstudies/blob/main/docs/STUDY-TIMELINE.md) and the Timeline lab specimen.
|
|
266
266
|
|
|
267
267
|
Canvas editions can import `PausedVehicleFrame` from `@motionstudies/core/render-frame` without loading Three.js. The existing Three.js entry point re-exports the same class. Record only a frame actually submitted; change its visibility revision (or replace the tracker) when data, projection, size or selection changes. Time changes invalidate even while paused.
|
|
268
|
+
|
|
269
|
+
Plain playback bars can use `@motionstudies/web/components/TimelineScrubber` with `@motionstudies/web/timeline-scrubber.css`: the same visible rail and 44px interaction area as `StudyTimeline`, without activity bins. Pass the time window, time, seek callback and accessible time label.
|
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.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.
|
|
104
|
+
"@motionstudies/core": "0.1.0-alpha.24",
|
|
105
105
|
"fast-xml-parser": "5.11.1"
|
|
106
106
|
}
|
|
107
107
|
}
|