Skip to main content
Glama
spatial-query.ts5.69 kB
import { TAKTool, ToolContext } from '../registry'; import * as turf from '@turf/turf'; import mgrs from 'mgrs'; import * as h3 from 'h3-js'; export const spatialQueryTool: TAKTool = { name: 'tak_spatial_query', description: 'Query TAK entities within a geographic area using point-radius or polygon', category: 'geospatial', requiresAuth: true, requiresWrite: false, inputSchema: { type: 'object', properties: { center: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'Center point [lat, lon] for radius search' }, radius: { type: 'number', minimum: 0, description: 'Search radius in meters (required if using center point)' }, polygon: { type: 'array', items: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 }, minItems: 3, description: 'Polygon coordinates [[lat, lon], ...] for polygon search' }, types: { type: 'array', items: { type: 'string' }, description: 'Filter by entity types' }, timeWindow: { type: 'object', properties: { start: { type: 'string', format: 'date-time' }, end: { type: 'string', format: 'date-time' } }, description: 'Time window for historical queries' }, includeStale: { type: 'boolean', default: false, description: 'Include stale entities in results' } }, oneOf: [ { required: ['center', 'radius'] }, { required: ['polygon'] } ] }, async handler(context: ToolContext) { const { takClient, params, logger } = context; logger.info('Performing spatial query', { params }); try { // Perform the spatial query const events = await takClient.spatialQuery({ center: params.center, radius: params.radius, polygon: params.polygon, types: params.types, timeWindow: params.timeWindow ? { start: new Date(params.timeWindow.start), end: new Date(params.timeWindow.end) } : undefined }); // Filter out stale events if requested const now = new Date(); const filteredEvents = params.includeStale ? events : events.filter(event => new Date(event.stale) > now); // Calculate additional geospatial properties const enrichedResults = filteredEvents.map(event => { const point = turf.point([event.point.lon, event.point.lat]); let distanceFromCenter = null; let bearing = null; if (params.center && params.radius) { const centerPoint = turf.point([params.center[1], params.center[0]]); distanceFromCenter = turf.distance(centerPoint, point, { units: 'meters' }); bearing = turf.bearing(centerPoint, point); } return { uid: event.uid, type: event.type, callsign: event.detail?.contact?.callsign, location: { lat: event.point.lat, lon: event.point.lon, alt: event.point.hae, mgrs: convertToMGRS(event.point.lat, event.point.lon), // Placeholder h3Index: getH3Index(event.point.lat, event.point.lon, 9) // Placeholder }, time: event.time, stale: event.stale, distanceFromCenter, bearing, team: event.detail?.group?.name, role: event.detail?.group?.role, status: event.detail?.status }; }); // Group results by type const groupedByType = enrichedResults.reduce((acc, event) => { const type = event.type || 'unknown'; if (!acc[type]) acc[type] = []; acc[type].push(event); return acc; }, {} as Record<string, any[]>); return { success: true, totalCount: enrichedResults.length, results: enrichedResults, summary: { byType: Object.entries(groupedByType).map(([type, items]) => ({ type, count: items.length })), bounds: calculateBounds(enrichedResults), timeRange: { earliest: enrichedResults.reduce((min, e) => new Date(e.time) < new Date(min) ? e.time : min, enrichedResults[0]?.time ), latest: enrichedResults.reduce((max, e) => new Date(e.time) > new Date(max) ? e.time : max, enrichedResults[0]?.time ) } } }; } catch (error) { logger.error('Spatial query failed', error); throw error; } } }; // Helper functions with real implementations function convertToMGRS(lat: number, lon: number): string { try { return mgrs.forward([lon, lat]); } catch (error) { // Fallback if MGRS conversion fails return `${Math.abs(lat).toFixed(6)}${lat >= 0 ? 'N' : 'S'} ${Math.abs(lon).toFixed(6)}${lon >= 0 ? 'E' : 'W'}`; } } function getH3Index(lat: number, lon: number, resolution: number): string { try { return h3.latLngToCell(lat, lon, resolution); } catch (error) { // Fallback if H3 conversion fails return `${lat.toFixed(2)}_${lon.toFixed(2)}_r${resolution}`; } } function calculateBounds(events: any[]): [number, number, number, number] | null { if (events.length === 0) return null; const lats = events.map(e => e.location.lat); const lons = events.map(e => e.location.lon); return [ Math.min(...lons), Math.min(...lats), Math.max(...lons), Math.max(...lats) ]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jfuginay/tak-server-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server