建立 SpatioTemporal 資產目錄 (STAC) 項目

瞭解如何為點陣地理空間數據資產建立 SpatioTemporal 資產目錄 (STAC) 項目。 上傳至 Microsoft Planetary Computer Pro GeoCatalog 的每個地理空間數據資產都必須有相關聯的符合 STAC 規範的項目。

在本指南中,您會:

  • 使用 pip 安裝必要的 Python 函式庫。
  • 使用提供的 Python 程式代碼顯示及檢查 GOES-18 數據。
  • 使用正則表達式從檔名擷取元數據。
  • 使用 rio-stac 從 Cloud-Optimized GeoTIFF 檔案建立 STAC 項目。
  • 使用從檔名擷取的元數據來增強 STAC 專案。
  • 將 STAC 專案新增至父 STAC 集合。
  • 驗證並儲存 STAC 目錄、集合和專案。
  • 將 STAC 專案新增至Microsoft行星電腦專業版。

本教學課程會透過代碼段示範及說明功能,以取得互動式筆記本樣式體驗, 以 Jupyter 筆記本的形式下載本教學課程。

先決條件

若要完成本快速入門,您需要:

  • 具有有效訂閱的 Azure 帳戶。 使用免費 建立帳戶的連結。
  • Azure CLI: 安裝 Azure CLI
  • 具有至少 3.8 版的 Python 環境。
  • 一些人對 STAC 標準及其在 Microsoft 行星計算機 Pro 中的實作有所熟悉 STAC 概觀

STAC的主要功能

SpatioTemporal 資產目錄 (STAC) 是建構和共用地理空間數據的開放標準。 它提供通用語言和格式來描述地理空間資產,讓您更輕鬆地跨不同平臺和應用程式探索、存取和使用這些資源。 以下是 STAC 標準的主要功能:

  • 互作性:標準化的 JSON 架構可確保跨工具和系統輕鬆整合和瞭解。
  • 擴充性:具有自定義擴充功能的彈性核心欄位,以符合特定需求。
  • 可探索性:一致的結構可增強地理空間資產的搜尋和探索。
  • 可得性:數據資產的直接連結有助於無縫擷取和整合。
  • 自動化:使用標準化元數據啟用自動化處理和分析。

將元數據標準化的 STAC 優點

  • 跨數據類型的一致性:適用於各種地理空間數據類型的統一架構。
  • 增強的共同作業:使用一般元數據格式簡化共用和共同作業。
  • 改善的數據整合:協助從多個來源整合數據集。
  • 簡化的數據管理:自動化工具可減少維護元數據的手動工作。
  • 未來證明:可延伸的本質會適應新的數據類型和技術。

Microsoft行星電腦專業版 (MC Pro) 會使用 STAC 作為其核心索引編製標準,以提供數據集之間的互作性。 本教學課程說明如何使用一般開放原始碼連結庫從頭開始建立 STAC 專案。

STAC 專案規格會詳細說明如何建構 STAC 專案,以及必須填入的必要最小元數據。 STAC 是一個彈性的標準,可讓用戶決定要包含在元數據中的哪些數據。 可用來填入 STAC 項目的中繼資料,可能包含在資料資產中的側車檔案 (.XML、.JSON、.TXT 等) 中,或甚至是檔名等位置中。 當使用者決定要包含的元數據時,用戶應該考慮未來可能想要搜尋和排序的元數據類型。

本教學課程使用來自國家海洋和大氣局 (NOAA) 靜止環境觀測衛星 R (GOES-R) 衛星陸地表面溫度 (LST) 資料集的範例雲端最佳化 GeoTIFF (COG) 影像資料。 此數據包含在 開啟的行星計算機中作為COG檔案,但缺少 STAC 元數據。 GOES 衛星會產生許多數據產品。 如需詳細資訊,請參閱 GOES-R 系列產品定義和使用者指南

本教學課程中使用的程式可以針對使用數個開放原始碼連結庫列舉索引鍵元數據欄位的任何數據類型進行一般化。

安裝 Python 程式庫

