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 and set up a Lattice environment.
  • The following examples use uuid to generate unique entity IDs. To use the TypeScript examples, install @types/uuid:
    TypeScript
    $npm install @types/uuid
  • Learn about required components and various entity shapes in Lattice.
gRPC authentication

If you are using gRPC with client credentials, set up the token refresh module 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:

1

Define the entity model’s required components. Together with the asset-specific fields, you get the following entity object:

entity.json
1{
2 "entityId": "$ENTITY_ID",
3 "description": "Friendly drone asset",
4 "isLive": true,
5 "createdTime": "2025-01-01T00:00:00.000Z",
6 "expiryTime": "2025-01-01T01:00:00.000Z",
7 "location": {
8 "position": {
9 "latitudeDegrees": 51.013685596367665,
10 "longitudeDegrees": 0.8002299716355741,
11 "altitudeAsfMeters": 1000
12 },
13 "velocityEnu": {
14 "e": 50,
15 "n": 50,
16 "u": 0
17 },
18 "attitudeEnu": {
19 "x": 0,
20 "y": 0,
21 "z": 0.3827,
22 "w": 0.9239
23 }
24 },
25 "aliases": {
26 "name": "Drone 1"
27 },
28 "milView": {
29 "disposition": "DISPOSITION_FRIENDLY",
30 "environment": "ENVIRONMENT_AIR"
31 },
32 "ontology": {
33 "platformType": "UAV",
34 "template": "TEMPLATE_ASSET"
35 },
36 "provenance": {
37 "integrationName": "your_integration_name",
38 "dataType": "your_data_type",
39 "sourceUpdateTime": "2025-05-19T20:02:16.753Z"
40 },
41 "taskCatalog": {
42 "taskDefinitions": [
43 {
44 "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.VisualId",
45 },
46 {
47 "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.Investigate"
48 }
49 ]
50 },
51 "health": {
52 "connectionStatus": "CONNECTION_STATUS_ONLINE",
53 "healthStatus": "HEALTH_STATUS_HEALTHY",
54 "components": [
55 {
56 "id": "battery-0",
57 "name": "Battery",
58 "health": "HEALTH_STATUS_HEALTHY",
59 "messages": [
60 {
61 "status": "HEALTH_STATUS_HEALTHY",
62 "message": "Battery at 87% charge."
63 }
64 ],
65 "updateTime": "2025-01-01T00:00:00.000Z"
66 }
67 ],
68 "updateTime": "2025-01-01T00:00:00.000Z"
69 },
70 "sensors": {
71 "sensors": [
72 {
73 "sensorId": "camera-1",
74 "sensorType": "SENSOR_TYPE_CAMERA",
75 "operationalState": "OPERATIONAL_STATE_OPERATIONAL",
76 "fieldsOfView": [
77 {
78 "centerRayPose": {
79 "orientation": { "x": 0, "y": 0, "z": 0, "w": 1 }
80 },
81 "horizontalFov": 1.047,
82 "verticalFov": 0.785,
83 "range": 2000,
84 "mode": "SENSOR_MODE_SEARCH"
85 }
86 ]
87 }
88 ]
89 }
90}
2

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 to TEMPLATE_ASSET.
  • Set platform_type to UAVrendering a drone icon in the Lattice UI.
  • Use taskCatalog to publicize the assets tasks. The asset can listen for, and act upon, any task assigned to it, matching its specified taskDefinition.
  • Use 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.
1"ontology": {
2 // Set the required template.
3 "template": "TEMPLATE_ASSET",
4 // Optionally, set platform_type to UAV.
5 "platform_type": "UAV"
6}

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.

3

Use the PublishEntity API method to publish the entity:

