@motionstudies/data 0.1.0-alpha.8 → 0.1.0-alpha.9

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 CHANGED
@@ -179,3 +179,17 @@ See [service architecture and operations](../docs/LIVE-AIRPORTS.md). The Worker
179
179
  The same `decodeAdsbHeatmap` now powers `enrichAirEndpoints`; endpoint inference retains full coordinate precision, while playback compilation retains the existing five-decimal coordinates. `transportAirTracks` and `chunkAirSnapshot` are also available for consumers that assemble their own pipeline. All functions have public TypeScript declarations and work in the packed Node package.
180
180
 
181
181
  Editions supply geographic bounds, service date, explicit UTC offset, optional timezone, input files and output paths. Flight IDs and chunk overlap retain the existing contracts. The default splits known callsign changes and gaps over 30 minutes. Set `splitTracks: false` only when reproducing a legacy opening snapshot with one ID per aircraft. See [adoption and compatibility](../docs/AIR-DATA.md).
182
+
183
+ ## Shared edition controllers and performance
184
+
185
+ `positionForTrain` now indexes chronological stop times with binary search and retains sequential behavior for unordered observations. Stop arrays are immutable: replace the array when a timetable changes. Arrival/departure boundaries, dwell, cancellation and backward seeking retain the existing contract.
186
+
187
+ `countableVehicleTrains(network, stations, selection)` and `createActiveTimetableVehicleCounter(trains, options)` from `@motionstudies/core/domain/vehicle-counts` separate station/route/category membership from clock updates. Build the selector and counter with `useMemo` when data or selection changes, then call the counter at the displayed time. It includes both interval endpoints and excludes cancellations and inverted intervals. Missing stations yield no matches. By default it counts timetable intervals even if a journey lacks enough stops to position; `{ requirePositionable: true }` excludes journeys with fewer than two stops. This distinction is explicit so an edition can retain its established metric.
188
+
189
+ The renderer now shares active GPU upload ranges, paused frame reuse, label and trail frame budgets, cached text comparators and batched hub lines. Custom layers can import the low-level helpers from `@motionstudies/three/render-performance`. Recreate frame trackers when their geometry/data/selection inputs change; `batchHubLines` takes ownership of two-vertex source line resources. Edition-specific worker transfer, picking and cartographic adapters remain consumer-owned.
190
+
191
+ `useJsonAsset<T>(url, enabled, parse?, optional?)` from `@motionstudies/web/use-json-asset` loads a single asset lazily and exposes `data`, `loading`, `error`, `unavailable`, and `retry`. Keep the parser stable and perform edition-specific schema/source compatibility checks there. Successful data remains cached while disabled; consumers decide whether to display it. Changing the URL or parser immediately discards prior-source state, and disabling/unmounting cancels requests. `retry()` discards cached state and requests again when enabled. Optional HTTP 404 responses are unavailable; other failures are errors. No source fallback or freshness policy is inferred.
192
+
193
+ `useTransitionValue(target, { durationMs, easing, steps })` from `@motionstudies/web/use-transition-value` animates a numeric value, returning `value` and `transitioning`. It reverses from the current frame, cancels on teardown, and settles immediately when reduced motion becomes active. `smoothTransition` is the default easing; `cosineTransition` and stepped progress support existing edition rhythms. Keep custom easing functions stable. Camera actions and lazy layout loading stay in the edition.
194
+
195
+ Edition chunk scripts can call `runNetworkChunkCli()` from `@motionstudies/data/network-chunk-cli`. It accepts the existing `--input`, `--manifest`, `--opening`, `--chunk-hours`, `--opening-start`, `--opening-end`, and `--focus` arguments. Source acquisition, provenance, output paths and command invocation remain edition-owned.
@@ -0,0 +1 @@
1
+ export function runNetworkChunkCli(argv?: readonly string[]): Promise<void>
@@ -0,0 +1,65 @@
1
+ import { chunkNetworkSnapshot, extractNetworkWindow } from './network-chunks.mjs'
2
+
3
+ import { createWriteStream } from 'node:fs'
4
+ import { mkdir, readFile } from 'node:fs/promises'
5
+ import { basename, dirname, extname, join, resolve } from 'node:path'
6
+
7
+ function argument(argv, name, fallback) {
8
+ const index = argv.indexOf(`--${name}`)
9
+ return index === -1 ? fallback : argv[index + 1]
10
+ }
11
+
12
+ function parseClock(value) {
13
+ const [hours, minutes = 0] = value.split(':').map(Number)
14
+ if (!Number.isFinite(hours) || !Number.isFinite(minutes)) {
15
+ throw new Error(`Invalid clock value ${value}`)
16
+ }
17
+ return hours * 3600 + minutes * 60
18
+ }
19
+
20
+ async function writeJson(filePath, value) {
21
+ await mkdir(dirname(filePath), { recursive: true })
22
+ await new Promise((resolvePromise, rejectPromise) => {
23
+ const output = createWriteStream(filePath)
24
+ output.once('error', rejectPromise)
25
+ output.once('finish', resolvePromise)
26
+ output.end(JSON.stringify(value))
27
+ })
28
+ }
29
+
30
+ export async function runNetworkChunkCli(argv = process.argv.slice(2)) {
31
+ const input = argument(argv, 'input')
32
+ const manifestOutput = argument(argv, 'manifest')
33
+ const openingOutput = argument(argv, 'opening')
34
+ if (!input || !manifestOutput || !openingOutput) {
35
+ throw new Error(
36
+ 'Usage: node scripts/chunk-network-snapshot.mjs --input day.json --manifest day-manifest.json --opening morning.json [--chunk-hours 2] [--opening-start 06:45] [--opening-end 08:45] [--focus 07:45]',
37
+ )
38
+ }
39
+ const snapshot = JSON.parse(await readFile(resolve(input), 'utf8'))
40
+ const manifestPath = resolve(manifestOutput)
41
+ const outputDirectory = dirname(manifestPath)
42
+ const outputStem = basename(manifestPath, extname(manifestPath)).replace(/-manifest$/, '')
43
+ const chunkDirectoryName = `${outputStem}-chunks`
44
+ const { manifest, chunks } = chunkNetworkSnapshot(
45
+ snapshot,
46
+ Number(argument(argv, 'chunk-hours', '2')) * 3600,
47
+ chunkDirectoryName,
48
+ )
49
+ const opening = extractNetworkWindow(
50
+ snapshot,
51
+ parseClock(argument(argv, 'opening-start', '06:45')),
52
+ parseClock(argument(argv, 'opening-end', '08:45')),
53
+ parseClock(argument(argv, 'focus', '07:45')),
54
+ )
55
+ await Promise.all([
56
+ writeJson(manifestPath, manifest),
57
+ writeJson(resolve(openingOutput), opening),
58
+ ...chunks.map(({ descriptor, payload }) =>
59
+ writeJson(join(outputDirectory, descriptor.path), payload),
60
+ ),
61
+ ])
62
+ console.log(
63
+ `Wrote ${manifestOutput}, ${chunks.length} progressive chunks and ${opening.trains.length} opening journeys.`,
64
+ )
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionstudies/data",
3
- "version": "0.1.0-alpha.8",
3
+ "version": "0.1.0-alpha.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Offline data preparation primitives for Motion Studies.",
@@ -46,6 +46,10 @@
46
46
  "./merge-network": {
47
47
  "types": "./merge-network.d.mts",
48
48
  "import": "./merge-network.mjs"
49
+ },
50
+ "./network-chunk-cli": {
51
+ "types": "./network-chunk-cli.d.mts",
52
+ "import": "./network-chunk-cli.mjs"
49
53
  }
50
54
  },
51
55
  "files": [
@@ -59,6 +63,6 @@
59
63
  ],
60
64
  "sideEffects": false,
61
65
  "dependencies": {
62
- "@motionstudies/core": "0.1.0-alpha.8"
66
+ "@motionstudies/core": "0.1.0-alpha.9"
63
67
  }
64
68
  }