> This is the Markdown version of the page. For the complete Lattice Developers documentation index, fetch https://developer.anduril.com/llms.txt. Append .md to any page URL for its clean Markdown. # Publish entities > Common patterns for representing entities in Lattice using the Lattice SDK This page explains how to create and update assets, tracks, and geo-entities - the most common `Entity` templates in Lattice. In the following steps, you publish various entities and learn how the entity model lets you represent a variety of real-world objects and geographic areas. ## Before you begin * To publish entities, [set up the Lattice SDK](/guides/getting-started/set-up) and set up a Lattice environment. * The following examples use `uuid` to generate unique entity IDs. To use the TypeScript examples, install [`@types/uuid`](https://www.npmjs.com/package/@types/uuid): **`TypeScript`** ```bash title="TypeScript" npm install @types/uuid ``` * Learn about required components and various [entity shapes](/guides/entities/overview) in Lattice. #### gRPC authentication These gRPC examples authenticate with a static environment token attached as request metadata. If you are using OAuth 2.0 client credentials instead, [set up the token refresh module](/guides/getting-started/authenticate#client-credentials) before running the examples on this page. ## Publish an asset An *asset* is an entity under your control, or under the control of another operator or system. Assets may accept tasks such as search or tracking. To publish an asset, do the following: #### Drone Define the entity model's required components. Together with the asset-specific fields, you get the following entity object: **`entity.json`** ```json maxLines=15 title="entity.json" { "entityId": "$ENTITY_ID", "description": "Friendly drone asset", "isLive": true, "createdTime": "2025-01-01T00:00:00.000Z", "expiryTime": "2025-01-01T01:00:00.000Z", "location": { "position": { "latitudeDegrees": 51.013685596367665, "longitudeDegrees": 0.8002299716355741, "altitudeAsfMeters": 1000 }, "velocityEnu": { "e": 50, "n": 50, "u": 0 }, "attitudeEnu": { "x": 0, "y": 0, "z": 0.3827, "w": 0.9239 } }, "aliases": { "name": "Drone 1" }, "milView": { "disposition": "DISPOSITION_FRIENDLY", "environment": "ENVIRONMENT_AIR" }, "ontology": { "platformType": "UAV", "template": "TEMPLATE_ASSET" }, "provenance": { "integrationName": "your_integration_name", "dataType": "your_data_type", "sourceUpdateTime": "2025-05-19T20:02:16.753Z" }, "taskCatalog": { "taskDefinitions": [ { "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.VisualId", }, { "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.Investigate" } ] }, "health": { "connectionStatus": "CONNECTION_STATUS_ONLINE", "healthStatus": "HEALTH_STATUS_HEALTHY", "components": [ { "id": "battery-0", "name": "Battery", "health": "HEALTH_STATUS_HEALTHY", "messages": [ { "status": "HEALTH_STATUS_HEALTHY", "message": "Battery at 87% charge." } ], "updateTime": "2025-01-01T00:00:00.000Z" } ], "updateTime": "2025-01-01T00:00:00.000Z" }, "sensors": { "sensors": [ { "sensorId": "camera-1", "sensorType": "SENSOR_TYPE_CAMERA", "operationalState": "OPERATIONAL_STATE_OPERATIONAL", "fieldsOfView": [ { "centerRayPose": { "orientation": { "x": 0, "y": 0, "z": 0, "w": 1 } }, "horizontalFov": 1.047, "verticalFov": 0.785, "range": 2000, "mode": "SENSOR_MODE_SEARCH" } ] } ] } } ``` Take a closer look at the following components in `entity.json` and familiarize yourself with common patterns used to model an asset in Lattice. In this example: * Set [`template`](/reference/rest/entities/publish-entity#request.body.ontology.template) to `TEMPLATE_ASSET`. * Set [`platform_type`](/reference/rest/entities/publish-entity#request.body.ontology.platformType) to `UAV`rendering a drone icon in the Lattice UI. * Use [`taskCatalog`](/reference/rest/entities/publish-entity#request.body.taskCatalog) to publicize the assets tasks. The asset can listen for, and act upon, any task assigned to it, matching its specified [`taskDefinition`](/reference/rest/entities/publish-entity#request.body.taskCatalog.taskDefinitions). * Use [`health`](/reference/rest/entities/publish-entity#request.body.health) to define the health status of the asset. Health reporting is exclusive to assets. Tracks and geo-entities never report health. When an asset sends updates to Lattice, Lattice automatically sets `health.connection_status` to `CONNECTION_STATUS_ONLINE`. If there are no updates after one minute, the field value changes to `CONNECTION_STATUS_OFFLINE`. Alongside the top-level `health_status`, report at least one entry in `components` to describe the health of an individual subsystem, such as the drone's battery. **`ontology`** ```json title="ontology" "ontology": { // Set the required template. "template": "TEMPLATE_ASSET", // Optionally, set platform_type to UAV. "platform_type": "UAV" } ``` **`milView`** ```json title="milView" "milView": { // Set the disposition to friendly. // Assets are generally under the control of friendly forces. "disposition": "DISPOSITION_FRIENDLY", // Set the environment field to specify the terrain in which // your asset is operating "environment": "ENVIRONMENT_AIR" } ``` **`location`** ```json title="location" "location": { // Set the position of the asset. "position": { // Set the asset's latitude, expressed in degrees. "latitudeDegrees": 50.91402185768586, // Set the asset's longitude, expressed in degrees. "longitudeDegrees": 0.79203612077257, // Set the asset's Above Ground Level (AGL) altitude, expressed in meters. "altitudeAglMeters": 1000 }, // Set the asset's velocity in the ENU frame, in meters per second. "velocityEnu": { "e": 50, "n": 50, "u": 0 }, // Set the asset's heading as a quaternion in the ENU frame. "attitudeEnu": { "x": 0, "y": 0, "z": 0.3827, "w": 0.9239 } } ``` **`taskCatalog`** ```json title="taskCatalog" "taskCatalog": { // Define the tasks the asset can perform. "taskDefinitions": [ { // Set the task specification URL. "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.VisualId", }, { // Set the task specification URL. "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.Investigate", } ], } ``` **`health`** ```json title="health" "health": { // Set the connection status to `CONNECTION_STATUS_ONLINE`. "connectionStatus": "CONNECTION_STATUS_ONLINE", // Set the top-level health status to `HEALTH_STATUS_HEALTHY`. "healthStatus": "HEALTH_STATUS_HEALTHY", // Report the health of individual subsystems in `components`. // Each component needs a stable id, a display name, and a health // status. Here, the drone reports its battery as healthy. "components": [ { "id": "battery-0", "name": "Battery", "health": "HEALTH_STATUS_HEALTHY", "messages": [ { "status": "HEALTH_STATUS_HEALTHY", "message": "Battery at 87% charge." } ] } ] } ``` **`sensors`** ```json title="sensors" "sensors": { // Declare the sensors carried by the asset. "sensors": [ { // Set a stable sensor_id and the sensor_type. "sensorId": "camera-1", "sensorType": "SENSOR_TYPE_CAMERA", "operationalState": "OPERATIONAL_STATE_OPERATIONAL", // Provide at least one field of view so Lattice can // render the sensor cone. "fieldsOfView": [ { "centerRayPose": { "orientation": { "x": 0, "y": 0, "z": 0, "w": 1 } }, "horizontalFov": 1.047, "verticalFov": 0.785, "range": 2000, "mode": "SENSOR_MODE_SEARCH" } ] } ] } ``` The `position` component supports four altitude references, specified in meters. Populate the references available to your integration: **`altitude_hae_meters`** `DoubleValue` The entity's height above the World Geodetic System 1984 (WGS84) ellipsoid. --- **`altitude_agl_meters`** `DoubleValue` The entity's height above the terrain. This is typically measured with a radar altimeter or by using a terrain tile set lookup. --- **`altitude_asf_meters`** `DoubleValue` The entity's height above the sea floor. --- **`pressure_depth_meters`** `DoubleValue` The depth of the entity from the surface of the water. --- Lattice doesn't support Mean Sea Level (MSL) references, such as EGM-96 and EGM-08. If the only altitude reference available to your integration is MSL, convert it to Height Above Ellipsoid (HAE) and populate the `altitude_hae_meters` field. To apply this conversion, use an open-source library, such as [EGM96 for Go](https://pkg.go.dev/github.com/westphae/geomag/pkg/egm96). Use the [`PublishEntity`](/reference/rest/entities/publish-entity) API method to publish the entity: ```go title={"Go (REST)"} startLine={80} maxLines={30} package main import ( "context" "fmt" "math" "net/http" "os" "time" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" "github.com/google/uuid" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion { return &Lattice.Quaternion{ X: Lattice.Float64(0.0), Y: Lattice.Float64(0.0), Z: Lattice.Float64(math.Sin(yawRad / 2.0)), W: Lattice.Float64(math.Cos(yawRad / 2.0)), } } func main() { // Get environment variables latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Initialize headers for sandbox authorization headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) // Create the client LatticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() // Continuously publish the entity for { latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := Lattice.Entity{ EntityID: &entityId, Description: Lattice.String("Friendly drone asset"), Aliases: &Lattice.Aliases{ Name: Lattice.String("Drone 1"), }, IsLive: Lattice.Bool(true), CreatedTime: Lattice.Time(creationTime), ExpiryTime: Lattice.Time(latestTimestamp.Add(1 * time.Hour)), Ontology: &Lattice.Ontology{ Template: Lattice.OntologyTemplateTemplateAsset.Ptr(), PlatformType: Lattice.String("UAV"), }, MilView: &Lattice.MilView{ Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(), Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(), }, Location: &Lattice.Location{ Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))), LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))), AltitudeAsfMeters: Lattice.Float64(1000), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &Lattice.Enu{ E: Lattice.Float64(velocityEast), N: Lattice.Float64(velocityNorth), U: Lattice.Float64(0.0), }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &Lattice.Provenance{ IntegrationName: Lattice.String("your_integration_name"), DataType: Lattice.String("your_data_type"), SourceUpdateTime: Lattice.Time(latestTimestamp), }, Health: &Lattice.Health{ ConnectionStatus: Lattice.HealthConnectionStatusConnectionStatusOnline.Ptr(), HealthStatus: Lattice.HealthHealthStatusHealthStatusHealthy.Ptr(), // Report the health of individual subsystems in components. // Each component needs a stable ID, a display name, and a // health status. Here, the drone reports its battery as healthy. Components: []*Lattice.ComponentHealth{ { ID: Lattice.String("battery-0"), Name: Lattice.String("Battery"), Health: Lattice.ComponentHealthHealthHealthStatusHealthy.Ptr(), Messages: []*Lattice.ComponentMessage{ { Status: Lattice.ComponentMessageStatusHealthStatusHealthy.Ptr(), Message: Lattice.String("Battery at 87% charge."), }, }, UpdateTime: Lattice.Time(latestTimestamp), }, }, // Live list of active alerts for the asset. Remove entries // when the underlying condition clears; Lattice does not // filter stale entries automatically. ActiveAlerts: []*Lattice.Alert{}, UpdateTime: Lattice.Time(latestTimestamp), }, // Declare the sensors carried by the asset. Each entry requires a // stable sensor_id, a sensor_type, and at least one field of view // so Lattice can render the sensor cone. Sensors: &Lattice.Sensors{ Sensors: []*Lattice.Sensor{ { SensorID: Lattice.String("camera-1"), SensorType: Lattice.SensorSensorTypeSensorTypeCamera.Ptr(), OperationalState: Lattice.SensorOperationalStateOperationalStateOperational.Ptr(), FieldsOfView: []*Lattice.FieldOfView{ { CenterRayPose: &Lattice.EntityManagerPose{ Orientation: &Lattice.Quaternion{ X: Lattice.Float64(0.0), Y: Lattice.Float64(0.0), Z: Lattice.Float64(0.0), W: Lattice.Float64(1.0), }, }, HorizontalFov: Lattice.Float64(1.047), VerticalFov: Lattice.Float64(0.785), Range: Lattice.Float64(2000.0), Mode: Lattice.FieldOfViewModeSensorModeSearch.Ptr(), }, }, }, }, }, TaskCatalog: &Lattice.TaskCatalog{ TaskDefinitions: []*Lattice.TaskDefinition{ {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.VisualId")}, {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.Investigate")}, }, }, } // Publish the entity _, err := LatticeClient.Entities.PublishEntity(ctx, &entity) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Println("Publishing asset") } // Wait before next request time.Sleep(1 * time.Second) } } ``` ```java title={"Java (REST)"} startLine={101} maxLines={35} package org.example; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import com.anduril.Lattice; import com.anduril.types.Alert; import com.anduril.types.Aliases; import com.anduril.types.ComponentHealth; import com.anduril.types.ComponentHealthHealth; import com.anduril.types.ComponentMessage; import com.anduril.types.ComponentMessageStatus; import com.anduril.types.Enu; import com.anduril.types.Entity; import com.anduril.types.EntityManagerPose; import com.anduril.types.FieldOfView; import com.anduril.types.FieldOfViewMode; import com.anduril.types.Health; import com.anduril.types.HealthConnectionStatus; import com.anduril.types.HealthHealthStatus; import com.anduril.types.Location; import com.anduril.types.MilView; import com.anduril.types.MilViewDisposition; import com.anduril.types.MilViewEnvironment; import com.anduril.types.Ontology; import com.anduril.types.OntologyTemplate; import com.anduril.types.Position; import com.anduril.types.Provenance; import com.anduril.types.Quaternion; import com.anduril.types.Sensor; import com.anduril.types.SensorOperationalState; import com.anduril.types.SensorSensorType; import com.anduril.types.Sensors; import com.anduril.types.TaskCatalog; import com.anduril.types.TaskDefinition; public class PublishAssetDrone { // Convert an ENU velocity vector into a yaw angle, in radians. Uses the // ENU mathematical convention where a yaw of 0 points East and a yaw of // pi/2 points North. private static double velocityToYawEnu(double velocityEast, double velocityNorth) { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has Z and W // components. The result is already unit-normalized. private static Quaternion yawToQuaternionEnu(double yawRad) { return Quaternion.builder() .x(0.0) .y(0.0) .z(Math.sin(yawRad / 2.0)) .w(Math.cos(yawRad / 2.0)) .build(); } public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxesToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables and ensure URL has https:// if (endpoint == null || environmentToken == null || sandboxesToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { // Create Lattice client with Sandbox authentication Lattice client = Lattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxesToken) .build(); // Generate a unique ID for the entity String entityId = UUID.randomUUID().toString(); // Set a radius, in degrees, to simulate the entity moving in a circle double radiusDegrees = 0.1; double count = 0.0; double centerLat = 50.91402185768586; double centerLon = 0.79203612077257; Instant creationTime = Instant.now(); // Continuously publish the entity while (true) { try { // Get current timestamp Instant latestTimestamp = Instant.now(); // Update position count += 0.1; double t = Math.toRadians(count); // Derive the ENU velocity from the circular motion, then // convert it into a yaw angle and an attitude_enu quaternion. double lat = centerLat + (radiusDegrees * Math.cos(t)); double metersPerDegreeLat = 111320.0; double metersPerDegreeLon = 111320.0 * Math.cos(Math.toRadians(lat)); double velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon; double velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat; double yawRad = velocityToYawEnu(velocityEast, velocityNorth); // Create the entity TaskDefinition visualId = TaskDefinition.builder() .taskSpecificationUrl("type.googleapis.com/anduril.tasks.v2.VisualId") .build(); TaskDefinition investigate = TaskDefinition.builder() .taskSpecificationUrl("type.googleapis.com/anduril.tasks.v2.Investigate") .build(); Entity entity = Entity.builder() .entityId(entityId) .description("Friendly drone asset") .aliases(Aliases.builder() .name("Drone 1") .build()) .isLive(true) .createdTime(OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) .expiryTime(OffsetDateTime.ofInstant(latestTimestamp.plus(1, ChronoUnit.HOURS), ZoneOffset.UTC)) .ontology(Ontology.builder() .template(OntologyTemplate.TEMPLATE_ASSET) .platformType("UAV") .build()) .milView(MilView.builder() .disposition(MilViewDisposition.DISPOSITION_FRIENDLY) .environment(MilViewEnvironment.ENVIRONMENT_AIR) .build()) .location(Location.builder() .position(Position.builder() .latitudeDegrees(centerLat + (radiusDegrees * Math.cos(t))) .longitudeDegrees(centerLon + (radiusDegrees * Math.sin(t))) .altitudeAglMeters(1000.0) .build()) // Report the velocity in the ENU frame, in meters // per second. Vertical velocity is 0 at constant // altitude. .velocityEnu(Enu.builder() .e(velocityEast) .n(velocityNorth) .u(0.0) .build()) // Report the heading as a quaternion derived from // velocity. .attitudeEnu(yawToQuaternionEnu(yawRad)) .build()) .provenance(Provenance.builder() .integrationName("your_integration_name") .dataType("your_data_type") .sourceUpdateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build()) .health(Health.builder() .connectionStatus(HealthConnectionStatus.CONNECTION_STATUS_ONLINE) .healthStatus(HealthHealthStatus.HEALTH_STATUS_HEALTHY) // Report the health of individual subsystems in // components. Each component needs a stable id, a // display name, and a health status. Here, the // drone reports its battery as healthy. .components(List.of( ComponentHealth.builder() .id("battery-0") .name("Battery") .health(ComponentHealthHealth.HEALTH_STATUS_HEALTHY) .messages(List.of( ComponentMessage.builder() .status(ComponentMessageStatus.HEALTH_STATUS_HEALTHY) .message("Battery at 87% charge.") .build() )) .updateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build() )) // Live list of active alerts for the asset. Remove // entries when the underlying condition clears; // Lattice does not filter stale entries // automatically. .activeAlerts(Collections.emptyList()) .updateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build()) // Declare the sensors carried by the asset. Each // entry requires a stable sensor_id, a sensor_type, // and at least one field of view so Lattice can // render the sensor cone. .sensors(Sensors.builder() .sensors(List.of( Sensor.builder() .sensorId("camera-1") .sensorType(SensorSensorType.SENSOR_TYPE_CAMERA) .operationalState(SensorOperationalState.OPERATIONAL_STATE_OPERATIONAL) .fieldsOfView(List.of( FieldOfView.builder() .centerRayPose(EntityManagerPose.builder() .orientation(Quaternion.builder() .x(0.0) .y(0.0) .z(0.0) .w(1.0) .build()) .build()) .horizontalFov(1.047f) .verticalFov(0.785f) .range(2000.0f) .mode(FieldOfViewMode.SENSOR_MODE_SEARCH) .build() )) .build() )) .build()) .taskCatalog(TaskCatalog.builder() .taskDefinitions(Arrays.asList(visualId, investigate)) .build()) .build(); // Publish the entity client.entities().publishEntity(entity); System.out.println("Publishing asset."); Thread.sleep(1000); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); Thread.sleep(10000); } } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={63} maxLines={30} from anduril import Lattice, Aliases, MilView, Location, Position,\ Ontology, Provenance, Health, ComponentHealth, ComponentMessage,\ TaskCatalog, TaskDefinition,\ Sensors, Sensor, FieldOfView, EntityManagerPose, Quaternion, Enu from datetime import datetime, timezone, timedelta import asyncio import math import os import sys from uuid import uuid4 lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables.") sys.exit(1) client = Lattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) id = str(uuid4()) # Base position the drone circles around. center_lat = 50.91402185768586 center_lon = 0.79203612077257 def velocity_to_yaw_enu(velocity_east, velocity_north): """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad): """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0) ) async def app(): try: count = 0 radius_degrees = .1 creation_time = datetime.now(timezone.utc) while(True): latest_timestamp = datetime.now(timezone.utc) count += .1 t = math.radians(count) # Derive the ENU velocity from the circular motion, then convert # it into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) client.entities.publish_entity( entity_id= id, description="Friendly drone asset", aliases=Aliases( name="Drone 1" ), is_live=True, created_time=creation_time, expiry_time=latest_timestamp + timedelta(hours=1), ontology=Ontology( template="TEMPLATE_ASSET", platform_type="UAV" ), mil_view=MilView( disposition="DISPOSITION_FRIENDLY", environment="ENVIRONMENT_AIR" ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_asf_meters=1000 ), # Report the velocity in the ENU frame, in meters per # second. Vertical velocity is 0 at constant altitude. velocity_enu=Enu( e=velocity_east, n=velocity_north, u=0.0 ), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad) ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_timestamp ), health=Health( connection_status="CONNECTION_STATUS_ONLINE", health_status="HEALTH_STATUS_HEALTHY", # Report the health of individual subsystems in # components. Each component needs a stable id, a display # name, and a health status. Here, the drone reports its # battery as healthy. components=[ ComponentHealth( id="battery-0", name="Battery", health="HEALTH_STATUS_HEALTHY", messages=[ ComponentMessage( status="HEALTH_STATUS_HEALTHY", message="Battery at 87% charge.", ), ], update_time=latest_timestamp, ), ], # Live list of active alerts for the asset. Remove # entries when the underlying condition clears; Lattice # does not filter stale entries automatically. active_alerts=[], update_time=latest_timestamp ), # Declare the sensors carried by the asset. Each entry # requires a stable sensor_id, a sensor_type, and at least # one field of view so Lattice can render the sensor cone. sensors=Sensors( sensors=[ Sensor( sensor_id="camera-1", sensor_type="SENSOR_TYPE_CAMERA", operational_state="OPERATIONAL_STATE_OPERATIONAL", fields_of_view=[ FieldOfView( center_ray_pose=EntityManagerPose( orientation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0), ), horizontal_fov=1.047, vertical_fov=0.785, range=2000.0, mode="SENSOR_MODE_SEARCH", ), ], ), ], ), task_catalog=TaskCatalog( task_definitions=[ TaskDefinition( task_specification_url="type.googleapis.com/anduril.tasks.v2.VisualId" ), TaskDefinition( task_specification_url="type.googleapis.com/anduril.tasks.v2.Investigate" ) ] ) ) await asyncio.sleep(1) except asyncio.CancelledError: print(">>> Exiting...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={60} maxLines={30} import { LatticeClient } from "@anduril-industries/lattice-sdk"; import { v4 as uuidv4 } from 'uuid'; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following if you are not developing on Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); const entityId = uuidv4() const expiryOffsetHours = 1; // Set a radius, in degrees, to simulate the entity moving in a circle. const radiusDegrees = .1 const centerLat = 50.91402185768586 const centerLon = 0.79203612077257 let count = 0 function toRadians(degrees: number): number { return degrees * (Math.PI / 180); } // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. function velocityToYawEnu(velocityEast: number, velocityNorth: number): number { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. function yawToQuaternionEnu(yawRad: number): { x: number; y: number; z: number; w: number } { return { x: 0.0, y: 0.0, z: Math.sin(yawRad / 2.0), w: Math.cos(yawRad / 2.0), }; } async function App() { try { const latestTimestamp = new Date(); count += .1 const t = toRadians(count) // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. const lat = centerLat + (radiusDegrees * Math.cos(t)) const metersPerDegreeLat = 111320.0 const metersPerDegreeLon = 111320.0 * Math.cos(toRadians(lat)) const velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon const velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat const yawRad = velocityToYawEnu(velocityEast, velocityNorth) await client.entities.publishEntity( { entityId: entityId, description: "Friendly drone asset", aliases: { name: "Drone 1" }, isLive: true, createdTime: latestTimestamp.toISOString(), expiryTime: new Date(latestTimestamp.getTime() + expiryOffsetHours * 3600000).toISOString(), ontology: { template: "TEMPLATE_ASSET", platformType: "UAV" }, milView: { disposition: "DISPOSITION_FRIENDLY", environment: "ENVIRONMENT_AIR", }, location: { position: { latitudeDegrees: centerLat + (radiusDegrees * Math.cos(t)), longitudeDegrees: centerLon + (radiusDegrees * Math.sin(t)), altitudeAsfMeters: 1000, }, // Report the velocity in the ENU frame, in meters per // second. Vertical velocity is 0 at constant altitude. velocityEnu: { e: velocityEast, n: velocityNorth, u: 0.0 }, // Report the heading as a quaternion derived from velocity. attitudeEnu: yawToQuaternionEnu(yawRad) }, provenance: { integrationName: "your_integration_name", dataType: "your_data_type", sourceUpdateTime: latestTimestamp.toISOString() }, health: { connectionStatus: "CONNECTION_STATUS_ONLINE", healthStatus: "HEALTH_STATUS_HEALTHY", // Report the health of individual subsystems in // components. Each component needs a stable id, a display // name, and a health status. Here, the drone reports its // battery as healthy. components: [ { id: "battery-0", name: "Battery", health: "HEALTH_STATUS_HEALTHY", messages: [ { status: "HEALTH_STATUS_HEALTHY", message: "Battery at 87% charge." } ], updateTime: latestTimestamp.toISOString() } ], // Live list of active alerts for the asset. Remove // entries when the underlying condition clears; Lattice // does not filter stale entries automatically. activeAlerts: [], updateTime: latestTimestamp.toISOString() }, // Declare the sensors carried by the asset. Each entry // requires a stable sensor_id, a sensor_type, and at least // one field of view so Lattice can render the sensor cone. sensors: { sensors: [ { sensorId: "camera-1", sensorType: "SENSOR_TYPE_CAMERA", operationalState: "OPERATIONAL_STATE_OPERATIONAL", fieldsOfView: [ { centerRayPose: { orientation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 } }, horizontalFov: 1.047, verticalFov: 0.785, range: 2000.0, mode: "SENSOR_MODE_SEARCH" } ] } ] }, taskCatalog: { taskDefinitions: [ { taskSpecificationUrl: "type.googleapis.com/anduril.tasks.v2.VisualId" }, { taskSpecificationUrl: "type.googleapis.com/anduril.tasks.v2.Investigate" } ] } } ); console.log(`Publishing asset.`); } catch (error) { console.log(`Encountered the following error while publishing entity: ${error}`); } } (async function runIndefinitely() { while (true) { await App(); // Wait for 1 second before the next iteration await new Promise(resolve => setTimeout(resolve, 1000)); } })(); ``` ```go title={"Go (gRPC)"} startLine={99} maxLines={30} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "fmt" "log" "math" "os" "time" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" ontologyv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/ontology/v1" tasksv2 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/tasks/v2" _type "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/type" "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *_type.Quaternion { return &_type.Quaternion{ X: 0.0, Y: 0.0, Z: math.Sin(yawRad / 2.0), W: math.Cos(yawRad / 2.0), } } func main() { // Get environment variables environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Setup authentication auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } // Setup gRPC connection options opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } // Create gRPC connection (using NewClient instead of Dial which is deprecated) conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() // Create EntityManager client client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() creationTimestamp := timestamppb.New(creationTime) // Continuously publish the entity for { // Get current timestamp for this update latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := &entitymanagerv1.Entity{ EntityId: entityId, Description: "Friendly drone asset", Aliases: &entitymanagerv1.Aliases{ Name: "Drone 1", }, IsLive: true, CreatedTime: creationTimestamp, ExpiryTime: timestamppb.New(latestTimestamp.Add(1 * time.Hour)), Ontology: &entitymanagerv1.Ontology{ Template: entitymanagerv1.Template_TEMPLATE_ASSET, PlatformType: "UAV", }, MilView: &entitymanagerv1.MilView{ Disposition: ontologyv1.Disposition_DISPOSITION_FRIENDLY, Environment: ontologyv1.Environment_ENVIRONMENT_AIR, }, Location: &entitymanagerv1.Location{ Position: &entitymanagerv1.Position{ LatitudeDegrees: centerLat + (radiusDegrees * math.Cos(t)), LongitudeDegrees: centerLon + (radiusDegrees * math.Sin(t)), AltitudeAsfMeters: wrapperspb.Double(1000), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &_type.ENU{ E: velocityEast, N: velocityNorth, U: 0.0, }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &entitymanagerv1.Provenance{ IntegrationName: "your_integration_name", DataType: "your_data_type", SourceUpdateTime: timestamppb.New(latestTimestamp), }, Health: &entitymanagerv1.Health{ ConnectionStatus: entitymanagerv1.ConnectionStatus_CONNECTION_STATUS_ONLINE, HealthStatus: entitymanagerv1.HealthStatus_HEALTH_STATUS_HEALTHY, // Report the health of individual subsystems in components. // Each component needs a stable ID, a display name, and a // health status. Here, the drone reports its battery as healthy. Components: []*entitymanagerv1.ComponentHealth{ { Id: "battery-0", Name: "Battery", Health: entitymanagerv1.HealthStatus_HEALTH_STATUS_HEALTHY, Messages: []*entitymanagerv1.ComponentMessage{ { Status: entitymanagerv1.HealthStatus_HEALTH_STATUS_HEALTHY, Message: "Battery at 87% charge.", }, }, UpdateTime: timestamppb.New(latestTimestamp), }, }, // Live list of active alerts for the asset. Remove entries // when the underlying condition clears; Lattice does not // filter stale entries automatically. ActiveAlerts: []*entitymanagerv1.Alert{}, UpdateTime: timestamppb.New(latestTimestamp), }, // Declare the sensors carried by the asset. Each entry requires a // stable sensor_id, a sensor_type, and at least one field of view // so Lattice can render the sensor cone. Sensors: &entitymanagerv1.Sensors{ Sensors: []*entitymanagerv1.Sensor{ { SensorId: "camera-1", SensorType: entitymanagerv1.SensorType_SENSOR_TYPE_CAMERA, OperationalState: entitymanagerv1.OperationalState_OPERATIONAL_STATE_OPERATIONAL, FieldsOfView: []*entitymanagerv1.FieldOfView{ { CenterRayPose: &entitymanagerv1.Pose{ Orientation: &_type.Quaternion{ X: 0.0, Y: 0.0, Z: 0.0, W: 1.0, }, }, HorizontalFov: 1.047, VerticalFov: 0.785, Range: wrapperspb.Float(2000.0), Mode: entitymanagerv1.SensorMode_SENSOR_MODE_SEARCH, }, }, }, }, }, TaskCatalog: &tasksv2.TaskCatalog{ TaskDefinitions: []*tasksv2.TaskDefinition{ { TaskSpecificationUrl: "type.googleapis.com/anduril.tasks.v2.VisualId", }, { TaskSpecificationUrl: "type.googleapis.com/anduril.tasks.v2.Investigate", }, }, }, } // Create publish request request := &entitymanagerv1.PublishEntityRequest{ Entity: entity, } // Publish the entity _, err := client.PublishEntity(ctx, request) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Printf("Publishing asset with entity ID: %s\n", entityId) } time.Sleep(5 * time.Second) } } ``` ```py title={"Python (gRPC)"} startLine={102} maxLines={30} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import math import os import sys import time import uuid from datetime import datetime, timedelta, timezone from bearer_auth import create_client from protobuf.wkt import DoubleValue, FloatValue, Timestamp from anduril.entitymanager.v1.entity_pub_pb import Aliases, Entity, Provenance from anduril.entitymanager.v1.entity_manager_api_pub_pb import PublishEntityRequest from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync from anduril.entitymanager.v1.health_status_pub_pb import ( ComponentHealth, ComponentMessage, ConnectionStatus, Health, HealthStatus, ) from anduril.entitymanager.v1.location_pub_pb import Location, Pose, Position from anduril.entitymanager.v1.ontology_pub_pb import MilView, Ontology from anduril.entitymanager.v1.sensors_pub_pb import ( FieldOfView, OperationalState, Sensor, SensorMode, Sensors, SensorType, ) from anduril.entitymanager.v1.types_pub_pb import Template from anduril.ontology.v1.type_pub_pb import Disposition, Environment from anduril.tasks.v2.catalog_pub_pb import TaskCatalog, TaskDefinition from anduril.type.coords_pub_pb import ENU, Quaternion def to_timestamp(dt: datetime) -> Timestamp: return Timestamp.from_datetime(dt) def velocity_to_yaw_enu(velocity_east: float, velocity_north: float) -> float: """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad: float) -> Quaternion: """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0), ) def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) entity_id = str(uuid.uuid4()) # Set a radius, in degrees, to simulate the entity moving in a circle radius_degrees = 0.1 count = 0.0 center_lat = 50.91402185768586 center_lon = 0.79203612077257 creation_timestamp = to_timestamp(datetime.now(timezone.utc)) while True: latest = datetime.now(timezone.utc) latest_ts = to_timestamp(latest) expiry_ts = to_timestamp(latest + timedelta(hours=1)) count += 0.1 t = math.pi * count / 180.0 # Derive the ENU velocity from the circular motion, then convert it # into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) entity = Entity( entity_id=entity_id, description="Friendly drone asset", aliases=Aliases(name="Drone 1"), is_live=True, created_time=creation_timestamp, expiry_time=expiry_ts, ontology=Ontology(template=Template.ASSET, platform_type="UAV"), mil_view=MilView( disposition=Disposition.FRIENDLY, environment=Environment.AIR, ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_asf_meters=DoubleValue(value=1000), ), # Report the velocity in the ENU frame, in meters per second. # Vertical velocity is 0 at constant altitude. velocity_enu=ENU(e=velocity_east, n=velocity_north, u=0.0), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad), ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_ts, ), health=Health( connection_status=ConnectionStatus.ONLINE, health_status=HealthStatus.HEALTHY, # Report the health of individual subsystems in components. # Each component needs a stable id, a display name, and a # health status. Here, the drone reports its battery as healthy. components=[ ComponentHealth( id="battery-0", name="Battery", health=HealthStatus.HEALTHY, messages=[ ComponentMessage( status=HealthStatus.HEALTHY, message="Battery at 87% charge.", ), ], update_time=latest_ts, ), ], # Live list of active alerts for the asset. Remove entries # when the underlying condition clears; Lattice does not # filter stale entries automatically. active_alerts=[], update_time=latest_ts, ), # Declare the sensors carried by the asset. Each entry requires a # stable sensor_id, a sensor_type, and at least one field of view # so Lattice can render the sensor cone. sensors=Sensors( sensors=[ Sensor( sensor_id="camera-1", sensor_type=SensorType.CAMERA, operational_state=OperationalState.OPERATIONAL, fields_of_view=[ FieldOfView( center_ray_pose=Pose( orientation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0), ), horizontal_fov=1.047, vertical_fov=0.785, range=FloatValue(value=2000.0), mode=SensorMode.SEARCH, ), ], ), ], ), task_catalog=TaskCatalog( task_definitions=[ TaskDefinition(task_specification_url="type.googleapis.com/anduril.tasks.v2.VisualId"), TaskDefinition(task_specification_url="type.googleapis.com/anduril.tasks.v2.Investigate"), ], ), ) try: client.publish_entity(PublishEntityRequest(entity=entity)) print(f"Publishing asset with entity ID: {entity_id}") except Exception as error: print(f"Error publishing entity: {error}", file=sys.stderr) time.sleep(5) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={117} maxLines={35} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::{ Aliases, ComponentHealth, ComponentMessage, ConnectionStatus, Entity, FieldOfView, Health, HealthStatus, Location, MilView, Ontology, OperationalState, Pose, Position, Provenance, PublishEntityRequest, Sensor, SensorMode, SensorType, Sensors, Template, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::ontology::v1::{ Disposition, Environment, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::r#type::{Enu, Quaternion}; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::tasks::v2::{ TaskCatalog, TaskDefinition, }; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use prost_types::Timestamp; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use uuid::Uuid; use std::env; use std::f64::consts::PI; use std::time::{Duration, SystemTime, UNIX_EPOCH}; // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. fn velocity_to_yaw_enu(velocity_east: f64, velocity_north: f64) -> f64 { velocity_north.atan2(velocity_east) } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. fn yaw_to_quaternion_enu(yaw_rad: f64) -> Quaternion { Quaternion { x: 0.0, y: 0.0, z: (yaw_rad / 2.0).sin(), w: (yaw_rad / 2.0).cos(), } } fn now_timestamp() -> Timestamp { let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); Timestamp { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32, } } fn add_seconds(ts: &Timestamp, seconds: i64) -> Timestamp { Timestamp { seconds: ts.seconds + seconds, nanos: ts.nanos, } } #[tokio::main] async fn main() -> Result<(), Box> { let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); // Generate a unique ID for the entity let entity_id = Uuid::new_v4().to_string(); // Set a radius, in degrees, to simulate the entity moving in a circle let radius_degrees = 0.1; let mut count = 0.0_f64; let center_lat = 50.91402185768586; let center_lon = 0.79203612077257; let creation_timestamp = now_timestamp(); // Continuously publish the entity loop { let latest_timestamp = now_timestamp(); // Update position count += 0.1; let t = PI * count / 180.0; // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. let lat = center_lat + radius_degrees * t.cos(); let meters_per_degree_lat = 111320.0_f64; let meters_per_degree_lon = 111320.0_f64 * lat.to_radians().cos(); let velocity_east = radius_degrees * t.cos() * meters_per_degree_lon; let velocity_north = -radius_degrees * t.sin() * meters_per_degree_lat; let yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north); // Create entity to publish let entity = Entity { entity_id: entity_id.clone(), description: "Friendly drone asset".to_string(), aliases: Some(Aliases { name: "Drone 1".to_string(), ..Default::default() }), is_live: true, created_time: Some(creation_timestamp.clone()), expiry_time: Some(add_seconds(&latest_timestamp, 3600)), ontology: Some(Ontology { template: Template::Asset as i32, platform_type: "UAV".to_string(), ..Default::default() }), mil_view: Some(MilView { disposition: Disposition::Friendly as i32, environment: Environment::Air as i32, ..Default::default() }), location: Some(Location { position: Some(Position { latitude_degrees: center_lat + radius_degrees * t.cos(), longitude_degrees: center_lon + radius_degrees * t.sin(), altitude_asf_meters: Some(1000.0), ..Default::default() }), // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. velocity_enu: Some(Enu { e: velocity_east, n: velocity_north, u: 0.0, }), // Report the heading as a quaternion derived from velocity. attitude_enu: Some(yaw_to_quaternion_enu(yaw_rad)), ..Default::default() }), provenance: Some(Provenance { integration_name: "your_integration_name".to_string(), data_type: "your_data_type".to_string(), source_update_time: Some(latest_timestamp.clone()), ..Default::default() }), health: Some(Health { connection_status: ConnectionStatus::Online as i32, health_status: HealthStatus::Healthy as i32, // Report the health of individual subsystems in components. // Each component needs a stable id, a display name, and a // health status. Here, the drone reports its battery as // healthy. components: vec![ComponentHealth { id: "battery-0".to_string(), name: "Battery".to_string(), health: HealthStatus::Healthy as i32, messages: vec![ComponentMessage { status: HealthStatus::Healthy as i32, message: "Battery at 87% charge.".to_string(), }], update_time: Some(latest_timestamp.clone()), }], // Live list of active alerts for the asset. Remove entries // when the underlying condition clears; Lattice does not // filter stale entries automatically. active_alerts: vec![], update_time: Some(latest_timestamp.clone()), ..Default::default() }), // Declare the sensors carried by the asset. Each entry requires // a stable sensor_id, a sensor_type, and at least one field of // view so Lattice can render the sensor cone. sensors: Some(Sensors { sensors: vec![Sensor { sensor_id: "camera-1".to_string(), sensor_type: SensorType::Camera as i32, operational_state: OperationalState::Operational as i32, fields_of_view: vec![FieldOfView { center_ray_pose: Some(Pose { orientation: Some(Quaternion { x: 0.0, y: 0.0, z: 0.0, w: 1.0, }), ..Default::default() }), horizontal_fov: 1.047, vertical_fov: 0.785, range: Some(2000.0), mode: SensorMode::Search as i32, ..Default::default() }], ..Default::default() }], }), task_catalog: Some(TaskCatalog { task_definitions: vec![ TaskDefinition { task_specification_url: "type.googleapis.com/anduril.tasks.v2.VisualId" .to_string(), }, TaskDefinition { task_specification_url: "type.googleapis.com/anduril.tasks.v2.Investigate".to_string(), }, ], }), ..Default::default() }; let request = Request::new(PublishEntityRequest { entity: Some(entity), }); match client.publish_entity(request).await { Ok(_) => println!("Publishing asset with entity ID: {}", entity_id), Err(e) => eprintln!("Error publishing entity: {}", e), } tokio::time::sleep(Duration::from_secs(5)).await; } } ``` ## Publish a track A *track* represents any entity tracked by another asset or integration source. Tracks are not directly under the control of friendly forces. This includes aircraft tracks from radar or sensor hits, signal detections, and vehicles, people, or animals detected through cameras. You can specify the type of track you want to publish by setting `ontology.template` field: #### Airplane Define the entity model's required components. To create a generic track, such as an airplane: * Set `template` to `TEMPLATE_TRACK`. * Set `platform_type` to `AIRPLANE`. Together with the track-specific fields, you get the following entity object: **`entity.json`** ```json maxLines=15 title="entity.json" { "entityId": "UNIQUE_ENTITY_ID", "description": "Airplane 1", "isLive": true, "createdTime": "2025-01-01T00:00:00.000000Z", "expiryTime": "2025-01-01T00:01:00.000000Z", "milView": { "disposition": "DISPOSITION_FRIENDLY", "environment": "ENVIRONMENT_AIR" }, "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "AIRPLANE" }, "aliases": { "name": "DL-1234" }, "location": { "position": { "latitudeDegrees": 50.91402185768586, "longitudeDegrees": 0.79203612077257, "altitudeHaeMeters": 2994, "altitudeAglMeters": 2972.8 }, "velocityEnu": { "e": 50, "n": 50, "u": 0 }, "attitudeEnu": { "x": 0, "y": 0, "z": 0.3827, "w": 0.9239 } }, "provenance": { "integrationName": "your integration", "dataType": "your_data_type", "sourceUpdateTime": "2025-01-01T00:00:00.000000Z" } } ``` Take a closer look at the components in `entity.json` to familiarize yourself with the track's properties. **`milView`** ```json title="milView" "milView": { // Set the disposition to friendly to represent a // friendly airplane detection. "disposition": "DISPOSITION_FRIENDLY", // Set the environment field to specify the terrain in which // detection is made. "environment": "ENVIRONMENT_AIR" } ``` **`location`** ```json title="location" "location": { // Set the position of the track. "position": { // Set the track's latitude, expressed in degrees. "latitudeDegrees": 50.91402185768586, // Set the track's longitude, expressed in degrees. "longitudeDegrees": 0.79203612077257, // Set the track's Height Above Ellipsoid (HAE), expressed in meters. "altitudeHaeMeters": 2994, // Set the track's Above Ground Level (AGL) altitude, expressed in meters. "altitudeAglMeters": 2972.8 }, // Set the track's velocity in the ENU frame, in meters per second. "velocityEnu": { "e": 50, "n": 50, "u": 0 }, // Set the track's heading as a quaternion in the ENU frame. "attitudeEnu": { "x": 0, "y": 0, "z": 0.3827, "w": 0.9239 } } ``` Because the airplane is moving, the example also reports the track's velocity and heading through the following `location` fields: **`velocity_enu`** `ENU` The track's velocity in an East-North-Up (ENU) reference frame centered on the track's position, measured in meters per second. --- **`attitude_enu`** `Quaternion` The track's heading in the ENU frame, represented as a quaternion that rotates from the entity body frame to the ENU frame. --- If both `attitude_enu` and `velocity_enu` are populated, Lattice prioritizes the value from the `attitude_enu` component. Convert the track's velocity into the `attitude_enu` quaternion. Sensors typically report velocity rather than heading, so derive the heading in two steps: 1. Convert the ENU velocity vector into a yaw angle with `atan2(velocity_north, velocity_east)`. This uses the ENU mathematical convention, where a yaw of 0 points East and a yaw of π/2 points North. 2. Convert the yaw angle into a quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion has only `z = sin(yaw / 2)` and `w = cos(yaw / 2)` components. The result is already unit-normalized. The example defines two helper functions for this conversion: ```go title={"Go (REST)"} startLine={20} maxLines={16} package main import ( "context" "fmt" "math" "net/http" "os" "time" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" "github.com/google/uuid" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion { return &Lattice.Quaternion{ X: Lattice.Float64(0.0), Y: Lattice.Float64(0.0), Z: Lattice.Float64(math.Sin(yawRad / 2.0)), W: Lattice.Float64(math.Cos(yawRad / 2.0)), } } func main() { // Get environment variables latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Initialize headers for sandbox authorization headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) // Create the client LatticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() // Continuously publish the entity for { latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := Lattice.Entity{ EntityID: &entityId, Description: Lattice.String("Friendly airplane"), Aliases: &Lattice.Aliases{ Name: Lattice.String("DL-1234"), }, IsLive: Lattice.Bool(true), CreatedTime: Lattice.Time(creationTime), ExpiryTime: Lattice.Time(latestTimestamp.Add(5 * time.Minute)), Ontology: &Lattice.Ontology{ Template: Lattice.OntologyTemplateTemplateTrack.Ptr(), PlatformType: Lattice.String("AIRPLANE"), }, MilView: &Lattice.MilView{ Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(), Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(), }, Location: &Lattice.Location{ Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))), LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))), AltitudeHaeMeters: Lattice.Float64(2994), AltitudeAglMeters: Lattice.Float64(2972.8), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &Lattice.Enu{ E: Lattice.Float64(velocityEast), N: Lattice.Float64(velocityNorth), U: Lattice.Float64(0.0), }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &Lattice.Provenance{ IntegrationName: Lattice.String("your_integration_name"), DataType: Lattice.String("your_data_type"), SourceUpdateTime: Lattice.Time(latestTimestamp), }, } // Publish the entity _, err := LatticeClient.Entities.PublishEntity(ctx, &entity) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Println("Publishing track") } // Wait before next request time.Sleep(1 * time.Second) } } ``` ```java title={"Java (REST)"} startLine={32} maxLines={20} package org.example; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.UUID; import com.anduril.Lattice; import com.anduril.types.Aliases; import com.anduril.types.Enu; import com.anduril.types.Entity; import com.anduril.types.Location; import com.anduril.types.MilView; import com.anduril.types.MilViewDisposition; import com.anduril.types.MilViewEnvironment; import com.anduril.types.Ontology; import com.anduril.types.OntologyTemplate; import com.anduril.types.Position; import com.anduril.types.Provenance; import com.anduril.types.Quaternion; /** * Example application that publishes a simulated airplane track to Lattice using the SDK. */ public class PublishTrackAirplane { // Convert an ENU velocity vector into a yaw angle, in radians. Uses the // ENU mathematical convention where a yaw of 0 points East and a yaw of // pi/2 points North. private static double velocityToYawEnu(double velocityEast, double velocityNorth) { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has Z and W // components. The result is already unit-normalized. private static Quaternion yawToQuaternionEnu(double yawRad) { return Quaternion.builder() .x(0.0) .y(0.0) .z(Math.sin(yawRad / 2.0)) .w(Math.cos(yawRad / 2.0)) .build(); } public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables and ensure URL has https:// if (endpoint == null || environmentToken == null || sandboxToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { // Create Lattice client with Sandbox authentication Lattice client = Lattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxToken) .build(); // Generate a unique ID for the entity String entityId = UUID.randomUUID().toString(); // Set a radius, in degrees, to simulate the entity moving in a circle double radiusDegrees = 0.1; double count = 0.0; double centerLat = 50.91402185768586; double centerLon = 0.79203612077257; Instant creationTime = Instant.now(); // Continuously publish the entity while (true) { try { // Get current timestamp Instant latestTimestamp = Instant.now(); // Update position count += 0.1; double t = Math.toRadians(count); // Derive the ENU velocity from the circular motion, then // convert it into a yaw angle and an attitude_enu quaternion. double lat = centerLat + (radiusDegrees * Math.cos(t)); double metersPerDegreeLat = 111320.0; double metersPerDegreeLon = 111320.0 * Math.cos(Math.toRadians(lat)); double velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon; double velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat; double yawRad = velocityToYawEnu(velocityEast, velocityNorth); // Prepare entity data // Create the entity (using the appropriate methods for the latest SDK version) Entity entity = Entity.builder() .entityId(entityId) .description("Friendly airplane") .aliases(Aliases.builder() .name("Airplane 1") .build()) .isLive(true) .createdTime(OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) .expiryTime(OffsetDateTime.ofInstant(latestTimestamp.plus(5, ChronoUnit.MINUTES), ZoneOffset.UTC)) .ontology(Ontology.builder() .template(OntologyTemplate.TEMPLATE_TRACK) .platformType("Airplane") .build()) .milView(MilView.builder() .disposition(MilViewDisposition.DISPOSITION_FRIENDLY) .environment(MilViewEnvironment.ENVIRONMENT_AIR) .build()) .location(Location.builder() .position(Position.builder() .latitudeDegrees(centerLat + (radiusDegrees * Math.cos(t))) .longitudeDegrees(centerLon + (radiusDegrees * Math.sin(t))) .altitudeHaeMeters(2994.0) .altitudeAglMeters(2972.8) .build()) // Report the velocity in the ENU frame, in meters // per second. Vertical velocity is 0 at constant // altitude. .velocityEnu(Enu.builder() .e(velocityEast) .n(velocityNorth) .u(0.0) .build()) // Report the heading as a quaternion derived from // velocity. .attitudeEnu(yawToQuaternionEnu(yawRad)) .build()) .provenance(Provenance.builder() .integrationName("your_integration_name") .dataType("your_data_type") .sourceUpdateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build()) .build(); // Publish the entity client.entities().publishEntity(entity); System.out.println("Published entity ID: " + entityId); Thread.sleep(5000); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); Thread.sleep(10000); } } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={31} maxLines={17} from anduril import Lattice, \ Aliases, MilView, Location, Position, Ontology, Provenance, Enu, Quaternion from datetime import datetime, timezone, timedelta import asyncio import math import os import sys from uuid import uuid4 lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables.") sys.exit(1) client = Lattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) id = str(uuid4()) # Base position the airplane circles around. center_lat = 50.91402185768586 center_lon = 0.79203612077257 def velocity_to_yaw_enu(velocity_east, velocity_north): """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad): """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0) ) async def app(): try: count = 0 radius_degrees = .1 creation_time = datetime.now(timezone.utc) while(True): latest_timestamp = datetime.now(timezone.utc) count += .1 t = math.radians(count) # Derive the ENU velocity from the circular motion, then convert # it into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) client.entities.publish_entity( entity_id= id, description="Friendly airplane", aliases=Aliases( name="Airplane 1" ), is_live=True, created_time=creation_time, expiry_time=latest_timestamp + timedelta(minutes=5), ontology=Ontology( template="TEMPLATE_TRACK", platform_type="Airplane" ), mil_view=MilView( disposition="DISPOSITION_FRIENDLY", environment="ENVIRONMENT_AIR" ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_hae_meters=2994, altitude_agl_meters=2972.8 ), # Report the velocity in the ENU frame, in meters per # second. Vertical velocity is 0 at constant altitude. velocity_enu=Enu( e=velocity_east, n=velocity_north, u=0.0 ), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad) ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_timestamp ) ) await asyncio.sleep(.1) except asyncio.CancelledError: print(">>>Exiting...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={38} maxLines={20} import { LatticeClient } from "@anduril-industries/lattice-sdk"; import { v4 as uuidv4 } from 'uuid'; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following if you are not developing Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); const entityId = uuidv4() const expiryOffsetMinutes = 2 // Set a radius, in degrees, to simulate the entity moving in a circle. const radiusDegrees = .1 const centerLat = 50.91402185768586 const centerLon = 0.79203612077257 let count = 0 function toRadians(degrees: number): number { return degrees * (Math.PI / 180); } // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. function velocityToYawEnu(velocityEast: number, velocityNorth: number): number { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. function yawToQuaternionEnu(yawRad: number): { x: number; y: number; z: number; w: number } { return { x: 0.0, y: 0.0, z: Math.sin(yawRad / 2.0), w: Math.cos(yawRad / 2.0), }; } async function App() { try { const latestTimestamp = new Date(); count += .1 const t = toRadians(count) // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. const lat = centerLat + (radiusDegrees * Math.cos(t)) const metersPerDegreeLat = 111320.0 const metersPerDegreeLon = 111320.0 * Math.cos(toRadians(lat)) const velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon const velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat const yawRad = velocityToYawEnu(velocityEast, velocityNorth) await client.entities.publishEntity( { entityId: entityId, description: "Friendly airplane", aliases: { name: "DL-1234" }, isLive: true, createdTime: latestTimestamp.toISOString(), expiryTime: new Date(new Date().setMinutes(latestTimestamp.getMinutes() + expiryOffsetMinutes)).toISOString(), ontology: { template: "TEMPLATE_TRACK", platformType: "AIRPLANE" }, milView: { disposition: "DISPOSITION_FRIENDLY", environment: "ENVIRONMENT_AIR" }, location: { position: { latitudeDegrees: centerLat + (radiusDegrees * Math.cos(t)), longitudeDegrees: centerLon + (radiusDegrees * Math.sin(t)), altitudeHaeMeters: 2994, altitudeAglMeters: 2972.8 }, // Report the velocity in the ENU frame, in meters per // second. Vertical velocity is 0 at constant altitude. velocityEnu: { e: velocityEast, n: velocityNorth, u: 0.0 }, // Report the heading as a quaternion derived from velocity. attitudeEnu: yawToQuaternionEnu(yawRad) }, provenance: { integrationName: "your_integration_name", dataType: "your_data_type", sourceUpdateTime: latestTimestamp.toISOString() } } ); console.log(`Publishing track.`); } catch (error) { console.log(`Encountered the following error while publishing entity: ${error}`); } } (async function runIndefinitely() { while (true) { await App(); // Wait for 1 second before the next iteration await new Promise(resolve => setTimeout(resolve, 1000)); } })(); ``` ```go title={"Go (gRPC)"} startLine={27} maxLines={16} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "fmt" "log" "math" "os" "time" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" ontologyv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/ontology/v1" _type "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/type" "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *_type.Quaternion { return &_type.Quaternion{ X: 0.0, Y: 0.0, Z: math.Sin(yawRad / 2.0), W: math.Cos(yawRad / 2.0), } } func main() { // Get environment variables environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Setup authentication auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } // Setup gRPC connection options opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() // Create EntityManager client client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() creationTimestamp := timestamppb.New(creationTime) // Continuously publish the entity for { // Get current timestamp for this update latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := &entitymanagerv1.Entity{ EntityId: entityId, Description: "Friendly airplane", Aliases: &entitymanagerv1.Aliases{ Name: "DL-1234", }, IsLive: true, CreatedTime: creationTimestamp, ExpiryTime: timestamppb.New(latestTimestamp.Add(1 * time.Minute)), Ontology: &entitymanagerv1.Ontology{ Template: entitymanagerv1.Template_TEMPLATE_TRACK, PlatformType: "AIRPLANE", }, MilView: &entitymanagerv1.MilView{ Disposition: ontologyv1.Disposition_DISPOSITION_FRIENDLY, Environment: ontologyv1.Environment_ENVIRONMENT_AIR, }, Location: &entitymanagerv1.Location{ Position: &entitymanagerv1.Position{ LatitudeDegrees: centerLat + (radiusDegrees * math.Cos(t)), LongitudeDegrees: centerLon + (radiusDegrees * math.Sin(t)), AltitudeHaeMeters: wrapperspb.Double(2994), AltitudeAglMeters: wrapperspb.Double(2972.8), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &_type.ENU{ E: velocityEast, N: velocityNorth, U: 0.0, }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &entitymanagerv1.Provenance{ IntegrationName: "your_integration_name", DataType: "your_data_type", SourceUpdateTime: timestamppb.New(latestTimestamp), }, } // Create publish request request := &entitymanagerv1.PublishEntityRequest{ Entity: entity, } // Publish the entity _, err := client.PublishEntity(ctx, request) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Printf("Publishing track with entity ID: %s\n", entityId) } time.Sleep(5 * time.Second) } } ``` ```py title={"Python (gRPC)"} startLine={31} maxLines={18} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import math import os import sys import time import uuid from datetime import datetime, timedelta, timezone from bearer_auth import create_client from protobuf.wkt import DoubleValue, Timestamp from anduril.entitymanager.v1.entity_pub_pb import Aliases, Entity, Provenance from anduril.entitymanager.v1.entity_manager_api_pub_pb import PublishEntityRequest from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync from anduril.entitymanager.v1.location_pub_pb import Location, Position from anduril.entitymanager.v1.ontology_pub_pb import MilView, Ontology from anduril.entitymanager.v1.types_pub_pb import Template from anduril.ontology.v1.type_pub_pb import Disposition, Environment from anduril.type.coords_pub_pb import ENU, Quaternion def to_timestamp(dt: datetime) -> Timestamp: return Timestamp.from_datetime(dt) def velocity_to_yaw_enu(velocity_east: float, velocity_north: float) -> float: """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad: float) -> Quaternion: """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0), ) def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) entity_id = str(uuid.uuid4()) # Set a radius, in degrees, to simulate the entity moving in a circle radius_degrees = 0.1 count = 0.0 center_lat = 50.91402185768586 center_lon = 0.79203612077257 creation_timestamp = to_timestamp(datetime.now(timezone.utc)) while True: latest = datetime.now(timezone.utc) latest_ts = to_timestamp(latest) expiry_ts = to_timestamp(latest + timedelta(minutes=1)) count += 0.1 t = math.pi * count / 180.0 # Derive the ENU velocity from the circular motion, then convert it # into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) entity = Entity( entity_id=entity_id, description="Friendly airplane", aliases=Aliases(name="DL-1234"), is_live=True, created_time=creation_timestamp, expiry_time=expiry_ts, ontology=Ontology(template=Template.TRACK, platform_type="AIRPLANE"), mil_view=MilView( disposition=Disposition.FRIENDLY, environment=Environment.AIR, ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_hae_meters=DoubleValue(value=2994), altitude_agl_meters=DoubleValue(value=2972.8), ), # Report the velocity in the ENU frame, in meters per second. # Vertical velocity is 0 at constant altitude. velocity_enu=ENU(e=velocity_east, n=velocity_north, u=0.0), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad), ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_ts, ), ) try: client.publish_entity(PublishEntityRequest(entity=entity)) print(f"Publishing track with entity ID: {entity_id}") except Exception as error: print(f"Error publishing entity: {error}", file=sys.stderr) time.sleep(5) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={28} maxLines={16} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::{ Aliases, Entity, Location, MilView, Ontology, Position, Provenance, PublishEntityRequest, Template, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::ontology::v1::{ Disposition, Environment, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::r#type::{Enu, Quaternion}; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use prost_types::Timestamp; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use uuid::Uuid; use std::env; use std::f64::consts::PI; use std::time::{Duration, SystemTime, UNIX_EPOCH}; // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. fn velocity_to_yaw_enu(velocity_east: f64, velocity_north: f64) -> f64 { velocity_north.atan2(velocity_east) } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. fn yaw_to_quaternion_enu(yaw_rad: f64) -> Quaternion { Quaternion { x: 0.0, y: 0.0, z: (yaw_rad / 2.0).sin(), w: (yaw_rad / 2.0).cos(), } } fn now_timestamp() -> Timestamp { let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); Timestamp { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32, } } fn add_seconds(ts: &Timestamp, seconds: i64) -> Timestamp { Timestamp { seconds: ts.seconds + seconds, nanos: ts.nanos, } } #[tokio::main] async fn main() -> Result<(), Box> { let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); // Generate a unique ID for the entity let entity_id = Uuid::new_v4().to_string(); // Set a radius, in degrees, to simulate the entity moving in a circle let radius_degrees = 0.1; let mut count = 0.0_f64; let center_lat = 50.91402185768586; let center_lon = 0.79203612077257; let creation_timestamp = now_timestamp(); // Continuously publish the entity loop { let latest_timestamp = now_timestamp(); // Update position count += 0.1; let t = PI * count / 180.0; // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. let lat = center_lat + radius_degrees * t.cos(); let meters_per_degree_lat = 111320.0_f64; let meters_per_degree_lon = 111320.0_f64 * lat.to_radians().cos(); let velocity_east = radius_degrees * t.cos() * meters_per_degree_lon; let velocity_north = -radius_degrees * t.sin() * meters_per_degree_lat; let yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north); // Create entity to publish let entity = Entity { entity_id: entity_id.clone(), description: "Friendly airplane".to_string(), aliases: Some(Aliases { name: "DL-1234".to_string(), ..Default::default() }), is_live: true, created_time: Some(creation_timestamp.clone()), expiry_time: Some(add_seconds(&latest_timestamp, 60)), ontology: Some(Ontology { template: Template::Track as i32, platform_type: "AIRPLANE".to_string(), ..Default::default() }), mil_view: Some(MilView { disposition: Disposition::Friendly as i32, environment: Environment::Air as i32, ..Default::default() }), location: Some(Location { position: Some(Position { latitude_degrees: center_lat + radius_degrees * t.cos(), longitude_degrees: center_lon + radius_degrees * t.sin(), altitude_hae_meters: Some(2994.0), altitude_agl_meters: Some(2972.8), ..Default::default() }), // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. velocity_enu: Some(Enu { e: velocity_east, n: velocity_north, u: 0.0, }), // Report the heading as a quaternion derived from velocity. attitude_enu: Some(yaw_to_quaternion_enu(yaw_rad)), ..Default::default() }), provenance: Some(Provenance { integration_name: "your_integration_name".to_string(), data_type: "your_data_type".to_string(), source_update_time: Some(latest_timestamp.clone()), ..Default::default() }), ..Default::default() }; let request = Request::new(PublishEntityRequest { entity: Some(entity), }); match client.publish_entity(request).await { Ok(_) => println!("Publishing track with entity ID: {}", entity_id), Err(e) => eprintln!("Error publishing entity: {}", e), } tokio::time::sleep(Duration::from_secs(5)).await; } } ``` Use the [`PublishEntity`](/reference/rest/entities/publish-entity) API method to publish the entity. The example derives the velocity from the simulated circular motion, then populates `velocity_enu` and `attitude_enu`: ```go title={"Go (REST)"} startLine={80} maxLines={30} package main import ( "context" "fmt" "math" "net/http" "os" "time" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" "github.com/google/uuid" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion { return &Lattice.Quaternion{ X: Lattice.Float64(0.0), Y: Lattice.Float64(0.0), Z: Lattice.Float64(math.Sin(yawRad / 2.0)), W: Lattice.Float64(math.Cos(yawRad / 2.0)), } } func main() { // Get environment variables latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Initialize headers for sandbox authorization headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) // Create the client LatticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() // Continuously publish the entity for { latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := Lattice.Entity{ EntityID: &entityId, Description: Lattice.String("Friendly airplane"), Aliases: &Lattice.Aliases{ Name: Lattice.String("DL-1234"), }, IsLive: Lattice.Bool(true), CreatedTime: Lattice.Time(creationTime), ExpiryTime: Lattice.Time(latestTimestamp.Add(5 * time.Minute)), Ontology: &Lattice.Ontology{ Template: Lattice.OntologyTemplateTemplateTrack.Ptr(), PlatformType: Lattice.String("AIRPLANE"), }, MilView: &Lattice.MilView{ Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(), Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(), }, Location: &Lattice.Location{ Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))), LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))), AltitudeHaeMeters: Lattice.Float64(2994), AltitudeAglMeters: Lattice.Float64(2972.8), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &Lattice.Enu{ E: Lattice.Float64(velocityEast), N: Lattice.Float64(velocityNorth), U: Lattice.Float64(0.0), }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &Lattice.Provenance{ IntegrationName: Lattice.String("your_integration_name"), DataType: Lattice.String("your_data_type"), SourceUpdateTime: Lattice.Time(latestTimestamp), }, } // Publish the entity _, err := LatticeClient.Entities.PublishEntity(ctx, &entity) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Println("Publishing track") } // Wait before next request time.Sleep(1 * time.Second) } } ``` ```java title={"Java (REST)"} startLine={89} maxLines={35} package org.example; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.UUID; import com.anduril.Lattice; import com.anduril.types.Aliases; import com.anduril.types.Enu; import com.anduril.types.Entity; import com.anduril.types.Location; import com.anduril.types.MilView; import com.anduril.types.MilViewDisposition; import com.anduril.types.MilViewEnvironment; import com.anduril.types.Ontology; import com.anduril.types.OntologyTemplate; import com.anduril.types.Position; import com.anduril.types.Provenance; import com.anduril.types.Quaternion; /** * Example application that publishes a simulated airplane track to Lattice using the SDK. */ public class PublishTrackAirplane { // Convert an ENU velocity vector into a yaw angle, in radians. Uses the // ENU mathematical convention where a yaw of 0 points East and a yaw of // pi/2 points North. private static double velocityToYawEnu(double velocityEast, double velocityNorth) { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has Z and W // components. The result is already unit-normalized. private static Quaternion yawToQuaternionEnu(double yawRad) { return Quaternion.builder() .x(0.0) .y(0.0) .z(Math.sin(yawRad / 2.0)) .w(Math.cos(yawRad / 2.0)) .build(); } public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables and ensure URL has https:// if (endpoint == null || environmentToken == null || sandboxToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { // Create Lattice client with Sandbox authentication Lattice client = Lattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxToken) .build(); // Generate a unique ID for the entity String entityId = UUID.randomUUID().toString(); // Set a radius, in degrees, to simulate the entity moving in a circle double radiusDegrees = 0.1; double count = 0.0; double centerLat = 50.91402185768586; double centerLon = 0.79203612077257; Instant creationTime = Instant.now(); // Continuously publish the entity while (true) { try { // Get current timestamp Instant latestTimestamp = Instant.now(); // Update position count += 0.1; double t = Math.toRadians(count); // Derive the ENU velocity from the circular motion, then // convert it into a yaw angle and an attitude_enu quaternion. double lat = centerLat + (radiusDegrees * Math.cos(t)); double metersPerDegreeLat = 111320.0; double metersPerDegreeLon = 111320.0 * Math.cos(Math.toRadians(lat)); double velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon; double velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat; double yawRad = velocityToYawEnu(velocityEast, velocityNorth); // Prepare entity data // Create the entity (using the appropriate methods for the latest SDK version) Entity entity = Entity.builder() .entityId(entityId) .description("Friendly airplane") .aliases(Aliases.builder() .name("Airplane 1") .build()) .isLive(true) .createdTime(OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) .expiryTime(OffsetDateTime.ofInstant(latestTimestamp.plus(5, ChronoUnit.MINUTES), ZoneOffset.UTC)) .ontology(Ontology.builder() .template(OntologyTemplate.TEMPLATE_TRACK) .platformType("Airplane") .build()) .milView(MilView.builder() .disposition(MilViewDisposition.DISPOSITION_FRIENDLY) .environment(MilViewEnvironment.ENVIRONMENT_AIR) .build()) .location(Location.builder() .position(Position.builder() .latitudeDegrees(centerLat + (radiusDegrees * Math.cos(t))) .longitudeDegrees(centerLon + (radiusDegrees * Math.sin(t))) .altitudeHaeMeters(2994.0) .altitudeAglMeters(2972.8) .build()) // Report the velocity in the ENU frame, in meters // per second. Vertical velocity is 0 at constant // altitude. .velocityEnu(Enu.builder() .e(velocityEast) .n(velocityNorth) .u(0.0) .build()) // Report the heading as a quaternion derived from // velocity. .attitudeEnu(yawToQuaternionEnu(yawRad)) .build()) .provenance(Provenance.builder() .integrationName("your_integration_name") .dataType("your_data_type") .sourceUpdateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build()) .build(); // Publish the entity client.entities().publishEntity(entity); System.out.println("Published entity ID: " + entityId); Thread.sleep(5000); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); Thread.sleep(10000); } } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={62} maxLines={30} from anduril import Lattice, \ Aliases, MilView, Location, Position, Ontology, Provenance, Enu, Quaternion from datetime import datetime, timezone, timedelta import asyncio import math import os import sys from uuid import uuid4 lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables.") sys.exit(1) client = Lattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) id = str(uuid4()) # Base position the airplane circles around. center_lat = 50.91402185768586 center_lon = 0.79203612077257 def velocity_to_yaw_enu(velocity_east, velocity_north): """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad): """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0) ) async def app(): try: count = 0 radius_degrees = .1 creation_time = datetime.now(timezone.utc) while(True): latest_timestamp = datetime.now(timezone.utc) count += .1 t = math.radians(count) # Derive the ENU velocity from the circular motion, then convert # it into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) client.entities.publish_entity( entity_id= id, description="Friendly airplane", aliases=Aliases( name="Airplane 1" ), is_live=True, created_time=creation_time, expiry_time=latest_timestamp + timedelta(minutes=5), ontology=Ontology( template="TEMPLATE_TRACK", platform_type="Airplane" ), mil_view=MilView( disposition="DISPOSITION_FRIENDLY", environment="ENVIRONMENT_AIR" ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_hae_meters=2994, altitude_agl_meters=2972.8 ), # Report the velocity in the ENU frame, in meters per # second. Vertical velocity is 0 at constant altitude. velocity_enu=Enu( e=velocity_east, n=velocity_north, u=0.0 ), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad) ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_timestamp ) ) await asyncio.sleep(.1) except asyncio.CancelledError: print(">>>Exiting...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={60} maxLines={30} import { LatticeClient } from "@anduril-industries/lattice-sdk"; import { v4 as uuidv4 } from 'uuid'; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following if you are not developing Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); const entityId = uuidv4() const expiryOffsetMinutes = 2 // Set a radius, in degrees, to simulate the entity moving in a circle. const radiusDegrees = .1 const centerLat = 50.91402185768586 const centerLon = 0.79203612077257 let count = 0 function toRadians(degrees: number): number { return degrees * (Math.PI / 180); } // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. function velocityToYawEnu(velocityEast: number, velocityNorth: number): number { return Math.atan2(velocityNorth, velocityEast); } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. function yawToQuaternionEnu(yawRad: number): { x: number; y: number; z: number; w: number } { return { x: 0.0, y: 0.0, z: Math.sin(yawRad / 2.0), w: Math.cos(yawRad / 2.0), }; } async function App() { try { const latestTimestamp = new Date(); count += .1 const t = toRadians(count) // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. const lat = centerLat + (radiusDegrees * Math.cos(t)) const metersPerDegreeLat = 111320.0 const metersPerDegreeLon = 111320.0 * Math.cos(toRadians(lat)) const velocityEast = radiusDegrees * Math.cos(t) * metersPerDegreeLon const velocityNorth = -radiusDegrees * Math.sin(t) * metersPerDegreeLat const yawRad = velocityToYawEnu(velocityEast, velocityNorth) await client.entities.publishEntity( { entityId: entityId, description: "Friendly airplane", aliases: { name: "DL-1234" }, isLive: true, createdTime: latestTimestamp.toISOString(), expiryTime: new Date(new Date().setMinutes(latestTimestamp.getMinutes() + expiryOffsetMinutes)).toISOString(), ontology: { template: "TEMPLATE_TRACK", platformType: "AIRPLANE" }, milView: { disposition: "DISPOSITION_FRIENDLY", environment: "ENVIRONMENT_AIR" }, location: { position: { latitudeDegrees: centerLat + (radiusDegrees * Math.cos(t)), longitudeDegrees: centerLon + (radiusDegrees * Math.sin(t)), altitudeHaeMeters: 2994, altitudeAglMeters: 2972.8 }, // Report the velocity in the ENU frame, in meters per // second. Vertical velocity is 0 at constant altitude. velocityEnu: { e: velocityEast, n: velocityNorth, u: 0.0 }, // Report the heading as a quaternion derived from velocity. attitudeEnu: yawToQuaternionEnu(yawRad) }, provenance: { integrationName: "your_integration_name", dataType: "your_data_type", sourceUpdateTime: latestTimestamp.toISOString() } } ); console.log(`Publishing track.`); } catch (error) { console.log(`Encountered the following error while publishing entity: ${error}`); } } (async function runIndefinitely() { while (true) { await App(); // Wait for 1 second before the next iteration await new Promise(resolve => setTimeout(resolve, 1000)); } })(); ``` ```go title={"Go (gRPC)"} startLine={97} maxLines={30} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "fmt" "log" "math" "os" "time" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" ontologyv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/ontology/v1" _type "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/type" "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) // velocityToYawEnu converts an ENU velocity vector into a yaw angle, in // radians. It uses the ENU mathematical convention where a yaw of 0 points // East and a yaw of pi/2 points North. func velocityToYawEnu(velocityEast, velocityNorth float64) float64 { return math.Atan2(velocityNorth, velocityEast) } // yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion. // Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z // and W components. The result is already unit-normalized. func yawToQuaternionEnu(yawRad float64) *_type.Quaternion { return &_type.Quaternion{ X: 0.0, Y: 0.0, Z: math.Sin(yawRad / 2.0), W: math.Cos(yawRad / 2.0), } } func main() { // Get environment variables environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Setup authentication auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } // Setup gRPC connection options opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() // Create EntityManager client client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) // Generate a unique ID for the entity entityId := uuid.New().String() // Set a radius, in degrees, to simulate the entity moving in a circle radiusDegrees := 0.1 count := 0.0 centerLat := 50.91402185768586 centerLon := 0.79203612077257 creationTime := time.Now().UTC() creationTimestamp := timestamppb.New(creationTime) // Continuously publish the entity for { // Get current timestamp for this update latestTimestamp := time.Now().UTC() ctx := context.Background() // Update position count += 0.1 t := math.Pi * count / 180.0 // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. lat := centerLat + (radiusDegrees * math.Cos(t)) metersPerDegreeLat := 111320.0 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0) velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat yawRad := velocityToYawEnu(velocityEast, velocityNorth) // Create entity to publish entity := &entitymanagerv1.Entity{ EntityId: entityId, Description: "Friendly airplane", Aliases: &entitymanagerv1.Aliases{ Name: "DL-1234", }, IsLive: true, CreatedTime: creationTimestamp, ExpiryTime: timestamppb.New(latestTimestamp.Add(1 * time.Minute)), Ontology: &entitymanagerv1.Ontology{ Template: entitymanagerv1.Template_TEMPLATE_TRACK, PlatformType: "AIRPLANE", }, MilView: &entitymanagerv1.MilView{ Disposition: ontologyv1.Disposition_DISPOSITION_FRIENDLY, Environment: ontologyv1.Environment_ENVIRONMENT_AIR, }, Location: &entitymanagerv1.Location{ Position: &entitymanagerv1.Position{ LatitudeDegrees: centerLat + (radiusDegrees * math.Cos(t)), LongitudeDegrees: centerLon + (radiusDegrees * math.Sin(t)), AltitudeHaeMeters: wrapperspb.Double(2994), AltitudeAglMeters: wrapperspb.Double(2972.8), }, // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. VelocityEnu: &_type.ENU{ E: velocityEast, N: velocityNorth, U: 0.0, }, // Report the heading as a quaternion derived from velocity. AttitudeEnu: yawToQuaternionEnu(yawRad), }, Provenance: &entitymanagerv1.Provenance{ IntegrationName: "your_integration_name", DataType: "your_data_type", SourceUpdateTime: timestamppb.New(latestTimestamp), }, } // Create publish request request := &entitymanagerv1.PublishEntityRequest{ Entity: entity, } // Publish the entity _, err := client.PublishEntity(ctx, request) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Printf("Publishing track with entity ID: %s\n", entityId) } time.Sleep(5 * time.Second) } } ``` ```py title={"Python (gRPC)"} startLine={88} maxLines={30} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import math import os import sys import time import uuid from datetime import datetime, timedelta, timezone from bearer_auth import create_client from protobuf.wkt import DoubleValue, Timestamp from anduril.entitymanager.v1.entity_pub_pb import Aliases, Entity, Provenance from anduril.entitymanager.v1.entity_manager_api_pub_pb import PublishEntityRequest from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync from anduril.entitymanager.v1.location_pub_pb import Location, Position from anduril.entitymanager.v1.ontology_pub_pb import MilView, Ontology from anduril.entitymanager.v1.types_pub_pb import Template from anduril.ontology.v1.type_pub_pb import Disposition, Environment from anduril.type.coords_pub_pb import ENU, Quaternion def to_timestamp(dt: datetime) -> Timestamp: return Timestamp.from_datetime(dt) def velocity_to_yaw_enu(velocity_east: float, velocity_north: float) -> float: """Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU mathematical convention where a yaw of 0 points East and a yaw of pi/2 points North. """ return math.atan2(velocity_north, velocity_east) def yaw_to_quaternion_enu(yaw_rad: float) -> Quaternion: """Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z and W components. The result is already unit-normalized. """ return Quaternion( x=0.0, y=0.0, z=math.sin(yaw_rad / 2.0), w=math.cos(yaw_rad / 2.0), ) def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) entity_id = str(uuid.uuid4()) # Set a radius, in degrees, to simulate the entity moving in a circle radius_degrees = 0.1 count = 0.0 center_lat = 50.91402185768586 center_lon = 0.79203612077257 creation_timestamp = to_timestamp(datetime.now(timezone.utc)) while True: latest = datetime.now(timezone.utc) latest_ts = to_timestamp(latest) expiry_ts = to_timestamp(latest + timedelta(minutes=1)) count += 0.1 t = math.pi * count / 180.0 # Derive the ENU velocity from the circular motion, then convert it # into a yaw angle and an attitude_enu quaternion. lat = center_lat + (radius_degrees * math.cos(t)) meters_per_degree_lat = 111320.0 meters_per_degree_lon = 111320.0 * math.cos(math.radians(lat)) velocity_east = radius_degrees * math.cos(t) * meters_per_degree_lon velocity_north = -radius_degrees * math.sin(t) * meters_per_degree_lat yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north) entity = Entity( entity_id=entity_id, description="Friendly airplane", aliases=Aliases(name="DL-1234"), is_live=True, created_time=creation_timestamp, expiry_time=expiry_ts, ontology=Ontology(template=Template.TRACK, platform_type="AIRPLANE"), mil_view=MilView( disposition=Disposition.FRIENDLY, environment=Environment.AIR, ), location=Location( position=Position( latitude_degrees=center_lat + (radius_degrees * math.cos(t)), longitude_degrees=center_lon + (radius_degrees * math.sin(t)), altitude_hae_meters=DoubleValue(value=2994), altitude_agl_meters=DoubleValue(value=2972.8), ), # Report the velocity in the ENU frame, in meters per second. # Vertical velocity is 0 at constant altitude. velocity_enu=ENU(e=velocity_east, n=velocity_north, u=0.0), # Report the heading as a quaternion derived from velocity. attitude_enu=yaw_to_quaternion_enu(yaw_rad), ), provenance=Provenance( integration_name="your_integration_name", data_type="your_data_type", source_update_time=latest_ts, ), ) try: client.publish_entity(PublishEntityRequest(entity=entity)) print(f"Publishing track with entity ID: {entity_id}") except Exception as error: print(f"Error publishing entity: {error}", file=sys.stderr) time.sleep(5) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={113} maxLines={35} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::{ Aliases, Entity, Location, MilView, Ontology, Position, Provenance, PublishEntityRequest, Template, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::ontology::v1::{ Disposition, Environment, }; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::r#type::{Enu, Quaternion}; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use prost_types::Timestamp; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use uuid::Uuid; use std::env; use std::f64::consts::PI; use std::time::{Duration, SystemTime, UNIX_EPOCH}; // Convert an ENU velocity vector into a yaw angle, in radians. Uses the ENU // mathematical convention where a yaw of 0 points East and a yaw of pi/2 // points North. fn velocity_to_yaw_enu(velocity_east: f64, velocity_north: f64) -> f64 { velocity_north.atan2(velocity_east) } // Convert a yaw angle into the attitude_enu quaternion. Yaw is a rotation // about the ENU Up (Z) axis, so the quaternion only has z and w components. // The result is already unit-normalized. fn yaw_to_quaternion_enu(yaw_rad: f64) -> Quaternion { Quaternion { x: 0.0, y: 0.0, z: (yaw_rad / 2.0).sin(), w: (yaw_rad / 2.0).cos(), } } fn now_timestamp() -> Timestamp { let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); Timestamp { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32, } } fn add_seconds(ts: &Timestamp, seconds: i64) -> Timestamp { Timestamp { seconds: ts.seconds + seconds, nanos: ts.nanos, } } #[tokio::main] async fn main() -> Result<(), Box> { let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); // Generate a unique ID for the entity let entity_id = Uuid::new_v4().to_string(); // Set a radius, in degrees, to simulate the entity moving in a circle let radius_degrees = 0.1; let mut count = 0.0_f64; let center_lat = 50.91402185768586; let center_lon = 0.79203612077257; let creation_timestamp = now_timestamp(); // Continuously publish the entity loop { let latest_timestamp = now_timestamp(); // Update position count += 0.1; let t = PI * count / 180.0; // Derive the ENU velocity from the circular motion, then convert it // into a yaw angle and an attitude_enu quaternion. let lat = center_lat + radius_degrees * t.cos(); let meters_per_degree_lat = 111320.0_f64; let meters_per_degree_lon = 111320.0_f64 * lat.to_radians().cos(); let velocity_east = radius_degrees * t.cos() * meters_per_degree_lon; let velocity_north = -radius_degrees * t.sin() * meters_per_degree_lat; let yaw_rad = velocity_to_yaw_enu(velocity_east, velocity_north); // Create entity to publish let entity = Entity { entity_id: entity_id.clone(), description: "Friendly airplane".to_string(), aliases: Some(Aliases { name: "DL-1234".to_string(), ..Default::default() }), is_live: true, created_time: Some(creation_timestamp.clone()), expiry_time: Some(add_seconds(&latest_timestamp, 60)), ontology: Some(Ontology { template: Template::Track as i32, platform_type: "AIRPLANE".to_string(), ..Default::default() }), mil_view: Some(MilView { disposition: Disposition::Friendly as i32, environment: Environment::Air as i32, ..Default::default() }), location: Some(Location { position: Some(Position { latitude_degrees: center_lat + radius_degrees * t.cos(), longitude_degrees: center_lon + radius_degrees * t.sin(), altitude_hae_meters: Some(2994.0), altitude_agl_meters: Some(2972.8), ..Default::default() }), // Report the velocity in the ENU frame, in meters per second. // Vertical velocity is 0 at constant altitude. velocity_enu: Some(Enu { e: velocity_east, n: velocity_north, u: 0.0, }), // Report the heading as a quaternion derived from velocity. attitude_enu: Some(yaw_to_quaternion_enu(yaw_rad)), ..Default::default() }), provenance: Some(Provenance { integration_name: "your_integration_name".to_string(), data_type: "your_data_type".to_string(), source_update_time: Some(latest_timestamp.clone()), ..Default::default() }), ..Default::default() }; let request = Request::new(PublishEntityRequest { entity: Some(entity), }); match client.publish_entity(request).await { Ok(_) => println!("Publishing track with entity ID: {}", entity_id), Err(e) => eprintln!("Error publishing entity: {}", e), } tokio::time::sleep(Duration::from_secs(5)).await; } } ``` ## Publish a geo-entity A *geo-entity* is a shape, region, or point of interest drawn on the map, which may not physically exist. Use geo-entities to represent an geographical areas of interest, such as airfield, or a control zone for autonomous vehicles to operate in. To publish a geo-entity, do the following: #### Polygon Define the entity model's required components. Together with the geo-entity-specific fields, you get the following entity object: **`entity_model.json`** ```json maxLines=20 title="entity_model.json" { "entityId": "UNIQUE_ENTITY_ID", "description": "Polygon entity", "isLive": true, "createdTime": "2025-05-28T18:16:34.969Z", "noExpiry": true, "aliases": { "name": "Control-Area 1" }, "ontology": { "template": "TEMPLATE_GEO" }, "geoDetails": { "type": "GEO_TYPE_CONTROL_AREA", "controlArea": { "type": "CONTROL_AREA_TYPE_LOITER_ZONE" } }, "geoShape": { "polygon": { "rings": [ { "positions": [ { "position": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.646911, "longitudeDegrees": -117.929123, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.646911, "longitudeDegrees": -117.929123, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.635059, "longitudeDegrees": -117.917482, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.635059, "longitudeDegrees": -117.917482, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 } ] } ] } }, "provenance": { "integrationName": "your_integration_name", "dataType": "test_data", "sourceUpdateTime": "2025-05-28T18:17:34.866Z", } } ``` Take a closer look at the following components in `entity_model.json` and familiarize yourself with common patterns used to model a get-entity in Lattice. You define `rings` to create the polygon. Each `ring` must have *at least four* points, each represented by a `position` component. The last point must be the same as the first point you define for the `ring` component. **`ontology`** ```json title="ontology" "ontology": { // Set the required template. "template": "TEMPLATE_GEO" } ``` **`getDetails`** ```json title="getDetails" "geoDetails": { // Set the type of the geo-entity to GEO_TYPE_CONTROL_AREA. // This type is commonly used to represent special zones for // autonomous entities to operate in. "type": "GEO_TYPE_CONTROL_AREA", "controlArea": { "type": "CONTROL_AREA_TYPE_LOITER_ZONE" } } ``` **`geoShape`** ```json title="geoShape" "geoShape": { "polygon": { "rings": [ { "positions": [ { "position": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.646911, "longitudeDegrees": -117.929123, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.646911, "longitudeDegrees": -117.929123, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.635059, "longitudeDegrees": -117.917482, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.635059, "longitudeDegrees": -117.917482, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 }, { "position": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "altitudeAglMeters": 500 }, "location": { "latitudeDegrees": 33.641132, "longitudeDegrees": -117.918669, "additionalAltitudes": [ { "agl": { "valueMeters": 500 } } ] }, "heightM": 1 } ] } ] } ``` Each point in the polygon has a minimum altitude defined as `altitudeAglMeters`. The first and the last point have a `heightM` component, which sets additional height, in meters, above `altitudeAglMeters`. This represents a three-sided polygon that defines a control area. Control areas include, for example, `CONTROL_AREA_TYPE_KEEP_IN_ZONE`, `CONTROL_AREA_TYPE_KEEP_OUT_ZONE`, and `CONTROL_AREA_TYPE_LOITER_ZONE`. Use the [`PublishEntity`](/reference/rest/entities/publish-entity) API method to publish the entity: ```go title={"Go (REST)"} startLine={93} maxLines={20} package main import ( "context" "fmt" "net/http" "os" "time" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" "github.com/google/uuid" ) func main() { // Get environment variables latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Initialize headers for sandbox authorization headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) // Create the client LatticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Generate a unique ID for the entity entityId := uuid.New().String() // Get creation time creationTime := time.Now().UTC() // Entities must be republished at least every 5 minutes to persist across Lattice service restarts for { latestTimestamp := time.Now().UTC() ctx := context.Background() // Create positions for the polygon positions := []*Lattice.GeoPolygonPosition{ // First point { Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(33.641132), LongitudeDegrees: Lattice.Float64(-117.918669), AltitudeAglMeters: Lattice.Float64(500), }, HeightM: Lattice.Float64(1), }, // Second point { Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(33.646911), LongitudeDegrees: Lattice.Float64(-117.929123), AltitudeAglMeters: Lattice.Float64(500), }, HeightM: Lattice.Float64(1), }, // Third point { Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(33.635059), LongitudeDegrees: Lattice.Float64(-117.917482), AltitudeAglMeters: Lattice.Float64(500), }, HeightM: Lattice.Float64(1), }, // Fourth point (same as first to close the polygon) { Position: &Lattice.Position{ LatitudeDegrees: Lattice.Float64(33.641132), LongitudeDegrees: Lattice.Float64(-117.918669), AltitudeAglMeters: Lattice.Float64(500), }, HeightM: Lattice.Float64(1), }, } // Create entity to publish entity := Lattice.Entity{ EntityID: &entityId, Description: Lattice.String("Polygon entity"), Aliases: &Lattice.Aliases{ Name: Lattice.String("Control-Area Loiter Zone"), }, IsLive: Lattice.Bool(true), CreatedTime: Lattice.Time(creationTime), NoExpiry: Lattice.Bool(true), Ontology: &Lattice.Ontology{ Template: Lattice.OntologyTemplateTemplateGeo.Ptr(), }, GeoDetails: &Lattice.GeoDetails{ Type: Lattice.GeoDetailsTypeGeoTypeControlArea.Ptr(), ControlArea: &Lattice.ControlAreaDetails{ Type: Lattice.ControlAreaDetailsTypeControlAreaTypeLoiterZone.Ptr(), }, }, GeoShape: &Lattice.GeoShape{ Polygon: &Lattice.GeoPolygon{ Rings: []*Lattice.LinearRing{ { Positions: positions, }, }, }, }, Provenance: &Lattice.Provenance{ IntegrationName: Lattice.String("your_integration_name"), DataType: Lattice.String("test_data"), SourceUpdateTime: Lattice.Time(latestTimestamp), }, } // Publish the entity _, err := LatticeClient.Entities.PublishEntity(ctx, &entity) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Println("Publishing geo entity") } // Republishing every 10 seconds time.Sleep(10 * time.Second) } } ``` ```java title={"Java (REST)"} startLine={140} maxLines={20} package org.example; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import com.anduril.Lattice; import com.anduril.types.Aliases; import com.anduril.types.ControlAreaDetails; import com.anduril.types.ControlAreaDetailsType; import com.anduril.types.Entity; import com.anduril.types.GeoDetails; import com.anduril.types.GeoDetailsType; import com.anduril.types.GeoPolygon; import com.anduril.types.GeoPolygonPosition; import com.anduril.types.GeoShape; import com.anduril.types.LinearRing; import com.anduril.types.Ontology; import com.anduril.types.OntologyTemplate; import com.anduril.types.Position; import com.anduril.types.Provenance; public class PublishGeoEntityPolygon { public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxesToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables and ensure URL has https:// if (endpoint == null || environmentToken == null || sandboxesToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { // Create Lattice client with Sandbox authentication Lattice client = Lattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxesToken) .build(); // Generate a unique ID for the entity String entityId = UUID.randomUUID().toString(); // Get creation time Instant creationTime = Instant.now(); // Entities must be republished at least every 5 minutes to persist across Lattice service restarts while (true) { try { // Get current timestamp Instant latestTimestamp = Instant.now(); // Create positions for the polygon List positions = new ArrayList<>(); // First point Position pos1 = Position.builder() .latitudeDegrees(33.641132) .longitudeDegrees(-117.918669) .altitudeAglMeters(500.0) .build(); GeoPolygonPosition geoPos1 = GeoPolygonPosition.builder() .position(pos1) .heightM(1.0f) .build(); positions.add(geoPos1); // Second point Position pos2 = Position.builder() .latitudeDegrees(33.646911) .longitudeDegrees(-117.929123) .altitudeAglMeters(500.0) .build(); GeoPolygonPosition geoPos2 = GeoPolygonPosition.builder() .position(pos2) .heightM(1.0f) .build(); positions.add(geoPos2); // Third point Position pos3 = Position.builder() .latitudeDegrees(33.635059) .longitudeDegrees(-117.917482) .altitudeAglMeters(500.0) .build(); GeoPolygonPosition geoPos3 = GeoPolygonPosition.builder() .position(pos3) .heightM(1.0f) .build(); positions.add(geoPos3); // Fourth point (same as first to close the polygon) Position pos4 = Position.builder() .latitudeDegrees(33.641132) .longitudeDegrees(-117.918669) .altitudeAglMeters(500.0) .build(); GeoPolygonPosition geoPos4 = GeoPolygonPosition.builder() .position(pos4) .heightM(1.0f) .build(); positions.add(geoPos4); // Create entity objects ControlAreaDetails controlArea = ControlAreaDetails.builder() .type(ControlAreaDetailsType.CONTROL_AREA_TYPE_LOITER_ZONE) .build(); GeoDetails geoDetails = GeoDetails.builder() .type(GeoDetailsType.GEO_TYPE_CONTROL_AREA) .controlArea(controlArea) .build(); LinearRing ring = LinearRing.builder() .positions(positions) .build(); GeoPolygon polygon = GeoPolygon.builder() .rings(Arrays.asList(ring)) .build(); GeoShape geoShape = GeoShape.builder() .polygon(polygon) .build(); // Create the entity Entity entity = Entity.builder() .entityId(entityId) .description("Polygon entity") .aliases(Aliases.builder() .name("Control-Area Loiter Zone") .build()) .isLive(true) .createdTime(OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) .noExpiry(true) .ontology(Ontology.builder() .template(OntologyTemplate.TEMPLATE_GEO) .build()) .geoDetails(geoDetails) .geoShape(geoShape) .provenance(Provenance.builder() .integrationName("your_integration_name") .dataType("test_data") .sourceUpdateTime(OffsetDateTime.ofInstant(latestTimestamp, ZoneOffset.UTC)) .build()) .build(); // Publish the entity client.entities().publishEntity(entity); System.out.println("Published geo entity ID: " + entityId); // Republishing every 10 seconds Thread.sleep(10000); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); Thread.sleep(10000); } } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={31} maxLines={20} from anduril import Lattice, Aliases, GeoDetails, GeoShape, GeoPolygon, \ GeoPolygonPosition, Position, ControlAreaDetails, LinearRing, Ontology, Provenance from datetime import datetime, timezone import asyncio import os import sys from uuid import uuid4 lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables.") sys.exit(1) client = Lattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) id = str(uuid4()) async def app(): try: creation_time = datetime.now(timezone.utc) # Entities must be republished at least every 5 minutes to persist across Lattice service restarts while(True): latest_timestamp = datetime.now(timezone.utc) client.entities.publish_entity( entity_id= id, description="Control area", aliases=Aliases( name="Loiter Zone 1", ), is_live=True, created_time=creation_time, no_expiry=True, ontology=Ontology( template="TEMPLATE_GEO" ), geo_details=GeoDetails( type="GEO_TYPE_CONTROL_AREA", control_area=ControlAreaDetails( type="CONTROL_AREA_TYPE_LOITER_ZONE" ) ), geo_shape=GeoShape( polygon=GeoPolygon( rings=[ LinearRing( positions=[ GeoPolygonPosition( position=Position( latitude_degrees=33.641132, longitude_degrees=-117.918669, altitude_agl_meters=500 ), height_m=1 ), GeoPolygonPosition( position=Position( latitude_degrees=33.646911, longitude_degrees=-117.929123, altitude_agl_meters=500 ), height_m=1 ), GeoPolygonPosition( position=Position( latitude_degrees=33.635059, longitude_degrees=-117.917482, altitude_agl_meters=500 ), height_m=1 ), GeoPolygonPosition( position=Position( latitude_degrees=33.641132, longitude_degrees=-117.918669, altitude_agl_meters=500 ), height_m=1 ) ] ) ] ) ), provenance=Provenance( integration_name="your_integration_name", data_type="test_data", source_update_time=latest_timestamp ) ) # Republish every 5 seconds await asyncio.sleep(5) except asyncio.CancelledError: print(">>>Exiting...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={28} maxLines={20} import { LatticeClient } from "@anduril-industries/lattice-sdk"; import { v4 as uuidv4 } from 'uuid'; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following if you are not developing on Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); const entityId = uuidv4() async function App() { try { const latestTimestamp = new Date(); await client.entities.publishEntity( { entityId: entityId, description: "Polygon entity", aliases: { name: "Control-Area Loiter Zone" }, isLive: true, createdTime: latestTimestamp.toISOString(), noExpiry: true, // Entities with noExpiry still need to be republished every 5 minutes to persist across service restarts ontology: { template: "TEMPLATE_GEO" }, geoDetails: { type: "GEO_TYPE_CONTROL_AREA", controlArea: { type: "CONTROL_AREA_TYPE_LOITER_ZONE" } }, geoShape: { polygon: { rings: [ { positions: [ { position: { latitudeDegrees: 33.641132, longitudeDegrees: -117.918669, altitudeAglMeters: 500 }, heightM: 1 }, { position: { latitudeDegrees: 33.646911, longitudeDegrees: -117.929123, altitudeAglMeters: 500 }, heightM: 1 }, { position: { latitudeDegrees: 33.635059, longitudeDegrees: -117.917482, altitudeAglMeters: 500 }, heightM: 1 }, { position: { latitudeDegrees: 33.641132, longitudeDegrees: -117.918669, altitudeAglMeters: 500 }, heightM: 1 } ] } ] } }, provenance: { integrationName: "your_integration_name", dataType: "your_data_type", sourceUpdateTime: latestTimestamp.toISOString() } } ); console.log(`Publishing geo entity.`); } catch (error) { console.log(`Encountered the following error while publishing entity: ${error}`); } } (async function runIndefinitely() { while (true) { await App(); // Wait for 10 seconds before the next iteration // Republishing entities at least every 5 minutes is required for them to persist across Lattice service restarts // This example republishes every 10 seconds, well within the 5-minute requirement await new Promise(resolve => setTimeout(resolve, 10000)); } })(); ``` ```go title={"Go (gRPC)"} startLine={109} maxLines={20} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "fmt" "log" "os" "time" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) func main() { // Get environment variables environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") // Check required environment variables if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } // Setup authentication auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } // Setup gRPC connection options opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } // Create gRPC connection conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() // Create EntityManager client client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) // Generate a unique ID for the entity entityId := uuid.New().String() // Get creation time creationTime := time.Now().UTC() creationTimestamp := timestamppb.New(creationTime) // Create positions for the polygon positions := []*entitymanagerv1.GeoPolygonPosition{ // First point { Position: &entitymanagerv1.Position{ LatitudeDegrees: 33.641132, LongitudeDegrees: -117.918669, AltitudeAglMeters: wrapperspb.Double(500), }, HeightM: wrapperspb.Float(1), }, // Second point { Position: &entitymanagerv1.Position{ LatitudeDegrees: 33.646911, LongitudeDegrees: -117.929123, AltitudeAglMeters: wrapperspb.Double(500), }, HeightM: wrapperspb.Float(1), }, // Third point { Position: &entitymanagerv1.Position{ LatitudeDegrees: 33.635059, LongitudeDegrees: -117.917482, AltitudeAglMeters: wrapperspb.Double(500), }, HeightM: wrapperspb.Float(1), }, // Fourth point (same as first to close the polygon) { Position: &entitymanagerv1.Position{ LatitudeDegrees: 33.641132, LongitudeDegrees: -117.918669, AltitudeAglMeters: wrapperspb.Double(500), }, HeightM: wrapperspb.Float(1), }, } // Continuously publish the entity for { // Get current timestamp for this update latestTimestamp := time.Now().UTC() ctx := context.Background() // Create entity to publish entity := &entitymanagerv1.Entity{ EntityId: entityId, Description: "Polygon entity", Aliases: &entitymanagerv1.Aliases{ Name: "Control-Area Loiter Zone", }, IsLive: true, CreatedTime: creationTimestamp, NoExpiry: true, Ontology: &entitymanagerv1.Ontology{ Template: entitymanagerv1.Template_TEMPLATE_GEO, }, GeoDetails: &entitymanagerv1.GeoDetails{ Type: entitymanagerv1.GeoType_GEO_TYPE_CONTROL_AREA, TypeDetails: &entitymanagerv1.GeoDetails_ControlArea{ ControlArea: &entitymanagerv1.ControlAreaDetails{ Type: entitymanagerv1.ControlAreaType_CONTROL_AREA_TYPE_LOITER_ZONE, }, }, }, GeoShape: &entitymanagerv1.GeoShape{ Shape: &entitymanagerv1.GeoShape_Polygon{ Polygon: &entitymanagerv1.GeoPolygon{ Rings: []*entitymanagerv1.LinearRing{ { Positions: positions, }, }, }, }, }, Provenance: &entitymanagerv1.Provenance{ IntegrationName: "your_integration_name", DataType: "test_data", SourceUpdateTime: timestamppb.New(latestTimestamp), }, } // Create publish request request := &entitymanagerv1.PublishEntityRequest{ Entity: entity, } // Publish the entity _, err := client.PublishEntity(ctx, request) // Handle errors if err != nil { fmt.Printf("Error publishing entity: %v\n", err) } else { fmt.Println("Publishing geo entity") } // Republishing every 10 seconds time.Sleep(10 * time.Second) } } ``` ```py title={"Python (gRPC)"} startLine={100} maxLines={20} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import os import sys import time import uuid from datetime import datetime, timezone from bearer_auth import create_client from protobuf import Oneof from protobuf.wkt import DoubleValue, FloatValue, Timestamp from anduril.entitymanager.v1.entity_pub_pb import Aliases, Entity, Provenance from anduril.entitymanager.v1.entity_manager_api_pub_pb import PublishEntityRequest from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync from anduril.entitymanager.v1.geoentity_pub_pb import ( ControlAreaDetails, ControlAreaType, GeoDetails, GeoPolygon, GeoPolygonPosition, GeoShape, GeoType, LinearRing, ) from anduril.entitymanager.v1.location_pub_pb import Position from anduril.entitymanager.v1.ontology_pub_pb import Ontology from anduril.entitymanager.v1.types_pub_pb import Template def to_timestamp(dt: datetime) -> Timestamp: return Timestamp.from_datetime(dt) def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) entity_id = str(uuid.uuid4()) creation_timestamp = to_timestamp(datetime.now(timezone.utc)) positions = [ # First point GeoPolygonPosition( position=Position( latitude_degrees=33.641132, longitude_degrees=-117.918669, altitude_agl_meters=DoubleValue(value=500), ), height_m=FloatValue(value=1), ), # Second point GeoPolygonPosition( position=Position( latitude_degrees=33.646911, longitude_degrees=-117.929123, altitude_agl_meters=DoubleValue(value=500), ), height_m=FloatValue(value=1), ), # Third point GeoPolygonPosition( position=Position( latitude_degrees=33.635059, longitude_degrees=-117.917482, altitude_agl_meters=DoubleValue(value=500), ), height_m=FloatValue(value=1), ), # Fourth point (same as first to close the polygon) GeoPolygonPosition( position=Position( latitude_degrees=33.641132, longitude_degrees=-117.918669, altitude_agl_meters=DoubleValue(value=500), ), height_m=FloatValue(value=1), ), ] while True: latest_ts = to_timestamp(datetime.now(timezone.utc)) entity = Entity( entity_id=entity_id, description="Polygon entity", aliases=Aliases(name="Control-Area Loiter Zone"), is_live=True, created_time=creation_timestamp, no_expiry=True, ontology=Ontology(template=Template.GEO), geo_details=GeoDetails( type=GeoType.CONTROL_AREA, type_details=Oneof( "control_area", ControlAreaDetails( type=ControlAreaType.LOITER_ZONE, ), ), ), geo_shape=GeoShape( shape=Oneof( "polygon", GeoPolygon( rings=[LinearRing(positions=positions)], ), ), ), provenance=Provenance( integration_name="your_integration_name", data_type="test_data", source_update_time=latest_ts, ), ) try: client.publish_entity(PublishEntityRequest(entity=entity)) print("Publishing geo entity") except Exception as error: print(f"Error publishing entity: {error}", file=sys.stderr) # Republishing every 10 seconds time.sleep(10) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={116} maxLines={20} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::{ geo_details, geo_shape, Aliases, ControlAreaDetails, ControlAreaType, Entity, GeoDetails, GeoPolygon, GeoPolygonPosition, GeoShape, GeoType, LinearRing, Ontology, Position, Provenance, PublishEntityRequest, Template, }; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use prost_types::Timestamp; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use uuid::Uuid; use std::env; use std::time::{Duration, SystemTime, UNIX_EPOCH}; fn now_timestamp() -> Timestamp { let d = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); Timestamp { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32, } } #[tokio::main] async fn main() -> Result<(), Box> { let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); let entity_id = Uuid::new_v4().to_string(); let creation_timestamp = now_timestamp(); // Create positions for the polygon let positions = vec![ // First point GeoPolygonPosition { position: Some(Position { latitude_degrees: 33.641132, longitude_degrees: -117.918669, altitude_agl_meters: Some(500.0), ..Default::default() }), height_m: Some(1.0), }, // Second point GeoPolygonPosition { position: Some(Position { latitude_degrees: 33.646911, longitude_degrees: -117.929123, altitude_agl_meters: Some(500.0), ..Default::default() }), height_m: Some(1.0), }, // Third point GeoPolygonPosition { position: Some(Position { latitude_degrees: 33.635059, longitude_degrees: -117.917482, altitude_agl_meters: Some(500.0), ..Default::default() }), height_m: Some(1.0), }, // Fourth point (same as first to close the polygon) GeoPolygonPosition { position: Some(Position { latitude_degrees: 33.641132, longitude_degrees: -117.918669, altitude_agl_meters: Some(500.0), ..Default::default() }), height_m: Some(1.0), }, ]; // Continuously publish the entity loop { let latest_timestamp = now_timestamp(); let entity = Entity { entity_id: entity_id.clone(), description: "Polygon entity".to_string(), aliases: Some(Aliases { name: "Control-Area Loiter Zone".to_string(), ..Default::default() }), is_live: true, created_time: Some(creation_timestamp.clone()), no_expiry: true, ontology: Some(Ontology { template: Template::Geo as i32, ..Default::default() }), geo_details: Some(GeoDetails { r#type: GeoType::ControlArea as i32, type_details: Some(geo_details::TypeDetails::ControlArea(ControlAreaDetails { r#type: ControlAreaType::LoiterZone as i32, })), visual_details: None, }), geo_shape: Some(GeoShape { shape: Some(geo_shape::Shape::Polygon(GeoPolygon { rings: vec![LinearRing { positions: positions.clone(), }], is_rectangle: false, })), }), provenance: Some(Provenance { integration_name: "your_integration_name".to_string(), data_type: "test_data".to_string(), source_update_time: Some(latest_timestamp.clone()), ..Default::default() }), ..Default::default() }; let request = Request::new(PublishEntityRequest { entity: Some(entity), }); match client.publish_entity(request).await { Ok(_) => println!("Publishing geo entity"), Err(e) => eprintln!("Error publishing entity: {}", e), } // Republish every 10 seconds tokio::time::sleep(Duration::from_secs(10)).await; } } ``` The example uses [`noExpiry`](/reference/rest/entities/publish-entity#request.body.noExpiry) instead of `expiryTime` used in the previous steps. Setting `noExpiry` to `true` indicates that the entity does not expire and persists indefinitely. Use this option *only* when the entity contains information that should be available to other tasks or integrations beyond its immediate operational context. In this case we assume that this long-living geographical entity maintains persistent relevance across multiple operations or tasks. Even when using `noExpiry: true`, entities must be republished at least every 5 minutes to ensure persistence across Lattice service restarts. If Lattice restarts and an entity hasn't been republished within the previous 5 minutes, it won't be restored after the restart. This is demonstrated in the example code, which republishes the entity every 10 seconds in an indefinite loop. ## Set platform type For iconography within Lattice, add a [`platform_type`](/reference/rest/entities/get-entity#response.body.ontology.platformType) value to the `ontology` component. The Lattice UI supports the following types: #### Airplane **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "AIRPLANE" } ``` ![Shows the airplane platform type icon.](/_fern-img/0d4df04b2e1acf6a57abafd39ded3e824cf0f46db3766b09c9eb07cb508966e3.webp) #### Animal **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "ANIMAL" } ``` ![Shows the animal platform type icon.](/_fern-img/7a1fefc3547f6948eba052eece6ddc337104877bd629ec458fd072352e827007.webp) #### Car **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "CAR" } ``` ![Shows the car platform type icon.](/_fern-img/f67c3d700204f4058408001cb908f3cdbfa62959b9f20592a3a5c2a0d01036d4.webp) #### Person **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "PERSON" } ``` ![Shows the person platform type icon.](/_fern-img/81fac650252b5f3243b15b0b11d2a37211528aa0b9544cb4c521d1a3b96d4228.webp) #### Radar **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_SENSOR_POINT_OF_INTEREST", "platform_type": "RADAR" } ``` ![Shows the radar platform type icon.](/_fern-img/4ba629d2b78c8f01afdc7c69805517fd3b06d0556150869bec56bdc0bd5aa0ed.webp) #### Satellite **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_SIGNAL_POINT_OF_INTEREST", "platform_type": "SATELLITE" } ``` ![Shows the satellite platform type icon.](/_fern-img/0e60836a67e8cebaa8b3f6c64007deb2551bdb9f73d452f14a392f22906c7d10.webp) #### Surface Vessel **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "SURFACE VESSEL" } ``` ![Shows the surface vessel platform type icon.](/_fern-img/4f4fb41d2f34e7fd61213c890470f78d30f7d01dd6838024d0a5032980e5cc3f.webp) #### Submarine **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_ASSET", "platform_type": "SUBMARINE" } ``` ![Shows the submarine platform type icon.](/_fern-img/4fe192198e46a35bd40911ee0ba9ac2a035a7edf1c2cb566c8cf08fe42d6294e.webp) #### UAV **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_ASSET", "platform_type": "UAV" } ``` ![Shows the UAV platform type icon.](/_fern-img/a4aa98326fc9decf78dfc44235ad8a58c6643ecb59913bcbfb89aec464af74a7.webp) #### Vehicle **`ontology`** ```json maxLines=5 title="ontology" "ontology": { "template": "TEMPLATE_TRACK", "platform_type": "VEHICLE" } ``` ![Shows the vehicle platform type icon.](/_fern-img/600f77e2bff8c1f962ec2402444c887de1b655516a5d2f4c136d943a24af7e0f.webp) ## What's next? * Learn more about the Entities API in [REST](/reference/rest/entities/publish-entity) and [gRPC](/reference/grpc/anduril-entitymanager-v-1/entity-manager-api/anduril-entitymanager-v-1-publish-entity). * Learn how to [task an asset](/guides/tasks/overview). * Check out the Lattice [sample apps](/samples/overview). > Common patterns for representing entities in Lattice using the Lattice SDK