首先,我們會使用 PIP 安裝必要的 Python 連結庫:

  • rasterio:此套件用於讀取和寫入地理空間點陣數據。 它提供使用點陣數據格式的工具,例如 GeoTIFF。

  • pystac:此套件用於使用 STAC 標準。 它提供用來建立、讀取及操作 STAC 元數據的工具。

  • rio-stac:此套件會將 rasteriopystac 整合,以從光柵數據建立 STAC 項目。 它簡化了為點陣數據集產生 STAC 元數據的程式。

  • matplotlib:此程式庫用於建立視覺化和繪圖。 在地理空間數據的內容中,可協助轉譯和顯示點陣影像,讓使用者在建立 STAC 元數據之前以可視化方式檢查數據。

  • azure-storage-blob:此 Azure 記憶體用戶端連結庫提供 Azure Blob 記憶體服務的存取權。 它可讓使用者直接與雲端儲存的地理空間數據互動,讓使用者讀取及處理儲存在 Azure Blob 容器中的檔案,而不需要先下載。

!pip install rasterio pystac rio_stac matplotlib azure-storage-blob 

下一節的程式代碼會顯示開啟的行星計算機中的一些 GOES-18 數據。 我們會從 2023 年的第 208 天起選取 GOES-R 進階基準影像層級 2 陸地表面溫度 - CONUS 數據集和任意檔案。

# Import Necessary Libraries 
import requests
from azure.storage.blob import ContainerClient
import matplotlib.pyplot as plt
from rasterio.io import MemoryFile
import os
from urllib.parse import urlparse

# Function to get the SAS token
def get_sas_token(endpoint):
    response = requests.get(endpoint)
    if response.status_code == 200:
        data = response.json()
        return data.get("token")
    else:
        raise Exception(f"Failed to get SAS token: {response.status_code} - {response.text}")

# Define Azure Blob Storage parameters
storage_account_name = "goeseuwest"
container_name = "noaa-goes-cogs"
blob_domain = f"https://{storage_account_name}.blob.core.windows.net"
sas_endpoint = f"https://planetarycomputer.microsoft.com/api/sas/v1/token/{storage_account_name}/{container_name}/"


# Get the SAS token
sas_token = get_sas_token(sas_endpoint)

# Construct the container URL with the SAS token
container_url = f"{blob_domain}/{container_name}?{sas_token}"

# Create a ContainerClient
container_client = ContainerClient.from_container_url(container_url)

# Define data you want, this can be changed for other datasets that are in storage in the open Planetary Computer
satellite = "goes-18"      # The specific GOES satellite (GOES-18, also known as GOES-West)
product = "ABI-L2-LSTC"    # The data product type (Advanced Baseline Imager Level 2 Land Surface Temperature - CONUS)
year = "2023"              # The year the data was collected
day_of_year = "208"        # The day of year (DOY) - day 208 corresponds to July 27, 2023

# Construct the directory path
directory_path = f"{satellite}/{product}/{year}/{day_of_year}/"

# Get just the first blob by using next() and limiting the iterator

first_blob = next(container_client.list_blobs(name_starts_with=directory_path))

# Function to read and display a .tif file from a URL
def display_tif_from_url(url, title):
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with MemoryFile(response.content) as memfile:
            with memfile.open() as dataset:
                plt.figure(figsize=(10, 10))
                plt.imshow(dataset.read(1), cmap='gray')
                plt.title(title)
                plt.colorbar()
                plt.show()
    else:
        raise Exception(f"Failed to read .tif file: {response.status_code} - {response.text}")

# Create the URL for the blob using the container_url components
file_url = f"{blob_domain}/{container_name}/{first_blob.name}?{sas_token}"

# Extract the filename safely from the URL without the SAS token
parsed_url = urlparse(file_url)
path = parsed_url.path  # Gets just the path portion of the URL
filename = os.path.basename(path)  # Get just the filename part

# Remove .tif extension if present
file_name = filename.replace('.tif', '')

print(f"Extracted File Name: {file_name}")

display_tif_from_url(file_url, file_name)

來自 GOES-18 衛星地理空間點陣數據的灰階視覺效果,顯示整個區域的陸地表面溫度模式。

從數據和檔名中查看,我們已經可以看到建置 STAC Item 所需的關鍵元數據片段。 檔名包含哪些衛星擷取數據以及擷取數據時的相關信息。

針對範例,檔名是OR_ABI-L2-LSTC-M6_G18_s20232080101177_e20232080103550_c20232080104570_DQF,根據產品指南,這表示:

詳細元件

