fix(traffic): retain line identity across clipping

This commit is contained in:
German Khokhlov committed 2026-11-03 15:14:20 +04:00
1 parent 2e9c146060
commit 342d960582
3 files changed
+97 -14

No files matched your search

+17 -12
View File
@@ -20,6 +20,19 @@ function tileFreshness(response) {
};
}
/**
* Clip a flow line to a box. Every piece keeps its uncut line as `line`, so
* pieces of one line that doubles back are never paired with each other.
*/
function clipFlowSegment(segment, bbox) {
const line = segment.line || segment;
return clipTileLine(segment.coords, bbox).map((coords) => ({
...segment,
coords,
line,
}));
}
/** Own one bounded decoded flow cache; every request still passes the server tile budget. */
export function createFlowTileSource({
fetchImpl = (...args) => globalThis.fetch(...args),
@@ -41,12 +54,7 @@ export function createFlowTileSource({
entry?.release();
const segments = decodeFlowTile(bytes, z, x, y, { strict: true })
.filter(isDrivableFlowSegment)
.flatMap((segment) =>
clipTileLine(segment.coords, tileToBBox(z, x, y)).map((coords) => ({
...segment,
coords,
})),
);
.flatMap((segment) => clipFlowSegment(segment, tileToBBox(z, x, y)));
if (mark) freshness.set(segments, mark);
return segments;
},
@@ -87,12 +95,9 @@ export function createFlowTileSource({
partial = result.partial;
const segments = bounds.coverage
? result.tiles.flat()
: result.tiles.flat().flatMap((segment) =>
clipTileLine(segment.coords, bounds).map((coords) => ({
...segment,
coords,
})),
);
: result.tiles
.flat()
.flatMap((segment) => clipFlowSegment(segment, bounds));
const marks = result.tiles.map((tile) => freshness.get(tile) || {});
const times = marks
.map((mark) => mark.fetchedAt)
+5 -2
View File
@@ -112,6 +112,7 @@ function pairedSampleShare(segment, index) {
}
const length = cum[cum.length - 1];
if (!(length > 0)) return 0;
const line = segment.line || segment;
let paired = 0;
let i = 1;
for (const share of PAIRED_SIDE_SAMPLES) {
@@ -126,8 +127,10 @@ function pairedSampleShare(segment, index) {
xs[i - 1] + dx * t,
ys[i - 1] + dy * t,
reverse,
// A line that doubles back beside itself is not its own pair.
(piece) => (piece.flow === segment ? 0 : PAIRED_SIDE_RADIUS_M),
// A line that doubles back beside itself, even clipped into pieces,
// is not its own pair.
(piece) =>
(piece.flow.line || piece.flow) === line ? 0 : PAIRED_SIDE_RADIUS_M,
).best
)
paired++;
+75
View File
@@ -1,10 +1,12 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { PbfWriter } from 'pbf';
import {
flowSegmentsToRoads,
resolveRoadMode,
selectTrafficRoads,
} from './roadModes.js';
import { createFlowTileSource } from './flowSource.js';
import { createTrafficSource } from './source.js';
import { createTrafficLayer } from './index.js';
import {
@@ -14,6 +16,7 @@ import {
serializeStoredLayerState,
parseStoredLayerState,
} from '../../data/layerState.js';
import { tileToBBox } from '../../data/tomtomTiles.js';
const coordinates = [
[-97.744, 30.267],
@@ -171,6 +174,78 @@ test('a TomTom side that doubles back beside itself is not its own pair', () =>
};
assert.equal(flowSegmentsToRoads([hairpin])[0].densityWeight ?? 1, 1);
});
/** A flow tile of one-way Major roads; each line is [x, y, x, y, ...]. */
function flowTile(lines) {
const writer = new PbfWriter();
writer.writeMessage(3, writeFlowLayer, lines);
return writer.finish();
}
function writeFlowLayer(lines, layer) {
layer.writeVarintField(15, 2);
layer.writeStringField(1, 'Traffic flow');
layer.writeVarintField(5, 4096);
layer.writeStringField(3, 'traffic_level');
layer.writeMessage(4, (_, value) => value.writeDoubleField(3, 0.6), null);
layer.writeStringField(3, 'road_type');
layer.writeMessage(
4,
(_, value) => value.writeStringField(1, 'Major road'),
null,
);
layer.writeStringField(3, 'traffic_road_coverage');
layer.writeMessage(
4,
(_, value) => value.writeStringField(1, 'one_side'),
null,
);
for (const xy of lines) {
// MoveTo one point, LineTo the rest, as zigzag deltas.
const geometry = [9];
for (let i = 0; i < xy.length; i++) {
if (i === 2) geometry.push(2 | ((xy.length / 2 - 1) << 3));
const delta = xy[i] - (i < 2 ? 0 : xy[i - 2]);
geometry.push((delta << 1) ^ (delta >> 31));
}
layer.writeMessage(
2,
(_, feature) => {
feature.writeVarintField(3, 2);
feature.writePackedVarint(2, [0, 0, 1, 1, 2, 2]);
feature.writePackedVarint(4, geometry);
},
null,
);
}
}
test('TomTom sides clipped by their tile and the view pair only with other lines', async () => {
// A one-way hairpin turning in the tile buffer, its arms one tile unit
// (about 2 m) apart, and a separate opposite pair. The tile clip cuts the
// hairpin into two arms; the view clip then cuts every line again.
const lines = [
[500, 1000, 4300, 1000, 4300, 1001, 500, 1001],
[-200, 3000, 4300, 3000],
[4300, 3001, -200, 3001],
];
const source = createFlowTileSource({
fetchImpl: async () => new Response(flowTile(lines)),
});
const tile = tileToBBox(12, 935, 1686);
const west = tile.west + (tile.east - tile.west) * 0.2;
const { segments } = await source.fetchFlowDetail({
...tile,
west,
east: tile.east - 1e-9,
south: tile.south + 1e-9,
});
assert.equal(segments.length, 4);
assert.ok(
segments.every((s) => s.coords.every(([lon]) => lon >= west - 1e-10)),
);
assert.deepEqual(
flowSegmentsToRoads(segments).map((road) => road.densityWeight ?? 1),
[1, 1, 0.5, 0.5],
);
});
test('bearing and distance reject nearby crossing and separate OSM roads', () => {
const parallel = {
...main,