@motionstudies/core 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +24 -0
- package/domain/aggregate-road.d.ts +48 -0
- package/domain/aggregate-road.js +22 -0
- package/domain/vehicle-calls.d.ts +11 -0
- package/domain/vehicle-calls.js +14 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -164,6 +164,28 @@ Rail and other transport consumers can share the same time filtering through `mo
|
|
|
164
164
|
|
|
165
165
|
`@motionstudies/data/air-endpoints` provides offline `enrichAirEndpoints` for existing air manifests, chunks and opening snapshots. Supply cached same-date global ADSB.lol heatmaps, an OurAirports CSV and the service date's local UTC offset. It associates only unambiguous low-altitude endpoints near a reference airport; cruise-only traces and uncertain routes stay unknown. Optional `AirEndpoint` origin/destination fields carry airport identity, observed boundary time and `observed-endpoint` evidence. `airportBoardMovements` maps full manifest entries to board rows without confusing playback chunk boundaries with flight endpoints. Input hashes and source/licence attribution are recorded in fixture metadata. These fields describe inferred observations, never flight schedules, gates or live status.
|
|
166
166
|
|
|
167
|
+
## Vehicle hero cards
|
|
168
|
+
|
|
169
|
+
`VehicleHeroCard` shows one vehicle's destination, next stop and remaining calling points. Import it from `@motionstudies/web/components/VehicleHeroCard` with `@motionstudies/web/vehicle-hero-card.css`. The **Vehicle heroes** lab compares `uk-bus` (amber onboard display), `uk-rail` (dark rail display), `yellow-bus` (yellow next-stop panel) and `sbb` (blue next-stop panel with a red service badge). These are presentation studies, not official operator components.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<VehicleHeroCard
|
|
173
|
+
presentation="sbb"
|
|
174
|
+
vehicle={{ service: 'IC 1', operator: 'SBB CFF FFS', destination: 'Genève-Aéroport' }}
|
|
175
|
+
stops={[
|
|
176
|
+
{ id: 'bern', name: 'Bern', time: '10:28', platform: '6' },
|
|
177
|
+
{ id: 'fribourg', name: 'Fribourg/Freiburg', time: '10:56' },
|
|
178
|
+
]}
|
|
179
|
+
note="Synthetic journey · Example calls and times."
|
|
180
|
+
onSelectStop={selectStation}
|
|
181
|
+
selectedStopId={selectedStationId}
|
|
182
|
+
/>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Consumers supply remaining `stops` in journey order and update them with playback, seeking or observations; the card has no independent clock. The first supplied call is the next stop. Optional `time`, `platform` and `detail` are consumer-formatted; absent values stay absent. `vehicle.destination` is optional and shows an explicit unavailable label when missing. The final supplied stop is never inferred to be the destination: set `isDestination` only when confirmed, especially when data is progressively chunked. Empty calls mean no upcoming stops are available, without asserting that the vehicle has arrived.
|
|
186
|
+
|
|
187
|
+
The required `note` explains provenance. Optional `status`, `statusTone`, `clockLabel`, localized `labels`, `loading`, `error` and `onRetry` follow the consumer's data; the component never manufactures an on-time status. Loading and errors hide the calling points. Stop selection is controlled through stable call IDs and does not alter playback or clear when a call leaves the list. All supplied calls are shown, with wrapping names and container-based compact layouts. The lab exercises advance/rewind, unknown destinations/times, localization, disruption, recovery and narrow widths. Edition adoption requires its own package upgrade and data adapter.
|
|
188
|
+
|
|
167
189
|
## Optional live airport feed
|
|
168
190
|
|
|
169
191
|
`AirportBoard` from `@motionstudies/web/components/AirportBoard` adds Study/Now controls around an existing `AirportHeroCard` configuration. Pass `studyCard` with the usual card props and `live={{ baseUrl, edition, airport }}` for the shared service. Import `airport-hero-card.css`. `labels` localizes the wrapper's control and availability messages. The lower-level `useAirportFeed` hook and core `domain/live-airport` contract are also public exports.
|
|
@@ -193,3 +215,5 @@ The renderer now shares active GPU upload ranges, paused frame reuse, label and
|
|
|
193
215
|
`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
216
|
|
|
195
217
|
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.
|
|
218
|
+
|
|
219
|
+
For timetable-backed vehicle selections, `NetworkVehicleHeroCard` from `@motionstudies/web/components/NetworkVehicleHeroCard` accepts `snapshot`, `train`, playback `time`, a required source `note` and the vehicle card's presentation/state/label options. Import `vehicle-hero-card.css`. It resolves the selected train ID against the current snapshot, retaining destination identity but showing no stale calls when that ID is absent. Calls stay in journey order, use stable journey/call IDs and follow backward seeking. During dwell the heading becomes `atStopLabel` (default “At stop”) and the time is the departure; otherwise it is the upcoming arrival. Times are formatted with `formatServiceTime` by default, without normalizing after midnight or adding delay twice. Use `showTimes={false}` for frequency-modelled journeys, and explain the model in `note`. The core `vehicleCalls` helper is also exported from `@motionstudies/core/domain/vehicle-calls` for custom adapters. Selection callback IDs refer to calls, not station indexes.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Counts are vehicle passages at a detector or a provider-defined link, not vehicle identities. */
|
|
2
|
+
export type AggregateRoadInterval = {
|
|
3
|
+
readonly kind: 'provider-slot';
|
|
4
|
+
readonly serviceDate: string;
|
|
5
|
+
readonly index: number;
|
|
6
|
+
readonly endingLabel: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'utc';
|
|
9
|
+
readonly from: string;
|
|
10
|
+
readonly to: string;
|
|
11
|
+
};
|
|
12
|
+
export interface AggregateRoadSample {
|
|
13
|
+
readonly interval: AggregateRoadInterval;
|
|
14
|
+
readonly vehicleCount: number | null;
|
|
15
|
+
readonly averageSpeed: {
|
|
16
|
+
readonly value: number;
|
|
17
|
+
readonly unit: 'mph' | 'km/h';
|
|
18
|
+
} | null;
|
|
19
|
+
readonly sourceRecordIds: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export interface AggregateRoadSeries {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly provider: string;
|
|
24
|
+
readonly dataset: string;
|
|
25
|
+
readonly siteId: string;
|
|
26
|
+
readonly modelVersion?: string;
|
|
27
|
+
readonly samples: readonly AggregateRoadSample[];
|
|
28
|
+
}
|
|
29
|
+
/** Geometry association is independent of measurements; a candidate is not a verified link. */
|
|
30
|
+
export interface AggregateRoadSection {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly seriesId: string;
|
|
33
|
+
readonly path: readonly (readonly [longitude: number, latitude: number])[];
|
|
34
|
+
readonly association: {
|
|
35
|
+
readonly kind: 'illustrative';
|
|
36
|
+
readonly method: string;
|
|
37
|
+
readonly maximumDistanceMetres: number;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: 'provider-link';
|
|
40
|
+
readonly linkId: string;
|
|
41
|
+
readonly modelVersion: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export declare function aggregateRoadValue(sample: AggregateRoadSample | undefined, metric: 'vehicleCount' | 'averageSpeed'): number | null;
|
|
45
|
+
/** Compile once per immutable series. Conflicting or duplicate slots remain unavailable.
|
|
46
|
+
* No interpolation, nearest-slot fallback or conversion between provider and UTC clocks.
|
|
47
|
+
*/
|
|
48
|
+
export declare function indexAggregateRoadSlots(series: AggregateRoadSeries, serviceDate: string): ReadonlyMap<number, AggregateRoadSample>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function aggregateRoadValue(sample, metric) {
|
|
2
|
+
const value = metric === 'vehicleCount' ? sample?.vehicleCount : sample?.averageSpeed?.value;
|
|
3
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && (metric !== 'vehicleCount' || Number.isInteger(value)) ? value : null;
|
|
4
|
+
}
|
|
5
|
+
/** Compile once per immutable series. Conflicting or duplicate slots remain unavailable.
|
|
6
|
+
* No interpolation, nearest-slot fallback or conversion between provider and UTC clocks.
|
|
7
|
+
*/
|
|
8
|
+
export function indexAggregateRoadSlots(series, serviceDate) {
|
|
9
|
+
const result = new Map(), conflicts = new Set();
|
|
10
|
+
for (const sample of series.samples) {
|
|
11
|
+
const interval = sample.interval;
|
|
12
|
+
if (interval.kind !== 'provider-slot' || interval.serviceDate !== serviceDate || !Number.isSafeInteger(interval.index) || interval.index < 0)
|
|
13
|
+
continue;
|
|
14
|
+
if (result.has(interval.index)) {
|
|
15
|
+
result.delete(interval.index);
|
|
16
|
+
conflicts.add(interval.index);
|
|
17
|
+
}
|
|
18
|
+
else if (!conflicts.has(interval.index))
|
|
19
|
+
result.set(interval.index, sample);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NetworkSnapshot, NetworkTrain } from './network.ts';
|
|
2
|
+
export interface VehicleCall {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly time: number;
|
|
6
|
+
readonly platform?: string;
|
|
7
|
+
readonly atStop: boolean;
|
|
8
|
+
readonly isDestination: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Remaining calls from this snapshot's train. Times may already include realtime adjustments. */
|
|
11
|
+
export declare function vehicleCalls(snapshot: NetworkSnapshot, train: NetworkTrain, time: number): readonly VehicleCall[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Remaining calls from this snapshot's train. Times may already include realtime adjustments. */
|
|
2
|
+
export function vehicleCalls(snapshot, train, time) {
|
|
3
|
+
if (!Number.isFinite(time) || train.realtime?.status === 'cancelled')
|
|
4
|
+
return [];
|
|
5
|
+
return train.stops.flatMap(([index, arrival, departure], callIndex) => {
|
|
6
|
+
const stop = snapshot.stops[index];
|
|
7
|
+
if (!stop || !Number.isFinite(arrival) || !Number.isFinite(departure) || departure < arrival || departure < time)
|
|
8
|
+
return [];
|
|
9
|
+
const atStop = arrival <= time && time <= departure;
|
|
10
|
+
return [{ id: `${train.id}:${callIndex}`, name: stop[2], time: atStop ? departure : arrival,
|
|
11
|
+
platform: stop[3]?.trim() || undefined, atStop,
|
|
12
|
+
isDestination: callIndex === train.stops.length - 1 && Boolean(train.headsign.trim()) && stop[2].trim() === train.headsign.trim() }];
|
|
13
|
+
});
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionstudies/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Transport contracts and motion primitives for Motion Studies.",
|
|
@@ -159,6 +159,16 @@
|
|
|
159
159
|
"types": "./domain/vehicle-counts.d.ts",
|
|
160
160
|
"import": "./domain/vehicle-counts.js",
|
|
161
161
|
"default": "./domain/vehicle-counts.js"
|
|
162
|
+
},
|
|
163
|
+
"./domain/aggregate-road": {
|
|
164
|
+
"types": "./domain/aggregate-road.d.ts",
|
|
165
|
+
"import": "./domain/aggregate-road.js",
|
|
166
|
+
"default": "./domain/aggregate-road.js"
|
|
167
|
+
},
|
|
168
|
+
"./domain/vehicle-calls": {
|
|
169
|
+
"types": "./domain/vehicle-calls.d.ts",
|
|
170
|
+
"import": "./domain/vehicle-calls.js",
|
|
171
|
+
"default": "./domain/vehicle-calls.js"
|
|
162
172
|
}
|
|
163
173
|
},
|
|
164
174
|
"files": [
|