Skip to main content
Glama
create-geofence.ts9.5 kB
import { TAKTool, ToolContext } from '../registry'; import * as turf from '@turf/turf'; import { v4 as uuidv4 } from 'uuid'; export const createGeofenceTool: TAKTool = { name: 'tak_create_geofence', description: 'Create and manage geofenced areas with alert triggers', category: 'geospatial', requiresAuth: true, requiresWrite: true, inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name for the geofence' }, shape: { type: 'object', properties: { type: { type: 'string', enum: ['circle', 'polygon', 'rectangle'], description: 'Type of geofence shape' }, center: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'Center point [lat, lon] for circle or rectangle' }, radius: { type: 'number', minimum: 0, description: 'Radius in meters (for circle)' }, vertices: { type: 'array', items: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 }, minItems: 3, description: 'Polygon vertices [[lat, lon], ...] (for polygon)' }, width: { type: 'number', minimum: 0, description: 'Width in meters (for rectangle)' }, height: { type: 'number', minimum: 0, description: 'Height in meters (for rectangle)' } }, required: ['type'] }, triggers: { type: 'object', properties: { onEntry: { type: 'boolean', default: true, description: 'Trigger alerts on entry' }, onExit: { type: 'boolean', default: true, description: 'Trigger alerts on exit' }, onDwell: { type: 'object', properties: { enabled: { type: 'boolean', default: false }, duration: { type: 'number', minimum: 1, description: 'Dwell time in seconds before alert' } }, description: 'Trigger alerts on dwelling inside' } } }, monitorTypes: { type: 'array', items: { type: 'string' }, description: 'Entity types to monitor (e.g., ["a-h-*", "a-u-*"])' }, alertLevel: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], default: 'medium', description: 'Alert severity level' }, active: { type: 'boolean', default: true, description: 'Whether the geofence is active' } }, required: ['name', 'shape'] }, handler: async (context: ToolContext) => { const { takClient, params, logger } = context; try { // Generate unique ID for the geofence const geofenceId = `geofence-${uuidv4()}`; // Create the geofence geometry let geometry: any; let area: number = 0; let perimeter: number = 0; switch (params.shape.type) { case 'circle': if (!params.shape.center || !params.shape.radius) { throw new Error('Circle geofence requires center and radius'); } const center = turf.point([params.shape.center[1], params.shape.center[0]]); geometry = turf.circle(center, params.shape.radius / 1000, { units: 'kilometers' }); area = Math.PI * Math.pow(params.shape.radius, 2); perimeter = 2 * Math.PI * params.shape.radius; break; case 'polygon': if (!params.shape.vertices || params.shape.vertices.length < 3) { throw new Error('Polygon geofence requires at least 3 vertices'); } const vertices = params.shape.vertices.map((v: number[]) => [v[1], v[0]]); vertices.push(vertices[0]); // Close the polygon geometry = turf.polygon([vertices]); area = turf.area(geometry); perimeter = turf.length(turf.polygonToLine(geometry), { units: 'meters' }) * 1000; break; case 'rectangle': if (!params.shape.center || !params.shape.width || !params.shape.height) { throw new Error('Rectangle geofence requires center, width, and height'); } const rectCenter = turf.point([params.shape.center[1], params.shape.center[0]]); const bearing = 0; // North-aligned rectangle // Calculate rectangle corners const halfWidth = params.shape.width / 2; const halfHeight = params.shape.height / 2; const topLeft = turf.destination( turf.destination(rectCenter, halfHeight / 1000, bearing, { units: 'kilometers' }), halfWidth / 1000, bearing - 90, { units: 'kilometers' } ); const topRight = turf.destination( turf.destination(rectCenter, halfHeight / 1000, bearing, { units: 'kilometers' }), halfWidth / 1000, bearing + 90, { units: 'kilometers' } ); const bottomRight = turf.destination( turf.destination(rectCenter, halfHeight / 1000, bearing + 180, { units: 'kilometers' }), halfWidth / 1000, bearing + 90, { units: 'kilometers' } ); const bottomLeft = turf.destination( turf.destination(rectCenter, halfHeight / 1000, bearing + 180, { units: 'kilometers' }), halfWidth / 1000, bearing - 90, { units: 'kilometers' } ); geometry = turf.polygon([[ topLeft.geometry.coordinates, topRight.geometry.coordinates, bottomRight.geometry.coordinates, bottomLeft.geometry.coordinates, topLeft.geometry.coordinates ]]); area = params.shape.width * params.shape.height; perimeter = 2 * (params.shape.width + params.shape.height); break; default: throw new Error(`Unknown geofence shape: ${params.shape.type}`); } // Create geofence object const geofence = { id: geofenceId, name: params.name, type: params.shape.type, geometry: geometry, triggers: params.triggers || { onEntry: true, onExit: true }, monitorTypes: params.monitorTypes || ['a-*'], // Monitor all by default alertLevel: params.alertLevel || 'medium', active: params.active !== false, created: new Date().toISOString(), creator: 'tak-server-mcp', stats: { area: Math.round(area), perimeter: Math.round(perimeter), areaUnits: 'square meters', perimeterUnits: 'meters' } }; // Send geofence as a CoT event const cotEvent = { type: 'u-d-f', // Drawing feature uid: geofenceId, time: new Date().toISOString(), start: new Date().toISOString(), stale: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours how: 'm-g', // Machine generated point: params.shape.center || turf.centroid(geometry).geometry.coordinates.reverse(), detail: { shape: geometry, fillColor: params.alertLevel === 'critical' ? 'FF0000' : params.alertLevel === 'high' ? 'FFA500' : params.alertLevel === 'medium' ? 'FFFF00' : '00FF00', strokeColor: '000000', strokeWidth: 2, labels_on: params.name, remarks: `Geofence: ${params.name}`, contact: { callsign: params.name }, usericon: { iconsetpath: '34ae1613-9645-4222-a9d2-e5f243dea2865/Military/Boundary.png' } } }; await takClient.sendCotEvent({ event: { _attributes: { version: '2.0', uid: geofenceId, type: cotEvent.type, time: cotEvent.time, start: cotEvent.start, stale: cotEvent.stale, how: cotEvent.how }, point: { _attributes: { lat: cotEvent.point[0].toString(), lon: cotEvent.point[1].toString(), hae: '0', ce: '10', le: '10' } }, detail: cotEvent.detail } }); logger.info(`Created geofence: ${geofenceId}`); return { success: true, data: { geofence: geofence, message: `Geofence '${params.name}' created successfully`, cotUid: geofenceId }, metadata: { timestamp: new Date().toISOString(), source: 'tak-server' } }; } catch (error) { logger.error('Failed to create geofence:', error); return { success: false, error: { code: 'TAK_GEOFENCE_ERROR', message: 'Failed to create geofence', details: error instanceof Error ? error.message : 'Unknown error' } }; } } };

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