Merge branch 'main' into fix/alpha5-norad-id

# Conflicts:
#	src/layers/satellites/ingestion.js
This commit is contained in:
Sameh Khamis committed 2026-10-17 11:36:21 -07:00
commit 6ae854c121
543 files changed
+42557 -4887

No files matched your search

+2
View File
@@ -111,6 +111,8 @@ export const aircraftInArea = defineTool({
(record) =>
areaContains(area, { lat: record.latitude, lon: record.longitude }) &&
!(args.airborne_only && record.onGround) &&
((args.min_altitude_m == null && args.max_altitude_m == null) ||
Number.isFinite(altitude(record))) &&
(args.min_altitude_m == null ||
altitude(record) >= args.min_altitude_m) &&
(args.max_altitude_m == null ||
+59
View File
@@ -388,3 +388,62 @@ test('the real OpenSky source marks an hour-old snapshot stale for searches', as
assert.deepEqual(result.data.stale_feeds, ['civil']);
assert.match(result.summary, /may be stale\.$/);
});
test('altitude bands require a reported altitude from normalized OpenSky records', async () => {
const { normalizeOpenSkyAircraft } =
await import('../../sources/live/aircraft.js');
const state = (id, baro, geometric) =>
normalizeOpenSkyAircraft([
id,
'TEST',
'Test',
1767225600,
1767225600,
0,
0,
baro,
false,
10,
90,
0,
[],
geometric,
]);
const records = [
state('abc001', null, null),
state('abc002', 0, null),
state('abc003', null, 50),
state('abc004', 500, null),
];
const catalog = composeCatalog({
tools: coreTools,
services: {
aircraft: { getSnapshot: async () => ({ records }) },
},
});
const area = { bbox: [-1, -1, 1, 1] };
assert.equal(
(await catalog.call('aircraft_in_area', { area })).data.total,
4,
'unfiltered answers retain unknown altitude',
);
for (const band of [
{ max_altitude_m: 100 },
{ min_altitude_m: -500, max_altitude_m: 100 },
]) {
const result = await catalog.call('aircraft_in_area', { area, ...band });
assert.deepEqual(
result.data.rows.map((row) => row.id),
['abc002', 'abc003'],
);
assert.deepEqual(
result.data.rows.map((row) => row.altitude_m),
[0, 50],
);
}
const minimum = await catalog.call('aircraft_in_area', {
area,
min_altitude_m: -500,
});
assert.equal(minimum.data.total, 3);
});
+9 -4
View File
@@ -288,20 +288,25 @@ export const getFirePerimeters = defineTool({
},
});
/** The average vertex of a perimeter's outer rings, enough to place it in an area. */
/** Average outer-ring vertices, using a circular longitude mean at the dateline. */
function polygonCenter(polygons) {
let lat = 0;
let lon = 0;
let sinLon = 0;
let cosLon = 0;
let count = 0;
const toRad = Math.PI / 180;
for (const polygon of polygons || []) {
for (const [x, y] of polygon?.[0] || []) {
if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
lon += x;
sinLon += Math.sin(x * toRad);
cosLon += Math.cos(x * toRad);
lat += y;
count += 1;
}
}
return count ? { lat: lat / count, lon: lon / count } : null;
return count
? { lat: lat / count, lon: Math.atan2(sinLon, cosLon) / toRad }
: null;
}
export const getTerrainHeight = defineTool({
+91
View File
@@ -227,6 +227,97 @@ test('fire perimeters are placed by their outline and sorted by size', async ()
});
});
test('fire perimeter queries include dateline interiors and exclude the opposite meridian', async () => {
const perimeters = {
getSnapshot: async () => [
{
stableId: 'dateline',
name: 'Dateline Fire',
acres: 100,
polygons: [
[
[
[179, -2],
[-179, -2],
[-179, 2],
[179, 2],
[179, -2],
],
],
],
},
],
};
const catalog = catalogWith({ perimeters });
for (const area of [
{ bbox: [179.4, -0.1, 179.6, 0.1] },
{ bbox: [-179.6, -0.1, -179.4, 0.1] },
{ lat: 0, lon: 180, radius_km: 10 },
]) {
const result = await catalog.call('get_fire_perimeters', { area });
assert.deepEqual(
result.data.rows.map((row) => row.id),
['dateline'],
);
}
const elsewhere = await catalog.call('get_fire_perimeters', {
area: { bbox: [-0.1, -0.1, 0.1, 0.1] },
});
assert.equal(elsewhere.data.total, 0);
});
test('fire perimeter coordinates stay at the dateline for closed and multipart outlines', async () => {
const perimeters = {
getSnapshot: async () => [
{
stableId: 'closed',
acres: 100,
polygons: [
[
[
[179, -2],
[-179, -2],
[-179, 2],
[179, 2],
[179, -2],
],
],
],
},
{
stableId: 'multipart',
acres: 50,
polygons: [
[
[
[179, -1],
[179.5, -1],
[179.5, 1],
[179, 1],
],
],
[
[
[-179.5, -1],
[-179, -1],
[-179, 1],
[-179.5, 1],
],
],
],
},
],
};
const result = await catalogWith({ perimeters }).call('get_fire_perimeters', {
area: { bbox: [178, -3, -178, 3] },
});
assert.equal(result.data.total, 2);
for (const row of result.data.rows) {
assert.ok(Math.abs(row.lon) > 179, `${row.id}: ${row.lon}`);
assert.ok(Math.abs(row.lon) <= 180);
}
});
test('terrain heights are returned in request order', async () => {
const terrain = {
async getHeights(points) {
+24 -6
View File
@@ -118,9 +118,11 @@ export const findCctvCameras = defineTool({
export const getCctvSnapshot = defineTool({
name: 'get_cctv_snapshot',
title: 'Current camera view',
title: 'Camera snapshot',
description:
'The current image from one public camera, by the id from find_cctv_cameras.',
'An image from one public camera, by the id from find_cctv_cameras. ' +
'Street View fallback imagery is historical, not the current camera view; ' +
'unknown frame provenance is explicit.',
inputSchema: {
type: 'object',
properties: { camera_id: { type: 'string', minLength: 1, maxLength: 200 } },
@@ -138,7 +140,7 @@ export const getCctvSnapshot = defineTool({
`No camera has id ${args.camera_id}; use find_cctv_cameras first`,
);
const frame = await services.cctv.getFrame(camera, { signal });
if (!FRAME_TYPES.has(frame.contentType))
if (frame.sourceKind === 'synthetic' || !FRAME_TYPES.has(frame.contentType))
throw new ToolError(
'unavailable',
`Camera ${camera.name || camera.id} has no still image right now`,
@@ -149,8 +151,22 @@ export const getCctvSnapshot = defineTool({
'The camera image is too large to return',
);
const name = text(camera.name) || camera.id;
const fallback = frame.sourceKind === 'streetview';
const current = ['upstream-image', 'hls-pull'].includes(frame.sourceKind);
const credit = fallback ? 'Google Street View' : text(camera.credit);
const label = fallback
? 'Street View fallback for'
: current
? 'Current view from'
: 'Image from';
return {
summary: `Current view from ${name}${camera.city ? ` in ${camera.city}` : ''}${camera.credit ? `, courtesy of ${camera.credit}` : ''}.`,
summary:
`${label} ${name}${camera.city ? ` in ${camera.city}` : ''}${credit ? `, courtesy of ${credit}` : ''}.` +
(fallback
? ' Historical imagery; the current camera frame is unavailable.'
: current
? ''
: ' Frame provenance is unknown.'),
data: {
view: suggestView(services, {
point: { lat: camera.lat, lon: camera.lon },
@@ -160,8 +176,10 @@ export const getCctvSnapshot = defineTool({
id: camera.id,
name,
city: text(camera.city),
provider: text(camera.provider),
credit: text(camera.credit),
provider: fallback ? 'Google Street View' : text(camera.provider),
credit,
source_kind: frame.sourceKind ?? null,
current_view: current ? true : fallback ? false : null,
mime_type: frame.contentType,
bytes: frame.bytes.byteLength,
},
+184 -1
View File
@@ -30,7 +30,13 @@ const cameras = [
{ id: 'no-position', name: 'Unplaced' },
];
const PNG = Uint8Array.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const cctv = (frame = { contentType: 'image/png', bytes: PNG }) => ({
const cctv = (
frame = {
contentType: 'image/png',
bytes: PNG,
sourceKind: 'upstream-image',
},
) => ({
frames: [],
async getCatalog() {
return { sources: cameras };
@@ -104,6 +110,183 @@ test('a snapshot returns the frame as an image with its credit', async () => {
});
});
test('mounted camera fallback provenance survives the source and MCP image result', async (t) => {
const { cctvProxy } = await import('../../../server/providers/cctv.js');
const { createCctvSource } = await import('../../layers/cctv/source.js');
const camera = {
id: 'fixture-camera',
name: 'Fixture crossing',
city: 'Fixture City',
provider: 'Fixture transport agency',
credit: 'Fixture transport agency',
lat: 30.2685,
lon: -97.7425,
headingDeg: 180,
fovDeg: 60,
url: 'https://camera.example/current.png',
feedType: 'image',
};
const environment = {
CCTV_SOURCES_JSON: JSON.stringify([camera]),
CCTV_PREFER_AUSTIN: '0',
CCTV_FORCE_AUSTIN: '0',
CCTV_SOURCES_FILE: 'missing-cctv-provenance-fixture.json',
GOOGLE_MAPS_SERVER_API_KEY: 'fixture-key',
GOOGLE_MAPS_API_KEY: '',
};
for (const [name, value] of Object.entries(environment)) {
const previous = process.env[name];
t.after(() => {
if (previous === undefined) delete process.env[name];
else process.env[name] = previous;
});
process.env[name] = value;
}
t.mock.method(console, 'warn', () => {});
let upstreamAvailable = false;
const calls = [];
t.mock.method(globalThis, 'fetch', async (raw) => {
const url = new URL(raw);
calls.push(url.origin);
if (url.origin === 'https://camera.example')
return upstreamAvailable
? new Response(PNG, { headers: { 'Content-Type': 'image/png' } })
: new Response(null, { status: 503 });
assert.equal(
url.origin,
'https://maps.googleapis.com',
'no actual upstream is contacted',
);
assert.equal(url.searchParams.get('key'), 'fixture-key');
return new Response(PNG, { headers: { 'Content-Type': 'image/png' } });
});
let middleware;
cctvProxy().configureServer({
middlewares: {
use(route, handler) {
assert.equal(route, '/api/cctv');
middleware = handler;
},
},
});
const source = createCctvSource({
fetchImpl: async (path) => {
assert.ok(path.startsWith('/api/cctv/'));
const response = {
writeHead(status, headers) {
this.status = status;
this.headers = headers;
},
end(body) {
this.body = body;
},
};
await middleware(
{ url: path.slice('/api/cctv'.length), method: 'GET', headers: {} },
response,
);
return new Response(response.body, {
status: response.status,
headers: response.headers,
});
},
});
const catalog = composeCatalog({
tools: coreTools,
services: { cctv: source },
});
const fallback = await catalog.call('get_cctv_snapshot', {
camera_id: camera.id,
});
assert.equal(fallback.data.source_kind, 'streetview');
assert.equal(fallback.data.current_view, false);
assert.equal(fallback.data.credit, 'Google Street View');
assert.match(fallback.summary, /Street View fallback/);
assert.match(fallback.summary, /Historical imagery/);
assert.doesNotMatch(
fallback.summary,
/Current view|courtesy of Fixture transport agency/,
);
assert.equal(
fallback.images.length,
1,
'the historical image stays available with its caveat',
);
assert.deepEqual(calls, [
'https://camera.example',
'https://maps.googleapis.com',
]);
upstreamAvailable = true;
const current = await catalog.call('get_cctv_snapshot', {
camera_id: camera.id,
});
assert.equal(current.data.source_kind, 'upstream-image');
assert.equal(current.data.current_view, true);
assert.equal(current.data.credit, camera.credit);
assert.match(current.summary, /^Current view from/);
const server = createMcpServer({ catalog, name: 'test', version: '1' });
upstreamAvailable = false;
const result = await server.handle({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: 'get_cctv_snapshot', arguments: { camera_id: camera.id } },
});
assert.ok(
result.result.content.some(
(entry) => entry.type === 'text' && /Historical imagery/.test(entry.text),
),
);
assert.ok(result.result.content.some((entry) => entry.type === 'image'));
process.env.GOOGLE_MAPS_SERVER_API_KEY = '';
const placeholder = await source.getFrame(camera);
assert.equal(placeholder.sourceKind, 'synthetic');
assert.equal(placeholder.contentType, 'image/svg+xml');
await assert.rejects(
catalog.call('get_cctv_snapshot', { camera_id: camera.id }),
(error) => error.code === 'unavailable',
);
const unavailable = await server.handle({
jsonrpc: '2.0',
id: 2,
method: 'tools/call',
params: { name: 'get_cctv_snapshot', arguments: { camera_id: camera.id } },
});
assert.equal(unavailable.result.isError, true);
assert.ok(
!unavailable.result.content.some((entry) => entry.type === 'image'),
);
});
test('snapshot placeholders are refused and unknown frame provenance is explicit', async () => {
const synthetic = composeCatalog({
tools: coreTools,
services: {
cctv: cctv({
contentType: 'image/png',
bytes: PNG,
sourceKind: 'synthetic',
}),
},
});
await assert.rejects(
synthetic.call('get_cctv_snapshot', { camera_id: 'austin-1' }),
(error) => error.code === 'unavailable',
);
const unknown = await composeCatalog({
tools: coreTools,
services: { cctv: cctv({ contentType: 'image/png', bytes: PNG }) },
}).call('get_cctv_snapshot', { camera_id: 'austin-1' });
assert.equal(unknown.data.current_view, null);
assert.equal(
unknown.data.credit,
cameras[0].credit,
'catalog attribution is preserved',
);
assert.match(unknown.summary, /provenance is unknown/);
assert.doesNotMatch(unknown.summary, /Current view/);
});
test('unknown cameras, video-only feeds and oversized frames are reported', async () => {
const catalog = (frame) =>
composeCatalog({ tools: coreTools, services: { cctv: cctv(frame) } });