mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
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:
1 parent
0d1a68160e
commit
7a39d2d29b
3 files changed
+121
-8
No files matched your search
@@ -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
|
||||
|
||||
@@ -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]. */
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user