領域 說明 目標
OR 作業系統環境 指定收集數據的系統環境
ABI 進階基準成像儀器 識別用於擷取數據的儀器
L2 層級 2 產品 (衍生產品) 表示數據是衍生產品,從原始觀察處理
LSTC 陸地表面溫度 (晴天) 產品 代表特定產品類型,著重於晴天條件下的土地表面溫度
M6 模式 6 掃描 (完整磁碟掃描) 描述掃描模式,涵蓋地球的完整磁碟
G18 GOES-18衛星(也稱為 GOES-West) 識別從中收集數據的衛星

觀察時間詳細數據

觀察開始 (s20232080201177)-

領域 說明 目標
Year 2023 指定觀察的年份
年份中的日期 208 指出觀察開始時的年份日期
Time 02:01:17 UTC 提供以UTC開始觀察的確切時間
十分之一秒 7 提高觀察開始時間的精確性

結束觀察 (e20232080203550)-

領域 說明 目標
Year 2023 指定觀察的年份
年份中的日期 208 指出觀察結束的年份日期
Time 02:03:55 UTC 提供觀察以UTC結束的確切時間
十分之一秒 0 增加觀察結束時間的精確度

檔案建立時間 (c20232080204563)-

領域 說明 目標
Year 2023 指定檔案建立的年份
年份中的日期 208 指出建立檔案的年份日期
Time 02:04:56 UTC 提供以UTC建立檔案的確切時間
十分之一秒 3 提高檔案建立時間的精確度

其他資訊:

領域 說明 目標
DQF 數據品質旗標,指出對應數據的質量資訊 提供數據質量的相關信息
.tif 副檔名 指出數據的檔案格式

下列程式代碼會使用正則表達式 (regex) 從檔名擷取此元數據。

import re
from datetime import datetime, timedelta

def extract_goes_metadata(filename):
    """
    Extracts key metadata from a NOAA GOES satellite filename using regular expressions.

    Args:
        filename (str): The filename to parse.

    Returns:
        dict: A dictionary containing the extracted metadata.
    """

    # Regular expression pattern to match the filename format
    pattern = re.compile(
        r"^(OR)_"  # System (OR)
        r"(ABI)-(L\d)-(LSTC)-(M\d)_"  # Instrument, Level, Product, Mode
        r"(G\d{2})_"  # Satellite (G18)
        r"s(\d{4})(\d{3})(\d{2})(\d{2})(\d{2})(\d)_"  # Start time
        r"e(\d{4})(\d{3})(\d{2})(\d{2})(\d{2})(\d)_"  # End time
        r"c(\d{4})(\d{3})(\d{2})(\d{2})(\d{2})(\d)_"  # Creation time
        r"([A-Z0-9]+)$"  # Data quality flag
    )

    match = pattern.match(filename)

    if not match:
        return None  # Or raise an exception if you prefer

    # Extract all fields from the regular expression match groups
    (
        system,          # Operational system environment
        instrument,      # Advanced Baseline Imager
        level,           # Product level (L2)
        product_type,    # Product type (LSTC - Land Surface Temperature)
        mode,            # Scanning mode (M6)
        satellite,       # Satellite identifier (G18)
        s_year, s_doy, s_hour, s_minute, s_second, s_tenth,  # Start time components
        e_year, e_doy, e_hour, e_minute, e_second, e_tenth,  # End time components
        c_year, c_doy, c_hour, c_minute, c_second, c_tenth,  # Creation time components
        data_quality_flag  # Quality flag indicator
    ) = match.groups()

    def parse_goes_time(year, doy, hour, minute, second, tenth):
        """Parses GOES time components into an ISO format string."""
        try:
            dt = datetime(int(year), 1, 1) + timedelta(
                days=int(doy) - 1,
                hours=int(hour),
                minutes=int(minute),
                seconds=int(second),
                microseconds=int(tenth) * 100000,
            )
            return dt
        except ValueError:
            return None

    # Parse the time components into datetime objects
    start_time = parse_goes_time(s_year, s_doy, s_hour, s_minute, s_second, s_tenth)
    end_time = parse_goes_time(e_year, e_doy, e_hour, e_minute, e_second, e_tenth)
    creation_time = parse_goes_time(c_year, c_doy, c_hour, c_minute, c_second, c_tenth)

    # Create a dictionary to organize all extracted metadata
    metadata = {
        "system": system,               # Operational system environment (e.g., "OR" for operational)
        "instrument": instrument,       # Instrument used to capture data (e.g., "ABI" for Advanced Baseline Imager)
        "level": level,                 # Processing level of the data (e.g., "L2" for Level 2)
        "product_type": product_type,   # Type of product (e.g., "LSTC" for Land Surface Temperature Clear Sky)
        "mode": mode,                   # Scanning mode (e.g., "M6" for Mode 6, full disk scan)
        "satellite": satellite,         # Satellite identifier (e.g., "G18" for GOES-18)
        "start_time": start_time,       # Observation start time as datetime object
        "end_time": end_time,           # Observation end time as datetime object
        "creation_time": creation_time, # File creation time as datetime object
        "data_quality_flag": data_quality_flag,  # Quality flag for the data (e.g., "DQF")
    }

    return metadata


