Google Earth Engine to GeoCroissant Support

GeoCroissant

Overview

This notebook demonstrates how to convert Google Earth Engine (GEE) satellite imagery assets into GeoCroissant format - a specialized extension of the ML Commons Croissant standard for geospatial datasets.

Authenticate and Initialize Earth Engine

This cell authenticates to Google Earth Engine using a service account and initializes the ee Python API for further operations.

!pip install earthengine-api -q
import ee
from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(
    "./code-earthengine.json",
    scopes=["https://www.googleapis.com/auth/earthengine"],
)
ee.Initialize(credentials=credentials)

Check Asset Metadata via REST API

This cell uses the REST API and your access token to fetch and print the metadata for a specific Earth Engine asset.

import requests

token = credentials.token

# API CHECK
url = "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG"
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers)
print(response.json())
{'type': 'IMAGE', 'name': 'projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG', 'id': 'COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG', 'properties': {'DATATAKE_IDENTIFIER': 'GS2A_20170430T190351_009691_N05.00', 'SPACECRAFT_NAME': 'Sentinel-2A', 'RADIO_ADD_OFFSET_B8A': -1000, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B8A': 114.911147676295, 'RADIO_ADD_OFFSET_B10': -1000, 'MEAN_SOLAR_AZIMUTH_ANGLE': 144.166615587836, 'SOLAR_IRRADIANCE_B12': 85.25, 'SOLAR_IRRADIANCE_B10': 367.15, 'SENSOR_QUALITY': 'PASSED', 'SOLAR_IRRADIANCE_B11': 245.59, 'GENERATION_TIME': 1695535572000, 'RADIO_ADD_OFFSET_B12': -1000, 'RADIO_ADD_OFFSET_B11': -1000, 'SOLAR_IRRADIANCE_B8A': 955.32, 'FORMAT_CORRECTNESS': 'PASSED', 'CLOUD_COVERAGE_ASSESSMENT': 0.255852831116763, 'SNOW_PIXEL_PERCENTAGE': 0.00209765272659894, 'RADIO_ADD_OFFSET_B1': -1000, 'RADIO_ADD_OFFSET_B2': -1000, 'DATASTRIP_ID': 'S2A_OPER_MSI_L1C_DS_S2RP_20230924T060612_S20170430T190351_N05.00', 'RADIO_ADD_OFFSET_B3': -1000, 'RADIO_ADD_OFFSET_B4': -1000, 'RADIO_ADD_OFFSET_B5': -1000, 'PROCESSING_BASELINE': '05.00', 'SENSING_ORBIT_NUMBER': 113, 'RADIO_ADD_OFFSET_B6': -1000, 'RADIO_ADD_OFFSET_B7': -1000, 'SENSING_ORBIT_DIRECTION': 'DESCENDING', 'GENERAL_QUALITY': 'FAILED', 'GRANULE_ID': 'L1C_T10SEG_A009691_20170430T190351', 'REFLECTANCE_CONVERSION_CORRECTION': 0.987349139279658, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B8': 119.437261123808, 'DATATAKE_TYPE': 'INS-NOBS', 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B9': 114.510594227324, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B6': 115.694920180767, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B7': 115.272311165959, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B4': 116.818521016093, 'MEAN_INCIDENCE_ZENITH_ANGLE_B1': 4.01002710851759, 'RADIO_ADD_OFFSET_B8': -1000, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B5': 116.211704638208, 'RADIOMETRIC_QUALITY': 'PASSED', 'RADIO_ADD_OFFSET_B9': -1000, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B2': 120.817153764908, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B3': 118.335666287999, 'MEAN_INCIDENCE_ZENITH_ANGLE_B5': 3.71790186283323, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B1': 114.772876267468, 'MEAN_INCIDENCE_ZENITH_ANGLE_B4': 3.65278993192244, 'MEAN_INCIDENCE_ZENITH_ANGLE_B3': 3.54497531210579, 'MEAN_INCIDENCE_ZENITH_ANGLE_B2': 3.44840391982784, 'MEAN_INCIDENCE_ZENITH_ANGLE_B9': 4.0941543848476, 'MEAN_INCIDENCE_ZENITH_ANGLE_B8': 3.4936762058753, 'MEAN_INCIDENCE_ZENITH_ANGLE_B7': 3.85962379212438, 'MEAN_INCIDENCE_ZENITH_ANGLE_B6': 3.78681583750869, 'MEAN_SOLAR_ZENITH_ANGLE': 26.371889065193, 'MEAN_INCIDENCE_ZENITH_ANGLE_B8A': 3.93547667443197, 'MGRS_TILE': '10SEG', 'CLOUDY_PIXEL_PERCENTAGE': 0.255852831116763, 'PRODUCT_ID': 'S2A_MSIL1C_20170430T190351_N0500_R113_T10SEG_20230924T060612', 'MEAN_INCIDENCE_ZENITH_ANGLE_B10': 3.60829266014082, 'SOLAR_IRRADIANCE_B9': 812.92, 'DEGRADED_MSI_DATA_PERCENTAGE': 0.0844, 'MEAN_INCIDENCE_ZENITH_ANGLE_B11': 3.76315609583164, 'MEAN_INCIDENCE_ZENITH_ANGLE_B12': 3.93813353642485, 'SOLAR_IRRADIANCE_B6': 1287.61, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B10': 117.01379889609, 'SOLAR_IRRADIANCE_B5': 1424.64, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B11': 115.504968700284, 'SOLAR_IRRADIANCE_B8': 1041.63, 'MEAN_INCIDENCE_AZIMUTH_ANGLE_B12': 114.679133996426, 'SOLAR_IRRADIANCE_B7': 1162.08, 'SOLAR_IRRADIANCE_B2': 1959.66, 'SOLAR_IRRADIANCE_B1': 1884.69, 'SOLAR_IRRADIANCE_B4': 1512.06, 'GEOMETRIC_QUALITY': 'PASSED', 'SOLAR_IRRADIANCE_B3': 1823.24}, 'updateTime': '2025-11-27T14:41:08.794343Z', 'startTime': '2017-04-30T19:04:11.415Z', 'endTime': '2017-04-30T19:04:11.415Z', 'geometry': {'type': 'Polygon', 'coordinates': [[[-123.00034179205349, 37.9475970867561], [-123.00033758169468, 37.03540523327759], [-123.00029882919976, 37.035370092333515], [-123.0002786366622, 37.035326084801994], [-123.00025234337808, 37.03531798690294], [-122.97146744090354, 37.02954498202823], [-122.78806337628276, 36.99798841554587], [-122.60481215367447, 36.966149922145085], [-122.5522785698939, 36.95712738922796], [-122.35600349740389, 36.95622304690406], [-122.15973391884847, 36.954995693957805], [-121.96347149860821, 36.95344535521865], [-121.7672179004439, 36.95157206201084], [-121.76716539030431, 36.95160813825383], [-121.76710775082039, 36.95163857717316], [-121.76710392557831, 36.951653376800685], [-121.75903140997843, 37.446330701468504], [-121.75075919661815, 37.94095746899704], [-121.75080484540763, 37.94099937286281], [-121.7508434512256, 37.94104550336488], [-121.75086214548108, 37.94104856554202], [-121.90701742537077, 37.942599864517334], [-122.06317885967235, 37.94394478501538], [-122.21934557169301, 37.94508330949206], [-122.37551668447043, 37.94601542308925], [-122.53169132080869, 37.946741113643746], [-122.68786860332327, 37.94726037168731], [-122.84404765451283, 37.947573190437666], [-123.0002275967593, 37.94767956582544], [-123.00028018331857, 37.947642997341596], [-123.00033818317065, 37.947611874966036], [-123.00034179205349, 37.9475970867561]]]}, 'bands': [{'id': 'B1', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B2', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 10980, 'height': 10980}, 'affineTransform': {'scaleX': 10, 'translateX': 499980, 'scaleY': -10, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B3', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 10980, 'height': 10980}, 'affineTransform': {'scaleX': 10, 'translateX': 499980, 'scaleY': -10, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B4', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 10980, 'height': 10980}, 'affineTransform': {'scaleX': 10, 'translateX': 499980, 'scaleY': -10, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B5', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B6', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B7', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B8', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 10980, 'height': 10980}, 'affineTransform': {'scaleX': 10, 'translateX': 499980, 'scaleY': -10, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B8A', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B9', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B10', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B11', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'B12', 'dataType': {'precision': 'INT', 'range': {'max': 65535}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 5490, 'height': 5490}, 'affineTransform': {'scaleX': 20, 'translateX': 499980, 'scaleY': -20, 'translateY': 4200000}}, 'pyramidingPolicy': 'MEAN'}, {'id': 'MSK_CLASSI_OPAQUE', 'dataType': {'precision': 'INT', 'range': {'max': 255}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MODE'}, {'id': 'MSK_CLASSI_CIRRUS', 'dataType': {'precision': 'INT', 'range': {'max': 255}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MODE'}, {'id': 'MSK_CLASSI_SNOW_ICE', 'dataType': {'precision': 'INT', 'range': {'max': 255}}, 'grid': {'crsCode': 'EPSG:32610', 'dimensions': {'width': 1830, 'height': 1830}, 'affineTransform': {'scaleX': 60, 'translateX': 499980, 'scaleY': -60, 'translateY': 4200000}}, 'pyramidingPolicy': 'MODE'}], 'sizeBytes': '1199846489'}

Convert Earth Engine Asset Metadata to GeoCroissant JSON-LD

This cell: - Authenticates to Earth Engine, - Fetches asset metadata, - Computes the bounding box and WKT geometry, - Builds a per-band asset dictionary, - Assembles a GeoCroissant-compliant JSON-LD object, - Saves it to gee.json.

import ee
from google.oauth2 import service_account
from google.auth.transport.requests import Request
import json
import datetime

# 1. Authenticate to Earth Engine
SERVICE_ACCOUNT_FILE = "code-earthengine.json"
ASSET_ID = "COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG"

creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=["https://www.googleapis.com/auth/earthengine"]
)
creds.refresh(Request())
ee.Initialize(credentials=creds)
token = creds.token

# 2. Fetch asset metadata
meta = ee.data.getAsset(ASSET_ID)
props = meta["properties"]

# 3. Compute bounding box
coords = meta["geometry"]["coordinates"][0]
lons, lats = zip(*coords)
bbox = f"{min(lats)} {min(lons)} {max(lats)} {max(lons)}"
wkt = "POLYGON((" + ", ".join(f"{x} {y}" for x, y in coords) + "))"

# 4. Build per-band assets
assets = {}
for band in meta["bands"]:
    band_id = band["id"]
    res = band["grid"]["affineTransform"]["scaleX"]
    assets[band_id] = {
        "href": f"ee://{ASSET_ID}/{band_id}",
        "type": "image/tiff",
        "roles": ["data"],
        "band_name": band_id,
        "data_type": band["dataType"]["precision"].lower(),
        "spatial_resolution": res,
        "description": f"Sentinel-2 band {band_id} of image {ASSET_ID}",
    }

# 5. Convert bbox to array format
bbox_coords = [min(lons), min(lats), max(lons), max(lats)]

# 6. Build fileSet for the bands
fileSet_id = f"sentinel2-bands-{ASSET_ID.replace('/', '-')}"
band_files = []
for band in meta["bands"]:
    band_files.append(
        {
            "name": f"{band['id']}.tif",
            "path": f"ee://{ASSET_ID}/{band['id']}",
            "contentSize": (
                band.get("dimensions", {}).get("width", 0)
                * band.get("dimensions", {}).get("height", 0)
                * 2
            ),  # Approximate size
        }
    )

# 7. Assemble Geo-Croissant JSON-LD (using correct prefixes & geocr IRIs)
geo_croissant = {
    "@context": {
        "@language": "en",
        "@vocab": "https://schema.org/",
        "citeAs": "cr:citeAs",
        "column": "cr:column",
        "conformsTo": "dct:conformsTo",
        "cr": "http://mlcommons.org/croissant/",
        "geocr": "http://mlcommons.org/croissant/geo/",
        "rai": "http://mlcommons.org/croissant/RAI/",
        "dct": "http://purl.org/dc/terms/",
        "sc": "https://schema.org/",
        "data": {"@id": "cr:data", "@type": "@json"},
        "examples": {"@id": "cr:examples", "@type": "@json"},
        "dataBiases": "cr:dataBiases",
        "dataCollection": "cr:dataCollection",
        "dataType": {"@id": "cr:dataType", "@type": "@vocab"},
        "extract": "cr:extract",
        "field": "cr:field",
        "fileProperty": "cr:fileProperty",
        "fileObject": "cr:fileObject",
        "fileSet": "cr:fileSet",
        "format": "cr:format",
        "includes": "cr:includes",
        "isLiveDataset": "cr:isLiveDataset",
        "jsonPath": "cr:jsonPath",
        "key": "cr:key",
        "md5": "cr:md5",
        "parentField": "cr:parentField",
        "path": "cr:path",
        "personalSensitiveInformation": "cr:personalSensitiveInformation",
        "recordSet": "cr:recordSet",
        "references": "cr:references",
        "regex": "cr:regex",
        "repeated": "cr:repeated",
        "replace": "cr:replace",
        "samplingRate": "cr:samplingRate",
        "separator": "cr:separator",
        "source": "cr:source",
        "subField": "cr:subField",
        "transform": "cr:transform",
    },
    "@type": "sc:Dataset",
    "name": ASSET_ID.replace("/", "_"),
    "alternateName": [
        ASSET_ID.replace("/", "-"),
        f"Sentinel-2-{props.get('MGRS_TILE', '')}",
    ],
    "description": (
        f"Sentinel-2 Level-1C image over MGRS tile {props.get('MGRS_TILE','')} acquired"
        f" on {meta['startTime'][:10]}. This dataset contains"
        f" {len(meta['bands'])} spectral bands with spatial resolutions ranging from"
        " 10m to 60m."
    ),
    "conformsTo": [
        "http://mlcommons.org/croissant/1.0",
        "http://mlcommons.org/croissant/geo/1.0"
    ],
    "version": "1.0.0",
    "creator": {
        "@type": "Organization",
        "name": "European Space Agency (ESA)",
        "url": "https://www.esa.int/",
    },
    "url": f"https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/{ASSET_ID}",
    "keywords": [
        "Sentinel-2",
        "satellite imagery",
        "remote sensing",
        "multispectral",
        "Earth observation",
        f"MGRS-{props.get('MGRS_TILE', '')}",
        "Level-1C",
        "ESA",
        "Copernicus",
    ],
    "citeAs": f"https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/{ASSET_ID}",
    "datePublished": meta["startTime"][:10],
    "license": "https://creativecommons.org/licenses/by/4.0/",
    "spatialCoverage": {
        "@type": "Place",
        "geo": {
            "@type": "GeoShape",
            "box": f"{min(lats)} {min(lons)} {max(lats)} {max(lons)}"
        }
    },
    "temporalCoverage": f"{meta['startTime']}/{meta['endTime']}",
    "geocr:spatialResolution": "10-60m",
    "geocr:coordinateReferenceSystem": "EPSG:4326",
    "variableMeasured": [
        {
            "@type": "sc:PropertyValue",
            "sc:name": "Cloudy pixel percentage",
            "sc:value": props.get("CLOUDY_PIXEL_PERCENTAGE", 0),
        },
        {
            "@type": "sc:PropertyValue",
            "sc:name": "Cloud coverage assessment",
            "sc:value": props.get("CLOUD_COVERAGE_ASSESSMENT", 0),
        },
    ],
    "recordSet": [
        {
            "@type": "cr:RecordSet",
            "@id": f"sentinel2_bands_{ASSET_ID.replace('/', '_')}",
            "name": f"sentinel2_bands_{ASSET_ID.replace('/', '_')}",
            "description": f"Spectral bands for Sentinel-2 image {ASSET_ID}",
            "field": [
                {
                    "@type": "cr:Field",
                    "@id": f"{ASSET_ID.replace('/', '_')}/asset_id",
                    "name": f"{ASSET_ID.replace('/', '_')}/asset_id",
                    "description": "Asset identifier",
                    "dataType": "sc:Text",
                    "source": {
                        "fileSet": {"@id": fileSet_id},
                        "extract": {"fileProperty": "fullpath"},
                    },
                },
                {
                    "@type": "cr:Field",
                    "@id": f"{ASSET_ID.replace('/', '_')}/image_data",
                    "name": f"{ASSET_ID.replace('/', '_')}/image_data",
                    "description": "Satellite imagery data",
                    "dataType": "sc:ImageObject",
                    "source": {
                        "fileSet": {"@id": fileSet_id},
                        "extract": {"fileProperty": "fullpath"},
                    },
                    "geocr:bandConfiguration": {
                        "@type": "geocr:BandConfiguration",
                        "geocr:totalBands": len(meta["bands"]),
                        "geocr:bandNameList": [band["id"] for band in meta["bands"]],
                    },
                },
            ],
        }
    ],
    "fileSet": [
        {
            "@type": "cr:FileSet",
            "@id": fileSet_id,
            "name": fileSet_id,
            "description": f"Sentinel-2 spectral bands for {ASSET_ID}",
            "includes": "*.tif",
            "encodingFormat": "image/tiff",
            "fileObject": [
                {
                    "@type": "cr:FileObject",
                    "name": f"{band['id']}.tif",
                    "contentUrl": f"https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/{ASSET_ID}/{band['id']}",
                    "encodingFormat": "image/tiff",
                    "contentSize": (
                        band.get("dimensions", {}).get("width", 0)
                        * band.get("dimensions", {}).get("height", 0)
                        * 2
                    ),
                }
                for band in meta["bands"]
            ],
        }
    ],
    "distribution": [
        {
            "@type": "cr:FileObject",
            "@id": f"sentinel2-bands-{ASSET_ID.replace('/', '-')}",
            "name": f"Sentinel-2 Bands for {ASSET_ID}",
            "description": f"Downloadable Sentinel-2 spectral bands for {ASSET_ID}",
            "contentUrl": f"https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/{ASSET_ID}",
            "encodingFormat": "application/json",
        }
    ],
}

# 8. Write to file
with open("gee.json", "w") as f:
    json.dump(geo_croissant, f, indent=2)

print("Geo-Croissant JSON-LD saved to gee.json")
Geo-Croissant JSON-LD saved to gee.json

Pretty Print GeoCroissant JSON-LD

This cell loads and pretty-prints the contents of gee.json for easy inspection.

import json

# Load and pretty-print the content of croissant.json
with open("gee.json", "r") as f:
    croissant_data = json.load(f)

# Pretty-print JSON to console
print(json.dumps(croissant_data, indent=2))
{
  "@context": {
    "@language": "en",
    "@vocab": "https://schema.org/",
    "citeAs": "cr:citeAs",
    "column": "cr:column",
    "conformsTo": "dct:conformsTo",
    "cr": "http://mlcommons.org/croissant/",
    "geocr": "http://mlcommons.org/croissant/geo/",
    "rai": "http://mlcommons.org/croissant/RAI/",
    "dct": "http://purl.org/dc/terms/",
    "sc": "https://schema.org/",
    "data": {
      "@id": "cr:data",
      "@type": "@json"
    },
    "examples": {
      "@id": "cr:examples",
      "@type": "@json"
    },
    "dataBiases": "cr:dataBiases",
    "dataCollection": "cr:dataCollection",
    "dataType": {
      "@id": "cr:dataType",
      "@type": "@vocab"
    },
    "extract": "cr:extract",
    "field": "cr:field",
    "fileProperty": "cr:fileProperty",
    "fileObject": "cr:fileObject",
    "fileSet": "cr:fileSet",
    "format": "cr:format",
    "includes": "cr:includes",
    "isLiveDataset": "cr:isLiveDataset",
    "jsonPath": "cr:jsonPath",
    "key": "cr:key",
    "md5": "cr:md5",
    "parentField": "cr:parentField",
    "path": "cr:path",
    "personalSensitiveInformation": "cr:personalSensitiveInformation",
    "recordSet": "cr:recordSet",
    "references": "cr:references",
    "regex": "cr:regex",
    "repeated": "cr:repeated",
    "replace": "cr:replace",
    "samplingRate": "cr:samplingRate",
    "separator": "cr:separator",
    "source": "cr:source",
    "subField": "cr:subField",
    "transform": "cr:transform"
  },
  "@type": "sc:Dataset",
  "name": "COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG",
  "alternateName": [
    "COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG",
    "Sentinel-2-10SEG"
  ],
  "description": "Sentinel-2 Level-1C image over MGRS tile 10SEG acquired on 2017-04-30. This dataset contains 16 spectral bands with spatial resolutions ranging from 10m to 60m.",
  "conformsTo": [
    "http://mlcommons.org/croissant/1.0",
    "http://mlcommons.org/croissant/geo/1.0"
  ],
  "version": "1.0.0",
  "creator": {
    "@type": "Organization",
    "name": "European Space Agency (ESA)",
    "url": "https://www.esa.int/"
  },
  "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
  "keywords": [
    "Sentinel-2",
    "satellite imagery",
    "remote sensing",
    "multispectral",
    "Earth observation",
    "MGRS-10SEG",
    "Level-1C",
    "ESA",
    "Copernicus"
  ],
  "citeAs": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
  "datePublished": "2017-04-30",
  "license": "https://creativecommons.org/licenses/by/4.0/",
  "spatialCoverage": {
    "@type": "Place",
    "geo": {
      "@type": "GeoShape",
      "box": "36.95157206201084 -123.00034179205349 37.94767956582544 -121.75075919661815"
    }
  },
  "temporalCoverage": "2017-04-30T19:04:11.415Z/2017-04-30T19:04:11.415Z",
  "geocr:spatialResolution": "10-60m",
  "geocr:coordinateReferenceSystem": "EPSG:4326",
  "variableMeasured": [
    {
      "@type": "sc:PropertyValue",
      "sc:name": "Cloudy pixel percentage",
      "sc:value": 0.255852831116763
    },
    {
      "@type": "sc:PropertyValue",
      "sc:name": "Cloud coverage assessment",
      "sc:value": 0.255852831116763
    }
  ],
  "recordSet": [
    {
      "@type": "cr:RecordSet",
      "@id": "sentinel2_bands_COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG",
      "name": "sentinel2_bands_COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG",
      "description": "Spectral bands for Sentinel-2 image COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
      "field": [
        {
          "@type": "cr:Field",
          "@id": "COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG/asset_id",
          "name": "COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG/asset_id",
          "description": "Asset identifier",
          "dataType": "sc:Text",
          "source": {
            "fileSet": {
              "@id": "sentinel2-bands-COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG"
            },
            "extract": {
              "fileProperty": "fullpath"
            }
          }
        },
        {
          "@type": "cr:Field",
          "@id": "COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG/image_data",
          "name": "COPERNICUS_S2_20170430T190351_20170430T190351_T10SEG/image_data",
          "description": "Satellite imagery data",
          "dataType": "sc:ImageObject",
          "source": {
            "fileSet": {
              "@id": "sentinel2-bands-COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG"
            },
            "extract": {
              "fileProperty": "fullpath"
            }
          },
          "geocr:bandConfiguration": {
            "@type": "geocr:BandConfiguration",
            "geocr:totalBands": 16,
            "geocr:bandNameList": [
              "B1",
              "B2",
              "B3",
              "B4",
              "B5",
              "B6",
              "B7",
              "B8",
              "B8A",
              "B9",
              "B10",
              "B11",
              "B12",
              "MSK_CLASSI_OPAQUE",
              "MSK_CLASSI_CIRRUS",
              "MSK_CLASSI_SNOW_ICE"
            ]
          }
        }
      ]
    }
  ],
  "fileSet": [
    {
      "@type": "cr:FileSet",
      "@id": "sentinel2-bands-COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG",
      "name": "sentinel2-bands-COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG",
      "description": "Sentinel-2 spectral bands for COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
      "includes": "*.tif",
      "encodingFormat": "image/tiff",
      "fileObject": [
        {
          "@type": "cr:FileObject",
          "name": "B1.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B1",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B2.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B2",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B3.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B3",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B4.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B4",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B5.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B5",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B6.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B6",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B7.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B7",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B8.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B8",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B8A.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B8A",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B9.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B9",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B10.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B10",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B11.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B11",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "B12.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/B12",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "MSK_CLASSI_OPAQUE.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/MSK_CLASSI_OPAQUE",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "MSK_CLASSI_CIRRUS.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/MSK_CLASSI_CIRRUS",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        },
        {
          "@type": "cr:FileObject",
          "name": "MSK_CLASSI_SNOW_ICE.tif",
          "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG/MSK_CLASSI_SNOW_ICE",
          "encodingFormat": "image/tiff",
          "contentSize": 0
        }
      ]
    }
  ],
  "distribution": [
    {
      "@type": "cr:FileObject",
      "@id": "sentinel2-bands-COPERNICUS-S2-20170430T190351_20170430T190351_T10SEG",
      "name": "Sentinel-2 Bands for COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
      "description": "Downloadable Sentinel-2 spectral bands for COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
      "contentUrl": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-public/assets/COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG",
      "encodingFormat": "application/json"
    }
  ]
}

Validate GeoCroissant JSON-LD

This cell runs the mlcroissant validator on your GeoCroissant JSON-LD file to check for schema compliance.

!mlcroissant validate --jsonld=gee.json
E0216 19:08:31.570067 127957704201728 validate.py:55] Found the following 1 error(s) during the validation:
  -  "_:a2fec96d-5ae9-49b5-b32d-9327d74d63ca" should have an attribute "@type": "https://schema.org/Text". Got None instead.

Visualize Earth Engine Asset and Thumbnails

This cell: - Loads the GeoCroissant JSON-LD, - Extracts the Earth Engine asset ID, - Authenticates and initializes Earth Engine, - Loads the image and centers a map, - Adds true-color and additional band layers, - Generates and displays RGB and band thumbnails, - Displays an interactive map using geemap.

import ee
import json
import numpy as np
import matplotlib.pyplot as plt
from google.oauth2 import service_account
from google.auth.transport.requests import Request
import requests
from PIL import Image
import io

# 1. Load Geo-Croissant JSON-LD
with open("gee.json") as f:
    md = json.load(f)

# 2. Extract EE asset ID
parts = md["url"].split("/")
ee_id = "/".join(
    parts[-3:]
)  # e.g. "COPERNICUS/S2/20170430T190351_20170430T190351_T10SEG"

# 3. Authenticate & init EE
SERVICE_ACCOUNT_FILE = "code-earthengine.json"
creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=["https://www.googleapis.com/auth/earthengine"]
)
creds.refresh(Request())
ee.Initialize(credentials=creds)

# 4. Load image and get metadata
img = ee.Image(ee_id)
geometry = img.geometry()
bounds = geometry.bounds().getInfo()["coordinates"][0]

# 5. Get image dimensions and create a region
region = geometry.buffer(1000)  # Buffer by 1km


# 6. Function to download band data as numpy array
def download_band_as_array(band_name, region, scale=30):
    """Download a single band from Earth Engine as numpy array"""
    band_img = img.select(band_name)
    thumb_url = band_img.getThumbURL(
        {"region": region, "dimensions": 512, "format": "png", "min": 0, "max": 3000}
    )
    response = requests.get(thumb_url)
    pil_image = Image.open(io.BytesIO(response.content))
    array = np.array(pil_image)
    return array


# 7. Download bands for visualization
print("Downloading band data...")
bands_to_visualize = ["B2", "B3", "B4", "B8", "B11", "B12"]
band_data = {}

for band in bands_to_visualize:
    print(f"Downloading {band}...")
    band_data[band] = download_band_as_array(band, region)
    print(f"  Shape: {band_data[band].shape}, dtype: {band_data[band].dtype}")


# 8. Create RGB composite
def create_rgb_composite(band_data, red_band="B4", green_band="B3", blue_band="B2"):
    red = band_data[red_band]
    green = band_data[green_band]
    blue = band_data[blue_band]
    if red.ndim == 3:
        red = red[:, :, 0]
    if green.ndim == 3:
        green = green[:, :, 0]
    if blue.ndim == 3:
        blue = blue[:, :, 0]
    red_norm = red.astype(np.float32) / 255.0
    green_norm = green.astype(np.float32) / 255.0
    blue_norm = blue.astype(np.float32) / 255.0
    rgb = np.stack([red_norm, green_norm, blue_norm], axis=2)
    return rgb


# 9. Create false color composite (NIR-Red-Green)
def create_false_color_composite(
    band_data, red_band="B8", green_band="B4", blue_band="B3"
):
    red = band_data[red_band]
    green = band_data[green_band]
    blue = band_data[blue_band]
    if red.ndim == 3:
        red = red[:, :, 0]
    if green.ndim == 3:
        green = green[:, :, 0]
    if blue.ndim == 3:
        blue = blue[:, :, 0]
    red_norm = red.astype(np.float32) / 255.0
    green_norm = green.astype(np.float32) / 255.0
    blue_norm = blue.astype(np.float32) / 255.0
    fcc = np.stack([red_norm, green_norm, blue_norm], axis=2)
    return fcc


# 10. Calculate NDVI
def calculate_ndvi(band_data, nir_band="B8", red_band="B4"):
    nir = band_data[nir_band].astype(np.float32)
    red = band_data[red_band].astype(np.float32)
    if nir.ndim == 3:
        nir = nir[:, :, 0]
    if red.ndim == 3:
        red = red[:, :, 0]
    ndvi = np.where((nir + red) == 0, 0, (nir - red) / (nir + red))
    return ndvi


# 11. Combined visualization: B2, B3, B4, B8, True Color, False Color, NDVI
rgb_composite = create_rgb_composite(band_data)
false_color = create_false_color_composite(band_data)
ndvi = calculate_ndvi(band_data)

fig, axes = plt.subplots(1, 7, figsize=(36, 6))
fig.suptitle(f"Sentinel-2 Imagery: {ee_id}", fontsize=18)

# Bands B2, B3, B4, B8
for i, band in enumerate(["B2", "B3", "B4", "B8"]):
    band_img = band_data[band]
    if band_img.ndim == 3:
        band_img = band_img[:, :, 0]
    axes[i].imshow(band_img, cmap="gray")
    axes[i].set_title(f"Band {band}")
    axes[i].axis("off")

# True Color
axes[4].imshow(rgb_composite)
axes[4].set_title("True Color (B4-B3-B2)")
axes[4].axis("off")

# False Color
axes[5].imshow(false_color)
axes[5].set_title("False Color (B8-B4-B3)")
axes[5].axis("off")

# NDVI
im = axes[6].imshow(ndvi, cmap="RdYlGn", vmin=-1, vmax=1)
axes[6].set_title("NDVI")
axes[6].axis("off")
fig.colorbar(im, ax=axes[6], fraction=0.03, pad=0.04, label="NDVI")

plt.tight_layout(rect=[0, 0, 1, 0.94])
plt.show()
Downloading band data...
Downloading B2...
  Shape: (512, 512, 2), dtype: uint8
Downloading B3...
  Shape: (512, 512, 2), dtype: uint8
Downloading B4...
  Shape: (512, 512, 2), dtype: uint8
Downloading B8...
  Shape: (512, 512, 2), dtype: uint8
Downloading B11...
  Shape: (512, 512, 2), dtype: uint8
Downloading B12...
  Shape: (512, 512, 2), dtype: uint8