Recorte em cor verdadeira do Sentinel-2 sobre lavouras de Sorriso, MT, com talhões retangulares em tons de verde e marrom. -12.55°, -55.72° · Sorriso, MT
tutorial · 13 de outubro de 2026

Buscando imagens Sentinel-2 via STAC

Encontre cenas Sentinel-2 por área, data e cobertura de nuvens no Microsoft Planetary Computer, sem login e sem download manual.

Nível
Iniciante
Tempo
15 min
Área de estudo
Sorriso, MT
Bibliotecas
pypystac-clientpyplanetary-computerpygeopandaspyrioxarraypymatplotlib

Objetivo

Antes de calcular qualquer índice, é preciso achar a imagem certa. Neste tutorial você vai usar o padrão STAC (SpatioTemporal Asset Catalog) para buscar cenas do Sentinel-2 no catálogo público do Microsoft Planetary Computer.

Ao final, você saberá:

  • o que é um catálogo STAC e como ele organiza coleções, itens e assets;
  • buscar cenas por área, intervalo de datas e cobertura de nuvens;
  • transformar o resultado em uma tabela (GeoDataFrame) para filtrar e ordenar;
  • abrir só o recorte da cena que interessa, sem baixar o arquivo inteiro.

A área de estudo é o entorno de Sorriso (MT), um dos maiores produtores de grãos do país.

Pré-requisitos

  • Python básico (listas, dicionários, funções).
  • Nenhuma conta ou chave de API: o Planetary Computer assina as URLs automaticamente.

Bibliotecas usadas (todas no environment.yml do site):

Biblioteca Para quê
pystac-client conversar com a API STAC
planetary-computer assinar as URLs dos arquivos
geopandas tabela com geometrias dos resultados
rioxarray ler o recorte da imagem (COG)
matplotlib gráficos
DicaRodando no Colab

Use o botão Abrir no Colab no topo da página e rode a célula abaixo antes das demais. No seu computador, com o ambiente do site ativo, ela não é necessária.

# Descomente no Colab:
# !pip install -q pystac-client planetary-computer geopandas rioxarray matplotlib
import geopandas as gpd
import matplotlib.pyplot as plt
import planetary_computer
import pystac_client
import rioxarray  # noqa: F401  (registra o acessor .rio)
from shapely.geometry import box, shape

plt.style.use("dark_background")  # opcional: gráficos no estilo escuro do site

Acesso aos dados: o catálogo STAC

Um catálogo STAC tem três níveis:

  • Coleção: um conjunto de dados, como sentinel-2-l2a (reflectância de superfície já corrigida da atmosfera);
  • Item: uma cena, com data, geometria e metadados (nuvens, órbita, tile…);
  • Asset: cada arquivo do item, como uma banda (B04, B08) ou a composição colorida (visual).

Abrimos o catálogo passando modifier=planetary_computer.sign_inplace, que assina cada URL para que possamos ler os arquivos diretamente.

catalogo = pystac_client.Client.open(
    "https://planetarycomputer.microsoft.com/api/stac/v1",
    modifier=planetary_computer.sign_inplace,
)
s2 = catalogo.get_collection("sentinel-2-l2a")
print(s2.title)
print("Extensão temporal:", s2.extent.temporal.intervals[0][0].date(), "até hoje")
Sentinel-2 Level-2A
Extensão temporal: 2015-06-27 até hoje

Definindo a busca

Vamos procurar cenas sobre um quadrado de cerca de 11 km ao redor de Sorriso, durante a estação seca de 2025 (junho a setembro), quando o céu do Mato Grosso costuma estar limpo.

O filtro de nuvens usa a propriedade eo:cloud_cover de cada item, com a extensão query da API.

lat, lon = -12.55, -55.72
d = 0.05  # meio lado do quadrado, em graus (~5,5 km)
bbox = [lon - d, lat - d, lon + d, lat + d]

busca = catalogo.search(
    collections=["sentinel-2-l2a"],
    bbox=bbox,
    datetime="2025-06-01/2025-09-30",
    query={"eo:cloud_cover": {"lt": 20}},
)
itens = busca.item_collection()
print(f"{len(itens)} cenas encontradas")
27 cenas encontradas

Os resultados como tabela

Cada item é um GeoJSON Feature. O geopandas transforma a coleção inteira em uma tabela com geometria, o que facilita ordenar e filtrar.

gdf = gpd.GeoDataFrame.from_features(itens.to_dict(), crs="EPSG:4326")
gdf["id"] = [i.id for i in itens]
gdf["data"] = gdf["datetime"].str[:10]
gdf["tile"] = gdf["s2:mgrs_tile"]
tabela = gdf[["data", "platform", "tile", "eo:cloud_cover"]].sort_values("data")
tabela.head(10)
data platform tile eo:cloud_cover
26 2025-06-04 Sentinel-2C 21LXG 12.963872
25 2025-06-06 Sentinel-2A 21LXG 0.138244
24 2025-06-09 Sentinel-2B 21LXG 0.002827
23 2025-06-14 Sentinel-2C 21LXG 0.611727
22 2025-06-16 Sentinel-2A 21LXG 8.199535
21 2025-06-19 Sentinel-2B 21LXG 8.898673
20 2025-06-26 Sentinel-2A 21LXG 0.083600
19 2025-06-29 Sentinel-2B 21LXG 6.090753
18 2025-07-06 Sentinel-2A 21LXG 0.123832
17 2025-07-09 Sentinel-2B 21LXG 0.001553