# Example usage:
print(file_name)
metadata_from_filename = extract_goes_metadata(file_name)
print(metadata_from_filename)
OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF
{'system': 'OR', 'instrument': 'ABI', 'level': 'L2', 'product_type': 'LSTC', 'mode': 'M6', 'satellite': 'G18', 'start_time': datetime.datetime(2023, 7, 27, 0, 1, 17, 700000), 'end_time': datetime.datetime(2023, 7, 27, 0, 3, 55), 'creation_time': datetime.datetime(2023, 7, 27, 0, 4, 56, 800000), 'data_quality_flag': 'DQF'}

從 Cloud-Optimized GeoTIFF 檔案建立 STAC 項目

下列程式碼區塊使用 rio-stac 函式庫自動化建立來自 Cloud-Optimized GeoTIFF(COG)的 STAC 項目。

指向 COG 檔案時, rio-stac 會自動將空間界限、投影資訊和點陣屬性等基本元數據擷取並組織成標準化 STAC 格式。 連結庫會處理從 GeoTIFF 讀取內嵌技術元數據的複雜工作,並將其轉換成符合 STAC 規範的欄位,包括:

  • 幾何學
  • 周框方塊 (bbox)
  • 投影詳細數據
  • 點陣特性
  • STAC 延伸模組

此自動化可大幅減少建立有效 STAC 專案所需的手動工作,並確保元數據中的一致性

備註

GeoCatalog 對可用於 STAC 專案標識碼和資產密鑰的字元有限制。 請確定您的識別碼不包含下列字元:-、、_+(、、 ).。 您可能需要修改產生邏輯, item_id 以取代或移除檔名中的這些字元。

from rio_stac import create_stac_item
from rasterio.io import MemoryFile
import json
from urllib.parse import urlparse, unquote

def create_stac_item_from_cog(url):
    """
    Create a basic STAC Item for GOES data using rio-stac with proper spatial handling
    
    Args:
        url (str): URL to the COG file
        
    Returns:
        pystac.Item: STAC Item with basic metadata and correct spatial information
    """
    
    # Extract the filename safely from the URL
    parsed_url = urlparse(url)
    path = parsed_url.path  # Gets just the path portion of the URL
    filename = os.path.basename(path)  # Get just the filename part
    
    # Remove .tif extension if present
    item_id = filename.replace('.tif', '')
    
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with MemoryFile(response.content) as memfile:
            with memfile.open() as dataset:
                # Create base STAC item from rasterio dataset calling create_stac_item from rio_stac
                stac_item = create_stac_item(
                    source=dataset,  # The rasterio dataset object representing the COG file
                    id=item_id,  # Generate a unique ID by extracting the filename without the .tif extension
                    asset_name='data',  # Name of the asset, indicating it contains the primary data
                    asset_href=url,  # URL to the COG file, used as the asset's location
                    with_proj=True,  # Include projection metadata (e.g., CRS, bounding box, etc.)
                    with_raster=True,  # Include raster-specific metadata (e.g., bands, resolution, etc.)
                    properties={
                        'datetime': None,  # Set datetime to None since explicit start/end times may be added later
                        # Add rasterio-specific metadata for the raster bands
                        'raster:bands': [
                            {
                                'nodata': dataset.nodata,  # Value representing no data in the raster
                                'data_type': dataset.dtypes[0],  # Data type of the raster (e.g., uint16)
                                'spatial_resolution': dataset.res[0]  # Spatial resolution of the raster in meters
                            }
                        ],
                        'file:size': len(response.content)  # Size of the file in bytes
                    },
                    extensions=[
                        'https://stac-extensions.github.io/file/v2.1.0/schema.json'  # Add the file extension schema for additional metadata
                    ]
                )
                
                return stac_item
    else:
        raise Exception(f"Failed to read .tif file: {response.status_code} - {response.text}")

