mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +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. */
|
/** Own one bounded decoded flow cache; every request still passes the server tile budget. */
|
||||||
export function createFlowTileSource({
|
export function createFlowTileSource({
|
||||||
fetchImpl = (...args) => globalThis.fetch(...args),
|
fetchImpl = (...args) => globalThis.fetch(...args),
|
||||||
@@ -41,12 +54,7 @@ export function createFlowTileSource({
|
|||||||
entry?.release();
|
entry?.release();
|
||||||
const segments = decodeFlowTile(bytes, z, x, y, { strict: true })
|
const segments = decodeFlowTile(bytes, z, x, y, { strict: true })
|
||||||
.filter(isDrivableFlowSegment)
|
.filter(isDrivableFlowSegment)
|
||||||
.flatMap((segment) =>
|
.flatMap((segment) => clipFlowSegment(segment, tileToBBox(z, x, y)));
|
||||||
clipTileLine(segment.coords, tileToBBox(z, x, y)).map((coords) => ({
|
|
||||||
...segment,
|
|
||||||
coords,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
if (mark) freshness.set(segments, mark);
|
if (mark) freshness.set(segments, mark);
|
||||||
return segments;
|
return segments;
|
||||||
},
|
},
|
||||||
@@ -87,12 +95,9 @@ export function createFlowTileSource({
|
|||||||
partial = result.partial;
|
partial = result.partial;
|
||||||
const segments = bounds.coverage
|
const segments = bounds.coverage
|
||||||
? result.tiles.flat()
|
? result.tiles.flat()
|
||||||
: result.tiles.flat().flatMap((segment) =>
|
: result.tiles
|
||||||
clipTileLine(segment.coords, bounds).map((coords) => ({
|
.flat()
|
||||||
...segment,
|
.flatMap((segment) => clipFlowSegment(segment, bounds));
|
||||||
coords,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
const marks = result.tiles.map((tile) => freshness.get(tile) || {});
|
const marks = result.tiles.map((tile) => freshness.get(tile) || {});
|
||||||
const times = marks
|
const times = marks
|
||||||
.map((mark) => mark.fetchedAt)
|
.map((mark) => mark.fetchedAt)
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ function pairedSampleShare(segment, index) {
|
|||||||
}
|
}
|
||||||
const length = cum[cum.length - 1];
|
const length = cum[cum.length - 1];
|
||||||
if (!(length > 0)) return 0;
|
if (!(length > 0)) return 0;
|
||||||
|
const line = segment.line || segment;
|
||||||
let paired = 0;
|
let paired = 0;
|
||||||
let i = 1;
|
let i = 1;
|
||||||
for (const share of PAIRED_SIDE_SAMPLES) {
|
for (const share of PAIRED_SIDE_SAMPLES) {
|
||||||
@@ -126,8 +127,10 @@ function pairedSampleShare(segment, index) {
|
|||||||
xs[i - 1] + dx * t,
|
xs[i - 1] + dx * t,
|
||||||
ys[i - 1] + dy * t,
|
ys[i - 1] + dy * t,
|
||||||
reverse,
|
reverse,
|
||||||
// A line that doubles back beside itself is not its own pair.
|
// A line that doubles back beside itself, even clipped into pieces,
|
||||||
(piece) => (piece.flow === segment ? 0 : PAIRED_SIDE_RADIUS_M),
|
// is not its own pair.
|
||||||
|
(piece) =>
|
||||||
|
(piece.flow.line || piece.flow) === line ? 0 : PAIRED_SIDE_RADIUS_M,
|
||||||
).best
|
).best
|
||||||
)
|
)
|
||||||
paired++;
|
paired++;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import { PbfWriter } from 'pbf';
|
||||||
import {
|
import {
|
||||||
flowSegmentsToRoads,
|
flowSegmentsToRoads,
|
||||||
resolveRoadMode,
|
resolveRoadMode,
|
||||||
selectTrafficRoads,
|
selectTrafficRoads,
|
||||||
} from './roadModes.js';
|
} from './roadModes.js';
|
||||||
|
import { createFlowTileSource } from './flowSource.js';
|
||||||
import { createTrafficSource } from './source.js';
|
import { createTrafficSource } from './source.js';
|
||||||
import { createTrafficLayer } from './index.js';
|
import { createTrafficLayer } from './index.js';
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +16,7 @@ import {
|
|||||||
serializeStoredLayerState,
|
serializeStoredLayerState,
|
||||||
parseStoredLayerState,
|
parseStoredLayerState,
|
||||||
} from '../../data/layerState.js';
|
} from '../../data/layerState.js';
|
||||||
|
import { tileToBBox } from '../../data/tomtomTiles.js';
|
||||||
|
|
||||||
const coordinates = [
|
const coordinates = [
|
||||||
[-97.744, 30.267],
|
[-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);
|
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', () => {
|
test('bearing and distance reject nearby crossing and separate OSM roads', () => {
|
||||||
const parallel = {
|
const parallel = {
|
||||||
...main,
|
...main,
|
||||||
|
|||||||
Reference in new issue
Block a user