refactor(street-level): remove the remaining duplicated code and fakes

A duplicate scan of every module the PR adds (repeated 4-line blocks,
normalised) after gekh's dedup round left these:

- glyphs.js: the cone, ring and position glyphs share cachedGlyph(), wedge(),
  ring() and dot(). Checked with a recording canvas: all 20 draw calls keep
  the same path and fill/stroke state, so the glyphs are pixel-identical.
- panelPositionControls.js: one placeAt() for the three places that pin a
  panel's top-left corner as a fixed window.
- tiles.js: one warn() for the three background-failure logs.
- Tests: src/testSupport/streetLevelFakes.mjs holds the provider snapshot
  (uiState + presentation tests), the stand-in provider (core + controls
  tests) and one real-ray pinhole camera (view + coverage tests, which had
  each written their own). The coverage line-of-sight test still fails when
  refresh() stops passing its focus.
- qa-street-level.mjs: one waitForCoverage() for the five copies of the
  "on, drawn, settled, not key-gated" wait.

No behaviour change: unit 5862 pass, fixture gate 16/16, panel gate 5/5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 14:27:58 +01:00
1 parent d70f468781
commit c2f1b54d00
12 files changed
+281 -345

No files matched your search

+56 -61
View File
@@ -6,81 +6,76 @@
const _cache = new Map();
function canvas(size) {
/** A square canvas glyph drawn once per key, centred at (c, c). */
function cachedGlyph(key, size, draw) {
if (_cache.has(key)) return _cache.get(key);
const element = document.createElement('canvas');
element.width = size;
element.height = size;
draw(element.getContext('2d'), size / 2);
_cache.set(key, element);
return element;
}
/** A filled wedge from the centre, pointing "up", `halfAngle` radians wide. */
function wedge(ctx, c, radius, halfAngle, color, alpha) {
ctx.beginPath();
ctx.moveTo(c, c);
ctx.arc(c, c, radius, -Math.PI / 2 - halfAngle, -Math.PI / 2 + halfAngle);
ctx.closePath();
ctx.fillStyle = color;
ctx.globalAlpha = alpha;
ctx.fill();
ctx.globalAlpha = 1;
}
/** A stroked circle around the centre. */
function ring(ctx, c, radius, color, width, alpha) {
ctx.beginPath();
ctx.arc(c, c, radius, 0, Math.PI * 2);
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.globalAlpha = alpha;
ctx.stroke();
ctx.globalAlpha = 1;
}
/** A filled dot at the centre with a dark outline. */
function dot(ctx, c, radius, color, outline, width) {
ctx.beginPath();
ctx.arc(c, c, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = width;
ctx.strokeStyle = outline;
ctx.stroke();
}
/** Cone glyph pointing "up"; rotate the billboard by the compass angle. */
export function imageConeGlyph({
size = 32,
color = '#e8eaed',
pano = false,
} = {}) {
const key = `cone:${size}:${color}:${pano}`;
if (_cache.has(key)) return _cache.get(key);
const element = canvas(size);
const ctx = element.getContext('2d');
const c = size / 2;
if (!pano) {
ctx.beginPath();
ctx.moveTo(c, c);
ctx.arc(c, c, size * 0.46, -Math.PI / 2 - 0.62, -Math.PI / 2 + 0.62);
ctx.closePath();
ctx.fillStyle = color;
ctx.globalAlpha = 0.42;
ctx.fill();
ctx.globalAlpha = 1;
} else {
ctx.beginPath();
ctx.arc(c, c, size * 0.4, 0, Math.PI * 2);
ctx.strokeStyle = color;
ctx.globalAlpha = 0.5;
ctx.lineWidth = Math.max(1.5, size * 0.09);
ctx.stroke();
ctx.globalAlpha = 1;
}
ctx.beginPath();
ctx.arc(c, c, size * 0.16, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = Math.max(1, size * 0.05);
ctx.strokeStyle = 'rgba(10,10,15,0.85)';
ctx.stroke();
_cache.set(key, element);
return element;
return cachedGlyph(`cone:${size}:${color}:${pano}`, size, (ctx, c) => {
if (pano) ring(ctx, c, size * 0.4, color, Math.max(1.5, size * 0.09), 0.5);
else wedge(ctx, c, size * 0.46, 0.62, color, 0.42);
dot(
ctx,
c,
size * 0.16,
color,
'rgba(10,10,15,0.85)',
Math.max(1, size * 0.05),
);
});
}
/** Pulsing-looking marker for the image the viewer currently shows. */
export function positionMarkerGlyph({ size = 44, color = '#ffb300' } = {}) {
const key = `pos:${size}:${color}`;
if (_cache.has(key)) return _cache.get(key);
const element = canvas(size);
const ctx = element.getContext('2d');
const c = size / 2;
ctx.beginPath();
ctx.moveTo(c, c);
ctx.arc(c, c, size * 0.48, -Math.PI / 2 - 0.55, -Math.PI / 2 + 0.55);
ctx.closePath();
ctx.fillStyle = color;
ctx.globalAlpha = 0.55;
ctx.fill();
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.arc(c, c, size * 0.2, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = '#0a0a0f';
ctx.stroke();
ctx.beginPath();
ctx.arc(c, c, size * 0.3, 0, Math.PI * 2);
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.globalAlpha = 0.8;
ctx.stroke();
_cache.set(key, element);
return element;
return cachedGlyph(`pos:${size}:${color}`, size, (ctx, c) => {
wedge(ctx, c, size * 0.48, 0.55, color, 0.55);
dot(ctx, c, size * 0.2, color, '#0a0a0f', 2);
ring(ctx, c, size * 0.3, color, 1.5, 0.8);
});
}
+1 -58
View File
@@ -3,64 +3,7 @@ import test from 'node:test';
import * as Cesium from 'cesium';
import { createStreetLevelLayer } from './index.js';
import { MAPILLARY_CREDIT_HTML } from './providers/mapillary/policy.js';
/**
* The smallest provider the core accepts. `stats` is what coverageStats()
* answers; `calls` counts what the core asked of the provider and its viewer.
*/
function fakeProvider({
id = 'mapillary',
pickPrefix = 'mly:',
html = MAPILLARY_CREDIT_HTML,
nearestImage = async () => null,
} = {}) {
const calls = { activate: 0, deactivate: 0, mount: 0, open: [], unmount: 0 };
const stats = {
count: 0,
zoom: null,
kind: null,
loading: false,
hint: '',
error: null,
keyRequired: false,
};
let context = null;
return {
calls,
stats,
context: () => context,
id,
name: 'Mapillary',
label: 'MAPILLARY',
requiresKeyId: null,
pickPrefix,
colors: { coverage: '#05cb63' },
credit: { html },
create: (providerContext) => {
context = providerContext;
return {
status: async () => ({ configured: true }),
init() {},
activate: () => calls.activate++,
deactivate: () => calls.deactivate++,
destroy() {},
refreshCoverage() {},
setFilter() {},
coverageStats: () => ({ ...stats }),
handlePick: () => false,
nearestImage,
viewer: {
mount: async () => calls.mount++,
open: async (imageId) => calls.open.push(imageId),
close() {},
unmount: () => calls.unmount++,
resize() {},
onPose: () => () => {},
},
};
},
};
}
import { fakeStreetLevelProvider as fakeProvider } from '../../testSupport/streetLevelFakes.mjs';
/**
* A stand-in Cesium viewer the layer can be enabled on: a canvas for the
@@ -3,6 +3,7 @@ import test from 'node:test';
import * as Cesium from 'cesium';
import { SWAP_MAX_WAIT_MS, createCoverage } from './coverage.js';
import { encodeCoverageTile } from './coverageFixture.mjs';
import { rayCamera } from '../../../../testSupport/streetLevelFakes.mjs';
import {
COLORS,
COVERAGE_MAX_SEQUENCES,
@@ -641,46 +642,19 @@ test('the draped lines go after the wait even if the cast lines never get ready'
coverage.clear();
});
/**
* A camera `height` m above WGS84 looking along `heading` with `pitch` (deg),
* a 60°×40° view on a 100×100 canvas, whose screen rays really meet the
* ellipsoid they are given (as Cesium's `pickEllipsoid` does).
*/
/** A camera over a 100×100 canvas with a 60°×40° view (see rayCamera). */
function tiltedCamera({ lon, lat, height, heading, pitch }) {
const position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
const frameAt = Cesium.Transforms.eastNorthUpToFixedFrame(position);
function ray(point) {
const h = (heading + (point.x / 100 - 0.5) * 60) * RAD;
const p = (pitch + (0.5 - point.y / 100) * 40) * RAD;
const local = new Cesium.Cartesian3(
Math.sin(h) * Math.cos(p),
Math.cos(h) * Math.cos(p),
Math.sin(p),
);
const direction = Cesium.Matrix4.multiplyByPointAsVector(
frameAt,
local,
new Cesium.Cartesian3(),
);
return new Cesium.Ray(
position,
Cesium.Cartesian3.normalize(direction, direction),
);
}
return {
positionWC: position,
positionCartographic: {
longitude: lon * RAD,
latitude: lat * RAD,
height,
},
pickEllipsoid(point, ellipsoid) {
const r = ray(point);
const hit = Cesium.IntersectionTests.rayEllipsoid(r, ellipsoid);
return hit ? Cesium.Ray.getPoint(r, hit.start) : undefined;
},
computeViewRectangle: () => undefined,
};
return rayCamera({
lon,
lat,
altitude: height,
heading,
pitch,
width: 100,
height: 100,
fovX: 60,
fovY: 40,
});
}
/**
+3 -15
View File
@@ -2,22 +2,10 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { composeUIState, summarizeCoverage } from './uiState.js';
import { COLORS } from './policy.js';
import { providerSnapshot } from '../../testSupport/streetLevelFakes.mjs';
const provider = (overrides = {}) => ({
id: 'mapillary',
name: 'Mapillary',
label: 'MAPILLARY',
on: true,
configured: true,
keyRequired: false,
requiresKeyId: 'mapillary',
loading: false,
count: 10,
hint: '',
error: null,
color: '#05cb63',
...overrides,
});
const provider = (overrides = {}) =>
providerSnapshot({ count: 10, ...overrides });
const base = {
enabled: true,
filter: { pano: 'all', sinceDays: 0 },
+10 -38
View File
@@ -7,6 +7,7 @@ import {
viewFocus,
visibleBbox,
} from './view.js';
import { rayCamera } from '../../testSupport/streetLevelFakes.mjs';
const RAD = Math.PI / 180;
/** A viewer 300 m above a street 1,600 m up (Denver-like). */
@@ -82,50 +83,21 @@ test('an ordinary view keeps west < east', () => {
);
});
/**
* A pinhole camera `agl` metres above ground `ground` m up, looking north at
* `pitch` degrees, whose rays meet whatever ellipsoid they are given (real
* Cesium ray maths, as `camera.pickEllipsoid` does it).
*/
/** A pinhole viewer `agl` m above ground `ground` m up, looking north. */
function pinhole({ lon, lat, ground, agl, pitch, w = 1600, h = 900 }) {
const position = Cesium.Cartesian3.fromDegrees(lon, lat, ground + agl);
const frame = Cesium.Transforms.eastNorthUpToFixedFrame(position);
const p = pitch * RAD;
const forward = [0, Math.cos(p), Math.sin(p)];
const up = [0, -Math.sin(p), Math.cos(p)];
const half = Math.tan(30 * RAD);
return {
scene: {
canvas: { clientWidth: w, clientHeight: h },
globe: { show: false, ellipsoid: Cesium.Ellipsoid.WGS84 },
},
camera: {
positionWC: position,
positionCartographic: Cesium.Cartographic.fromDegrees(
lon,
lat,
ground + agl,
),
pickEllipsoid(point, ellipsoid = Cesium.Ellipsoid.WGS84) {
const sx = ((2 * point.x) / w - 1) * half * (w / h);
const sy = (1 - (2 * point.y) / h) * half;
const local = [0, 1, 2].map(
(i) => forward[i] + sx * (i === 0 ? 1 : 0) + sy * up[i],
);
const direction = Cesium.Cartesian3.normalize(
Cesium.Matrix4.multiplyByPointAsVector(
frame,
new Cesium.Cartesian3(...local),
new Cesium.Cartesian3(),
),
new Cesium.Cartesian3(),
);
const ray = new Cesium.Ray(position, direction);
const hit = Cesium.IntersectionTests.rayEllipsoid(ray, ellipsoid);
return hit ? Cesium.Ray.getPoint(ray, hit.start) : undefined;
},
computeViewRectangle: () => undefined,
},
camera: rayCamera({
lon,
lat,
altitude: ground + agl,
pitch,
width: w,
height: h,
}),
};
}