# Example usage
sas_token = get_sas_token(sas_endpoint) # refresh the SAS token prior to creating STAC item
# Create file URL using the first_blob variable that's already defined
file_url = f"{blob_domain}/{container_name}/{first_blob.name}?{sas_token}"
# Create STAC item for the first blob
stac_item = create_stac_item_from_cog(file_url)

# Print the STAC item as JSON
print(json.dumps(stac_item.to_dict(), indent=2))
    {
      "type": "Feature",
      "stac_version": "1.0.0",
      "stac_extensions": [
        "https://stac-extensions.github.io/file/v2.1.0/schema.json",
        "https://stac-extensions.github.io/projection/v1.1.0/schema.json",
        "https://stac-extensions.github.io/raster/v1.1.0/schema.json"
      ],
      "id": "OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -161.57885623466754,
              14.795666555826678
            ],
            [
              -112.42114380921453,
              14.79566655500485
            ],
            [
              -89.5501123912648,
              53.52729778421186
            ],
            [
              175.5501122574517,
              53.52729779865781
            ],
            [
              -161.57885623466754,
              14.795666555826678
            ]
          ]
        ]
      },
      "bbox": [
        -161.57885623466754,
        14.79566655500485,
        175.5501122574517,
        53.52729779865781
      ],
      "properties": {
        "datetime": "2025-03-26T14:46:05.484602Z",
        "raster:bands": [
          {
            "nodata": 65535.0,
            "data_type": "uint16",
            "spatial_resolution": 2004.017315487541
          }
        ],
        "file:size": 118674,
        "proj:epsg": null,
        "proj:geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -2505021.6463773525,
                1583173.791653181
              ],
              [
                2505021.6423414997,
                1583173.791653181
              ],
              [
                2505021.6423414997,
                4589199.764884492
              ],
              [
                -2505021.6463773525,
                4589199.764884492
              ],
              [
                -2505021.6463773525,
                1583173.791653181
              ]
            ]
          ]
        },
        "proj:bbox": [
          -2505021.6463773525,
          1583173.791653181,
          2505021.6423414997,
          4589199.764884492
        ],
        "proj:shape": [
          1500,
          2500
        ],
        "proj:transform": [
          2004.017315487541,
          0.0,
          -2505021.6463773525,
          0.0,
          -2004.017315487541,
          4589199.764884492,
          0.0,
          0.0,
          1.0
        ],
        "proj:wkt2": "PROJCS[\"unnamed\",GEOGCS[\"unknown\",DATUM[\"unnamed\",SPHEROID[\"Spheroid\",6378137,298.2572221]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]],PROJECTION[\"Geostationary_Satellite\"],PARAMETER[\"central_meridian\",-137],PARAMETER[\"satellite_height\",35786023],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION[\"PROJ4\",\"+proj=geos +lon_0=-137 +h=35786023 +x_0=0 +y_0=0 +ellps=GRS80 +units=m +no_defs +sweep=x\"]]"
      },
      "links": [],
      "assets": {
        "data": {
          "href": "https://goeseuwest.blob.core.windows.net/noaa-goes-cogs/goes-18/ABI-L2-LSTC/2023/208/00/OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF.tif?st=2025-03-25T14%3A46%3A03Z&se=2025-03-26T15%3A31%3A03Z&sp=rl&sv=2024-05-04&sr=c&skoid=9c8ff44a-6a2c-4dfb-b298-1c9212f64d9a&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2025-03-26T12%3A41%3A49Z&ske=2025-04-02T12%3A41%3A49Z&sks=b&skv=2024-05-04&sig=BuMxN2NUTdrhzY7Dvpd/X4yfX8gnFpHOzANHQLkKE1k%3D",
          "type": "image/tiff; application=geotiff",
          "raster:bands": [
            {
              "data_type": "uint16",
              "scale": 1.0,
              "offset": 0.0,
              "sampling": "area",
              "nodata": 65535.0,
              "unit": "1",
              "statistics": {
                "mean": 2.780959413109756,
                "minimum": 0,
                "maximum": 3,
                "stddev": 0.710762086175375,
                "valid_percent": 100.0
              },
              "histogram": {
                "count": 11,
                "min": 0.0,
                "max": 3.0,
                "buckets": [
                  26655,
                  0,
                  0,
                  25243,
                  0,
                  0,
                  7492,
                  0,
                  0,
                  570370
                ]
              }
            }
          ],
          "roles": []
        }
      }
    }