1package main
2
3import (
4 "context"
5 "fmt"
6 "math"
7 "net/http"
8 "os"
9 "time"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14 "github.com/google/uuid"
15)
16
17// velocityToYawEnu converts an ENU velocity vector into a yaw angle, in
18// radians. It uses the ENU mathematical convention where a yaw of 0 points
19// East and a yaw of pi/2 points North.
20func velocityToYawEnu(velocityEast, velocityNorth float64) float64 {
21 return math.Atan2(velocityNorth, velocityEast)
22}
23
24// yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion.
25// Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z
26// and W components. The result is already unit-normalized.
27func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion {
28 return &Lattice.Quaternion{
29 X: Lattice.Float64(0.0),
30 Y: Lattice.Float64(0.0),
31 Z: Lattice.Float64(math.Sin(yawRad / 2.0)),
32 W: Lattice.Float64(math.Cos(yawRad / 2.0)),
33 }
34}
35
36func main() {
37 // Get environment variables
38 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
39 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
40 clientId := os.Getenv("LATTICE_CLIENT_ID")
41
42 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes
43 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
44
45 // Check required environment variables
46 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
47 fmt.Println("Missing required environment variables")
48 os.Exit(1)
49 }
50
51 // Initialize headers for sandbox authorization
52 headers := http.Header{}
53 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
54
55 // Create the client
56 LatticeClient := client.NewClient(
57 option.WithClientCredentials(clientId, clientSecret),
58 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
59 option.WithHTTPHeader(headers),
60 )
61
62 // Generate a unique ID for the entity
63 entityId := uuid.New().String()
64
65 // Set a radius, in degrees, to simulate the entity moving in a circle
66 radiusDegrees := 0.1
67 count := 0.0
68 centerLat := 50.91402185768586
69 centerLon := 0.79203612077257
70 creationTime := time.Now().UTC()
71
72 // Continuously publish the entity
73 for {
74 latestTimestamp := time.Now().UTC()
75 ctx := context.Background()
76
77 // Update position
78 count += 0.1
79 t := math.Pi * count / 180.0
80
81 // Derive the ENU velocity from the circular motion, then convert it
82 // into a yaw angle and an attitude_enu quaternion.
83 lat := centerLat + (radiusDegrees * math.Cos(t))
84 metersPerDegreeLat := 111320.0
85 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0)
86 velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon
87 velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat
88 yawRad := velocityToYawEnu(velocityEast, velocityNorth)
89
90 // Create entity to publish
91 entity := Lattice.Entity{
92 EntityID: &entityId,
93 Description: Lattice.String("Friendly drone asset"),
94 Aliases: &Lattice.Aliases{
95 Name: Lattice.String("Drone 1"),
96 },
97 IsLive: Lattice.Bool(true),
98 CreatedTime: Lattice.Time(creationTime),
99 ExpiryTime: Lattice.Time(latestTimestamp.Add(1 * time.Hour)),
100 Ontology: &Lattice.Ontology{
101 Template: Lattice.OntologyTemplateTemplateAsset.Ptr(),
102 PlatformType: Lattice.String("UAV"),
103 },
104 MilView: &Lattice.MilView{
105 Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(),
106 Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(),
107 },
108 Location: &Lattice.Location{
109 Position: &Lattice.Position{
110 LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))),
111 LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))),
112 AltitudeAsfMeters: Lattice.Float64(1000),
113 },
114 // Report the velocity in the ENU frame, in meters per second.
115 // Vertical velocity is 0 at constant altitude.
116 VelocityEnu: &Lattice.Enu{
117 E: Lattice.Float64(velocityEast),
118 N: Lattice.Float64(velocityNorth),
119 U: Lattice.Float64(0.0),
120 },
121 // Report the heading as a quaternion derived from velocity.
122 AttitudeEnu: yawToQuaternionEnu(yawRad),
123 },
124 Provenance: &Lattice.Provenance{
125 IntegrationName: Lattice.String("your_integration_name"),
126 DataType: Lattice.String("your_data_type"),
127 SourceUpdateTime: Lattice.Time(latestTimestamp),
128 },
129 Health: &Lattice.Health{
130 ConnectionStatus: Lattice.HealthConnectionStatusConnectionStatusOnline.Ptr(),
131 HealthStatus: Lattice.HealthHealthStatusHealthStatusHealthy.Ptr(),
132 // Report the health of individual subsystems in components.
133 // Each component needs a stable ID, a display name, and a
134 // health status. Here, the drone reports its battery as healthy.
135 Components: []*Lattice.ComponentHealth{
136 {
137 ID: Lattice.String("battery-0"),
138 Name: Lattice.String("Battery"),
139 Health: Lattice.ComponentHealthHealthHealthStatusHealthy.Ptr(),
140 Messages: []*Lattice.ComponentMessage{
141 {
142 Status: Lattice.ComponentMessageStatusHealthStatusHealthy.Ptr(),
143 Message: Lattice.String("Battery at 87% charge."),
144 },
145 },
146 UpdateTime: Lattice.Time(latestTimestamp),
147 },
148 },
149 // Live list of active alerts for the asset. Remove entries
150 // when the underlying condition clears; Lattice does not
151 // filter stale entries automatically.
152 ActiveAlerts: []*Lattice.Alert{},
153 UpdateTime: Lattice.Time(latestTimestamp),
154 },
155 // Declare the sensors carried by the asset. Each entry requires a
156 // stable sensor_id, a sensor_type, and at least one field of view
157 // so Lattice can render the sensor cone.
158 Sensors: &Lattice.Sensors{
159 Sensors: []*Lattice.Sensor{
160 {
161 SensorID: Lattice.String("camera-1"),
162 SensorType: Lattice.SensorSensorTypeSensorTypeCamera.Ptr(),
163 OperationalState: Lattice.SensorOperationalStateOperationalStateOperational.Ptr(),
164 FieldsOfView: []*Lattice.FieldOfView{
165 {
166 CenterRayPose: &Lattice.EntityManagerPose{
167 Orientation: &Lattice.Quaternion{
168 X: Lattice.Float64(0.0),
169 Y: Lattice.Float64(0.0),
170 Z: Lattice.Float64(0.0),
171 W: Lattice.Float64(1.0),
172 },
173 },
174 HorizontalFov: Lattice.Float64(1.047),
175 VerticalFov: Lattice.Float64(0.785),
176 Range: Lattice.Float64(2000.0),
177 Mode: Lattice.FieldOfViewModeSensorModeSearch.Ptr(),
178 },
179 },
180 },
181 },
182 },
183 TaskCatalog: &Lattice.TaskCatalog{
184 TaskDefinitions: []*Lattice.TaskDefinition{
185 {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.VisualId")},
186 {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.Investigate")},
187 },
188 },
189 }
190
191 // Publish the entity
192 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
193
194 // Handle errors
195 if err != nil {
196 fmt.Printf("Error publishing entity: %v\n", err)
197 } else {
198 fmt.Println("Publishing asset")
199 }
200
201 // Wait before next request
202 time.Sleep(1 * time.Second)
203 }
204}

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:

