Upload objects

Storing binary data in Lattice using the SDK

This page explains how to upload an object to Lattice and associate it with an entity using the Lattice SDK. In the following steps, you use an image, and a CSV file. Lattice, however, supports all binary data, letting you use objects to implement a variety of use-cases.

For example, consider a scenario where an image sensor integrated with Lattice detects a track image and uploads an image of it to Lattice. After the file is uploaded, any device, app, or operator in Lattice can access the image associated with the track.

To implement this use-case, you use the following API operations:

  • UploadObject — Uploads an object using multiform data.

  • PutEntityOverride — Override the entity and update the entity’s media component.

    Entity overrides let any integration update certain components in an entity, even if the entity was initially published by another data source. This means that anyone connected to Lattice can update the media associated with the entity, giving you more flexibility.

Before you begin

Upload an object

In Lattice, an object is a data model that represents a single file or a piece of data. To upload an object to Lattice, do the following:

1

Choose an image

Choose an image to upload to Lattice, and save it locally.

2

Upload the image to Lattice

Run the following code from the same folder where you saved the image. To upload, use UploadObject and replace the following:

  • file_path: the path to the file you want to upload to Lattice.
1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8 "path/filepath"
9
10 "github.com/anduril/lattice-sdk-go/v2/client"
11 "github.com/anduril/lattice-sdk-go/v2/option"
12)
13
14func main() {
15 // Get environment variables.
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
18
19 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes.
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21
22 // Check required environment variables.
23 if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" {
24 fmt.Println("Missing required environment variables")
25 os.Exit(1)
26 }
27
28 // Initialize headers for sandbox authorization.
29 headers := http.Header{}
30 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
31
32 // Create the client
33 c := client.NewClient(
34 option.WithToken(environmentToken),
35 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
36 option.WithHTTPHeader(headers),
37 )
38
39 // Set the file path.
40 filePath := "./images/N113PF.jpeg"
41
42 // Get the file name.
43 fileName := filepath.Base(filePath)
44
45 // Read the file contents into a byte slice.
46 fileContent, err := os.ReadFile(filePath)
47 if err != nil {
48 fmt.Printf("Error reading file: %v\n", err)
49 os.Exit(1)
50 }
51
52 // Create context for the request.
53 ctx := context.Background()
54
55 // Upload the file to Lattice.
56 response, err := c.Objects.UploadObject(ctx, fileName, fileContent)
57 if err != nil {
58 fmt.Printf("Error uploading object: %v\n", err)
59 os.Exit(1)
60 }
61
62 // Get the content identifier path.
63 path := response.ContentIdentifier.Path
64
65 fmt.Printf("Object path: /api/v1/objects/%s\n", path)
66}

An object path must only contain the following characters: A-Z, a-z, 0-9, . _, -.

3

Verify the response

If successful, you’ll see the following output.

$$ python upload.py
>INFO - HTTP Request: POST https://lattice-50802.env.sandboxes.developer.anduril.com/api/v1/objects/test-1.test.jpg "HTTP/1.1 200 OK"
>INFO - Object path: /api/v1/objects/cessna.jpg

Note the object path: /api/v1/objects/cessna.jpg. In the following section, you’ll override an entity to associate it with this object using its unique object path. This lets Lattice know to display the image in the entity panel as a thumbnail.

To associate an image with an entity, do the following:

1

Override the entity

Use the OverrideEntity operation to override the entity’s media component and replace MediaItem. To implement a thumbnail, set MediaItem.type to MEDIA_TYPE_IMAGE. This lets Lattice know to process the binary object as an image.

Replace the temporary values with your information, then run the code:

  • object_path: The unique path of the object in Lattice. For this example, use /api/v1/objects/test-1.test.jpg.
  • entity_id: The ID of the entity you want to associate with the object. If you are developing in Sandboxes, enter adsbEntity to use an existing simulated 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)