來自 rio-stac 的 STAC 項目 JSON

rio-stac 連結庫會自動讀取 GOES COG 檔案,並自動擷取密鑰元數據。

此外,根據包含的元數據類型,rio-stac 新增了相關的 STAC 延伸模組。 STAC延伸模組會藉由新增標準化的網域特定元數據來增強核心規格。

  • 檔案副檔名提供重要的檔案中繼資料,例如大小和總和檢查碼。
  • 投影延伸模組 會擷取空間參考資訊,包括座標系統和周框方塊。
  • 點陣延伸模組 會將點陣數據特有的屬性標準化,例如頻帶資訊和空間解析度。

以下的表格提供 rio-stac 找到的所有元數據的說明。

核心欄位

領域 說明 目標
型別 一律為「功能」 將類型識別為 GeoJSON 功能
stac_version "1.0.0" 指定 STAC 標準版本
id 唯一識別碼 包含衛星、產品和時間資訊
stac_extensions 架構 URL 清單 定義額外的元數據欄位

空間資訊

領域 說明 目標
幾何 GeoJSON 多邊形 定義 WGS84 座標下的數據範圍
bbox 週框方塊座標 提供簡單的空間範圍來快速篩選
proj:geometry 投影-特定多邊形 原生投影座標中的磁碟使用量
proj:bbox 原生投影界限 衛星座標系統中的空間範圍
proj:圖形 [1500, 2500] 以像素為單位的影像尺寸
proj:轉換 仿射轉換 將像素對應至座標空間
proj:wkt2 Well-Known Text 完整投影定義
proj:epsg null 此地圖投影沒有標準 EPSG 程式碼

時間資訊

領域 說明 目標
日期時間 建立時間戳 建立此 STAC 項目時

點陣資訊

領域 說明 目標
點陣:波段 頻帶對象的陣列 描述點陣數據屬性
→ 資料型別 “uint16” 像素數據類型
→ 空間解析度 2004.02m 地面樣本距離
→縮放/位移 轉換因數 將像素值轉換為實體單位(Kelvin)
無數據 65535.0 表示無數據的值
→統計數據 統計摘要 提供數據分佈資訊
→直方圖 值分佈 將數據分佈可視化

資產資訊

領域 說明 目標
assets.data 主要數據資產 指向實際的資料檔案
→ href URL Cloud-Optimized GeoTIFF 的位置
→類型 媒體類型 識別檔案格式

檔案元數據

領域 說明 目標
檔案大小 943,250 個字節 數據檔的大小

從檔名新增元數據

接下來,我們會新增我們在檔名中找到的數據,以完成填寫此 STAC 專案的元數據。

要注意的一件事是 STAC 專案的所有日期時間都必須符合 ISO 8601。 PySTAC 連結庫具有datetime_to_str函式,可確保數據的格式正確。

import pystac

def enhance_stac_item_with_metadata(stac_item, metadata_from_filename):
    """
    Enhances a STAC Item with additional metadata from GOES filename.
    
    Args:
        stac_item (pystac.Item): Existing STAC Item created by rio-stac
        metadata_from_filename (dict): Metadata extracted from filename
        
    Returns:
        pystac.Item: Enhanced STAC Item
    """
    # Add satellite/sensor properties to the STAC item
    stac_item.properties.update({
        'platform': f"GOES-{metadata_from_filename['satellite'][1:]}",
        'instruments': [metadata_from_filename['instrument']],
        'constellation': 'GOES'
    })
    
    # Add temporal properties to the STAC item, use pystac to ensure time conforms to ISO 8601
    stac_item.datetime = None  # Clear the default datetime
    stac_item.properties.update({
        'start_datetime': pystac.utils.datetime_to_str(metadata_from_filename['start_time']),
        'end_datetime': pystac.utils.datetime_to_str(metadata_from_filename['end_time']),
        'created': pystac.utils.datetime_to_str(metadata_from_filename['creation_time'])
    })
    
    # Add GOES-specific properties to the STAC item
    stac_item.properties.update({
        'goes:system': metadata_from_filename['system'],
        'goes:level': metadata_from_filename['level'],
        'goes:product_type': metadata_from_filename['product_type'],
        'goes:mode': metadata_from_filename['mode'],
        'goes:processing_level': metadata_from_filename['level'],
        'goes:data_quality_flag': metadata_from_filename['data_quality_flag']
    })
    
    return stac_item


