Merge pull request #1442 from Rohilalala/fix/e-traffic-coverage-rounding

fix(traffic): stop a few matched roads reading as "no matches"
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-11-01 23:42:28 -08:00
commit bd4f10b225
4 files changed
+42 -5

No files matched your search

@@ -0,0 +1,4 @@
- Keep Street Traffic's flow status honest at the edges. A few TomTom-matched
dots among hundreds rounded to 0% and read "(no matches)", and a few
simulated dots rounded to "100% cov"; coverage now stays within 1–99% while
both are shown (Aditya, @Rohilalala, #1442).
+5 -3
View File
@@ -5987,9 +5987,11 @@ easier to meet (detection is now on more often), but does not create it.
OSM choice), so Hybrid fill is never replayed as OSM roads. Failed flow
refreshes clear old matches and report simulation. `flowCoveragePct` is the
percentage of shown dots on roads with flow, including all unmatched dots in
the denominator and excluding dots hidden by closures. The OSM keyed status
reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`. Road fetch
coverage follows a 5×5 ellipsoid sample of the central screen square.
the denominator and excluding dots hidden by closures. It reads 1–99 whenever
both matched and simulated dots are shown, so rounding never turns a few
matches into `(no matches)` or a few simulated dots into full coverage. The
OSM keyed status reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`.
Road fetch coverage follows a 5×5 ellipsoid sample of the central screen square.
Horizon rays are capped at 10 km from nadir (reduced if necessary to fit
16 z12 tiles); polygon-intersecting tiles load nearest first. Up to 16 z14
tiles provide near detail, with z12 roads beyond them. Cached tiles serve
+13 -2
View File
@@ -259,7 +259,8 @@ export function createControls({ state: layerState, services, parts, source }) {
* `error` carries this instant's health, so a live-configured layer whose
* flow feed went down reads DEGRADED with the reason instead of a stale
* LIVE coverage number. `flowCoveragePct` is shown dots on matched roads /
* all shown dots (0–100 int), excluding closures; `tilesFetched` counts flow-tile requests
* all shown dots (0–100 int, kept within 1–99 while both kinds are shown),
* excluding closures; `tilesFetched` counts flow-tile requests
* issued to the proxy this session (decode-cache hits excluded).
* @returns {{count:number, lastUpdate:number|null, loading:boolean,
* mode:'live'|'sim', error:string|null, flowCoveragePct:number,
@@ -304,8 +305,18 @@ export function createControls({ state: layerState, services, parts, source }) {
Boolean(layerState._heightPending);
const { free, slow, jam, sim } = layerState._bucketCounts;
const matched = free + slow + jam;
// The label reads 0 as "no matches" and 100 as nothing simulated, so
// rounding must not reach either while both kinds of dot are shown.
const flowCoveragePct =
matched + sim > 0 ? Math.round((100 * matched) / (matched + sim)) : 0;
matched + sim > 0
? Math.min(
sim > 0 ? 99 : 100,
Math.max(
matched > 0 ? 1 : 0,
Math.round((100 * matched) / (matched + sim)),
),
)
: 0;
const feed = parts.model.trafficFeedPresentation({
liveMode: layerState._liveMode,
fetching: loading,
+20
View File
@@ -516,6 +516,26 @@ test('late closures hide and stop dots; coverage counts shown matched dots, and
assert.equal(controls.getStats().flowCoveragePct, 80);
});
test('coverage never rounds to "no matches" or full coverage while both kinds of dot are shown', async () => {
const { state, controls } = await matchingContext(async () => []);
state._bucketCounts = { free: 1, slow: 0, jam: 0, sim: 300 };
assert.equal(controls.getStats().flowCoveragePct, 1);
assert.equal(
controls.getStats().loadingLabel,
'LIVE · Roads: OpenStreetMap · Flow: TomTom · 1% cov · Unmatched: simulated',
);
state._bucketCounts = { free: 300, slow: 0, jam: 0, sim: 1 };
assert.equal(controls.getStats().flowCoveragePct, 99);
assert.equal(
controls.getStats().loadingLabel,
'LIVE · Roads: OpenStreetMap · Flow: TomTom · 99% cov · Unmatched: simulated',
);
state._bucketCounts = { free: 300, slow: 0, jam: 0, sim: 0 };
assert.equal(controls.getStats().flowCoveragePct, 100);
state._bucketCounts = { free: 0, slow: 0, jam: 0, sim: 300 };
assert.equal(controls.getStats().flowCoveragePct, 0);
});
test('refresh failure removes cached matches and reports simulated flow', async () => {
const { flow, state, model, controls } = await matchingContext(async () => {
throw new Error('HTTP 429');