1

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
1{
2 "entityId": "UNIQUE_ENTITY_ID",
3 "description": "Airplane 1",
4 "isLive": true,
5 "createdTime": "2025-01-01T00:00:00.000000Z",
6 "expiryTime": "2025-01-01T00:01:00.000000Z",
7 "milView": {
8 "disposition": "DISPOSITION_FRIENDLY",
9 "environment": "ENVIRONMENT_AIR"
10 },
11 "ontology": {
12 "template": "TEMPLATE_TRACK",
13 "platform_type": "AIRPLANE"
14 },
15 "aliases": {
16 "name": "DL-1234"
17 },
18 "location": {
19 "position": {
20 "latitudeDegrees": 50.91402185768586,
21 "longitudeDegrees": 0.79203612077257,
22 "altitudeHaeMeters": 2994,
23 "altitudeAglMeters": 2972.8
24 },
25 "velocityEnu": {
26 "e": 50,
27 "n": 50,
28 "u": 0
29 },
30 "attitudeEnu": {
31 "x": 0,
32 "y": 0,
33 "z": 0.3827,
34 "w": 0.9239
35 }
36 },
37 "provenance": {
38 "integrationName": "your integration",
39 "dataType": "your_data_type",
40 "sourceUpdateTime": "2025-01-01T00:00:00.000000Z"
41 }
42}
2

Take a closer look at the components in entity.json to familiarize yourself with the track’s properties.

1"milView": {
2 // Set the disposition to friendly to represent a
3 // friendly airplane detection.
4 "disposition": "DISPOSITION_FRIENDLY",
5 // Set the environment field to specify the terrain in which
6 // detection is made.
7 "environment": "ENVIRONMENT_AIR"
8}

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.

3

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:

