Registering Cloud optimized GeoTIFF (COG) assets

Earth Engine supports assets backed by Cloud Optimized GeoTIFFs (COGs) stored in Google Cloud Storage (GCS).

When you register a COG-backed asset, Earth Engine indexes the spatial and metadata fields of the image at creation time. This makes the image performant when used in collections, with performance comparable to standard ingested assets (see the Cloud optimized GeoTIFF (COG) overview).

Prerequisites and Cloud Storage setup

Before registering a COG, you must set up your Google Cloud Storage resources correctly.

1. Bucket location (strict requirement)

For Earth Engine to access your Cloud Storage bucket, the bucket must be located in one of the following data center regions or multi-regions:

  • The us multi-region
  • Any US dual-region that includes us-central1
  • The us-central1 region

If your bucket is placed in any other regional data center (such as us-east4 or europe-west1), Earth Engine will reject registration and read requests.

2. Storage class and billing

  • We recommend using the Standard storage class. Standard storage has no minimum storage duration rules, preventing early deletion billing penalties if you update or replace temporary files.
  • While Earth Engine computation loops don't incur Cloud Storage data retrieval surcharges, accessing non-Standard storage tiers from external tools (such as desktop GIS software) incurs costly retrieval fees.
  • You must have billing enabled on your Google Cloud project to use GCS. See the Google Cloud Storage Quickstart to get started.

Step 1: Prepare your COGs

Your TIFF files must be optimized for the cloud. The TIFF must be:

  • Tiled, with tile dimensions of: 256x256, 512x512, 1024x1024, or 2048x2048. We recommend 512x512 or higher for best performance.
  • Arranged so that all Image File Directories (IFDs) are at the beginning.
  • Include power-of-2 overviews.
  • Use BAND interleave.

You can use GDAL to convert your images.

Using GDAL COG Driver (GDAL >= 3.1 recommended):

gdal_translate in.tif out.tif \
  -of COG \
  -co BIGTIFF=YES \
  -co OVERVIEWS=IGNORE_EXISTING \
  -co COMPRESS=ZSTD \
  -co LEVEL=16 \
  -co BLOCKSIZE=512 \
  -co INTERLEAVE=BAND \
  -co NUM_THREADS=ALL_CPUS \
  -co PREDICTOR=2  # 2 for integers, 3 for floating point

Using standard GTiff driver:

First, generate internal power-of-2 overviews (using gdal raster overview add or gdaladdo), then copy the image and overviews into a tiled COG layout:

gdaladdo -r nearest in.tif 2 4 8 16 32  # or -r cubic for continuous/float data

gdal_translate in.tif out.tif \
  -co BIGTIFF=YES \
  -co COPY_SRC_OVERVIEWS=YES \
  -co TILED=YES \
  -co BLOCKXSIZE=512 \
  -co BLOCKYSIZE=512 \
  -co COMPRESS=ZSTD \
  -co ZSTD_LEVEL=16 \
  -co INTERLEAVE=BAND \
  -co NUM_THREADS=ALL_CPUS \
  -co PREDICTOR=2  # 2 for integers, 3 for floating point

Step 2: Construct the manifest

Whether you register a COG using the Earth Engine Python client library, the earthengine command-line tool, or the Earth Engine REST API, you must define an image upload manifest specifying the asset destination, Google Cloud Storage URIs, and optional band metadata.

Example: Single COG backing all bands If your COG contains all bands (e.g., RGB), the manifest requires only a single tileset:

{
  "name": "projects/my-project/assets/my_cog_asset",
  "tilesets": [
    {
      "id": "default_tileset",
      "sources": [
        {
          "uris": [
            "gs://google-sample-bucket/my_optimized_image.tif"
          ]
        }
      ]
    }
  ]
}

Example: Multiple COGs (One per band) If you have separate files for different bands (e.g., Red, Green, Blue in separate files):

{
  "name": "projects/my-project/assets/my_multi_band_cog",
  "tilesets": [
    {
      "id": "red_tileset",
      "sources": [ { "uris": [ "gs://google-sample-bucket/band_red.tif" ] } ]
    },
    {
      "id": "green_tileset",
      "sources": [ { "uris": [ "gs://google-sample-bucket/band_green.tif" ] } ]
    }
  ],
  "bands": [
    { "id": "R", "tilesetId": "red_tileset", "tilesetBandIndex": 0 },
    { "id": "G", "tilesetId": "green_tileset", "tilesetBandIndex": 0 }
  ]
}

Step 3: Submit the registration request

You can submit your manifest to Earth Engine using the Earth Engine Python client library, the earthengine command-line tool, or the Earth Engine REST API. Choose the method that best fits your workflow.

Python client library

In a Python script or Jupyter notebook, use the Earth Engine Python client library (ee.data) after initializing your project:

import ee

# 1. Authenticate and initialize
ee.Authenticate()
ee.Initialize(project='your-project-id')