# Example usage in new cell
stac_item = enhance_stac_item_with_metadata(stac_item, metadata_from_filename)
print(json.dumps(stac_item.to_dict(), indent=2))

STAC 項目:

    {
      "type": "Feature",
      "stac_version": "1.0.0",
      "stac_extensions": [
        "https://stac-extensions.github.io/file/v2.1.0/schema.json",
        "https://stac-extensions.github.io/projection/v1.1.0/schema.json",
        "https://stac-extensions.github.io/raster/v1.1.0/schema.json"
      ],
      "id": "OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -161.57885623466754,
              14.795666555826678
            ],
            [
              -112.42114380921453,
              14.79566655500485
            ],
            [
              -89.5501123912648,
              53.52729778421186
            ],
            [
              175.5501122574517,
              53.52729779865781
            ],
            [
              -161.57885623466754,
              14.795666555826678
            ]
          ]
        ]
      },
      "bbox": [
        -161.57885623466754,
        14.79566655500485,
        175.5501122574517,
        53.52729779865781
      ],
      "properties": {
        "datetime": null,
        "raster:bands": [
          {
            "nodata": 65535.0,
            "data_type": "uint16",
            "spatial_resolution": 2004.017315487541
          }
        ],
        "file:size": 118674,
        "proj:epsg": null,
        "proj:geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -2505021.6463773525,
                1583173.791653181
              ],
              [
                2505021.6423414997,
                1583173.791653181
              ],
              [
                2505021.6423414997,
                4589199.764884492
              ],
              [
                -2505021.6463773525,
                4589199.764884492
              ],
              [
                -2505021.6463773525,
                1583173.791653181
              ]
            ]
          ]
        },
        "proj:bbox": [
          -2505021.6463773525,
          1583173.791653181,
          2505021.6423414997,
          4589199.764884492
        ],
        "proj:shape": [
          1500,
          2500
        ],
        "proj:transform": [
          2004.017315487541,
          0.0,
          -2505021.6463773525,
          0.0,
          -2004.017315487541,
          4589199.764884492,
          0.0,
          0.0,
          1.0
        ],
        "proj:wkt2": "PROJCS[\"unnamed\",GEOGCS[\"unknown\",DATUM[\"unnamed\",SPHEROID[\"Spheroid\",6378137,298.2572221]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]],PROJECTION[\"Geostationary_Satellite\"],PARAMETER[\"central_meridian\",-137],PARAMETER[\"satellite_height\",35786023],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION[\"PROJ4\",\"+proj=geos +lon_0=-137 +h=35786023 +x_0=0 +y_0=0 +ellps=GRS80 +units=m +no_defs +sweep=x\"]]",
        "platform": "GOES-18",
        "instruments": [
          "ABI"
        ],
        "constellation": "GOES",
        "start_datetime": "2023-07-27T00:01:17.700000Z",
        "end_datetime": "2023-07-27T00:03:55Z",
        "created": "2023-07-27T00:04:56.800000Z",
        "goes:system": "OR",
        "goes:level": "L2",
        "goes:product_type": "LSTC",
        "goes:mode": "M6",
        "goes:processing_level": "L2",
        "goes:data_quality_flag": "DQF"
      },
      "links": [],
      "assets": {
        "data": {
          "href": "https://goeseuwest.blob.core.windows.net/noaa-goes-cogs/goes-18/ABI-L2-LSTC/2023/208/00/OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF.tif?st=2025-03-25T14%3A46%3A03Z&se=2025-03-26T15%3A31%3A03Z&sp=rl&sv=2024-05-04&sr=c&skoid=9c8ff44a-6a2c-4dfb-b298-1c9212f64d9a&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2025-03-26T12%3A41%3A49Z&ske=2025-04-02T12%3A41%3A49Z&sks=b&skv=2024-05-04&sig=BuMxN2NUTdrhzY7Dvpd/X4yfX8gnFpHOzANHQLkKE1k%3D",
          "type": "image/tiff; application=geotiff",
          "raster:bands": [
            {
              "data_type": "uint16",
              "scale": 1.0,
              "offset": 0.0,
              "sampling": "area",
              "nodata": 65535.0,
              "unit": "1",
              "statistics": {
                "mean": 2.780959413109756,
                "minimum": 0,
                "maximum": 3,
                "stddev": 0.710762086175375,
                "valid_percent": 100.0
              },
              "histogram": {
                "count": 11,
                "min": 0.0,
                "max": 3.0,
                "buckets": [
                  26655,
                  0,
                  0,
                  25243,
                  0,
                  0,
                  7492,
                  0,
                  0,
                  570370
                ]
              }
            }
          ],
          "roles": []
        }
      }
    }