1package main
2
3import (
4 "context"
5 "fmt"
6 "math"
7 "net/http"
8 "os"
9 "time"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14 "github.com/google/uuid"
15)
16
17// velocityToYawEnu converts an ENU velocity vector into a yaw angle, in
18// radians. It uses the ENU mathematical convention where a yaw of 0 points
19// East and a yaw of pi/2 points North.
20func velocityToYawEnu(velocityEast, velocityNorth float64) float64 {
21 return math.Atan2(velocityNorth, velocityEast)
22}
23
24// yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion.
25// Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z
26// and W components. The result is already unit-normalized.
27func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion {
28 return &Lattice.Quaternion{
29 X: Lattice.Float64(0.0),
30 Y: Lattice.Float64(0.0),
31 Z: Lattice.Float64(math.Sin(yawRad / 2.0)),
32 W: Lattice.Float64(math.Cos(yawRad / 2.0)),
33 }
34}
35
36func main() {
37 // Get environment variables
38 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
39 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
40 clientId := os.Getenv("LATTICE_CLIENT_ID")
41
42 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes
43 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
44
45 // Check required environment variables
46 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
47 fmt.Println("Missing required environment variables")
48 os.Exit(1)
49 }
50
51 // Initialize headers for sandbox authorization
52 headers := http.Header{}
53 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
54
55 // Create the client
56 LatticeClient := client.NewClient(
57 option.WithClientCredentials(clientId, clientSecret),
58 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
59 option.WithHTTPHeader(headers),
60 )
61
62 // Generate a unique ID for the entity
63 entityId := uuid.New().String()
64
65 // Set a radius, in degrees, to simulate the entity moving in a circle
66 radiusDegrees := 0.1
67 count := 0.0
68 centerLat := 50.91402185768586
69 centerLon := 0.79203612077257
70 creationTime := time.Now().UTC()
71
72 // Continuously publish the entity
73 for {
74 latestTimestamp := time.Now().UTC()
75 ctx := context.Background()
76
77 // Update position
78 count += 0.1
79 t := math.Pi * count / 180.0
80
81 // Derive the ENU velocity from the circular motion, then convert it
82 // into a yaw angle and an attitude_enu quaternion.
83 lat := centerLat + (radiusDegrees * math.Cos(t))
84 metersPerDegreeLat := 111320.0
85 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0)
86 velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon
87 velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat
88 yawRad := velocityToYawEnu(velocityEast, velocityNorth)
89
90 // Create entity to publish
91 entity := Lattice.Entity{
92 EntityID: &entityId,
93 Description: Lattice.String("Friendly airplane"),
94 Aliases: &Lattice.Aliases{
95 Name: Lattice.String("DL-1234"),
96 },
97 IsLive: Lattice.Bool(true),
98 CreatedTime: Lattice.Time(creationTime),
99 ExpiryTime: Lattice.Time(latestTimestamp.Add(5 * time.Minute)),
100 Ontology: &Lattice.Ontology{
101 Template: Lattice.OntologyTemplateTemplateTrack.Ptr(),
102 PlatformType: Lattice.String("AIRPLANE"),
103 },
104 MilView: &Lattice.MilView{
105 Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(),
106 Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(),
107 },
108 Location: &Lattice.Location{
109 Position: &Lattice.Position{
110 LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))),
111 LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))),
112 AltitudeHaeMeters: Lattice.Float64(2994),
113 AltitudeAglMeters: Lattice.Float64(2972.8),
114 },
115 // Report the velocity in the ENU frame, in meters per second.
116 // Vertical velocity is 0 at constant altitude.
117 VelocityEnu: &Lattice.Enu{
118 E: Lattice.Float64(velocityEast),
119 N: Lattice.Float64(velocityNorth),
120 U: Lattice.Float64(0.0),
121 },
122 // Report the heading as a quaternion derived from velocity.
123 AttitudeEnu: yawToQuaternionEnu(yawRad),
124 },
125 Provenance: &Lattice.Provenance{
126 IntegrationName: Lattice.String("your_integration_name"),
127 DataType: Lattice.String("your_data_type"),
128 SourceUpdateTime: Lattice.Time(latestTimestamp),
129 },
130 }
131
132 // Publish the entity
133 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
134
135 // Handle errors
136 if err != nil {
137 fmt.Printf("Error publishing entity: %v\n", err)
138 } else {
139 fmt.Println("Publishing track")
140 }
141
142 // Wait before next request
143 time.Sleep(1 * time.Second)
144 }
145}
4

Use the PublishEntity API method to publish the entity. The example derives the velocity from the simulated circular motion, then populates velocity_enu and attitude_enu:

