fix(traffic): give paired TomTom sides of one street a two-way share of dots

TomTom draws a two-way street whose directions differ as two one_side
lines on one centreline, and each got a one-way road's full share of
simulated vehicles, so the street drew twice the dots of the same street as
one full-coverage line or a two-way OSM road. A one_side line with an
opposite-direction one_side line within 5 m of at least two of three points
along it now gets a two-way direction's half share. On the decoded Austin
flow fixture every one_side line is paired, and road length weighted by
direction share falls from 553 to 417 km. Pairing a 16-tile area takes
about 41 ms per road pass. Refs #1455.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-11-01 06:16:23 -08:00
1 parent 0d1a68160e
commit 7a39d2d29b
3 files changed
+121 -8

No files matched your search

+5
View File
@@ -5897,6 +5897,11 @@ easier to meet (detection is now on more often), but does not create it.
TomTom draws only flow-tile lines with their own flow; roads without flow
are not drawn and the status says so. A `full`-coverage flow line carries
both travel directions, a `one_side` line the direction it is drawn in.
When an opposite-direction `one_side` line runs within 5 m of at least two
of three points a quarter, half and three quarters along a `one_side` line,
the two are one street's directions and each
gets a two-way direction's half share of dots, so the street is as dense as
one `full` line (#1455).
OSM draws OpenFreeMap roads with flow matched onto them (below). Hybrid
draws eligible TomTom motor-road lines and adds OpenFreeMap roads only where no eligible TomTom line
runs within 35 m in the same travel direction (30 degrees), tested every
+69 -8
View File
@@ -35,6 +35,14 @@ const MIN_FILL_M = 40;
* Same-class overlap keeps the full 35 m matcher radius.
*/
const CLASS_MISMATCH_RADIUS_M = 15;
/**
* Opposite-direction TomTom lines this close share one street: TomTom draws
* both directions of a two-way street a metre or two apart, and a divided
* carriageway's sides further apart.
*/
const PAIRED_SIDE_RADIUS_M = 5;
/** Arc-length positions sampled along a `one_side` line for its partner. */
const PAIRED_SIDE_SAMPLES = [0.25, 0.5, 0.75];
const CONTROLLED_ACCESS_FLOW = new Set(['Motorway', 'International road']);
const CONTROLLED_ACCESS_OSM = new Set(['motorway', 'trunk']);
@@ -85,20 +93,65 @@ export function isDrivableFlowSegment(segment) {
);
}
/**
* Share of a `one_side` line's sample points (PAIRED_SIDE_SAMPLES) with an
* opposite-direction TomTom line beside them: TomTom draws a two-way street
* whose directions differ as two `one_side` lines on one centreline, while a
* divided carriageway's other side is further away than PAIRED_SIDE_RADIUS_M.
* @returns {number} Paired share in [0, 1].
*/
function pairedSampleShare(segment, index) {
const xs = [];
const ys = [];
const cum = [0];
for (const [lon, lat] of segment.coords) {
const [x, y] = index.project(lon, lat);
const i = xs.push(x) - 1;
ys.push(y);
if (i) cum.push(cum[i - 1] + Math.hypot(x - xs[i - 1], y - ys[i - 1]));
}
const length = cum[cum.length - 1];
if (!(length > 0)) return 0;
let paired = 0;
let i = 1;
for (const share of PAIRED_SIDE_SAMPLES) {
const target = share * length;
while (i < cum.length - 1 && cum[i] < target) i++;
const dx = xs[i] - xs[i - 1];
const dy = ys[i] - ys[i - 1];
const t = (target - cum[i - 1]) / (cum[i] - cum[i - 1] || 1);
const reverse = (Math.atan2(-dx, -dy) * 180) / Math.PI;
if (
index.nearest(
xs[i - 1] + dx * t,
ys[i - 1] + dy * t,
reverse,
() => PAIRED_SIDE_RADIUS_M,
).best
)
paired++;
}
return paired / PAIRED_SIDE_SAMPLES.length;
}
/**
* Convert decoded TomTom flow lines into road records that carry their own
* flow. A line with `coverage: 'full'` describes both travel directions;
* `one_side` lines describe the direction they are drawn in.
* `one_side` lines describe the direction they are drawn in. A `one_side`
* line paired with the opposite direction on the same street gets that
* direction's half share of dots, as a two-way road's direction does.
* @param {Array<{coords:number[][], trafficLevel:number, roadType:string, closure:boolean, coverage?:string}>} segments
* @returns {Array<object>} Road records (`directFlow: true`).
*/
export function flowSegmentsToRoads(segments) {
return (segments || [])
.filter(
(segment) =>
segment?.coords?.length >= 2 && isDrivableFlowSegment(segment),
)
.map((segment) => ({
const drivable = (segments || []).filter(
(segment) => segment?.coords?.length >= 2 && isDrivableFlowSegment(segment),
);
const sides = createFlowSegmentIndex(
drivable.filter((segment) => segment.coverage !== 'full'),
);
return drivable.map((segment) => {
const road = {
coordinates: segment.coords,
type: FLOW_ROAD_TYPES[segment.roadType],
drivable: true,
@@ -110,7 +163,15 @@ export function flowSegmentsToRoads(segments) {
oneway: segment.coverage === 'full' ? 0 : 1,
flow: { level: segment.trafficLevel, closure: segment.closure === true },
directFlow: true,
}));
};
if (
segment.coverage !== 'full' &&
sides &&
pairedSampleShare(segment, sides) >= 0.5
)
road.densityWeight = 0.5;
return road;
});
}
/** Cut a projected polyline to the arc-length interval [from, to]. */
+47
View File
@@ -116,6 +116,53 @@ test('a full-coverage TomTom line is two-way and covers both OSM directions', ()
assert.equal(roads.length, 1);
assert.equal(roads[0].directFlow, true);
});
test("opposite TomTom sides of one street share a two-way street's dots", () => {
// About 1 m and 20 m north of `coordinates`, drawn westbound.
const back = (dLat) => ({
...flow,
coords: coordinates.map(([lon, lat]) => [lon, lat + dLat]).reverse(),
});
const weights = (segments) =>
flowSegmentsToRoads(segments).map((road) => road.densityWeight ?? 1);
assert.deepEqual(weights([flow, back(0.00001)]), [0.5, 0.5]);
assert.deepEqual(weights([flow]), [1], 'a lone side keeps the one-way share');
assert.deepEqual(
weights([flow, back(0.00018)]),
[1, 1],
'a divided carriageway keeps one-way shares',
);
assert.deepEqual(
weights([
flow,
{
...flow,
coords: coordinates.map(([lon, lat]) => [lon, lat + 0.00001]),
},
]),
[1, 1],
'same-direction lines are not a pair',
);
assert.deepEqual(
weights([{ ...flow, coverage: 'full' }, back(0.00001)]),
[1, 1],
'a full-coverage line is not a side, and pairs with nothing',
);
});
test('a TomTom side paired along only part of its length keeps its share', () => {
// Westbound partners about 1 m north, from the start of `flow` to 30% and
// to 60% of its length: one or two of its three sample points.
const partner = (toLon) => ({
...flow,
coords: [
[toLon, 30.26701],
[-97.744, 30.26701],
],
});
const weightOfFlow = (other) =>
flowSegmentsToRoads([flow, other])[0].densityWeight ?? 1;
assert.equal(weightOfFlow(partner(-97.7434)), 1);
assert.equal(weightOfFlow(partner(-97.7428)), 0.5);
});
test('bearing and distance reject nearby crossing and separate OSM roads', () => {
const parallel = {
...main,