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 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.

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 },
14 "aliases": {
15 "name": "Drone 1"
16 },
17 "milView": {
18 "disposition": "DISPOSITION_FRIENDLY",
19 "environment": "ENVIRONMENT_AIR"
20 },
21 "ontology": {
22 "platformType": "UAV",
23 "template": "TEMPLATE_ASSET"
24 },
25 "provenance": {
26 "integrationName": "your_integration_name",
27 "dataType": "your_data_type",
28 "sourceUpdateTime": "2025-05-19T20:02:16.753Z"
29 },
30 "taskCatalog": {
31 "taskDefinitions": [
32 {
33 "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.VisualId",
34 },
35 {
36 "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.Monitor",
37 },
38 {
39 "taskSpecificationUrl": "type.googleapis.com/anduril.tasks.v2.Investigate"
40 }
41 ]
42 },
43 "health": {
44 "connectionStatus": "CONNECTION_STATUS_ONLINE",
45 "healthStatus": "HEALTH_STATUS_HEALTHY",
46 "updateTime": "2025-01-01T00:00:00.000Z",
47 }
48}
2

Take a closer look at the following components in entity.json and familiarize yourself with common pattenrs 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. 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.
1"ontology": {
2 // Set the required template.
3 "template": "TEMPLATE_ASSET",
4 // Optionally, set platform_type to UAV.
5 "platform_type": "UAV"
6}
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/v2"
12 "github.com/anduril/lattice-sdk-go/v2/client"
13 "github.com/anduril/lattice-sdk-go/v2/option"
14 "github.com/google/uuid"
15)
16
17func main() {
18 // Get environment variables
19 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
20 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
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 == "" || environmentToken == "" || 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.WithToken(environmentToken),
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 // Set a radius, in degrees, to simulate the entity moving in a circle
46 radiusDegrees := 0.1
47 count := 0.0
48 creationTime := time.Now().UTC()
49
50 // Continuously publish the entity
51 for {
52 latestTimestamp := time.Now().UTC()
53 ctx := context.Background()
54
55 // Update position
56 count += 0.1
57 t := math.Pi * count / 180.0
58
59 // Create entity to publish
60 entity := Lattice.Entity{
61 EntityID: &entityId,
62 Description: Lattice.String("Friendly drone asset"),
63 Aliases: &Lattice.Aliases{
64 Name: Lattice.String("Drone 1"),
65 },
66 IsLive: Lattice.Bool(true),
67 CreatedTime: Lattice.Time(creationTime),
68 ExpiryTime: Lattice.Time(creationTime.Add(1 * time.Minute)),
69 Ontology: &Lattice.Ontology{
70 Template: Lattice.OntologyTemplateTemplateAsset.Ptr(),
71 PlatformType: Lattice.String("UAV"),
72 },
73 MilView: &Lattice.MilView{
74 Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(),
75 Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(),
76 },
77 Location: &Lattice.Location{
78 Position: &Lattice.Position{
79 LatitudeDegrees: Lattice.Float64(50.91402185768586 + (radiusDegrees * math.Cos(t))),
80 LongitudeDegrees: Lattice.Float64(0.79203612077257 + (radiusDegrees * math.Sin(t))),
81 AltitudeAsfMeters: Lattice.Float64(1000),
82 },
83 },
84 Provenance: &Lattice.Provenance{
85 IntegrationName: Lattice.String("your_integration_name"),
86 DataType: Lattice.String("your_data_type"),
87 SourceUpdateTime: Lattice.Time(latestTimestamp),
88 },
89 Health: &Lattice.Health{
90 ConnectionStatus: Lattice.HealthConnectionStatusConnectionStatusOnline.Ptr(),
91 HealthStatus: Lattice.HealthHealthStatusHealthStatusHealthy.Ptr(),
92 UpdateTime: Lattice.Time(latestTimestamp),
93 },
94 TaskCatalog: &Lattice.TaskCatalog{
95 TaskDefinitions: []*Lattice.TaskDefinition{
96 {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.VisualId")},
97 {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.Monitor")},
98 {TaskSpecificationURL: Lattice.String("type.googleapis.com/anduril.tasks.v2.Investigate")},
99 },
100 },
101 }
102
103 // Publish the entity
104 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
105
106 // Handle errors
107 if err != nil {
108 fmt.Printf("Error publishing entity: %v\n", err)
109 } else {
110 fmt.Println("Publishing asset")
111 }
112
113 // Wait before next request
114 time.Sleep(1 * time.Second)
115 }
116}

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 },
26 "provenance": {
27 "integrationName": "your integration",
28 "dataType": "your_data_type",
29 "sourceUpdateTime": "2025-01-01T00:00:00.000000Z"
30 }
31}
2

