Files
gods-eye-view/src/sources/overpassFeatures.test.mjs
T
f4fcf267c6 fix(osm): stop using public Overpass by default (#648) (#742)
Traffic roads come from OpenFreeMap vector tiles, or TomTom flow tiles with a
key (Hybrid by default). Military areas use OpenFreeMap polygons with a bundled
worldwide name index; wide views show named points. ALPR reads an hourly
OpenStreetMap extract. Cockpit place context and country, state and US county
outlines use bundled Natural Earth and US Census data. Overpass is used only
when an operator configures their own endpoint.

Also: camera arrivals no longer land below ground, keyless terrain retries on
429, bounded relation geometry for configured Overpass, one shared
OpenStreetMap credit, and separate tile and API transports for embedders.

Co-authored-by: theGaryLarson <[email protected]>
Co-authored-by: Milan Khanal <[email protected]>
2026-09-28 19:07:57 -05:00

347 lines
10 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
createOverpassFeatureSource,
geometryOutputBox,
FEATURE_SOURCE_METHODS,
} from './overpassFeatures.js';
import { normalizeOverpassFeatures } from './overpassFeaturesRecords.js';
import { createAnnotationResolver } from '../annotations/resolver.js';
const geometry = [
{ lat: 30, lon: -97 },
{ lat: 30, lon: -96.999 },
{ lat: 30.001, lon: -96.999 },
{ lat: 30.001, lon: -97 },
{ lat: 30, lon: -97 },
];
const raw = {
type: 'way',
id: 42,
tags: { name: 'Fixture Tower', building: 'yes', height: '100 ft' },
geometry,
};
const candidate = {
id: 42,
category: 'feature',
names: { primary: 'Fixture Tower' },
building: true,
heightM: 30.48,
coordinates: geometry,
};
const featureSource = (overrides) =>
Object.assign(
Object.fromEntries(
FEATURE_SOURCE_METHODS.map((method) => [method, async () => []]),
),
overrides,
);
test('Overpass adapter owns all feature query expressions and normalizes returned records', async () => {
const calls = [];
const source = createOverpassFeatureSource({
boundarySource: {
query: async (query, options) => {
calls.push({ query, options });
return [raw, { ...raw, type: 'relation' }];
},
},
});
for (const method of FEATURE_SOURCE_METHODS) {
const records = await source[method](
method === 'getAreaGeometry' ? 3600000042 : { lat: 30, lon: -97 },
);
assert.equal(records[0].names.primary, 'Fixture Tower');
assert.equal(records[0].heightM, 30.48);
assert.deepEqual(records[0].coordinates, geometry);
assert.equal('tags' in records[0], false);
assert.equal(records.length, method === 'getAreaGeometry' ? 1 : 2);
}
assert.equal(calls.length, 9);
assert.ok(
calls.every(
({ query, options }) =>
query.includes('[out:json]') && options.signal instanceof AbortSignal,
),
);
assert.match(calls[1].query, /rel\(pivot.x\)/);
assert.match(calls[4].query, /\["highway"\]/);
assert.match(calls[7].query, /memorial\|monument/);
assert.match(calls[8].query, /around:180,/);
assert.throws(
() => source.getFootprints({ lat: '30);out;', lon: -97 }),
/coordinates/,
);
assert.throws(() => source.getAreaGeometry('1);out;'), /reference/);
assert.equal(calls.length, 9);
});
test('provider preserves empty, transient and throttled responses and rejects late canceled data', async () => {
const lifetime = new AbortController();
let value = [];
const source = createOverpassFeatureSource({
signal: lifetime.signal,
boundarySource: { query: async () => value },
});
assert.deepEqual(await source.getFootprints({ lat: 30, lon: -97 }), []);
value = null;
assert.equal(await source.getFootprints({ lat: 30, lon: -97 }), null);
value = { rateLimited: true, retryAfterMs: 1500 };
assert.deepEqual(await source.getFootprints({ lat: 30, lon: -97 }), value);
let respond, signal;
const pendingSource = createOverpassFeatureSource({
signal: lifetime.signal,
boundarySource: {
query: async (_q, options) => {
signal = options.signal;
return new Promise((resolve) => {
respond = resolve;
});
},
},
});
const pending = pendingSource.getFootprints({ lat: 30, lon: -97 });
lifetime.abort();
respond([raw]);
assert.equal(signal.aborted, true);
assert.equal(await pending, null);
});
test('relation normalization keeps the largest closed outer and rejects a missing boundary', () => {
const relation = {
type: 'relation',
id: 1,
tags: { name: 'Region' },
members: [
{ role: 'outer', geometry: geometry.slice(0, 3) },
{ role: 'outer', geometry: geometry.slice(2) },
{
role: 'inner',
geometry: geometry.map((p) => ({ lat: p.lat + 1, lon: p.lon + 1 })),
},
],
};
assert.deepEqual(
normalizeOverpassFeatures([relation])[0].coordinates,
geometry,
);
relation.members = [{ role: 'outer', geometry: geometry.slice(0, 3) }];
assert.deepEqual(normalizeOverpassFeatures([relation])[0].coordinates, []);
assert.deepEqual(
normalizeOverpassFeatures([{ ...raw, geometry: undefined, members: {} }], {
focus: true,
})[0].coordinates,
[],
);
});
test('annotation selection accepts another feature provider with no query transport', async () => {
let calls = 0;
const resolver = createAnnotationResolver({
featureSource: featureSource({
getFootprints: async () => {
calls++;
return [candidate];
},
}),
});
const args = {
latitude: 30.0005,
longitude: -96.9995,
target: 'Fixture Tower',
entityKind: 'building',
footprint: true,
};
const first = await resolver.resolveAnnotationTarget(args);
const second = await resolver.resolveAnnotationTarget(args);
assert.equal(first.footprintKind, 'building');
assert.equal(first.buildingHeight, 30.48);
assert.deepEqual(
first.ring,
geometry.map((p) => [p.lon, p.lat]),
);
assert.deepEqual(second.ring, first.ring);
assert.equal(
calls,
1,
'ranking cache is independent of the provider implementation',
);
});
test('annotation retries transient feature failures but caches definitive misses', async () => {
let calls = 0;
const resolver = createAnnotationResolver({
featureSource: featureSource({
getFootprints: async () => {
calls++;
return calls === 1 ? null : [];
},
}),
});
const anchor = await resolver.resolveAnnotationTarget({
latitude: 30.0005,
longitude: -96.9995,
target: 'Missing Tower',
entityKind: 'building',
footprint: true,
deferFootprint: true,
});
assert.equal(await anchor.resolveOutline(), undefined);
assert.equal(await anchor.resolveOutline(), null);
assert.equal(await anchor.resolveOutline(), null);
assert.equal(calls, 2);
});
// A multipolygon relation as Overpass prints it with `out geom`: the outer
// boundary split across two ways, an inner ring, and a label node.
const relationRing = [
{ lat: 30, lon: -97 },
{ lat: 30, lon: -96.994 },
{ lat: 30.005, lon: -96.994 },
{ lat: 30.005, lon: -97 },
{ lat: 30, lon: -97 },
];
const relationFixture = (name, tags) => ({
type: 'relation',
id: 7,
bounds: { minlat: 30, minlon: -97, maxlat: 30.005, maxlon: -96.994 },
members: [
{ type: 'way', ref: 71, role: 'outer', geometry: relationRing.slice(0, 3) },
{ type: 'way', ref: 72, role: 'outer', geometry: relationRing.slice(2) },
{
type: 'way',
ref: 73,
role: 'inner',
geometry: relationRing.map((p) => ({ lat: p.lat + 0.001, lon: p.lon })),
},
{ type: 'node', ref: 74, role: 'label', lat: 30.0025, lon: -96.997 },
],
tags: { name, ...tags },
});
// Overpass leaves members out at `tags` verbosity, so `geom` has nothing to
// attach to and a relation keeps only its bounds.
const overpassPrinting = (element) => ({
query: async (text) =>
/\bout\s+tags\b/.test(text)
? [
{
type: element.type,
id: element.id,
bounds: element.bounds,
tags: element.tags,
},
]
: [element],
});
test('neighborhood and street-area lookups keep relation member geometry', async () => {
const calls = [];
const source = createOverpassFeatureSource({
boundarySource: {
query: async (text) => {
calls.push(text);
return overpassPrinting(
relationFixture('Fixture Quarter', { place: 'quarter' }),
).query(text);
},
},
});
for (const method of ['getNeighborhoodAreas', 'getStreetAreas']) {
const [record] = await source[method]({ lat: 30.002, lon: -96.997 });
assert.deepEqual(record.coordinates, relationRing, method);
}
// Member geometry is printed only inside a box around the point.
assert.ok(
calls[0].endsWith(');out geom(29.9481,-97.05924,30.0559,-96.93476);'),
);
assert.ok(
calls[1].endsWith(');out geom(29.97505,-97.02812,30.02895,-96.96588);'),
);
for (const text of calls) assert.doesNotMatch(text, /\bout\s+tags\b/);
});
test('the geometry output box is a bounded square around the point', () => {
const box = geometryOutputBox(30, -97, 6000)
.slice(1, -1)
.split(',')
.map(Number);
const [south, west, north, east] = box;
assert.ok(Math.abs((north - south) * 111320 - 12000) < 2);
assert.ok(
Math.abs((east - west) * 111320 * Math.cos((30 * Math.PI) / 180) - 12000) <
2,
);
assert.equal(geometryOutputBox(89.99, 179.99, 6000).includes('90,180'), true);
});
test('annotations outline a neighborhood or street area mapped as a relation', async () => {
const ring = relationRing.map((p) => [p.lon, p.lat]);
const areas = (tags, name) => {
const source = createOverpassFeatureSource({
boundarySource: overpassPrinting(relationFixture(name, tags)),
});
return (point, options) =>
source[tags.place ? 'getNeighborhoodAreas' : 'getStreetAreas'](
point,
options,
);
};
const neighborhood = await createAnnotationResolver({
featureSource: featureSource({
getNeighborhoodAreas: areas(
{ place: 'neighbourhood' },
'Fixture Heights',
),
}),
}).resolveAnnotationTarget({
latitude: 30.002,
longitude: -96.997,
target: 'Fixture Heights',
entityKind: 'district',
footprint: true,
});
assert.equal(neighborhood.footprintKind, 'area');
assert.deepEqual(neighborhood.ring, ring);
const street = await createAnnotationResolver({
featureSource: featureSource({
getStreetAreas: areas(
{ landuse: 'retail', type: 'multipolygon' },
'Fixture Street Market',
),
}),
}).resolveAnnotationTarget({
latitude: 30.002,
longitude: -96.997,
target: 'Fixture Street',
entityKind: 'street',
footprint: true,
});
assert.equal(street.footprintKind, 'area');
assert.deepEqual(street.ring, ring);
});
test('focus footprints keep relation members and centers', async () => {
let query;
const element = relationFixture('Fixture Building', { building: 'yes' });
const source = createOverpassFeatureSource({
boundarySource: {
query: async (text) => {
query = text;
return overpassPrinting(element).query(text);
},
},
});
const [record] = await source.getFocusFootprints({
lat: 30.002,
lon: -96.997,
});
assert.deepEqual(
record.coordinates,
element.members.flatMap((member) => member.geometry || []),
);
assert.match(query, /out center geom\([-\d.,]+\);/);
assert.doesNotMatch(query, /out tags/);
});