mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
keep tile outlines honest when the anchor tile fails to load
A building or grounds outline from tiles now needs the tile holding the anchor: when that tile fails and only neighbours load, no neighbouring building or area stands in. Nominatim name tags holding several ";"-separated names match each name. Document recovery from a stuck public-Nominatim dispatch marker, correct the phone credit comment, drive the slow-device queue test with a mocked performance clock, and credit GitHub handles in the changelog.
This commit is contained in:
1 parent
c61ddb38c2
commit
62a36b9609
10 files changed
+134
-32
No files matched your search
@@ -723,6 +723,10 @@ test('a building queued behind others gets its full budget on a slow device', as
|
||||
installBrowserGlobals(t);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
t.after(() => t.mock.timers.reset());
|
||||
// The renderers' per-frame budget checks read performance.now(); it follows
|
||||
// the same mocked clock as the timers.
|
||||
let now = 0;
|
||||
t.mock.method(globalThis.performance, 'now', () => now);
|
||||
const { viewer, frames } = fakeViewer(-99, 31);
|
||||
const renderer = createHybridAnnotationRenderer(viewer);
|
||||
viewer.scene.clampToHeight = (position) => position;
|
||||
@@ -732,17 +736,22 @@ test('a building queued behind others gets its full budget on a slow device', as
|
||||
pendingVisible: false,
|
||||
workTrace: traces[index],
|
||||
}));
|
||||
// Eight probes per building at one probe per frame; 1.5 s passes while
|
||||
// each of the first two buildings samples, so the third waits 3 s in line.
|
||||
// Eight probes per building at one probe per frame, at about 5 fps: each
|
||||
// building samples for 1.5 s, so the third waits 3 s in line. Pending
|
||||
// promise work runs between frames, as it does in a browser.
|
||||
for (let building = 0; building < 3; building += 1) {
|
||||
for (let frame = 0; frame < 8; frame += 1) frames.emit();
|
||||
if (building < 2) t.mock.timers.tick(1500);
|
||||
for (let frame = 0; frame < 8; frame += 1) {
|
||||
now += 187.5;
|
||||
t.mock.timers.tick(187.5);
|
||||
frames.emit();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
}
|
||||
}
|
||||
const results = await Promise.all(completions);
|
||||
assert.deepEqual(results.map((result) => result.status), ['installed', 'installed', 'installed']);
|
||||
assert.deepEqual(traces.map((trace) => trace.samplerStatus), ['installed', 'installed', 'installed']);
|
||||
assert.ok(traces[2].samplerHeadAt - traces[2].samplerStart >= 2000, 'the third waited longer than one budget');
|
||||
assert.ok(traces[2].samplerHeadAt >= traces[1].samplerHeadAt);
|
||||
assert.ok(traces[2].samplerHeadAt >= traces[2].samplerStart);
|
||||
});
|
||||
|
||||
test('building cancellation, queued deadline and destroy prevent saved callbacks from committing', async (t) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '../sources/featureGeometry.js';
|
||||
import { isUnavailableCapability } from '../sources/capability.js';
|
||||
import { polygonsContain } from '../data/adminBoundaries.js';
|
||||
import { lonLatToTile } from '../data/tomtomTiles.js';
|
||||
import {
|
||||
BUILDING_MAX_DISTANCE_M,
|
||||
OUTLINE_TILE_ZOOM,
|
||||
@@ -173,13 +174,27 @@ export function createOutlineRungs({
|
||||
tiles = sharedOutlineTiles(),
|
||||
nominatim = createNominatimOutlineClient(),
|
||||
} = {}) {
|
||||
/** Decoded tiles around a point, or null when the tiles cannot be read. */
|
||||
async function tilesAround(lat, lon, radiusM, signal) {
|
||||
/**
|
||||
* Decoded tiles around a point, or null when the tiles cannot be read. With
|
||||
* `needAnchor`, a partial load that lost the point's own tile is null too: a
|
||||
* neighbouring tile alone cannot show what is at the point, so a nearby
|
||||
* building or area would stand in for it.
|
||||
*/
|
||||
async function tilesAround(lat, lon, radiusM, signal, needAnchor = false) {
|
||||
try {
|
||||
const { tiles: decoded } = await tiles.fetchBounds(
|
||||
boxAround(lat, lon, radiusM),
|
||||
{ zoom: OUTLINE_TILE_ZOOM, signal },
|
||||
);
|
||||
const {
|
||||
tiles: decoded,
|
||||
partial,
|
||||
loadedTiles,
|
||||
} = await tiles.fetchBounds(boxAround(lat, lon, radiusM), {
|
||||
zoom: OUTLINE_TILE_ZOOM,
|
||||
signal,
|
||||
});
|
||||
if (needAnchor && partial && Array.isArray(loadedTiles)) {
|
||||
const anchor = lonLatToTile(lon, lat, OUTLINE_TILE_ZOOM);
|
||||
if (!loadedTiles.some((t) => t.x === anchor.x && t.y === anchor.y))
|
||||
return null;
|
||||
}
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
signal?.throwIfAborted();
|
||||
@@ -212,6 +227,7 @@ export function createOutlineRungs({
|
||||
ctx.lon,
|
||||
BUILDING_MAX_DISTANCE_M * 2,
|
||||
ctx.signal,
|
||||
true,
|
||||
);
|
||||
const building = decoded && buildingFromTiles(decoded, ctx);
|
||||
if (!building || (requireContainment && building.contains !== true))
|
||||
@@ -228,7 +244,7 @@ export function createOutlineRungs({
|
||||
/** The open area enclosing `seed` (the named feature) or the anchor. */
|
||||
async function groundsRung(ctx, seed = null) {
|
||||
const at = seed || { lat: ctx.lat, lon: ctx.lon };
|
||||
const decoded = await tilesAround(at.lat, at.lon, 600, ctx.signal);
|
||||
const decoded = await tilesAround(at.lat, at.lon, 600, ctx.signal, true);
|
||||
const area =
|
||||
decoded &&
|
||||
enclosingAreaFromTiles(decoded, {
|
||||
|
||||
@@ -340,6 +340,60 @@ test('named building tile fallbacks require the anchor in filled geometry', asyn
|
||||
assert.equal(pointed.outlineCredit, 'tiles');
|
||||
});
|
||||
|
||||
test('a partial tile load that lost the point tile lets nothing stand in', async () => {
|
||||
// The point (z14 tile 8192/8192) is ~28 m from a building in the tile that
|
||||
// did load; without the point's own tile, that neighbour must not answer.
|
||||
const anchor = { lat: 0, lon: 0.00035 };
|
||||
const school = square(CAPITOL.lat, CAPITOL.lon, 0.002);
|
||||
const load = (loadedTiles) => ({
|
||||
fetchBounds: async () => ({
|
||||
tiles: [
|
||||
{
|
||||
buildings: [{ rings: [square(0, 0, 0.0001)], heightM: null }],
|
||||
areas: [
|
||||
{ name: null, rings: [school], class: 'school', clipped: false },
|
||||
],
|
||||
},
|
||||
],
|
||||
loadedTiles,
|
||||
partial: true,
|
||||
}),
|
||||
});
|
||||
const pointAt = (tiles) =>
|
||||
createOutlineRungs({ tiles, nominatim: fakeNominatim(null) }).resolve(
|
||||
ctx({
|
||||
...anchor,
|
||||
view: anchor,
|
||||
scope: 'building',
|
||||
target: 'this building',
|
||||
fromName: false,
|
||||
}),
|
||||
ladder(UNAVAILABLE),
|
||||
);
|
||||
assert.equal(await pointAt(load([{ z: 14, x: 8191, y: 8192 }])), UNAVAILABLE);
|
||||
const kept = await pointAt(load([{ z: 14, x: 8192, y: 8192 }]));
|
||||
assert.equal(kept.footprintKind, 'building', 'the point tile loaded');
|
||||
|
||||
const grounds = (loaded) =>
|
||||
createOutlineRungs({
|
||||
tiles: load(loaded),
|
||||
nominatim: fakeNominatim(null),
|
||||
}).resolve(
|
||||
ctx({
|
||||
scope: 'building',
|
||||
target: 'Lincoln High School grounds',
|
||||
groundsLike: true,
|
||||
}),
|
||||
ladder(UNAVAILABLE),
|
||||
);
|
||||
// The Capitol fixture point lies in tile 3743/6745.
|
||||
assert.equal(await grounds([{ z: 14, x: 3744, y: 6745 }]), UNAVAILABLE);
|
||||
assert.equal(
|
||||
(await grounds([{ z: 14, x: 3743, y: 6745 }])).footprintKind,
|
||||
'area',
|
||||
);
|
||||
});
|
||||
|
||||
test('a city with no configured Overpass is outlined by Nominatim', async () => {
|
||||
const ring = square(43.6, 1.44, 0.05);
|
||||
const nominatim = fakeNominatim({ polygons: [[ring]], class: 'boundary' });
|
||||
|
||||
@@ -884,6 +884,18 @@ test('a result whose name does not answer the ask is passed over', () => {
|
||||
selectOutlineResult([city('City of York', { short_name: 'York' })], york)
|
||||
.outline,
|
||||
);
|
||||
// A name tag holding several names answers for each of them, not the joint text.
|
||||
assert.ok(
|
||||
selectOutlineResult([city('Eboracum', { alt_name: 'Jorvik;York' })], york)
|
||||
.outline,
|
||||
);
|
||||
assert.equal(
|
||||
selectOutlineResult(
|
||||
[city('Eboracum', { alt_name: 'Jorvik;New York' })],
|
||||
york,
|
||||
).outline,
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
selectOutlineResult([city('新東京')], { kind: 'city', query: '東京' })
|
||||
.outline,
|
||||
|
||||
@@ -500,8 +500,8 @@ test('the phone shell keeps the required attribution visible', () => {
|
||||
/display:\s*none|visibility:\s*hidden|opacity:\s*0/,
|
||||
);
|
||||
// A long credit line (map plus outline credits) wraps inside the screen:
|
||||
// the box needs a real width for Cesium's absolutely positioned credit
|
||||
// block, set before the 0.8 scale, so it is divided by it.
|
||||
// it wraps, and the box gets a real width, set before the 0.8 scale, so it
|
||||
// is divided by it.
|
||||
assert.match(idleCredit, /white-space: normal;/);
|
||||
assert.match(
|
||||
idleCredit,
|
||||
|
||||
@@ -378,12 +378,12 @@ body.mobile-shell:not(.recording-mode):not(.cockpit-mode):not(.ui-clean-view)
|
||||
right: auto;
|
||||
bottom: calc(var(--mobile-tab-space) + 106px) !important;
|
||||
left: 10px;
|
||||
/* Cesium's credit block is absolutely positioned inside this box, so the
|
||||
box needs a real width (a max-width alone leaves it 0 wide) for the
|
||||
credits to wrap at the screen width rather than one word per line. The
|
||||
width applies before the 0.8 scale, keeping the scaled line 10px from the
|
||||
right edge, so every credit and "Data attribution" stay on screen. Like
|
||||
the offset above, it must beat the dock's width: auto !important. */
|
||||
/* With the base rule's nowrap the credits run off the right edge, and a
|
||||
max-width alone wrapped them into a narrow column: the line needs wrapping
|
||||
and a real width. The width applies before the 0.8 scale, keeping the
|
||||
scaled line 10px from the right edge, so every credit and "Data
|
||||
attribution" stay on screen. Like the offset above, it must beat the
|
||||
dock's width: auto !important. */
|
||||
width: calc((100vw - 20px) / 0.8) !important;
|
||||
max-width: none !important;
|
||||
white-space: normal;
|
||||
|
||||
Reference in new issue
Block a user