refactor: separate traffic and bikeshare components

This commit is contained in:
Sameh Khamis committed 2026-09-13 14:10:38 -07:00
1 parent 3143d00c90
commit 903fe6ba37
45 files changed
+6159 -4363

No files matched your search

+37 -1
View File
@@ -390,5 +390,41 @@
"src/layers/awareness/subject.js",
"src/data/militaryInstallations.js",
"src/data/militaryAwareness.js",
"src/layers/installations/source.test.mjs"
"src/layers/installations/source.test.mjs",
"src/layers/traffic/flow.js",
"src/layers/traffic/lifecycle.js",
"src/layers/traffic/viewport.js",
"src/layers/traffic/controls.js",
"src/layers/traffic/source.js",
"src/layers/traffic/state.js",
"src/layers/traffic/policy.js",
"src/layers/traffic/rendering.js",
"src/layers/traffic/ingestion.js",
"src/layers/traffic/flowSource.js",
"src/layers/traffic/animation.js",
"src/layers/traffic/index.js",
"src/layers/traffic/flowDecode.js",
"src/layers/traffic/model.js",
"src/layers/traffic/timing.js",
"src/layers/traffic/style.js",
"src/layers/bikeshare/lifecycle.js",
"src/layers/bikeshare/viewport.js",
"src/layers/bikeshare/registry.js",
"src/layers/bikeshare/controls.js",
"src/layers/bikeshare/source.js",
"src/layers/bikeshare/state.js",
"src/layers/bikeshare/policy.js",
"src/layers/bikeshare/rendering.js",
"src/layers/bikeshare/testing.js",
"src/layers/bikeshare/ingestion.js",
"src/layers/bikeshare/queries.js",
"src/layers/bikeshare/index.js",
"src/layers/bikeshare/model.js",
"src/layers/bikeshare/selection.js",
"src/data/traffic.js",
"src/data/bikeshare.js",
"src/data/flowTiles.js",
"src/layers/traffic/source.test.mjs",
"src/layers/bikeshare/source.test.mjs",
"scripts/traffic-fixtures.mjs"
]
+48
View File
@@ -808,5 +808,53 @@
"src/ui/panelRailGeometry.js"
],
"external": ["cesium"]
},
"traffic-layer": {
"exports": ["./layers/traffic"],
"modules": [
"src/data/flowMatch.js",
"src/data/tomtomTiles.js",
"src/data/trafficBounds.js",
"src/data/trafficFlowStyle.js",
"src/data/trafficPresetStyle.js",
"src/data/trafficQueue.js",
"src/layers/traffic/animation.js",
"src/layers/traffic/controls.js",
"src/layers/traffic/flow.js",
"src/layers/traffic/flowDecode.js",
"src/layers/traffic/flowSource.js",
"src/layers/traffic/index.js",
"src/layers/traffic/ingestion.js",
"src/layers/traffic/lifecycle.js",
"src/layers/traffic/model.js",
"src/layers/traffic/policy.js",
"src/layers/traffic/rendering.js",
"src/layers/traffic/source.js",
"src/layers/traffic/state.js",
"src/layers/traffic/style.js",
"src/layers/traffic/timing.js",
"src/layers/traffic/viewport.js"
],
"external": ["cesium", "pbf", "@mapbox/vector-tile"]
},
"bikeshare-layer": {
"exports": ["./layers/bikeshare"],
"modules": [
"src/layers/bikeshare/controls.js",
"src/layers/bikeshare/index.js",
"src/layers/bikeshare/ingestion.js",
"src/layers/bikeshare/lifecycle.js",
"src/layers/bikeshare/model.js",
"src/layers/bikeshare/policy.js",
"src/layers/bikeshare/queries.js",
"src/layers/bikeshare/registry.js",
"src/layers/bikeshare/rendering.js",
"src/layers/bikeshare/selection.js",
"src/layers/bikeshare/source.js",
"src/layers/bikeshare/state.js",
"src/layers/bikeshare/testing.js",
"src/layers/bikeshare/viewport.js"
],
"external": ["cesium"]
}
}
+21 -6
View File
@@ -25,6 +25,8 @@
* Visual proof saved to qa-shots/ (gitignored).
*
* Run: node scripts/qa-traffic.mjs --url http://localhost:4410
* Add --fixtures for synthetic roads and the recorded flow tile without a key;
* that mode does not qualify live source access or quota accounting.
* Exits non-zero on any FAIL. Does not commit anything.
*/
@@ -44,6 +46,8 @@ const getOpt = (name, dflt) => {
};
const APP_URL = getOpt('--url', 'http://localhost:4410');
const HEADFUL = argv.includes('--headful');
const FIXTURES = argv.includes('--fixtures');
const fixtureResponse = FIXTURES ? (await import('./traffic-fixtures.mjs')).trafficFixtureResponse : () => null;
const CHROME_EXECUTABLE_CANDIDATES = [
process.env.PUPPETEER_EXECUTABLE_PATH,
@@ -112,7 +116,8 @@ async function main() {
process.exit(2);
}
const statusBefore = await fetch(`${APP_URL}/api/tomtom/status`).then((r) => r.json()).catch(() => null);
const statusBefore = FIXTURES ? {hasKey:true,dailyCount:0} : await fetch(`${APP_URL}/api/tomtom/status`).then((r) => r.json()).catch(() => null);
if (FIXTURES) console.log('Source mode: synthetic roads and recorded flow tiles; live quota is not qualified.');
if (!statusBefore?.hasKey) {
console.error('\x1b[31mServer has no TomTom key — run against the keyed dev server (:4410).\x1b[0m');
process.exit(2);
@@ -133,6 +138,11 @@ async function main() {
let exitCode = 0;
try {
const page = await browser.newPage();
const onFixture = (request) => {
const response = fixtureResponse(request);
if (response) void request.respond(response); else void request.continue();
};
if (FIXTURES) { await page.setRequestInterception(true); page.on('request', onFixture); }
await page.setViewport({ width: 1440, height: 900 });
// Track flow-tile requests + traffic console lines for (ii)/(iii)/(v).
@@ -152,7 +162,8 @@ async function main() {
() => window.__godsEyeView?.viewer && window.__godsEyeView?.dataManager,
{ timeout: 60000 },
);
await sleep(1500);
await sleep(5000);
await page.keyboard.press('Escape');
// ── (i) LIVE mode: San Antonio — fast Overpass extract, partial TomTom
// coverage (sim dots exist for (iv)). Mumbai proved too Overpass-cold for
@@ -240,16 +251,17 @@ async function main() {
// ── (iii) budget honesty ─────────────────────────────────────────────────
console.log('\n(iii) Budget — dailyCount grew by ≤ requests this run...');
{
const statusAfter = await fetch(`${APP_URL}/api/tomtom/status`).then((r) => r.json());
const statusAfter = FIXTURES ? statusBefore : await fetch(`${APP_URL}/api/tomtom/status`).then((r) => r.json());
const grew = statusAfter.dailyCount - statusBefore.dailyCount;
const ok = grew >= 0 && grew <= flowRequests.length;
record('BUDGET: /api/tomtom/status growth ≤ page tile requests', ok,
`before=${statusBefore.dailyCount} after=${statusAfter.dailyCount} pageRequests=${flowRequests.length}`);
record('BUDGET: /api/tomtom/status growth ≤ page tile requests', FIXTURES ? null : ok,
FIXTURES ? 'Synthetic source mode; live quota was not exercised' : `before=${statusBefore.dailyCount} after=${statusAfter.dailyCount} pageRequests=${flowRequests.length}`);
if (!ok) exitCode = 1;
}
// ── (v) KEYLESS fallback (intercepted — server key untouched) ────────────
console.log('\n(v) KEYLESS — intercepted status, expect pure simulation...');
if (FIXTURES) page.off('request', onFixture);
await page.setRequestInterception(true);
const keylessFlowReqs = [];
page.on('request', (req) => {
@@ -263,6 +275,8 @@ async function main() {
req.respond({ status: 503, contentType: 'application/json', body: JSON.stringify({ error: 'no_key' }) });
return;
}
const response = fixtureResponse(req);
if (response) { void req.respond(response); return; }
try { req.continue(); } catch { /* already handled */ }
});
await page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 });
@@ -270,7 +284,8 @@ async function main() {
() => window.__godsEyeView?.viewer && window.__godsEyeView?.dataManager,
{ timeout: 60000 },
);
await sleep(1500);
await sleep(5000);
await page.keyboard.press('Escape');
let simStats = await settleTraffic(page, { lon: -98.4936, lat: 29.4241, height: 3000 }, { minCount: 100, timeoutS: 45 });
if (!simStats || simStats.count === 0) {
// Public Overpass can throttle bursts across harness runs — one retry.
+56
View File
@@ -0,0 +1,56 @@
import { readFileSync } from 'node:fs';
import { decodeFlowTile } from '../src/layers/traffic/flowDecode.js';
import { tilesForBounds } from '../src/data/tomtomTiles.js';
const tile = readFileSync(
new URL(
'../src/data/fixtures/tomtom-flow-austin-12-935-1686.pbf',
import.meta.url,
),
);
const json = (body) => ({
status: 200,
contentType: 'application/json',
body: JSON.stringify(body),
});
/** Reuse the recorded flow tile for an explicitly synthetic browser exercise. */
export function trafficFixtureResponse(request) {
const url = new URL(request.url());
if (url.pathname === '/api/tomtom/status')
return json({ hasKey: true, dailyCount: 0 });
if (/^\/api\/tomtom\/flow\/\d+\/\d+\/\d+\.pbf$/.test(url.pathname))
return { status: 200, contentType: 'application/x-protobuf', body: tile };
if (url.pathname !== '/api/overpass') return null;
const query = new URLSearchParams(request.postData()).get('data') || '';
if (!query.includes('highway')) return null;
const match = query.match(/\((-?[\d.]+),(-?[\d.]+),(-?[\d.]+),(-?[\d.]+)\)/);
if (!match) throw new Error('Missing road fixture viewport');
const [south, west, north, east] = match.slice(1).map(Number);
const inside = ([lon, lat]) =>
lat >= south && lat <= north && lon >= west && lon <= east;
const segments = tilesForBounds({ south, west, north, east }, 12).flatMap(
({ z, x, y }) => decodeFlowTile(tile, z, x, y),
);
const elements = segments
.filter((segment) => segment.coords.some(inside))
.slice(0, 240)
.map((segment, index) => ({
type: 'way',
id: index + 1,
tags: { highway: 'primary', oneway: 'yes' },
geometry: segment.coords.map(([lon, lat]) => ({ lon, lat })),
}));
const latitude = (south + north) / 2,
longitude = (west + east) / 2;
elements.push({
type: 'way',
id: 99999,
tags: { highway: 'residential' },
geometry: [
{ lat: latitude, lon: longitude },
{ lat: latitude + 0.003, lon: longitude + 0.003 },
],
});
return json({ elements });
}