geotimelapse 0.1.0
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/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/bounds.d.ts +5 -0
- package/dist/bounds.d.ts.map +1 -0
- package/dist/bounds.js +9 -0
- package/dist/bounds.js.map +1 -0
- package/dist/clock.d.ts +24 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +108 -0
- package/dist/clock.js.map +1 -0
- package/dist/counter.d.ts +18 -0
- package/dist/counter.d.ts.map +1 -0
- package/dist/counter.js +54 -0
- package/dist/counter.js.map +1 -0
- package/dist/duckdb-source.d.ts +24 -0
- package/dist/duckdb-source.d.ts.map +1 -0
- package/dist/duckdb-source.js +211 -0
- package/dist/duckdb-source.js.map +1 -0
- package/dist/geo-timelapse.d.ts +8 -0
- package/dist/geo-timelapse.d.ts.map +1 -0
- package/dist/geo-timelapse.js +143 -0
- package/dist/geo-timelapse.js.map +1 -0
- package/dist/glow-layer.d.ts +47 -0
- package/dist/glow-layer.d.ts.map +1 -0
- package/dist/glow-layer.js +129 -0
- package/dist/glow-layer.js.map +1 -0
- package/dist/hooks.d.ts +32 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +142 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/map-style.d.ts +3 -0
- package/dist/map-style.d.ts.map +1 -0
- package/dist/map-style.js +832 -0
- package/dist/map-style.js.map +1 -0
- package/dist/map.d.ts +11 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +60 -0
- package/dist/map.js.map +1 -0
- package/dist/player-bar.d.ts +4 -0
- package/dist/player-bar.d.ts.map +1 -0
- package/dist/player-bar.js +118 -0
- package/dist/player-bar.js.map +1 -0
- package/dist/settings-menu.d.ts +17 -0
- package/dist/settings-menu.d.ts.map +1 -0
- package/dist/settings-menu.js +18 -0
- package/dist/settings-menu.js.map +1 -0
- package/dist/synthetic-source.d.ts +18 -0
- package/dist/synthetic-source.d.ts.map +1 -0
- package/dist/synthetic-source.js +192 -0
- package/dist/synthetic-source.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +28 -0
- package/dist/utils.js.map +1 -0
- package/package.json +67 -0
- package/src/bounds.tsx +14 -0
- package/src/clock.tsx +130 -0
- package/src/counter.tsx +96 -0
- package/src/duckdb-source.ts +238 -0
- package/src/geo-timelapse.tsx +204 -0
- package/src/glow-layer.test.ts +51 -0
- package/src/glow-layer.ts +166 -0
- package/src/hooks.ts +167 -0
- package/src/index.ts +5 -0
- package/src/map-style.ts +833 -0
- package/src/map.tsx +107 -0
- package/src/player-bar.tsx +164 -0
- package/src/settings-menu.tsx +117 -0
- package/src/synthetic-source.test.ts +58 -0
- package/src/synthetic-source.ts +218 -0
- package/src/types.ts +66 -0
- package/src/utils.test.ts +60 -0
- package/src/utils.ts +32 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { GlowTuning } from './glow-layer.js';
|
|
2
|
+
|
|
3
|
+
/** Visible map area in degrees, WGS84. */
|
|
4
|
+
export interface MapBounds {
|
|
5
|
+
west: number;
|
|
6
|
+
south: number;
|
|
7
|
+
east: number;
|
|
8
|
+
north: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Events of one frame window, aggregated by location. */
|
|
12
|
+
export interface FramePoints {
|
|
13
|
+
/** Interleaved [lon, lat] pairs, one per distinct location. */
|
|
14
|
+
positions: Float32Array;
|
|
15
|
+
/** Events at each location; the rendered point AREA is linear in it. */
|
|
16
|
+
weights: Float32Array;
|
|
17
|
+
/** Number of locations (positions holds 2x this many floats). */
|
|
18
|
+
count: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Cumulative totals since the start of the day. */
|
|
22
|
+
export interface Totals {
|
|
23
|
+
count: number;
|
|
24
|
+
value: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A day of geolocated events, queryable by the component. Times are seconds
|
|
29
|
+
* since the day's midnight, in [0, 86400). Every read reflects the scope set
|
|
30
|
+
* by the latest setScope() call (null scope = everything).
|
|
31
|
+
*/
|
|
32
|
+
export interface GeoTimelapseSource {
|
|
33
|
+
/** Prepares the day (downloads, tables...). Called once on mount. */
|
|
34
|
+
load(onProgress?: (loadedBytes: number, totalBytes: number) => void): Promise<void>;
|
|
35
|
+
/** Events won during [fromSecond, toSecond), aggregated by location. */
|
|
36
|
+
frame(fromSecond: number, toSecond: number): Promise<FramePoints>;
|
|
37
|
+
/** Cumulative totals from midnight up to (excluding) `second`. */
|
|
38
|
+
totals(second: number): Promise<Totals>;
|
|
39
|
+
/** Event count for each of the day's 1440 minutes. */
|
|
40
|
+
activity(): Promise<Float32Array>;
|
|
41
|
+
/** Scopes subsequent totals() and activity() reads to the given viewport. */
|
|
42
|
+
setScope(bounds: MapBounds | null): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Releases everything the source holds (workers, in-memory tables...). The
|
|
45
|
+
* source is unusable afterwards. The component never calls it: the owner
|
|
46
|
+
* who created the source disposes it when done.
|
|
47
|
+
*/
|
|
48
|
+
dispose(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface GeoTimelapseProps {
|
|
52
|
+
source: GeoTimelapseSource;
|
|
53
|
+
mapboxAccessToken: string;
|
|
54
|
+
/** Area the map initially fits; defaults to the continental US. */
|
|
55
|
+
initialBounds?: MapBounds;
|
|
56
|
+
/** Overrides on the glow rendering knobs (see GlowTuning). Calibrate
|
|
57
|
+
* pointAlpha to the day's density: the playback trail stacks 12 additive
|
|
58
|
+
* frames, so dense datasets need it well below the bench value. */
|
|
59
|
+
tuning?: Partial<GlowTuning>;
|
|
60
|
+
/** Shown before the clock time, e.g. "Sat, Sep 5, 2026". */
|
|
61
|
+
dateLabel?: string;
|
|
62
|
+
/** Shown after the clock time, e.g. "EDT". */
|
|
63
|
+
timeZoneLabel?: string;
|
|
64
|
+
formatValue?: (value: number) => string;
|
|
65
|
+
formatCount?: (count: number) => string;
|
|
66
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { formatDayTime, formatDayTime12, formatDayTime24, formatHourLabel } from './utils.js';
|
|
5
|
+
|
|
6
|
+
describe('formatDayTime', () => {
|
|
7
|
+
it('formats midnight', () => {
|
|
8
|
+
assert.equal(formatDayTime(0), '00:00:00');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('truncates fractional seconds', () => {
|
|
12
|
+
assert.equal(formatDayTime(59.9), '00:00:59');
|
|
13
|
+
assert.equal(formatDayTime(3661), '01:01:01');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('pads every unit', () => {
|
|
17
|
+
assert.equal(formatDayTime(7 * 3600 + 5 * 60 + 3), '07:05:03');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('formats the last second and the end of day', () => {
|
|
21
|
+
assert.equal(formatDayTime(86399), '23:59:59');
|
|
22
|
+
assert.equal(formatDayTime(86400), '24:00:00');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('clamps negative values', () => {
|
|
26
|
+
assert.equal(formatDayTime(-10), '00:00:00');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('formatDayTime12', () => {
|
|
31
|
+
it('formats midnight and noon on the 12-hour clock', () => {
|
|
32
|
+
assert.equal(formatDayTime12(0), '12:00 AM');
|
|
33
|
+
assert.equal(formatDayTime12(12 * 3600), '12:00 PM');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('formats morning and afternoon times', () => {
|
|
37
|
+
assert.equal(formatDayTime12(2 * 3600 + 30 * 60), '2:30 AM');
|
|
38
|
+
assert.equal(formatDayTime12(13 * 3600 + 5 * 60), '1:05 PM');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('wraps the end of day back to midnight', () => {
|
|
42
|
+
assert.equal(formatDayTime12(86400), '12:00 AM');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('formatDayTime24', () => {
|
|
47
|
+
it('formats without a leading zero on the hour', () => {
|
|
48
|
+
assert.equal(formatDayTime24(2 * 3600 + 30 * 60), '2:30');
|
|
49
|
+
assert.equal(formatDayTime24(13 * 3600 + 5 * 60), '13:05');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('formatHourLabel', () => {
|
|
54
|
+
it('labels the day boundaries and afternoon hours', () => {
|
|
55
|
+
assert.equal(formatHourLabel(0), '0:00');
|
|
56
|
+
assert.equal(formatHourLabel(2), '2:00');
|
|
57
|
+
assert.equal(formatHourLabel(13), '13:00');
|
|
58
|
+
assert.equal(formatHourLabel(24), '24:00');
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { format } from 'date-fns';
|
|
2
|
+
|
|
3
|
+
/** Manual on purpose: the end-of-day marker 24:00:00 has no date-fns spelling. */
|
|
4
|
+
export function formatDayTime(daySeconds: number): string {
|
|
5
|
+
const total = Math.max(Math.floor(daySeconds), 0);
|
|
6
|
+
const hours = Math.floor(total / 3600);
|
|
7
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
8
|
+
const seconds = total % 60;
|
|
9
|
+
const pad = (value: number) => String(value).padStart(2, '0');
|
|
10
|
+
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// An arbitrary DST-free local date carrying a time of day for date-fns.
|
|
14
|
+
function dayTimeAsDate(daySeconds: number): Date {
|
|
15
|
+
const total = Math.max(Math.floor(daySeconds), 0);
|
|
16
|
+
return new Date(2000, 0, 1, Math.floor(total / 3600) % 24, Math.floor((total % 3600) / 60), total % 60);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** US-style clock time, e.g. "2:30 AM"; the end of day wraps to "12:00 AM". */
|
|
20
|
+
export function formatDayTime12(daySeconds: number): string {
|
|
21
|
+
return format(dayTimeAsDate(daySeconds), 'h:mm a');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Compact 24-hour time, e.g. "2:30" or "13:05". */
|
|
25
|
+
export function formatDayTime24(daySeconds: number): string {
|
|
26
|
+
return format(dayTimeAsDate(daySeconds), 'H:mm');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Compact 24-hour label for a full hour, e.g. 2 → "2:00"; 24 marks the end of day. */
|
|
30
|
+
export function formatHourLabel(hour: number): string {
|
|
31
|
+
return hour === 24 ? '24:00' : formatDayTime24(hour * 3600);
|
|
32
|
+
}
|