14
15func main() {
16 // Get environment variables
17 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
18 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
19
20 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes.
21 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
22
23 // Check required environment variables
24 if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" {
25 fmt.Println("Missing required environment variables")
26 os.Exit(1)
27 }
28
29 // Initialize headers for sandbox authorization
30 headers := http.Header{}
31 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
32
33 // Create the client
34 LatticeClient := client.NewClient(
35 option.WithToken(environmentToken),
36 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
37 option.WithHTTPHeader(headers),
38 )
39
40 // Set the path to the object and entity ID
41 objectPath := "/api/v1/objects/<objectPath>"
42 entityID := "adsbEntity"
43
44 // Create context for the request
45 ctx := context.Background()
46
47 mediaItem := &Lattice.MediaItem{
48 Type: Lattice.MediaItemTypeMediaTypeImage.Ptr(),
49 RelativePath: &objectPath,
50 }
51
52 // Create media object with the media item
53 mediaItems := []*Lattice.MediaItem{mediaItem}
54 media := Lattice.Media{
55 Media: mediaItems,
56 }
57
58 // Create a provenance object
59 latestTimestamp := time.Now().UTC()
60
61 // Create an entity with the media
62 entity := Lattice.EntityOverride{
63 Entity: &Lattice.Entity{
64 EntityID: &entityID,
65 Media: &media,
66 },
67 Provenance: &Lattice.Provenance{
68 IntegrationName: Lattice.String("your_integration_name"),
69 DataType: Lattice.String("test_data"),
70 SourceUpdateTime: Lattice.Time(latestTimestamp),
71 },
72 }
73
74 // Override the entity's media
75 fieldPath := "media.media"
76 _, err := LatticeClient.Entities.OverrideEntity(
77 ctx,
78 entityID,
79 fieldPath,
80 &entity,
81 )
82
83 // Handle errors
84 if err != nil {
85 fmt.Printf("Exception: %v\n", err)
86 } else {
87 fmt.Printf("Successfully added media to entity: %s\n", entityID)
88 }
89}
2

Verify the response

If successful, you’ll see the following output:

$$ python override_media_add.py
>INFO - HTTP Request: PUT https://your_lattice_endpoint.env.sandboxes.developer.anduril.com/api/v1/entities/adsbEntity/override/media.media "HTTP/1.1 200 OK"

Check the thumbnail

To see the track thumbnail, open the your environment Lattice UI, and do the following:

1

Open the track panel

Open the track panel from the left hand sidebar. Enter the aliases.name of the surface vessel in the search bar.

If you are developing on Sandboxes, enter the following name: N113PF.

Shows the panel where you can search for the surface vessel.
2

Verify the thumbnail

Select the thumbnail associated with the track to expand the image:

Shows the uploaded track thumbnail.

In some cases, you might associate more than one object with an entity. An airplane, for instance, might link to an image to display a thumbnail, and a CSV that contains additional metadata, such as the airplane’s flight manifest.

To do this, get the existing media linked with the entity, then override the entity’s media component to append a new MediaItem to the list:

1

Create a manifest

Copy and paste the the example into a new CSV file, named manifest.csv:

manifest.csv
1FlightID,FlightName,PilotName,DepartureAirport,ArrivalAirport,DepartureTime,ArrivalTime
267890,Cessna Flight,Jane Pilot,John Wayne Airport,San Francisco International,2023-10-01 09:00,2023-10-01 11:00
3
4PassengerID,PassengerName,Weight
5P001,Thomas Pynchon,180
6P002,Anthony Burgess,150
7
8CargoID,Description,Weight
9001,Luggage,50
10002,Equipment,30
2

Upload the file

Run the following code from the same folder where you saved manifest.csv. To upload, use UploadObject and replace the following:

  • file_path: The path to the file you want to upload to Lattice, in this case, ./manifest.csv
