mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
fix(traffic): retain line identity across clipping
This commit is contained in:
1 parent
2e9c146060
commit
342d960582
3 files changed
+97
-14
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user