Take a closer look at ocomponents 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}
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/v2"
12 "github.com/anduril/lattice-sdk-go/v2/client"
13 "github.com/anduril/lattice-sdk-go/v2/option"
14 "github.com/google/uuid"
15)
16
17func main() {
18 // Get environment variables
19 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
20 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
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 == "" || environmentToken == "" || 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.WithToken(environmentToken),
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 // Set a radius, in degrees, to simulate the entity moving in a circle
46 radiusDegrees := 0.1
47 count := 0.0
48 creationTime := time.Now().UTC()
49
50 // Continuously publish the entity
51 for {
52 latestTimestamp := time.Now().UTC()
53 ctx := context.Background()
54
55 // Update position
56 count += 0.1
57 t := math.Pi * count / 180.0
58
59 // Create entity to publish
60 entity := Lattice.Entity{
61 EntityID: &entityId,
62 Description: Lattice.String("Friendly airplane"),
63 Aliases: &Lattice.Aliases{
64 Name: Lattice.String("DL-1234"),
65 },
66 IsLive: Lattice.Bool(true),
67 CreatedTime: Lattice.Time(creationTime),
68 ExpiryTime: Lattice.Time(creationTime.Add(5 * time.Minute)),
69 Ontology: &Lattice.Ontology{
70 Template: Lattice.OntologyTemplateTemplateTrack.Ptr(),
71 PlatformType: Lattice.String("AIRPLANE"),
72 },
73 MilView: &Lattice.MilView{
74 Disposition: Lattice.MilViewDispositionDispositionFriendly.Ptr(),
75 Environment: Lattice.MilViewEnvironmentEnvironmentAir.Ptr(),
76 },
77 Location: &Lattice.Location{
78 Position: &Lattice.Position{
79 LatitudeDegrees: Lattice.Float64(50.91402185768586 + (radiusDegrees * math.Cos(t))),
80 LongitudeDegrees: Lattice.Float64(0.79203612077257 + (radiusDegrees * math.Sin(t))),
81 AltitudeHaeMeters: Lattice.Float64(2994),
82 AltitudeAglMeters: Lattice.Float64(2972.8),
83 },
84 },
85 Provenance: &Lattice.Provenance{
86 IntegrationName: Lattice.String("your_integration_name"),
87 DataType: Lattice.String("your_data_type"),
88 SourceUpdateTime: Lattice.Time(latestTimestamp),
89 },
90 }
91
92 // Publish the entity
93 _, err := LatticeClient.Entities.PublishEntity(ctx, &entity)
94
95 // Handle errors
96 if err != nil {
97 fmt.Printf("Error publishing entity: %v\n", err)
98 } else {
99 fmt.Println("Publishing track")
100 }
101
102 // Wait before next request
103 time.Sleep(1 * time.Second)
104 }
105}

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 "expiryTime": "2025-05-28T18:17:34.969Z",
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 pattenrs 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.

In the folloiwing example, you publish a three-sided polygon. 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:

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

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.

Specify motion

The entity location component contains kinematic fields, including position, attitude, and velocity, that represent the motion of entities over time. Third-party integrations are responsible for providing all available kinematic field data.

An entity contains four altitude references, specified in meters:

altitude_hae_meters
DoubleValue

The entity’s height above the World Geodetic System 984 (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 does not support Mean Sea Level (MSL) references, EGM-96 and the 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. We recommend using an open-source library, such as EGM96 for Go to apply this conversion

For example, to indicate to Lattice that a plane is flying at an altitude of 2,994 meters higher than the World Geodetic System 1984 (WGS-84) ellipsoid, you would populate an entity with the following data:

entity_model.json
1"location": {
2 "position": {
3 "latitudeDegrees": 42.2,
4 "longitudeDegrees": -71.1,
5 "altitudeHaeMeters": 2994.0,
6 "altitudeAglMeters": 2972.8
7 },
8}

Set transponder code

For most civilian aircraft it is common to assign a unique 24-bit ICAO address to identify the aircraft. In order to add a badge to Lattice with the ADS-B identifier, populate the Mode-S.Address field.

To indicate that an entity has an ADS-B transponder:

Shows an entity with the ADS-B transonder details displayed in the Lattice UI.

What’s next?