將 STAC 專案新增至集合

MC Pro 要求所有 STAC 項目都有其內嵌的上層 STAC 集合識別碼參考。 在本教程中,STAC Collection ID 是衛星名稱和數據產品名稱。

使用 PySTAC 時,可以輕鬆地使用從來源檔案收集的一些元數據,填入 STAC 集合的主要欄位,並使用內建的驗證函式。

下列程式代碼會建立父 STAC 集合來存放 GOES 數據。 然後,程式代碼會將此資訊儲存至用來建立Microsoft行星計算機 Pro STAC 集合的檔案,並將 STAC 專案內嵌到行星電腦專業版中。

# Define collection properties
collection_id = f"{satellite}-{product}"
collection_title = f"{satellite.upper()} {product} Collection" 
collection_desc = f"Collection of {satellite} {product} Earth observation data"

# Create spatial extent
bbox = [-180, -60, 10, 60]  # placeholder, replace with actual data at a later date
spatial_extent = pystac.SpatialExtent([bbox])

# Create temporal extent, use current date time or replace with existing datetimes in stac_item
start_datetime = datetime.now()
if hasattr(metadata_from_filename, 'get'): 
    if metadata_from_filename.get('start_time'):
        start_datetime = metadata_from_filename.get('start_time')

temporal_extent = pystac.TemporalExtent([[start_datetime, None]])
extent = pystac.Extent(spatial=spatial_extent, temporal=temporal_extent)

# Create the STAC Collection
collection = pystac.Collection(
    id=collection_id,
    description=collection_desc,
    extent=extent,
    title=collection_title,
    license="public-domain",
)

# Add keywords and provider
collection.keywords = ["GOES", "satellite", "weather", "NOAA", satellite, product]
collection.providers = [
    pystac.Provider(
        name="NOAA",
        roles=["producer", "licensor"],
        url="https://www.noaa.gov/"
    )
]

# Create output directories
output_dir = "stac_catalog"
collection_dir = os.path.join(output_dir, collection_id)
items_dir = os.path.join(collection_dir, "items")
os.makedirs(items_dir, exist_ok=True)

# Important: Save the collection first to generate proper file paths
collection_path = os.path.join(collection_dir, "collection.json")
collection.set_self_href(collection_path)

# Extract filename for the item
original_filename = first_blob.name.split('/')[-1]
base_filename = original_filename.replace('.tif', '')
item_path = os.path.join(items_dir, f"{base_filename}.json")

# Set the item's proper href
stac_item.set_self_href(item_path)

# Now associate the item with the collection (after setting hrefs)
collection.add_item(stac_item)

# Create a catalog to contain the collection
catalog = pystac.Catalog(
    id="goes-data-catalog",
    description="GOES Satellite Data Catalog",
    title="GOES Data"
)
catalog_path = os.path.join(output_dir, "catalog.json")
catalog.set_self_href(catalog_path)
catalog.add_child(collection)

# Validate the collection and contained items
print("Validating collection and items...")
try:
    collection.validate_all()
    print("✅ Collection and items validated successfully")
    
    # Save everything to disk
    catalog.normalize_and_save(catalog_path, pystac.CatalogType.SELF_CONTAINED)
    print(f"✅ STAC catalog saved at: {catalog_path}")
    print(f"✅ STAC collection saved at: {collection_path}")
    print(f"✅ STAC item saved at: {item_path}")
    
except Exception as e:
    print(f"❌ Validation error: {str(e)}")
Validating collection and items...
✅ Collection and items validated successfully
✅ STAC catalog saved at: stac_catalog/catalog.json
✅ STAC collection saved at: stac_catalog/goes-18-ABI-L2-LSTC/collection.json
✅ STAC item saved at: stac_catalog/goes-18-ABI-L2-LSTC/items/OR_ABI-L2-LSTC-M6_G18_s20232080001177_e20232080003550_c20232080004568_DQF.json

後續步驟

現在您已建立一些 STAC 項目,接下來是將數據導入 Microsoft Planetary Computer 專業版。

針對單一項目擷取:

針對大量擷取:

我們也提供 STAC Forge 工具,其使用範本來增強數據自動化。