# 2. Define the manifest as a Python dictionary
manifest = {
  "name": "projects/your-project-id/assets/my_cog_asset",
  "tilesets": [
    {
      "id": "default_tileset",
      "sources": [
        {
          "uris": [
            "gs://google-sample-bucket/my_optimized_image.tif"
          ]
        }
      ]
    }
  ]
}

# 3. Start the ingestion task (allow_overwrite=True corresponds to --force)
result = ee.data.startExternalImageIngestion(manifest, allow_overwrite=True)
print(f"Asset registered: {result['name']}")
      

Command-line tool

In your terminal, save your manifest to a file named manifest.json and run the earthengine upload command:

# 1. Verify your environment is authenticated
earthengine authenticate

# 2. Submit the external image upload task
earthengine --project your-project-id \
  upload external_image \
  --manifest manifest.json
      

REST API (Python)

For automated workflows without the client library, send an authorized POST request directly to the Earth Engine REST API endpoint projects.image.importExternal (demonstrated here using Python):

import json
from pprint import pprint
import ee
from google.auth.transport.requests import AuthorizedSession

# 1. Authenticate and start an authorized HTTP session
ee.Authenticate()
ee_project = 'your-project-id'
session = AuthorizedSession(
    ee.data.get_persistent_credentials().with_quota_project(ee_project)
)

# 2. Define the REST request body wrapping the manifest
request = {
  'imageManifest': {
    'name': f'projects/{ee_project}/assets/my_cog_asset',
    'tilesets': [
      {
        'id': 'default_tileset',
        'sources': [
          {
            'uris': [
              'gs://google-sample-bucket/my_optimized_image.tif'
            ]
          }
        ]
      }
    ]
  }
}

# 3. Send the POST request
url = (
    'https://earthengine.googleapis.com/v1alpha/'
    f'projects/{ee_project}/image:importExternal'
)
response = session.post(url=url, data=json.dumps(request))
pprint(json.loads(response.content))
      

Once submitted, the registration request initiates a lightweight metadata indexing operation. Because COG registration does not copy pixel data, this operation typically completes in seconds.

Manifest restrictions for COG assets

When creating a manifest for COG-backed assets, note these strict restrictions (which differ from standard ingestion manifests):

  • Single bucket: All URIs in the manifest must reside in the same GCS bucket.
  • One URI per source: Each ImageSource must contain exactly one URI. You cannot mosaic multiple files together into a single tileset source. To mosaic multiple COG scenes, register each COG as an asset and combine them into an ee.ImageCollection using .mosaic().
  • No mask_bands: Top-level mask_bands is not supported. Use missingData in the manifest or ensure your TIFF has an internal nodata value.
  • No data_type in tileset: Do not specify data_type in the tilesets section; it is automatically detected.
  • No dimensions in source: Do not specify dimensions in the ImageSource.
  • No subdataset_prefix / subdataset_suffix.
  • No min_tile_area_ratio or skip_metadata_read.

Step 4: Configure permissions (crucial for sharing)

Unlike standard Earth Engine assets, the pixel data of a COG remains in GCS. When sharing a COG-backed asset with others, you must grant them read access to both the Earth Engine asset and the GCS files.

1. Grant GCS bucket permissions

Collaborators need permissions to read the files from GCS.

Required permissions:

  • storage.buckets.get (to retrieve bucket metadata and location)
  • storage.objects.get (to read the pixel data)

You can grant these permissions using the following standard Cloud Storage roles:

  • Storage Bucket Viewer (provides storage.buckets.get to read bucket metadata and location)
  • Storage Object Viewer (provides storage.objects.get to read pixel data)

Steps to configure in Google Cloud Console:

  1. Go to the Cloud Storage Browser.
  2. Click your bucket name.
  3. Select the Permissions tab.
  4. Click GRANT ACCESS.
  5. Add the email addresses of your collaborators (or a Google Group, or allUsers for public access).
  6. Assign the roles Storage Bucket Viewer and Storage Object Viewer.
  7. Click Save.

2. Share the Earth Engine asset

Share the asset itself using the Earth Engine Asset Manager or CLI. See the Asset Management Guide for details.

Managing object generations

When you register a COG, Earth Engine stores the specific generation (version) of the GCS object. If you update the file in GCS, its generation changes.

  • By default, Earth Engine will fail to load the asset and return an error: Failed to load the GeoTIFF at gs://google-sample-bucket/my-object#old-generation.
  • This is designed to prevent metadata (like projection or footprint) from getting out of sync with the underlying pixels.
  • Best practice: If you need to update the underlying image data, register it as a new asset or re-run the upload command with the --force flag rather than overwriting files in place:

    earthengine --project your-project-id upload external_image --force --manifest manifest.json
    

Using registered assets

Once registered, a COG-backed asset is accessed in Earth Engine scripts just like any standard ingested image asset:

JavaScript

var image = ee.Image('projects/your-project-id/assets/my_cog_asset');

Python

image = ee.Image('projects/your-project-id/assets/my_cog_asset')