1package main
2
3import (
4 "context"
5 "fmt"
6 "math"
7 "net/http"
8 "os"
9 "time"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14 "github.com/google/uuid"
15)
16
17// velocityToYawEnu converts an ENU velocity vector into a yaw angle, in
18// radians. It uses the ENU mathematical convention where a yaw of 0 points
19// East and a yaw of pi/2 points North.
20func velocityToYawEnu(velocityEast, velocityNorth float64) float64 {
21 return math.Atan2(velocityNorth, velocityEast)
22}
23
24// yawToQuaternionEnu converts a yaw angle into the attitude_enu quaternion.
25// Yaw is a rotation about the ENU Up (Z) axis, so the quaternion only has Z
26// and W components. The result is already unit-normalized.
27func yawToQuaternionEnu(yawRad float64) *Lattice.Quaternion {
28 return &Lattice.Quaternion{
29 X: Lattice.Float64(0.0),
30 Y: Lattice.Float64(0.0),
31 Z: Lattice.Float64(math.Sin(yawRad / 2.0)),
32 W: Lattice.Float64(math.Cos(yawRad / 2.0)),
33 }
34}
35
36func main() {
37 // Get environment variables
38 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
39 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
40 clientId := os.Getenv("LATTICE_CLIENT_ID")
41
42 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes
43 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
44
45 // Check required environment variables
46 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
47 fmt.Println("Missing required environment variables")
48 os.Exit(1)
49 }
50
51 // Initialize headers for sandbox authorization
52 headers := http.Header{}
53 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
54
55 // Create the client
56 LatticeClient := client.NewClient(
57 option.WithClientCredentials(clientId, clientSecret),
58 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
59 option.WithHTTPHeader(headers),
60 )
61
62 // Generate a unique ID for the entity
63 entityId := uuid.New().String()
64
65 // Set a radius, in degrees, to simulate the entity moving in a circle
66 radiusDegrees := 0.1
67 count := 0.0
68 centerLat := 50.91402185768586
69 centerLon := 0.79203612077257
70 creationTime := time.Now().UTC()
71
72 // Continuously publish the entity
73 for {
74 latestTimestamp := time.Now().UTC()
75 ctx := context.Background()
76
77 // Update position
78 count += 0.1
79 t := math.Pi * count / 180.0
80
81 // Derive the ENU velocity from the circular motion, then convert it
82 // into a yaw angle and an attitude_enu quaternion.
83 lat := centerLat + (radiusDegrees * math.Cos(t))
84 metersPerDegreeLat := 111320.0
85 metersPerDegreeLon := 111320.0 * math.Cos(lat*math.Pi/180.0)
86 velocityEast := radiusDegrees * math.Cos(t) * metersPerDegreeLon
87 velocityNorth := -radiusDegrees * math.Sin(t) * metersPerDegreeLat
88 yawRad := velocityToYawEnu(velocityEast, velocityNorth)
89
90 // Create entity to publish
91 entity := Lattice.Entity{
92 EntityID: &entityId,
93 Description: Lattice.String("Friendly airplane"),
94 Aliases: &Lattice.Aliases{
95 Name: Lattice.String("DL-1234"),
96 },
97 IsLive: Lattice.Bool(true),
98 CreatedTime: Lattice.Time(creationTime),
99 ExpiryTime: Lattice.Time(latestTimestamp.Add(5 * time.Minute)),
100 Ontology: &Lattice.Ontology{
101 Template: Lattice.OntologyTemplateTemplateTrack.Ptr(),
102 PlatformType: Lattice.String("AIRPLANE"),
103 },
104 MilView: &Lattice.MilView{
105 Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(),
106 Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(),
107 },
108 Location: &Lattice.Location{
109 Position: &Lattice.Position{
110 LatitudeDegrees: Lattice.Float64(centerLat + (radiusDegrees * math.Cos(t))),
111 LongitudeDegrees: Lattice.Float64(centerLon + (radiusDegrees * math.Sin(t))),
112 AltitudeHaeMeters: Lattice.Float64(2994),
113 AltitudeAglMeters: Lattice.Float64(2972.8),
114 },
115 // Report the velocity in the ENU frame, in meters per second.
116 // Vertical velocity is 0 at constant altitude.
117 VelocityEnu: &Lattice.Enu{
118 E: Lattice.Float64(velocityEast),
119 N: Lattice.Float64(velocityNorth),
120 U: Lattice.Float64(0.0),
121 },
122 // Report the heading as a quaternion derived from velocity.
123 AttitudeEnu: yawToQuaternionEnu(yawRad),
124 },
125 Provenance: &Lattice.Provenance{
126 IntegrationName: Lattice.String("your_integration_name"),
127 DataType: Lattice.String("your_data_type"),
128 SourceUpdateTime: Lattice.Time(latestTimestamp),
129 },
130 }
131
132 // Publish the entity
133 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
134
135 // Handle errors
136 if err != nil {
137 fmt.Printf("Error publishing entity: %v\n", err)
138 } else {
139 fmt.Println("Publishing track")
140 }
141
142 // Wait before next request
143 time.Sleep(1 * time.Second)
144 }
145}

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:

