Skip to main content

Publish entities to Lattice

This page covers how to create and update different types of known objects, known as entities in Lattice, with the Lattice SDK. For reference, see PublishEntity (gRPC | HTTP).

Before you begin

To publish entities, set up your environment.

Publish an entity

Create and publish entity objects:

  1. Add the following code to your app that creates a single entity in Lattice and replace $YOUR_LATTICE_URL and $YOUR_BEARER_TOKEN with your information:

    To create and update an entity, use the gRPC PublishEntity API.

     #include <thread>
    #include <google/protobuf/timestamp.pb.h>
    #include <google/protobuf/util/time_util.h>

    #include <grpc/grpc.h>
    #include <grpcpp/channel.h>
    #include <grpcpp/create_channel.h>
    #include <grpcpp/security/credentials.h>

    #include <anduril/entitymanager/v1/entity_manager_api.pub.grpc.pb.h>

    int main(int argc, char *argv[]) {
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    auto url = "$YOUR_LATTICE_URL:443";

    auto creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
    std::shared_ptr<anduril::entitymanager::v1::EntityManagerAPI::Stub> stub =
    anduril::entitymanager::v1::EntityManagerAPI::NewStub(grpc::CreateChannel(url, creds));

    double radius_degrees = 0.1;
    double count = 0.;
    google::protobuf::Timestamp start_time = google::protobuf::util::TimeUtil::GetCurrentTime();

    while(true) {
    grpc::ClientContext ctx;
    // Setting custom metadata to be sent to the server
    ctx.AddMetadata("authorization", "Bearer $YOUR_BEARER_TOKEN");

    double t = count * M_PI / 180.0;

    google::protobuf::Timestamp cur_time = google::protobuf::util::TimeUtil::GetCurrentTime();
    google::protobuf::Timestamp expiry_time = cur_time;

    expiry_time.set_seconds(cur_time.seconds() + 600); // entity expires in 10 minutes

    anduril::entitymanager::v1::PublishEntityRequest req;
    auto entity = req.mutable_entity();

    // define the entity
    entity->set_entity_id("C++ Entity (from docs)");

    *entity->mutable_aliases()->mutable_name() = "C++ Entity";

    *entity->mutable_created_time() = start_time;

    *entity->mutable_expiry_time() = expiry_time;

    entity->mutable_mil_view()->set_disposition(anduril::ontology::v1::DISPOSITION_FRIENDLY);
    entity->mutable_location()->mutable_position()->set_latitude_degrees(33.69447852698943 +
    (radius_degrees * std::cos(t)));
    entity->mutable_location()->mutable_position()->set_longitude_degrees(-117.9173785693163 +
    (radius_degrees * std::sin(t)));

    entity->mutable_ontology()->set_template_(anduril::entitymanager::v1::TEMPLATE_ASSET);

    entity->mutable_provenance()->set_integration_name("Anduril Docs");
    entity->mutable_provenance()->set_data_type("ANDURIL_DOCS");
    *entity->mutable_provenance()->mutable_source_update_time() = cur_time;

    entity->set_is_live(true);

    anduril::entitymanager::v1::PublishEntityResponse res;
    grpc::Status status = stub->PublishEntity(&ctx, req, &res);

    if (!status.ok()) {
    std::cerr << "Error code: " << status.error_code() << std::endl;
    std::cerr << "Error message: " << status.error_message() << std::endl;
    break;
    }

    count += .1;
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    return 0;
    }

    This code creates an entity that moves in a circle around Southern California and receives an update every 10 milliseconds.

  1. Verify the published entity in the $YOUR_LATTICE_URL/c2 page.

Customize entities

Customize entities by modifying the entity data:

  1. Update the following required components of the entity model:

    Component in entity modelDescription
    entity_idUnique string identifier. Can be a Globally Unique Identifier (GUID).
    expiry_timeExpiration time that must be greater than the current time and less than 30 days in the future. The Entities API will reject any entity update with an expiry_time in the past. When the expiry_time has passed, the Entities API will delete the entity from the COP and send a DELETE event.
    is_liveBoolean that when true, creates or updates the entity. If false and the entity is still live, triggers a DELETE event.
    provenance.integration_nameString that uniquely identifies the integration responsible for publishing the entity.
    provenance.data_typeString that identifies the source data type for the entity. Multiple integrations can publish entities of the same data type.
    provenance.source_update_timeLast modification time of the entity's data at the original source, which determines the acceptance of updates to an entity.
    aliases.nameHuman-readable string that represents the name of an entity.

    For more information on entity component fields, see the entity object API reference.

  2. Set the entity template in the ontology.template field.

    This field indicates which panel in the Lattice UI (track, asset, or geo-entity panel) to display entity details and render icon types. For more conceptual information on tracks, assets, and other entities, see Entity shapes.

  3. Depending on your entity shape, complete the following:

    To create an asset, define the ontology.template field with the TEMPLATE_ASSET value, and add the required location and milView fields to the entity.

    Asset entity example

    To use the following asset entity, replace the UNIQUE_ENTITY_ID with a GUID for your object in Lattice and ensure the time for ExpiryTime and SourceUpdateTime is current:


    {
    "entityId": "UNIQUE_ENTITY_ID",
    "description": "Test asset",
    "isLive": true,
    "createdTime": "2024-12-07T00:09:42.816877Z",
    "expiryTime": "2024-12-17T00:09:42.816878Z",
    "location": {
    "position": {
    "latitudeDegrees": 50.91402185768586,
    "longitudeDegrees": 0.79203612077257,
    "altitudeHaeMeters": 2994,
    "altitudeAglMeters": 2972.8
    }
    },
    "aliases": {
    "name": "Anduril Unmanned Surface Vessel"
    },
    "milView": {
    "disposition": "DISPOSITION_FRIENDLY",
    "environment": "ENVIRONMENT_SURFACE"
    },
    "ontology": {
    "template": "TEMPLATE_ASSET"
    },
    "provenance": {
    "integrationName": "Anduril",
    "dataType": "Radar",
    "sourceUpdateTime": "2024-12-07T00:09:42.816878Z"
    }
    }

    This sample adds an Entity representing a friendly surface vessel:

    Asset List

Assign entity icons

For iconography within Lattice, you must provide an entity's specific_type field and platform_type field. See the following example iconography:

Platform Type - Car

Specific Type - ""

Radar Icon

Platform Type - person

Specific Type - ""

Group 1-2 Icon

Platform Type - Group1-2

Specific Type - Rotary Wing

Group 1-2 Rotary

Platform Type - Surface_Vessel

Specific Type - ""

Group 1-2 Icon

Specify motion

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

Altitude

The data model contains four altitude references, specified in meters:

  • altitude_hae_meters: The entity's height above the World Geodetic System 1984 (WGS84) ellipsoid.
  • altitude_agl_meters: 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: The entity's height above the sea floor.
  • pressure_depth_meters: The depth of the entity from the surface of the water.
Support for MSL references

The data model does not currently support Mean Sea Level (MSL) references, such as the Earth Gravitational Model 1996 (EGM-96) and the Earth Gravitational Model 2008 (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. There are many open source libraries available (for example: Go) that allow for this conversion.

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

"location": {
"position": {
"latitudeDegrees": 42.2,
"longitudeDegrees": -71.1,
"altitudeHaeMeters": 2994.0,
"altitudeAglMeters": 2972.8
},
}

Attitude

The entity's attitude is represented by a quaternion in the attitude_enu field. This representation is used to translate from the entity's body frame to its East-North-Up (ENU) frame. If the attitude_enu field isn't populated, Lattice will use the velocity_enu field to calculate the heading for display in Lattice UI. If both fields are populated, Lattice will prioritize the value from attitude_enu.

For example, if your entity has a yaw value of 40 degrees, you would need to convert to the attitude_enu values with the following:

import math
from typing import List
from anduril.entitymanager.v1 import Entity, Location

def yaw_to_quaternion_enu(yaw_degrees: float) -> List[float]:
# Convert degrees to radians
yaw_rad = math.radians(90 - yaw_degrees) # Adjust NED yaw by 90 degrees for ENU

qw = math.cos(yaw_rad * 0.5)
qz = math.sin(yaw_rad * 0.5)

return [qw, 0, 0, qz]

quaternion = yaw_to_quaternion_enu(40)

entity = Entity(
location = Location(
attitude_enu = Quaternion(
w = quaternion[0],
x = quaternion[1],
y = quaternion[2],
z = quaternion[3],
)
)
)

Specify health

Use the health component to define the health status that the entity reports. This component should only be used for health status that the entity reports back to you. For example, health status should be sources through telemetry or health reports over a radio or network connection. You should not add a health component to tracks whose telemetry or other health reporting data you do not have access to.

When an asset sends an update to the Entities API, the health.connection_status field changes to CONNECTION_STATUS_ONLINE. If there are no updates after one minute, the field value changes to CONNECTION_STATUS_OFFLINE.

"health": {
"healthStatus": "HEALTH_STATUS_WARN",
"components": [
{
"id": "atc-radar-calibration",
"name": "Air Traffic Control Radar Calibration",
"health": "HEALTH_STATUS_WARN",
"messages": [
{
"status": "HEALTH_STATUS_WARN",
"message": "Calibration has not been completed in 30 days"
}
]
}
]
}

ADS-B

To indicate that an Entity has an ADS-B transponder, there are a few components on the Entity that can be set. Specifically:

Transponder Codes The Transponder Code mode should be sent depending on if it is a military or civilian aircraft. For civilian aircraft it's common to to have a 24-bit ICAO address to uniquely identify an aircraft. In order to add a badge to Lattice with the ADS-B identifier, it's necessary to populate the Mode-S.Address field

Aliases.Name If there is a flight number associated with the aircraft (e.g. UAL 1393), it's expected to set it as the name of the Entity within aliases.name.

Aliases.AltId

The call sign of the aircraft can also be associated in a structured way as an alternate ID within the aliases.alternate_ids.id field. This should have an alt ID type of ALT_ID_TYPE_CALLSIGN.

The end result of populating these fields on the Entity are that the Lattice UI renders the specific details in the Entity card:

ADS-B Panel

Representing time in the data model

The entity data model contains a few different timestamps. The most important timestamp is the provenance.source_update_time field. This field is required on every update and is the overall time of validity for the entity update. Lattice will only apply an entity update if the new provenance.source_update_time is later than the previous one. Otherwise it will drop the message.

Other components may include a timestamp when the latest time of observation for that particular component is important to record for general situational awareness. For example, the tracked_by contains a last_measurement_timestamp that records the latest time the upstream source observed a target in a camera frame, in a radar dwell, or an RF signal receipt. In any update driven by a change in track data derived from sensor observation, the tracked_by.last_measurement_timestamp will be the same as the provenance.source_update_time. However, the tracked_by.last_measurement_timestamp may differ in a few different scenarios. For example:

  • If a user overrides the track name, the provenance.source_update_time will reflect the time of that change but the tracked_by.last_measurement_timestamp will still reflect the time of observation.
  • If the integration extrapolates the target track forward in time (sometimes called a "coast"), it would report a new provenance.source_update_time while retaining the original tracked_by.last_measurement_timestamp.