merge main f13c60c into feat/voice-outlines

reconcile voice outlines with main f13c60c, retaining German/Japanese,
Operator HUD retirement and Context/Radio cleanup, named-building
containment, courtyard-hole, non-Latin identity, attribution, cancellation
and antimeridian repairs

keep outline callouts above the open voice preview when an above-panel
slot fits without colliding with another callout,
and clip the compact dock location label on narrow screens
This commit is contained in:
manjunath committed 2026-10-30 19:02:47 +05:30
commit 578ebdc030
449 files changed
+30400 -3429

No files matched your search

-42
View File
@@ -295,21 +295,8 @@ function decodeSharedOpenFreeMapTile(bytes, z, x, y) {
let baseBytes = 0;
let projectionBytes = 0;
let resizeCacheEntry = null;
let cacheBound = false;
const idleCallbacks = new Map();
const cancelIdleProjection = (name) => {
if (!idleCallbacks.has(name)) return;
const handle = idleCallbacks.get(name);
idleCallbacks.delete(name);
globalThis.cancelIdleCallback?.(handle);
};
const cancelIdleProjections = () => {
for (const name of [...idleCallbacks.keys()]) cancelIdleProjection(name);
};
const project = (name) => {
if (projections.has(name)) return projections.get(name);
cancelIdleProjection(name);
const projector = TILE_PROJECTIONS.get(name);
if (!projector)
throw new Error(`Unknown OpenFreeMap tile projection: ${name}`);
@@ -320,45 +307,16 @@ function decodeSharedOpenFreeMapTile(bytes, z, x, y) {
resizeCacheEntry?.(baseBytes + projectionBytes);
return value;
};
const scheduleIdleProjections = () => {
if (
!cacheBound ||
typeof globalThis.requestIdleCallback !== 'function' ||
typeof globalThis.cancelIdleCallback !== 'function'
)
return;
for (const name of TILE_PROJECTIONS.keys())
if (!projections.has(name) && !idleCallbacks.has(name)) {
const handle = globalThis.requestIdleCallback(
() => {
if (!cacheBound || idleCallbacks.get(name) !== handle) return;
idleCallbacks.delete(name);
try {
project(name);
} catch {
// The explicit consumer path retries and reports projection errors.
}
},
{ timeout: 1000 },
);
idleCallbacks.set(name, handle);
}
};
return {
...base,
project,
bindSharedCacheEntry(resize, initialBytes) {
cancelIdleProjections();
resizeCacheEntry = resize;
baseBytes = initialBytes;
cacheBound = true;
resizeCacheEntry(baseBytes + projectionBytes);
scheduleIdleProjections();
},
releaseSharedCacheEntry() {
cacheBound = false;
cancelIdleProjections();
projections.clear();
baseBytes = 0;
projectionBytes = 0;
+49 -40
View File
@@ -17,9 +17,8 @@ import { normalizePlaceName } from '../data/placeBoundaries.js';
/**
* Street and building outlines from the same OpenFreeMap z14 vector tiles the
* traffic layer reads. No new tile provider: the source below points at the
* same immutable tile URLs and bounded decoded owner. Traffic prepares the
* outline projection in idle time; nothing here starts provider I/O without
* an explicit traffic or outline ask.
* same immutable tile URLs and bounded decoded owner. Outline extraction runs
* only when an outline consumer asks for it; traffic loading never warms it.
*/
/** Tile zoom used for outlines (the highest OpenMapTiles zoom with buildings). */
@@ -120,6 +119,7 @@ export function decodeOpenFreeMapOutlineTile(
const height = Number(props.render_height);
return { heightM: Number.isFinite(height) && height > 0 ? height : null };
},
true,
);
// Open areas that can enclose grounds: parks, landuse and park-like cover.
const areas = [];
@@ -172,7 +172,15 @@ function pixelToLonLat(px, py, extent, z, x, y) {
* and tile, and is marked `clipped` so it is never used as a whole outline. A
* neighbouring tile may carry the same feature complete within its buffer.
*/
function decodePolygons(layer, z, x, y, keep, describe) {
function decodePolygons(
layer,
z,
x,
y,
keep,
describe,
componentLocal = false,
) {
if (!layer) return [];
if (layer.length > LAYER_FEATURE_LIMIT)
throw new Error('Vector tile feature limit exceeded');
@@ -210,41 +218,43 @@ function decodePolygons(layer, z, x, y, keep, describe) {
: geometry.type === 'MultiPolygon'
? geometry.coordinates
: [];
// Preserve one feature's complete topology, including features without an
// id. A cut or malformed part makes every part incomplete, not a smaller
// exact building that happens to contain the pointer.
const incomplete =
!polygons.length ||
polygons.some(
(rings) =>
!Array.isArray(rings) ||
!rings.length ||
rings.some(
(ring) =>
!Array.isArray(ring) ||
ring.length < 4 ||
ring.some(
(point) =>
!Array.isArray(point) ||
point.length < 2 ||
!Number.isFinite(point[0]) ||
!Number.isFinite(point[1]),
),
),
);
const clipped =
incomplete || polygons.some((rings) => rings[0].some(onCut));
// A tile building feature may aggregate unrelated structures. Validate its
// selected component independently; named grounds retain whole topology.
const incompleteParts = polygons.map(
(rings) =>
!Array.isArray(rings) ||
!rings.length ||
rings.some(
(ring) =>
!Array.isArray(ring) ||
ring.length < 4 ||
ring.some(
(point) =>
!Array.isArray(point) ||
point.length < 2 ||
!Number.isFinite(point[0]) ||
!Number.isFinite(point[1]),
),
),
);
const clippedParts = polygons.map(
(rings, part) => incompleteParts[part] || rings[0].some(onCut),
);
for (let part = 0; part < polygons.length; part++) {
const rings = polygons[part];
if (!Array.isArray(rings?.[0]) || rings[0].length < 4) continue;
out.push({
rings,
featureParts: polygons,
featureParts: componentLocal ? null : polygons,
id: feature.id ?? null,
part,
tile: `${z}/${x}/${y}`,
clipped,
incomplete,
clipped: componentLocal
? clippedParts[part]
: clippedParts.some(Boolean),
incomplete: componentLocal
? incompleteParts[part]
: incompleteParts.some(Boolean),
...describe(props),
});
}
@@ -297,12 +307,13 @@ const STREET_WORDS = Object.freeze({
const DIRECTIONS = new Set(['north', 'south', 'east', 'west']);
/** Comparable street name: accents off, abbreviations expanded. */
/** Comparable street name: Latin accents off, abbreviations expanded. */
export function normalizeStreetName(value) {
return String(value ?? '')
.split(',')[0]
.normalize('NFKD')
.replace(/\p{M}+/gu, '')
.replace(/(\p{Script=Latin})\p{M}+/gu, '$1')
.normalize('NFKC')
.toLocaleLowerCase('en-US')
.replace(/[\p{P}\p{S}]+/gu, ' ')
.split(/\s+/)
@@ -470,8 +481,8 @@ function distanceToLineM(lon, lat, ring) {
* The building at a point: the smallest footprint containing it, else the
* nearest within `maxDistanceM`, else null.
*
* The selected part remains primary; `polygons` carries every part and hole
* of the same complete feature, regardless of whether it has an id.
* Returns only the selected footprint component and its holes. A vector-tile
* feature id does not certify that disjoint siblings are one physical building.
*/
export function buildingFromTiles(
tiles,
@@ -521,11 +532,9 @@ export function buildingFromTiles(
const pick =
containing?.building || (fragmentContains ? null : nearest?.building);
if (!pick) return null;
const parts = pick.featureParts || [pick.rings];
const polygons = [
pick.rings,
...parts.filter((part) => part !== pick.rings),
].map((part) => part.map((ring) => closeRing(ring.map((p) => [p[0], p[1]]))));
const polygons = [pick.rings].map((part) =>
part.map((ring) => closeRing(ring.map((p) => [p[0], p[1]]))),
);
const rings = polygons[0];
return {
ring: rings[0],
+28 -74
View File
@@ -59,6 +59,9 @@ test('street names compare with abbreviations and a leading direction only', ()
assert.ok(!streetNameMatches('Congress Avenue', 'South Congress Avenue'));
assert.ok(!streetNameMatches('Congress Street', 'Congress Avenue'));
assert.ok(!streetNameMatches('East 11th Street', '11th'));
assert.ok(!streetNameMatches('कुल मार्ग', 'काली मार्ग'));
assert.ok(streetNameMatches('काली मार्ग', 'काली मार्ग'));
assert.ok(streetNameMatches('Champs-Élysées', 'Champs Elysees'));
});
test('same-name segments merge into polylines around the point (Congress Avenue)', () => {
@@ -153,7 +156,7 @@ test('tile enclosure alone does not identify the named Capitol grounds', () => {
);
});
test('traffic idle preparation gives outlines the same decoded tile without another request', async () => {
test('traffic projects outlines only on demand and shares the decoded tile', async () => {
clearSharedOpenFreeMapTiles();
const idle = [];
const previousIdle = globalThis.requestIdleCallback;
@@ -163,6 +166,11 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
return idle.length;
};
globalThis.cancelIdleCallback = () => {};
let projectionRuns = 0;
registerOpenFreeMapProjection('requested-outlines-test', (...args) => {
projectionRuns++;
return decodeOpenFreeMapOutlineTile(...args);
});
let requests = 0;
const bytes = readFileSync(
new URL(
@@ -183,16 +191,26 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
fetchImpl,
});
const outlines = createOpenFreeMapOutlineSource({
projection: 'requested-outlines-test',
template:
'https://tiles.openfreemap.org/planet/v/20250910_001001_pt/{z}/{x}/{y}.pbf',
fetchImpl,
});
await traffic.fetchBounds(box, { zoom: 14, tiles });
assert.ok(idle.length > 0, 'traffic schedules the registered projection');
for (const callback of idle)
callback({ didTimeout: false, timeRemaining: () => 50 });
const trafficResult = await traffic.fetchBounds(box, { zoom: 14, tiles });
assert.equal(idle.length, 0, 'traffic schedules no outline extraction');
assert.equal(projectionRuns, 0);
const result = await outlines.fetchBounds(box, { zoom: 14, tiles });
assert.ok(result.tiles[0].buildings.length > 0);
await outlines.fetchBounds(box, { zoom: 14, tiles });
assert.equal(
projectionRuns,
1,
'the first explicit request owns the cached projection',
);
assert.deepEqual(
(await traffic.fetchBounds(box, { zoom: 14, tiles })).tiles,
trafficResult.tiles,
);
assert.equal(requests, 1);
} finally {
globalThis.requestIdleCallback = previousIdle;
@@ -201,20 +219,9 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
}
});
test('idle projections are retired on shared eviction, clear, and self-eviction', async () => {
const previousIdle = globalThis.requestIdleCallback;
const previousCancelIdle = globalThis.cancelIdleCallback;
const scheduled = new Map();
const cancelled = new Set();
let nextHandle = 1;
globalThis.requestIdleCallback = (callback) => {
const handle = nextHandle++;
scheduled.set(handle, callback);
return handle;
};
globalThis.cancelIdleCallback = (handle) => cancelled.add(handle);
test('an explicitly requested over-budget projection releases its shared entry', async () => {
let projectionRuns = 0;
registerOpenFreeMapProjection('idle-lifecycle-test', () => {
registerOpenFreeMapProjection('projection-lifecycle-test', () => {
projectionRuns++;
return { coordinates: Array.from({ length: 20_000 }, (_, i) => [i, i]) };
});
@@ -224,23 +231,15 @@ test('idle projections are retired on shared eviction, clear, and self-eviction'
import.meta.url,
),
);
const secondBytes = readFileSync(
new URL(
'../data/fixtures/ofm-outlines-austin-14-3743-6746.pbf',
import.meta.url,
),
);
const sharedDecodedCache = createSharedDecodedTileCache({
maxEntries: 1,
maxCacheBytes: 100_000,
});
const source = createOpenFreeMapSource({
template: 'https://tiles.openfreemap.org/planet/v/test/{z}/{x}/{y}.pbf',
projection: 'projection-lifecycle-test',
sharedDecodedCache,
fetchImpl: async (url) =>
new Response(
String(url).includes('/6746.pbf') ? secondBytes : firstBytes,
),
fetchImpl: async () => new Response(firstBytes),
});
const box = { south: 30.24, north: 30.29, west: -97.75, east: -97.73 };
try {
@@ -248,55 +247,10 @@ test('idle projections are retired on shared eviction, clear, and self-eviction'
zoom: 14,
tiles: [{ z: 14, x: 3743, y: 6745 }],
});
const firstHandles = [...scheduled.keys()];
assert.ok(firstHandles.length >= 2);
await source.fetchBounds(box, {
zoom: 14,
tiles: [{ z: 14, x: 3743, y: 6746 }],
});
assert.ok(firstHandles.every((handle) => cancelled.has(handle)));
for (const handle of firstHandles) scheduled.get(handle)();
assert.equal(projectionRuns, 0, 'late callbacks after eviction are inert');
const secondHandles = [...scheduled.keys()].filter(
(handle) => !firstHandles.includes(handle),
);
sharedDecodedCache.clear();
assert.ok(secondHandles.every((handle) => cancelled.has(handle)));
for (const handle of secondHandles) scheduled.get(handle)();
assert.equal(projectionRuns, 0, 'late callbacks after clear are inert');
const selfEvictingCache = createSharedDecodedTileCache({
maxEntries: 1,
maxCacheBytes: 100_000,
});
const selfEvictingSource = createOpenFreeMapSource({
template: 'https://tiles.openfreemap.org/planet/v/self/{z}/{x}/{y}.pbf',
sharedDecodedCache: selfEvictingCache,
fetchImpl: async () => new Response(firstBytes),
});
const beforeSelfEviction = nextHandle;
await selfEvictingSource.fetchBounds(box, {
zoom: 14,
tiles: [{ z: 14, x: 3743, y: 6745 }],
});
const selfEvictingHandles = [...scheduled.keys()].filter(
(handle) => handle >= beforeSelfEviction,
);
scheduled.get(selfEvictingHandles.at(-1))();
assert.equal(projectionRuns, 1);
assert.deepEqual(selfEvictingCache.stats(), { entries: 0, bytes: 0 });
assert.ok(
selfEvictingHandles.slice(0, -1).every((handle) => cancelled.has(handle)),
);
for (const handle of selfEvictingHandles)
if (!cancelled.has(handle)) scheduled.get(handle)();
assert.equal(projectionRuns, 1, 'self-eviction retires sibling idle work');
assert.deepEqual(sharedDecodedCache.stats(), { entries: 0, bytes: 0 });
} finally {
source.clear();
sharedDecodedCache.clear();
globalThis.requestIdleCallback = previousIdle;
globalThis.cancelIdleCallback = previousCancelIdle;
}
});
+25 -15
View File
@@ -64,43 +64,53 @@ test('decoded polygons keep identity and mark tile-cut fragments', () => {
});
const multipart = decode(parts);
assert.equal(multipart.buildings[0].id, null);
assert.equal(
multipart.buildings[0].featureParts,
multipart.buildings[1].featureParts,
'null ids do not split one feature into independent buildings',
);
const picked = buildingFromTiles([multipart], { lon: 0.0035, lat: 0.0005 });
assert.deepEqual(
picked.polygons,
[parts[1], parts[0]],
'selected part first; all parts and holes retained',
[parts[1]],
'an anonymous tile feature does not certify sibling building identity',
);
assert.deepEqual(
buildingFromTiles([multipart], { lon: 0.0008, lat: 0.0008 }).polygons,
[parts[0]],
'the selected component retains its courtyard hole',
);
const clipped = decode(
[parts[0], [square(-180 - 360 / 4096, 0, 0.001)]],
true,
);
assert.ok(clipped.buildings.every((part) => part.clipped));
assert.equal(clipped.buildings[0].clipped, false);
assert.equal(clipped.buildings[1].clipped, true);
assert.deepEqual(
buildingFromTiles([clipped], { lon: 0.0008, lat: 0.0008 }).polygons,
[parts[0]],
'a remote cut sibling cannot disqualify an intact component',
);
assert.equal(
buildingFromTiles([clipped], { lon: 0.0008, lat: 0.0008 }),
buildingFromTiles([clipped], {
lon: -180 - 360 / 4096 + 0.0005,
lat: 0.0005,
}),
null,
'a cut sibling prevents a partial exact footprint',
);
const malformed = decode([parts[0], [parts[1][0], null]]);
const neighbor = {
rings: [square(0.0011, 0, 0.001)],
rings: [square(0.0041, 0, 0.001)],
heightM: 20,
clipped: false,
};
assert.ok(
malformed.buildings.every((part) => part.clipped && part.incomplete),
assert.deepEqual(
buildingFromTiles([malformed], { lon: 0.0008, lat: 0.0008 }).polygons,
[parts[0]],
'a remote malformed sibling cannot disqualify an intact component',
);
assert.equal(
buildingFromTiles(
[{ ...malformed, buildings: [...malformed.buildings, neighbor] }],
{ lon: 0.0008, lat: 0.0008 },
{ lon: 0.0038, lat: 0.0008 },
),
null,
'a malformed sibling cannot become a partial exact building',
'a selected malformed component never substitutes its neighbour',
);
});
+3
View File
@@ -89,6 +89,9 @@ export function createOverpassFeatureSource({
}
}
return {
// Only this concrete OSM adapter declares outline origin; generic injected
// feature sources do not acquire attribution from their feature IDs.
outlineCredit: 'osm',
getFocusFootprints({ lat, lon }, options = {}) {
validPoint(lat, lon);
return query(
+43
View File
@@ -7,3 +7,46 @@ export function phaseTiming(phase, start, detail = {}, scope = 'roads') {
detail,
});
}
/** Bounded development evidence for one physical outline installation. */
export function createWorkTrace() {
if (!import.meta.env?.DEV) return null;
try {
return {
timeOrigin: performance.timeOrigin,
slices: [],
dropped: 0,
probes: 0,
samplerStart: null,
samplerStatus: null,
rendererStatus: null,
status: 'pending',
completedAt: null,
};
} catch {
return null;
}
}
/** Never let optional diagnostic recording change an installation outcome. */
export function recordWorkSlice(trace, phase, start, end, detail) {
if (!trace) return;
try {
if (trace.slices.length >= 16) {
trace.dropped += 1;
return;
}
trace.slices.push({
phase,
start,
end: end ?? performance.now(),
...(detail === undefined ? {} : { detail }),
});
} catch {
try {
trace.dropped += 1;
} catch {
// Missing or incomplete evidence fails the measurement, not the product.
}
}
}
+43
View File
@@ -0,0 +1,43 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createWorkTrace, recordWorkSlice } from './phaseTiming.js';
test('unsupported DEV does not create a recorder and absent recording is inert', () => {
assert.equal(createWorkTrace(), null);
assert.doesNotThrow(() => recordWorkSlice(null, 'unused', 0));
});
test('work intervals preserve raw endpoints and fail closed on bounded-record loss', () => {
const trace = { slices: [], dropped: 0 };
recordWorkSlice(trace, 'ground-step', 100, 123);
assert.deepEqual(trace.slices[0], {
phase: 'ground-step',
start: 100,
end: 123,
});
for (let i = 1; i <= 16; i += 1)
recordWorkSlice(trace, 'ground-step', 200 + i, 201 + i);
assert.equal(trace.slices.length, 16);
assert.equal(trace.dropped, 1);
assert.deepEqual(trace.slices[0], {
phase: 'ground-step',
start: 100,
end: 123,
});
const detail = { index: 0, clampStart: 2, clampEnd: 3 };
const nested = { slices: [], dropped: 0 };
recordWorkSlice(nested, 'ground-step', 1, 4, detail);
assert.deepEqual(nested.slices[0], {
phase: 'ground-step',
start: 1,
end: 4,
detail,
});
const unavailable = { slices: Object.freeze([]), dropped: 0 };
assert.doesNotThrow(() =>
recordWorkSlice(unavailable, 'ground-step', 1, 2, detail),
);
assert.equal(unavailable.dropped, 1);
});