1

Define the entity model’s required components. Together with the geo-entity-specific fields, you get the following entity object:

entity_model.json
1{
2 "entityId": "UNIQUE_ENTITY_ID",
3 "description": "Polygon entity",
4 "isLive": true,
5 "createdTime": "2025-05-28T18:16:34.969Z",
6 "noExpiry": true,
7 "aliases": {
8 "name": "Control-Area 1"
9 },
10 "ontology": {
11 "template": "TEMPLATE_GEO"
12 },
13 "geoDetails": {
14 "type": "GEO_TYPE_CONTROL_AREA",
15 "controlArea": {
16 "type": "CONTROL_AREA_TYPE_LOITER_ZONE"
17 }
18 },
19 "geoShape": {
20 "polygon": {
21 "rings": [
22 {
23 "positions": [
24 {
25 "position": {
26 "latitudeDegrees": 33.641132,
27 "longitudeDegrees": -117.918669,
28 "altitudeAglMeters": 500
29 },
30 "location": {
31 "latitudeDegrees": 33.641132,
32 "longitudeDegrees": -117.918669,
33 "additionalAltitudes": [
34 {
35 "agl": {
36 "valueMeters": 500
37 }
38 }
39 ]
40 },
41 "heightM": 1
42 },
43 {
44 "position": {
45 "latitudeDegrees": 33.646911,
46 "longitudeDegrees": -117.929123,
47 "altitudeAglMeters": 500
48 },
49 "location": {
50 "latitudeDegrees": 33.646911,
51 "longitudeDegrees": -117.929123,
52 "additionalAltitudes": [
53 {
54 "agl": {
55 "valueMeters": 500
56 }
57 }
58 ]
59 },
60 "heightM": 1
61 },
62 {
63 "position": {
64 "latitudeDegrees": 33.635059,
65 "longitudeDegrees": -117.917482,
66 "altitudeAglMeters": 500
67 },
68 "location": {
69 "latitudeDegrees": 33.635059,
70 "longitudeDegrees": -117.917482,
71 "additionalAltitudes": [
72 {
73 "agl": {
74 "valueMeters": 500
75 }
76 }
77 ]
78 },
79 "heightM": 1
80 },
81 {
82 "position": {
83 "latitudeDegrees": 33.641132,
84 "longitudeDegrees": -117.918669,
85 "altitudeAglMeters": 500
86 },
87 "location": {
88 "latitudeDegrees": 33.641132,
89 "longitudeDegrees": -117.918669,
90 "additionalAltitudes": [
91 {
92 "agl": {
93 "valueMeters": 500
94 }
95 }
96 ]
97 },
98 "heightM": 1
99 }
100 ]
101 }
102 ]
103 }
104 },
105 "provenance": {
106 "integrationName": "your_integration_name",
107 "dataType": "test_data",
108 "sourceUpdateTime": "2025-05-28T18:17:34.866Z",
109 }
110}
2

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.

1"ontology": {
2 // Set the required template.
3 "template": "TEMPLATE_GEO"
4}

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.

3

Use the PublishEntity API method to publish the entity:

1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8 "time"
9
10 Lattice "github.com/anduril/lattice-sdk-go/v4"
11 "github.com/anduril/lattice-sdk-go/v4/client"
12 "github.com/anduril/lattice-sdk-go/v4/option"
13 "github.com/google/uuid"
14)
15
16func main() {
17 // Get environment variables
18 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
19 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
20 clientId := os.Getenv("LATTICE_CLIENT_ID")
21
22 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes
23 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
24
25 // Check required environment variables
26 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
27 fmt.Println("Missing required environment variables")
28 os.Exit(1)
29 }
30
31 // Initialize headers for sandbox authorization
32 headers := http.Header{}
33 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
34
35 // Create the client
36 LatticeClient := client.NewClient(
37 option.WithClientCredentials(clientId, clientSecret),
38 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
39 option.WithHTTPHeader(headers),
40 )
41
42 // Generate a unique ID for the entity
43 entityId := uuid.New().String()
44
45 // Get creation time
46 creationTime := time.Now().UTC()
47
48 // Entities must be republished at least every 5 minutes to persist across Lattice service restarts
49 for {
50 latestTimestamp := time.Now().UTC()
51 ctx := context.Background()
52
53 // Create positions for the polygon
54 positions := []*Lattice.GeoPolygonPosition{
55 // First point
56 {
57 Position: &Lattice.Position{
58 LatitudeDegrees: Lattice.Float64(33.641132),
59 LongitudeDegrees: Lattice.Float64(-117.918669),
60 AltitudeAglMeters: Lattice.Float64(500),
61 },
62 HeightM: Lattice.Float64(1),
63 },
64 // Second point
65 {
66 Position: &Lattice.Position{
67 LatitudeDegrees: Lattice.Float64(33.646911),
68 LongitudeDegrees: Lattice.Float64(-117.929123),
69 AltitudeAglMeters: Lattice.Float64(500),
70 },
71 HeightM: Lattice.Float64(1),
72 },
73 // Third point
74 {
75 Position: &Lattice.Position{
76 LatitudeDegrees: Lattice.Float64(33.635059),
77 LongitudeDegrees: Lattice.Float64(-117.917482),
78 AltitudeAglMeters: Lattice.Float64(500),
79 },
80 HeightM: Lattice.Float64(1),
81 },
82 // Fourth point (same as first to close the polygon)
83 {
84 Position: &Lattice.Position{
85 LatitudeDegrees: Lattice.Float64(33.641132),
86 LongitudeDegrees: Lattice.Float64(-117.918669),
87 AltitudeAglMeters: Lattice.Float64(500),
88 },
89 HeightM: Lattice.Float64(1),
90 },
91 }
92
93 // Create entity to publish
94 entity := Lattice.Entity{
95 EntityID: &entityId,
96 Description: Lattice.String("Polygon entity"),
97 Aliases: &Lattice.Aliases{
98 Name: Lattice.String("Control-Area Loiter Zone"),
99 },
100 IsLive: Lattice.Bool(true),
101 CreatedTime: Lattice.Time(creationTime),
102 NoExpiry: Lattice.Bool(true),
103 Ontology: &Lattice.Ontology{
104 Template: Lattice.OntologyTemplateTemplateGeo.Ptr(),
105 },
106 GeoDetails: &Lattice.GeoDetails{
107 Type: Lattice.GeoDetailsTypeGeoTypeControlArea.Ptr(),
108 ControlArea: &Lattice.ControlAreaDetails{
109 Type: Lattice.ControlAreaDetailsTypeControlAreaTypeLoiterZone.Ptr(),
110 },
111 },
112 GeoShape: &Lattice.GeoShape{
113 Polygon: &Lattice.GeoPolygon{
114 Rings: []*Lattice.LinearRing{
115 {
116 Positions: positions,
117 },
118 },
119 },
120 },
121 Provenance: &Lattice.Provenance{
122 IntegrationName: Lattice.String("your_integration_name"),
123 DataType: Lattice.String("test_data"),
124 SourceUpdateTime: Lattice.Time(latestTimestamp),
125 },
126 }
127
128 // Publish the entity
129 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
130
131 // Handle errors
132 if err != nil {
133 fmt.Printf("Error publishing entity: %v\n", err)
134 } else {
135 fmt.Println("Publishing geo entity")
136 }
137
138 // Republishing every 10 seconds
139 time.Sleep(10 * time.Second)
140 }
141}

The example uses 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 value to the ontology component. The Lattice UI supports the following types:

ontology
1"ontology": {
2 "template": "TEMPLATE_TRACK",
3 "platform_type": "AIRPLANE"
4}
Shows the airplane platform type icon.

What’s next?