mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
`createVectorTileSource` took `tileJsonUrl` and `allowedOrigin` as independent options, and `createOpenFreeMapSource` hardcoded both to the default host. A caller that repointed only `tileJsonUrl` — the obvious single change — kept the default `allowedOrigin`, so the TileJSON's absolute upstream `tiles[0]` passed the origin check and every tile was fetched from the default host instead. No error, no warning, and nothing reached the configured mirror. `allowedOrigin` now defaults to the origin of `tileJsonUrl`, so repointing one option is enough, and a mirror that serves the default host's TileJSON verbatim fails with the existing `Invalid vector tile origin` instead of silently bypassing itself. The origin check is unchanged: a TileJSON still cannot point the client at a host the caller did not configure. All three existing callers pass `allowedOrigin` explicitly (`openFreeMap.js`, `alpr/source.js`, `traffic/flowSource.js`), and an explicit value still overrides the derived one, so behaviour is unchanged for every current call site.
361 lines
11 KiB
JavaScript
361 lines
11 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import {
|
|
decodeOpenFreeMapTile,
|
|
openMapRoad,
|
|
polygonCentroid,
|
|
clipTileLine,
|
|
createOpenFreeMapSource,
|
|
} from './openFreeMap.js';
|
|
import { createVectorTileSource } from './vectorTiles.js';
|
|
const fixture = (name) =>
|
|
readFileSync(new URL(`../data/fixtures/${name}`, import.meta.url));
|
|
|
|
test('real z14 and z12 Austin tiles decode only drivable roads', () => {
|
|
for (const [z, x, y, name] of [
|
|
[14, 3743, 6745, 'ofm-austin-14-3743-6745.pbf'],
|
|
[12, 935, 1686, 'ofm-austin-12-935-1686.pbf'],
|
|
]) {
|
|
const { roads } = decodeOpenFreeMapTile(fixture(name), z, x, y);
|
|
assert.ok(roads.length > 0);
|
|
assert.ok(
|
|
roads.every((r) =>
|
|
[
|
|
'motorway',
|
|
'trunk',
|
|
'primary',
|
|
'secondary',
|
|
'tertiary',
|
|
'residential',
|
|
'unclassified',
|
|
].includes(r.type),
|
|
),
|
|
);
|
|
assert.ok(roads.some((r) => r.oneway === 1));
|
|
}
|
|
});
|
|
|
|
test('OpenMapTiles class mapping and reverse oneway preserve the animated-road contract', () => {
|
|
const coords = [
|
|
[-97, 30],
|
|
[-97.001, 30.001],
|
|
];
|
|
assert.deepEqual(
|
|
openMapRoad(coords, { class: 'minor', oneway: -1 }).coordinates,
|
|
coords.slice().reverse(),
|
|
);
|
|
assert.equal(openMapRoad(coords, { class: 'minor', oneway: -1 }).oneway, 1);
|
|
assert.equal(openMapRoad(coords, { class: 'minor' }).type, 'residential');
|
|
assert.equal(openMapRoad(coords, { class: 'service' }), null);
|
|
assert.equal(openMapRoad(coords, { class: 'primary', oneway: 0 }).oneway, 0);
|
|
for (const klass of [
|
|
'track',
|
|
'path',
|
|
'rail',
|
|
'pier',
|
|
'motorway_construction',
|
|
'bridge',
|
|
])
|
|
assert.equal(openMapRoad(coords, { class: klass }), null);
|
|
});
|
|
|
|
test('tile buffer clipping rejects slivers and does not join disconnected lines', () => {
|
|
const box = { west: 0, east: 1, south: 0, north: 1 };
|
|
assert.deepEqual(
|
|
clipTileLine(
|
|
[
|
|
[-1, 0.5],
|
|
[2, 0.5],
|
|
],
|
|
box,
|
|
),
|
|
[
|
|
[
|
|
[0, 0.5],
|
|
[1, 0.5],
|
|
],
|
|
],
|
|
);
|
|
assert.deepEqual(
|
|
clipTileLine(
|
|
[
|
|
[0, 0],
|
|
[0.00001, 0],
|
|
],
|
|
box,
|
|
),
|
|
[],
|
|
);
|
|
});
|
|
|
|
test('Camp Mabry tile emits an unnamed polygon and its centroid marker', () => {
|
|
const { military } = decodeOpenFreeMapTile(
|
|
fixture('ofm-camp-mabry-12-935-1685.pbf'),
|
|
12,
|
|
935,
|
|
1685,
|
|
);
|
|
assert.ok(military.length > 0);
|
|
const camp = military.find(
|
|
(record) =>
|
|
Math.abs(record.latitude - 30.314) < 0.03 &&
|
|
Math.abs(record.longitude + 97.763) < 0.03,
|
|
);
|
|
assert.ok(camp);
|
|
assert.equal(camp.name, 'Military area');
|
|
assert.equal(camp.kind, 'installation');
|
|
assert.equal(camp.class, 'military_land');
|
|
assert.ok(camp.footprint.length > 3);
|
|
assert.deepEqual(
|
|
polygonCentroid([
|
|
[0, 0],
|
|
[4, 0],
|
|
[0, 4],
|
|
[0, 0],
|
|
]),
|
|
[4 / 3, 4 / 3],
|
|
);
|
|
});
|
|
|
|
test('TileJSON is resolved once and decoded tile LRU prevents per-pan refetch', async () => {
|
|
const calls = [];
|
|
const source = createOpenFreeMapSource({
|
|
fetchImpl: async (url) => {
|
|
calls.push(url);
|
|
return url.endsWith('/planet')
|
|
? Response.json({
|
|
tiles: [
|
|
'https://tiles.openfreemap.org/planet/version/{z}/{x}/{y}.pbf',
|
|
],
|
|
})
|
|
: new Response(fixture('ofm-austin-14-3743-6745.pbf'));
|
|
},
|
|
});
|
|
const box = { south: 30.267, north: 30.268, west: -97.744, east: -97.743 };
|
|
await source.fetchBounds(box, { zoom: 14 });
|
|
await source.fetchBounds({ ...box, north: 30.269 }, { zoom: 14 });
|
|
assert.equal(calls.length, 2);
|
|
assert.equal(source.getStats().cacheEntries, 1);
|
|
source.clear();
|
|
assert.equal(source.getStats().cacheBytes, 0);
|
|
});
|
|
|
|
test('tile reads cap view size, concurrency and retained bytes, and reject oversized bodies', async () => {
|
|
let concurrent = 0,
|
|
peak = 0;
|
|
const source = createVectorTileSource({
|
|
template: 'https://tiles.example/{z}/{x}/{y}',
|
|
allowedOrigin: 'https://tiles.example',
|
|
maxTiles: 8,
|
|
concurrency: 2,
|
|
maxEntries: 2,
|
|
maxCacheBytes: 100,
|
|
decode: () => [1, 2, 3],
|
|
fetchImpl: async () => {
|
|
peak = Math.max(peak, ++concurrent);
|
|
await new Promise((r) => setImmediate(r));
|
|
concurrent--;
|
|
return new Response('bytes');
|
|
},
|
|
});
|
|
await assert.rejects(
|
|
source.fetchBounds(
|
|
{ south: 20, north: 40, west: -100, east: -80 },
|
|
{ zoom: 14 },
|
|
),
|
|
{ code: 'TILE_VIEW_TOO_WIDE' },
|
|
);
|
|
await source.fetchBounds(
|
|
{ south: 30.26, north: 30.3, west: -97.8, east: -97.7 },
|
|
{ zoom: 12 },
|
|
);
|
|
assert.ok(peak <= 2);
|
|
assert.ok(source.getStats().cacheEntries <= 2);
|
|
assert.ok(source.getStats().cacheBytes <= 100);
|
|
const huge = createVectorTileSource({
|
|
template: 'https://tiles.example/{z}/{x}/{y}',
|
|
allowedOrigin: 'https://tiles.example',
|
|
maxResponseBytes: 4,
|
|
decode: () => assert.fail('oversize decoded'),
|
|
fetchImpl: async () => new Response('too many bytes'),
|
|
});
|
|
await assert.rejects(
|
|
huge.fetchBounds(
|
|
{ south: 30.267, north: 30.268, west: -97.744, east: -97.743 },
|
|
{ zoom: 12 },
|
|
),
|
|
{ code: 'RESPONSE_TOO_LARGE' },
|
|
);
|
|
});
|
|
|
|
test('shared TileJSON survives one camera cancellation and aborts when its last owner leaves', async () => {
|
|
let finish,
|
|
calls = 0,
|
|
upstreamSignal;
|
|
const source = createOpenFreeMapSource({
|
|
fetchImpl: (url, { signal }) => {
|
|
calls++;
|
|
upstreamSignal = signal;
|
|
return new Promise((resolve, reject) => {
|
|
finish = () =>
|
|
resolve(
|
|
Response.json({
|
|
tiles: ['https://tiles.openfreemap.org/planet/v/{z}/{x}/{y}.pbf'],
|
|
}),
|
|
);
|
|
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
once: true,
|
|
});
|
|
});
|
|
},
|
|
});
|
|
const a = new AbortController(),
|
|
b = new AbortController();
|
|
const first = source.getMetadata(a.signal),
|
|
second = source.getMetadata(b.signal);
|
|
a.abort();
|
|
await assert.rejects(first, { name: 'AbortError' });
|
|
assert.equal(upstreamSignal.aborted, false);
|
|
finish();
|
|
await second;
|
|
assert.equal(calls, 1);
|
|
const cancelled = createOpenFreeMapSource({
|
|
fetchImpl: (url, { signal }) => {
|
|
upstreamSignal = signal;
|
|
return new Promise((resolve, reject) =>
|
|
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
once: true,
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
const c = new AbortController();
|
|
const waiting = cancelled.getMetadata(c.signal);
|
|
c.abort();
|
|
await assert.rejects(waiting, { name: 'AbortError' });
|
|
assert.equal(upstreamSignal.aborted, true);
|
|
});
|
|
|
|
test('destroying a tile source aborts in-flight bodies and prevents stale cache refill', async () => {
|
|
let started;
|
|
const ready = new Promise((resolve) => {
|
|
started = resolve;
|
|
});
|
|
const source = createVectorTileSource({
|
|
template: 'https://tiles.example/{z}/{x}/{y}',
|
|
allowedOrigin: 'https://tiles.example',
|
|
decode: () => [],
|
|
fetchImpl: (url, { signal }) => {
|
|
started();
|
|
return new Promise((resolve, reject) =>
|
|
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
once: true,
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
const work = source.fetchBounds(
|
|
{ south: 30.267, north: 30.268, west: -97.744, east: -97.743 },
|
|
{ zoom: 12 },
|
|
);
|
|
await ready;
|
|
source.clear();
|
|
await assert.rejects(work, { name: 'AbortError' });
|
|
assert.equal(source.getStats().cacheEntries, 0);
|
|
});
|
|
|
|
// Representative provider properties from the Capitol, Camp Mabry greenbelt
|
|
// and commercial car parks. Tiny property fixtures retain the rejection cause.
|
|
test('public road admission rejects paths, private access, service areas and tunnels', () => {
|
|
const coordinates = [
|
|
[-97.7404, 30.2747],
|
|
[-97.7404, 30.2757],
|
|
];
|
|
for (const properties of [
|
|
{ class: 'minor', access: 'no' },
|
|
{ class: 'minor', access: 'private' },
|
|
{ class: 'path', subclass: 'footway' },
|
|
{ class: 'path', subclass: 'cycleway' },
|
|
{ class: 'track' },
|
|
{ class: 'minor', subclass: 'pedestrian' },
|
|
{ class: 'minor', subclass: 'steps', ramp: 1 },
|
|
{ class: 'minor', subclass: 'platform' },
|
|
{ class: 'service' },
|
|
{ class: 'service', service: 'parking_aisle' },
|
|
{ class: 'service', service: 'driveway' },
|
|
{ class: 'service', service: 'drive-through' },
|
|
{ class: 'service', service: 'alley' },
|
|
{ class: 'primary', brunnel: 'tunnel' },
|
|
])
|
|
assert.equal(
|
|
openMapRoad(coordinates, properties),
|
|
null,
|
|
JSON.stringify(properties),
|
|
);
|
|
for (const properties of [
|
|
{ class: 'minor' },
|
|
{ class: 'primary', ramp: 1 },
|
|
{ class: 'tertiary', brunnel: 'bridge' },
|
|
{ class: 'minor', brunnel: 'ford' },
|
|
]) {
|
|
const road = openMapRoad(coordinates, properties);
|
|
assert.equal(road.drivable, true);
|
|
assert.deepEqual(road.roadProperties, properties);
|
|
}
|
|
});
|
|
|
|
test('Capitol, greenbelt and commercial property fixtures exclude non-public driving space', () => {
|
|
const records = JSON.parse(fixture('traffic-road-access.json'));
|
|
for (const record of records) {
|
|
const road = openMapRoad(record.coordinates, record.properties);
|
|
assert.equal(
|
|
Boolean(road),
|
|
record.drivable,
|
|
`${record.area}: ${JSON.stringify(record.properties)}`,
|
|
);
|
|
}
|
|
for (const area of ['capitol', 'greenbelt', 'commercial']) {
|
|
assert.ok(records.some((r) => r.area === area && r.drivable));
|
|
assert.ok(records.some((r) => r.area === area && !r.drivable));
|
|
}
|
|
});
|
|
|
|
test('repointing tileJsonUrl at a mirror never falls back to the default tile host', async () => {
|
|
const urls = [];
|
|
const mirror = 'http://127.0.0.1:12348';
|
|
const source = createOpenFreeMapSource({
|
|
fetchImpl: async (url) => {
|
|
urls.push(url);
|
|
return url.endsWith('/planet')
|
|
? Response.json({ tiles: [`${mirror}/planet/v/{z}/{x}/{y}.pbf`] })
|
|
: new Response(fixture('ofm-austin-14-3743-6745.pbf'));
|
|
},
|
|
tileJsonUrl: `${mirror}/planet`,
|
|
});
|
|
await source.fetchBounds(
|
|
{ south: 30.264, north: 30.271, west: -97.75, east: -97.74 },
|
|
{ zoom: 14 },
|
|
);
|
|
assert.ok(urls.length > 1, 'tiles were fetched');
|
|
assert.deepEqual(
|
|
urls.filter((url) => !url.startsWith(mirror)),
|
|
[],
|
|
'a repointed source must not fetch from tiles.openfreemap.org',
|
|
);
|
|
});
|
|
|
|
test('a mirror that serves the default host TileJSON verbatim fails loudly', async () => {
|
|
// A transparent caching proxy returns upstream's TileJSON unchanged, whose
|
|
// tiles[0] is an absolute upstream URL. That must be an error, not 100% of
|
|
// tile traffic quietly leaving the configured mirror.
|
|
const source = createOpenFreeMapSource({
|
|
fetchImpl: async () =>
|
|
Response.json({
|
|
tiles: ['https://tiles.openfreemap.org/planet/v/{z}/{x}/{y}.pbf'],
|
|
}),
|
|
tileJsonUrl: 'http://127.0.0.1:12348/planet',
|
|
});
|
|
await assert.rejects(source.getMetadata(), { retryable: false });
|
|
});
|