feat: add a tool that links to an area in the app

Add open_in_gods_eye_view. It builds a version 2 share link looking
straight down on an area, with altitude chosen from the area's size, on
the app address the application supplies.
This commit is contained in:
Sameh Khamis committed 2026-10-01 12:42:08 -07:00
1 parent 9d12da0a79
commit b6af8b34e0
8 files changed
+120 -3

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
/** A link that opens God's Eye View on an area. */
import { defineTool, ToolError } from '../catalog.js';
import { AREA_SCHEMA, areaCenter, areaRadiusKm, resolveArea } from '../area.js';
const MIN_ALTITUDE_M = 500;
const MAX_ALTITUDE_M = 15_000_000;
// A top-down view sees roughly this many meters of ground per meter of altitude.
const GROUND_PER_ALTITUDE = 0.55;
export const openInGodsEyeView = defineTool({
name: 'open_in_gods_eye_view',
title: "Open in God's Eye View",
description:
"A link that opens God's Eye View looking straight down on an area.",
inputSchema: {
type: 'object',
properties: { area: AREA_SCHEMA },
required: ['area'],
additionalProperties: false,
},
requires: ['app'],
async run(args, { services, signal }) {
let base;
try {
base = new URL(services.app.baseUrl);
} catch {
throw new ToolError('unavailable', "The app's address is not configured");
}
const area = await resolveArea(args.area, { services, signal });
const center = areaCenter(area);
const altitude = Math.round(
Math.min(
MAX_ALTITUDE_M,
Math.max(
MIN_ALTITUDE_M,
(areaRadiusKm(area) * 1000) / GROUND_PER_ALTITUDE,
),
),
);
// The share-link camera fields; the app restores its defaults for the rest.
base.hash = new URLSearchParams({
v: '2',
lat: center.lat.toFixed(4),
lon: center.lon.toFixed(4),
alt: String(altitude),
heading: '0',
pitch: '-90',
}).toString();
return {
summary: `Open ${area.label} in God's Eye View: ${base.href}`,
data: { url: base.href, center, altitude_m: altitude },
};
},
});
+55
View File
@@ -0,0 +1,55 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { composeCatalog, coreTools } from '../index.js';
test('links open the app looking straight down on the area', async () => {
const catalog = composeCatalog({
tools: coreTools,
services: { app: { baseUrl: 'https://maps.example/view?x=1' } },
});
const result = await catalog.call('open_in_gods_eye_view', {
area: { lat: 30.2672, lon: -97.7431, radius_km: 5 },
});
const url = new URL(result.data.url);
assert.equal(
url.origin + url.pathname + url.search,
'https://maps.example/view?x=1',
);
assert.deepEqual(Object.fromEntries(new URLSearchParams(url.hash.slice(1))), {
v: '2',
lat: '30.2672',
lon: '-97.7431',
alt: '9091',
heading: '0',
pitch: '-90',
});
assert.equal(
result.summary,
`Open 5 km around 30.267, -97.743 in God's Eye View: ${url.href}`,
);
});
test('altitude is bounded for tiny and planet-sized areas', async () => {
const catalog = composeCatalog({
tools: coreTools,
services: { app: { baseUrl: 'http://localhost:4173/' } },
});
const tiny = await catalog.call('open_in_gods_eye_view', {
area: { lat: 0, lon: 0, radius_km: 0.1 },
});
assert.equal(tiny.data.altitude_m, 500);
const world = await catalog.call('open_in_gods_eye_view', {
area: { bbox: [-180, -85, 180, 85] },
});
assert.equal(world.data.altitude_m, 15000000);
const broken = composeCatalog({
tools: coreTools,
services: { app: { baseUrl: 'not a url' } },
});
await assert.rejects(
broken.call('open_in_gods_eye_view', {
area: { lat: 0, lon: 0, radius_km: 1 },
}),
(error) => error.code === 'unavailable',
);
});