1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8 "path/filepath"
9
10 "github.com/anduril/lattice-sdk-go/v2/client"
11 "github.com/anduril/lattice-sdk-go/v2/option"
12)
13
14func main() {
15 // Get environment variables.
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
18
19 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes.
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21
22 // Check required environment variables.
23 if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" {
24 fmt.Println("Missing required environment variables")
25 os.Exit(1)
26 }
27
28 // Initialize headers for sandbox authorization.
29 headers := http.Header{}
30 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
31
32 // Create the client
33 c := client.NewClient(
34 option.WithToken(environmentToken),
35 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
36 option.WithHTTPHeader(headers),
37 )
38
39 // Set the file path.
40 filePath := "./images/N113PF.jpeg"
41
42 // Get the file name.
43 fileName := filepath.Base(filePath)
44
45 // Read the file contents into a byte slice.
46 fileContent, err := os.ReadFile(filePath)
47 if err != nil {
48 fmt.Printf("Error reading file: %v\n", err)
49 os.Exit(1)
50 }
51
52 // Create context for the request.
53 ctx := context.Background()
54
55 // Upload the file to Lattice.
56 response, err := c.Objects.UploadObject(ctx, fileName, fileContent)
57 if err != nil {
58 fmt.Printf("Error uploading object: %v\n", err)
59 os.Exit(1)
60 }
61
62 // Get the content identifier path.
63 path := response.ContentIdentifier.Path
64
65 fmt.Printf("Object path: /api/v1/objects/%s\n", path)
66}
3

Override the entity

Use OverrideEntity to modify the entity’s media component and append a new MediaItem. This MediaItem references manifest.csv. To run the code, replace the following:

  • object_path: The unique path of the object in Lattice. For this example, use /api/v1/objects/manifest.csv.
  • entity_id: The ID of the entity you want to associate with the object.
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)
14
15func main() {
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 environmentToken := os.Getenv("ENVIRONMENT_TOKEN")
18
19 // Remove sandboxesToken from the following statements if you are not developing on Sandboxes.
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21 if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" {
22 fmt.Println("Missing required environment variables")
23 os.Exit(1)
24 }
25 headers := http.Header{}
26 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
27
28 LatticeClient := client.NewClient(
29 option.WithToken(environmentToken),
30 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
31 option.WithHTTPHeader(headers),
32 )
33
34 // Set the path to the object and entity ID.
35 objectPath := "/api/v1/objects/<objectPath>"
36 entityID := "adsbEntity"
37 ctx := context.Background()
38
39 // Retrieve the existing entity to get its current media items.
40 existingEntity, err := LatticeClient.Entities.GetEntity(ctx, entityID)
41 if err != nil {
42 fmt.Printf("Exception when retrieving entity: %v\n", err)
43 return
44 }
45 var existingMediaItems []*Lattice.MediaItem
46 if existingEntity.Media != nil && existingEntity.Media.Media != nil {
47 existingMediaItems = existingEntity.Media.Media
48 }
49
50 // Create a new media item to append.
51 newMediaItem := &Lattice.MediaItem{
52 Type: Lattice.MediaItemTypeMediaTypeImage.Ptr(),
53 RelativePath: &objectPath,
54 }
55
56 // Append the new media item to the existing ones.
57 updatedMediaItems := append(existingMediaItems, newMediaItem)
58 updatedMedia := Lattice.Media{
59 Media: updatedMediaItems,
60 }
61 latestTimestamp := time.Now().UTC()
62
63 // Create an entity override with the updated media.
64 entityOverride := Lattice.EntityOverride{
65 Entity: &Lattice.Entity{
66 EntityID: &entityID,
67 Media: &updatedMedia,
68 },
69 Provenance: &Lattice.Provenance{
70 IntegrationName: Lattice.String("your_integration_name"),
71 DataType: Lattice.String("test_data"),
72 SourceUpdateTime: Lattice.Time(latestTimestamp),
73 },
74 }
75
76 // Override the entity's media.
77 fieldPath := "media.media"
78 _, err = LatticeClient.Entities.OverrideEntity(
79 ctx,
80 entityID,
81 fieldPath,
82 &entityOverride,
83 )
84
85 if err != nil {
86 fmt.Printf("Exception: %v\n", err)
87 } else {
88 fmt.Printf("Successfully appended media to entity: %s\n", entityID)
89 }
90}
4

Verify the response

If successful, you’ll see the entity updated with the following media component:

1"media": {
2 "media": [
3 {
4 "url": "",
5 "type": "MEDIA_TYPE_IMAGE",
6 "relativePath": "/api/v1/objects/cessna.jpg"
7 },
8 {
9 "url": "",
10 "type": "MEDIA_TYPE_INVALID",
11 "relativePath": "/api/v1/objects/manifest.csv"
12 }
13 ]
14}

Because the manifest is not an image used as a thumbnail, by default, Lattice assigns the item’s media type to MEDIA_TYPE_INVALID.

What’s next?