O Sentinel-2 é distribuído em tiles da grade MGRS, quadrados de 110 × 110 km. A coluna tile diz em qual deles cada cena está. Se a sua área cair na borda entre dois tiles, a mesma data aparece duas vezes, uma por tile. Vamos ver onde a nossa área fica.

aoi = gpd.GeoDataFrame(geometry=[box(*bbox)], crs="EPSG:4326")

fig, ax = plt.subplots(figsize=(7, 6))
gdf.drop_duplicates("tile").plot(
    ax=ax,
    column="tile",
    alpha=0.35,
    edgecolor="white",
    legend=True,
    legend_kwds={"title": "Tile MGRS", "loc": "upper left"},
)
aoi.boundary.plot(ax=ax, color="#FF7A45", linewidth=2.5)
ax.set_title("Footprints dos tiles e área de interesse (laranja)")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
plt.show()

E a cobertura de nuvens ao longo da estação seca:

por_data = gdf.groupby("data")["eo:cloud_cover"].min()

fig, ax = plt.subplots(figsize=(8, 3.2))
ax.bar(por_data.index, por_data.values, color="#22D3EE")
ax.axhline(5, color="#FF7A45", ls="--", lw=1, label="5%")
ax.set_ylabel("Nuvens (%)")
ax.set_title("Cobertura de nuvens por data")
ax.tick_params(axis="x", rotation=60, labelsize=8)
ax.legend()
plt.tight_layout()
plt.show()

Escolhendo a melhor cena

Ficamos com a cena de menor cobertura de nuvens cujo footprint contém toda a área de interesse (assim não precisamos fazer mosaico).

area = box(*bbox)
cobrem = [i for i in itens if shape(i.geometry).contains(area)]
print(f"{len(cobrem)} de {len(itens)} cenas cobrem toda a área")

melhor = min(cobrem, key=lambda i: i.properties["eo:cloud_cover"])

print("Cena:", melhor.id)
print("Data:", melhor.datetime.date())
print(f"Nuvens: {melhor.properties['eo:cloud_cover']:.2f}%")
print("Assets disponíveis:", ", ".join(sorted(melhor.assets)))
27 de 27 cenas cobrem toda a área
Cena: S2A_MSIL2A_20250726T140111_R067_T21LXG_20250726T183013
Data: 2025-07-26
Nuvens: 0.00%
Assets disponíveis: AOT, B01, B02, B03, B04, B05, B06, B07, B08, B09, B11, B12, B8A, SCL, WVP, datastrip-metadata, granule-metadata, inspire-metadata, product-metadata, rendered_preview, safe-manifest, tilejson, visual

Cada asset é um COG (Cloud Optimized GeoTIFF): um GeoTIFF organizado em blocos, que permite ler só a janela que interessa via HTTP. É isso que vamos fazer com o asset visual, a composição em cor verdadeira (B04, B03, B02) já pronta, em 8 bits.

visual = rioxarray.open_rasterio(melhor.assets["visual"].href)
print("Cena completa:", visual.shape, "pixels — CRS", visual.rio.crs)

recorte = visual.rio.clip_box(*bbox, crs="EPSG:4326")
print("Recorte:", recorte.shape)
Cena completa: (3, 10980, 10980) pixels — CRS EPSG:32721
Recorte: (3, 1112, 1093)

Visualização do resultado

O recorte tem pouco mais de mil pixels de lado (10 m de resolução). Os talhões retangulares das lavouras e as matas ciliares ao longo dos rios ficam bem visíveis.

fig, ax = plt.subplots(figsize=(8, 8))
recorte.plot.imshow(ax=ax)
ax.set_title(f"Sentinel-2 · cor verdadeira · {melhor.datetime.date()} · Sorriso (MT)")
ax.set_xlabel("x (m, UTM)")
ax.set_ylabel("y (m, UTM)")
ax.set_aspect("equal")
plt.show()

Por fim, salvamos uma miniatura sem eixos. É ela que aparece no card deste tutorial no site.

img = recorte.transpose("y", "x", "band").values
altura = img.shape[0]
faixa = img[int(altura * 0.2) : int(altura * 0.2) + int(img.shape[1] * 9 / 16)]  # recorte 16:9

fig = plt.figure(figsize=(8, 4.5), dpi=150)
ax = fig.add_axes([0, 0, 1, 1])
ax.imshow(faixa)
ax.axis("off")
fig.savefig("thumbnail.webp", dpi=150)
plt.close(fig)
NotaCréditos

Contains modified Copernicus Sentinel data 2025, acessados pelo Microsoft Planetary Computer.

Exercícios

  1. Troque a área para o entorno de Brasília (lat, lon = -15.78, -47.93) e repita a busca. Quantas cenas com menos de 5% de nuvens existem entre junho e setembro?
  2. Busque cenas da estação chuvosa (dezembro a fevereiro) em Sorriso. Como muda a cobertura de nuvens?
  3. Use query={"s2:mgrs_tile": {"eq": "21LXG"}} (ou o tile que apareceu no seu mapa) para restringir a busca a um único tile.
  4. Repita o recorte com o asset B08 (infravermelho próximo). O que acontece com a vegetação?

Referências