How the Annotation Engine Converts Voice to GeoJSON in God's Eye View
The annotation engine converts spoken commands into GeoJSON by resolving voice input into runtime annotation objects, then serializing them through a pure conversion module that maps internal types to GeoJSON geometries.
The bilawalsidhu/gods-eye-view repository implements a sophisticated pipeline that bridges natural language interactions with geospatial data standards. This article examines the exact mechanism by which voice commands traverse through the system and ultimately materialize as standards-compliant GeoJSON FeatureCollections.
The Voice-to-GeoJSON Pipeline
The conversion process operates across three distinct architectural layers, beginning with voice capture and ending with structured geospatial output.
Voice Command Initialization
The voice subsystem initializes the annotation engine through initGevVoiceCommands in src/voice/gevRealtime.js. This function registers the engine with the voice action runner, establishing the connection between speech recognition and annotation creation.
// src/voice/gevRealtime.js
export function initGevVoiceCommands({ viewer, styleManager, dataManager, sceneDirector = null, annotations = null }) {
const runner = createGevActionRunner({ viewer, styleManager, dataManager, sceneDirector, annotations });
// ... runner configuration
}
When the LLM issues an annotate_map action, the system routes the request through src/voice/gevActions.js. The annotateMap function at approximately line 290 forwards the structured voice command to the engine's core processing method.
// src/voice/gevActions.js (≈ line 290)
async function annotateMap(annotations, args = {}) {
// ... argument processing
const result = await annotations.annotate(requests, { ... });
}
Resolution and Runtime Object Creation
Inside src/annotations/annotationEngine.js, the annotate method coordinates the transformation from abstract command to concrete geospatial data. The engine maintains a live annotation store using a JavaScript Map and processes each request through the annotationResolver, which handles geocoding, Overpass OSM queries, and Places API integration.
// src/annotations/annotationEngine.js (≈ line 96-104)
const annotations = new Map(); // live annotation store
// ...
async function annotate(requests, opts = {}) {
// ...
const settled = await Promise.allSettled(
list.map(spec => resolveSpec(spec, controller.signal))
);
}
The resolver returns a normalized anchor (longitude, latitude, height) and an optional deferred footprint outline. The engine then constructs a runtime annotation object via buildAnnotation (lines 88-95), capturing only semantic fields required for rendering: type, label, color, and geometry.
// src/annotations/annotationEngine.js (≈ line 88-95)
function buildAnnotation(spec, resolved, persist) {
const type = normalizeType(spec?.type);
const id = `anno-${++_seq}`;
// ...
return { ...base, anchor, ... }; // e.g., pin, area, route, arrow
}
Serializing Runtime Annotations to GeoJSON
The repository provides a Cesium-agnostic conversion module at src/annotations/annotationGeoJson.js that handles bidirectional translation between runtime objects and GeoJSON standards.
From Annotation to Feature
The annotationToFeature function inspects the annotation's type property and constructs the appropriate GeoJSON geometry. Pins and labels generate Point geometries, routes become LineString objects, and areas with sufficient vertices convert to Polygon features.
// src/annotations/annotationGeoJson.js (≈ line 51-103)
export function annotationToFeature(anno) {
if (!anno || typeof anno !== 'object' || !VALID_TYPES.has(anno.type)) return null;
// ...
if (type === 'route') {
geometry = { type: 'LineString', coordinates: ... }
}
else if (type === 'area' && anno.ring?.length >= 3) {
geometry = { type: 'Polygon', ... }
}
else {
geometry = { type: 'Point', ... }
}
return { type: 'Feature', geometry, properties };
}
Engine-specific metadata persists under the gev: namespace within the GeoJSON properties, ensuring that styling information and annotation semantics survive the serialization process without polluting standard geospatial fields.
From Feature to Annotation
Reverse conversion occurs through featureToAnnotation, which reconstructs runtime objects from GeoJSON Features while intentionally discarding transient render state such as alpha values and animation timers. The function validates the input structure and extracts the gev:type property to determine the appropriate reconstruction logic.
// src/annotations/annotationGeoJson.js (≈ line 112-176)
export function featureToAnnotation(feature) {
if (!feature || feature.type !== 'Feature' ...) return null;
const type = p['gev:type'];
// ...
if (type === 'route') {
return { ..., path, anchor: path[0] }
}
else if (g.type === 'Polygon') { ... }
else { ... } // Point geometry → pin/highlight/label
}
Batch Conversion
For persistence and transmission operations, the module exposes batch helpers that wrap single-item conversions into FeatureCollections:
// src/annotations/annotationGeoJson.js (≈ line 191-202)
export function annotationsToFeatureCollection(annotations) {
const features = (Array.isArray(annotations) ? annotations : [])
.map(annotationToFeature).filter(Boolean);
return { type: 'FeatureCollection', features };
}
export function featureCollectionToAnnotations(collection) {
if (!collection || collection.type !== 'FeatureCollection') return [];
return collection.features.map(featureToAnnotation).filter(Boolean);
}
Practical Implementation Examples
The following patterns demonstrate the complete workflow from voice capture to GeoJSON export and restoration.
Exporting Voice-Captured Annotations
This example captures spoken place references and converts the resulting annotations to a downloadable GeoJSON file:
// 1️⃣ Capture voice-driven annotations
const voiceResult = await annotations.annotate([
{ type: 'pin', target: 'Eiffel Tower, Paris', label: 'Eiffel', color: 'red' },
{ type: 'area', target: 'Central Park, NY', footprint: true }
]);
// 2️⃣ Serialize the live annotations to GeoJSON
import { annotationsToFeatureCollection } from './annotations/annotationGeoJson.js';
const geojson = annotationsToFeatureCollection(annotations.list());
// 3️⃣ Save / transmit the GeoJSON (e.g., download as file)
const blob = new Blob([JSON.stringify(geojson, null, 2)], {type: 'application/json'});
saveAs(blob, 'my-annotations.geojson');
Restoring Annotations from GeoJSON
To load previously saved annotations back into the engine:
import { featureCollectionToAnnotations } from './annotations/annotationGeoJson.js';
// Assume `fileContent` holds the JSON string of a previously exported file
const collection = JSON.parse(fileContent);
const restoredAnnotations = featureCollectionToAnnotations(collection);
// Re-inject them into the live engine (here we simply add them)
await annotations.annotate(restoredAnnotations, { persist: true });
Summary
- Voice integration begins in
src/voice/gevRealtime.js, whereinitGevVoiceCommandsregisters the annotation engine with the voice action runner. - Action routing occurs in
src/voice/gevActions.js, specifically theannotateMapfunction that handlesannotate_mapLLM actions. - Runtime resolution happens in
src/annotations/annotationEngine.js, where the resolver geocodes targets andbuildAnnotationconstructs normalized objects with anchors and geometry. - GeoJSON serialization is handled by
src/annotations/annotationGeoJson.js, which provides type-aware conversion throughannotationToFeatureandfeatureToAnnotation. - Persistence is achieved via
annotationsToFeatureCollectionandfeatureCollectionToAnnotations, enabling full round-trip data integrity.
Frequently Asked Questions
How does the engine handle different annotation types when converting to GeoJSON?
The annotationToFeature function in src/annotations/annotationGeoJson.js uses a type-specific branching strategy. Routes generate LineString geometries, areas become Polygons (when the ring has three or more points), and all other types default to Point geometries. Each type stores its specific metadata under the gev: namespace in the GeoJSON properties.
What geocoding services does the annotation resolver use?
According to the source code analysis, the annotationResolver (located in src/annotations/annotationResolver.js) integrates multiple geospatial services including Overpass OSM for footprint data, Places API for point-of-interest resolution, and standard geocoding endpoints. These services supply the longitude, latitude, and height coordinates that anchor each annotation in 3D space.
Can the GeoJSON output be used outside of God's Eye View?
Yes. The annotationGeoJson.js module produces standards-compliant GeoJSON FeatureCollections that conform to RFC 7946. While the module stores engine-specific metadata under the gev: property namespace, the core geometry types (Point, LineString, Polygon) and coordinate systems remain compatible with standard geospatial tools like QGIS, Mapbox, and PostGIS.
How does the system preserve transient render states during serialization?
The conversion process intentionally strips transient states. According to the implementation in featureToAnnotation, the system discards runtime-specific properties such as alpha transparency values and animation timers during GeoJSON export. Only persistent semantic data—including type, label, color, and geometry—survives the round-trip conversion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →