fix: hover-route line not drawn in e2e — MapLibre clips queried lines across tiles and quantizes their coordinates (strict single-feature equality never matched)
CI / build + unit + e2e (pull_request) Failing after 6m44s
CI / build + unit + e2e (pull_request) Failing after 6m44s
This commit is contained in:
+22
-12
@@ -174,22 +174,32 @@ async function airportLonLat(page: Page, icao: string): Promise<[number, number]
|
||||
return [f.properties.lon, f.properties.lat];
|
||||
}
|
||||
|
||||
// ISS-022/025/027: the hover-route layer must hold exactly one LineString whose first and last
|
||||
// coordinates are the two airports' exact dataset coords (greatCircleArc returns them verbatim
|
||||
// at t=0/t=1, so strict equality is safe). Call fitToAirports([from, to]) first — rendered
|
||||
// features are viewport-scoped.
|
||||
// ISS-022/025/027: the hover-route layer must hold the great-circle trace from origin to dest.
|
||||
// MapLibre stores GeoJSON geometry in per-tile INTEGER grids and clips long lines across tiles, so
|
||||
// queryRenderedFeatures returns ONE FEATURE PER TILE the line crosses (the 1420 km EDDF→LEMD leg is
|
||||
// 4 segments at z=4 / 5 at z=5) with coordinates quantized to the rendered zoom's grid — up to ~half
|
||||
// a grid step off (~0.001° observed at z≥4, ~0.02° worst case at z=0). A strict single-feature +
|
||||
// exact-coords comparison can therefore never match even when the line is drawn correctly (CI run #69:
|
||||
// all three hover-route timeouts while the trace was visibly rendered), so we accept any number of
|
||||
// segments and require some segment to START within EPS of `from` and some to END within EPS of `to`.
|
||||
// The true endpoints survive clipping verbatim in whichever tile contains them. Call
|
||||
// fitToAirports([from, to]) first — rendered features are viewport-scoped.
|
||||
const HOVER_ROUTE_EPS_DEG = 0.05; // ~5 km: above worst-case quantization at any zoom, below the distance between any two EU airports
|
||||
|
||||
async function expectHoverRoute(page: Page, fromIcao: string, toIcao: string): Promise<void> {
|
||||
const [from, to] = await Promise.all([airportLonLat(page, fromIcao), airportLonLat(page, toIcao)]);
|
||||
const near = (a: [number, number], b: [number, number]) =>
|
||||
Math.abs(a[0] - b[0]) <= HOVER_ROUTE_EPS_DEG && Math.abs(a[1] - b[1]) <= HOVER_ROUTE_EPS_DEG;
|
||||
await expect.poll(async () => {
|
||||
const feats = (await renderedFeatures(page, ['hover-route'])) as Array<{ geometry: { coordinates: [number, number][] } }>;
|
||||
if (feats.length !== 1) return false;
|
||||
const coords = feats[0].geometry.coordinates;
|
||||
return (
|
||||
coords[0][0] === from[0] &&
|
||||
coords[0][1] === from[1] &&
|
||||
coords[coords.length - 1][0] === to[0] &&
|
||||
coords[coords.length - 1][1] === to[1]
|
||||
);
|
||||
if (!feats.length) return false;
|
||||
// The line may be clipped into several per-tile segments — check the endpoints across all of them.
|
||||
const startsAtFrom = feats.some((f) => near(f.geometry.coordinates[0], from));
|
||||
const endsAtTo = feats.some((f) => {
|
||||
const c = f.geometry.coordinates;
|
||||
return near(c[c.length - 1], to);
|
||||
});
|
||||
return startsAtFrom && endsAtTo;
|
||||
}, { timeout: 15_000, message: `waiting for the hover-route line ${fromIcao} → ${toIcao}` }).toBe(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user