mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Tilted views: the visible box came from screen rays against the bare
ellipsoid and the 9 tiles kept were the ones nearest its centre, so a view
over high ground (Denver, 1,600 m up) or near the horizon dropped the
streets in front of the camera. At street zooms the rays now meet an
ellipsoid raised to the ground under the camera, hits beyond a range tied
to camera height are ignored, tilted views also keep the ground around the
camera, and tiles are ranked along the line of sight (nadir to screen
centre). Simulated with real Cesium ray maths over Sacramento and Denver
from 2 m to 1,000 m: the nearest visible ground and the screen centre are
always loaded.
Rejected token: Mapillary's 401/403 reached the panel as a raw "Mapillary
tiles HTTP 403" while the pill said ON, and every pan asked Mapillary again.
The proxy now answers a refusal as {keyRejected: true} and holds it for that
token (5 min), and a 429 with its Retry-After (held until then). The panel
reads KEY REJECTED, names MAPILLARY_CLIENT_TOKEN and Provider Settings,
gates its controls like KEY REQUIRED, requests nothing more, and clears the
message when the layer goes off. A rate limit keeps the drawn tiles and
refreshes once the wait is over.
Found in the IC8 review of #768 (P1 tile selection, P2 rejected token).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
82 lines
2.6 KiB
JavaScript
82 lines
2.6 KiB
JavaScript
import { COLORS } from './policy.js';
|
|
|
|
/**
|
|
* @typedef {object} ProviderSnapshot
|
|
* @property {string} id
|
|
* @property {string} name
|
|
* @property {string} label
|
|
* @property {boolean} on
|
|
* @property {boolean|null} configured null until the status call answers
|
|
* @property {boolean} keyRequired no usable key: missing or rejected
|
|
* @property {boolean} [keyRejected] the provider refused the configured key
|
|
* @property {string|null} requiresKeyId
|
|
* @property {boolean} loading
|
|
* @property {number} count
|
|
* @property {string} hint
|
|
* @property {string|null} error
|
|
* @property {string} color The source's one colour.
|
|
*/
|
|
|
|
/**
|
|
* What the switched-on providers add up to: summed counts, any loading, the
|
|
* first hint and error, whether every one of them lacks its key, and whether
|
|
* that is because a provider rejected the key it was given.
|
|
* @param {Array<ProviderSnapshot>} providers
|
|
*/
|
|
export function summarizeCoverage(providers) {
|
|
const active = providers.filter((p) => p.on);
|
|
return {
|
|
count: active.reduce((sum, p) => sum + (p.count || 0), 0),
|
|
loading: active.some((p) => p.loading),
|
|
hint: active.find((p) => p.hint)?.hint || '',
|
|
error: active.find((p) => p.error)?.error || null,
|
|
keyRequired:
|
|
active.length > 0 && active.every((p) => p.keyRequired === true),
|
|
keyRejected:
|
|
active.length > 0 &&
|
|
active.every((p) => p.keyRequired === true) &&
|
|
active.some((p) => p.keyRejected === true),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Compose the snapshot the panel renders from the core state and one
|
|
* snapshot per registered provider. Pure, so the merge rules are testable:
|
|
* counts add up across active providers, the layer is key-gated only when
|
|
* every switched-on provider lacks its key, and the legend lists one swatch
|
|
* per active source, in its colour and under its name, followed by the
|
|
* shared selection colour.
|
|
* @param {{enabled: boolean, filter: object, providers: Array<ProviderSnapshot>, street: object, sequence: object}} input
|
|
*/
|
|
export function composeUIState({
|
|
enabled,
|
|
filter,
|
|
providers,
|
|
street,
|
|
sequence,
|
|
surface = 'draped',
|
|
}) {
|
|
const active = providers.filter((p) => p.on);
|
|
const { keyRequired, keyRejected, ...coverage } =
|
|
summarizeCoverage(providers);
|
|
const legend = active.map((provider) => ({
|
|
key: provider.id,
|
|
label: provider.name,
|
|
color: provider.color,
|
|
}));
|
|
if (active.length)
|
|
legend.push({ key: 'selected', label: 'Selected', color: COLORS.selected });
|
|
return {
|
|
enabled,
|
|
keyRequired,
|
|
keyRejected,
|
|
filter: { ...filter },
|
|
providers: providers.map((p) => ({ ...p })),
|
|
coverage,
|
|
legend,
|
|
sequence: { ...sequence },
|
|
street: { ...street },
|
|
surface,
|
|
};
|
|
}
|