UC02: Deforestation in Acre, Brazil

Querying and visualizing deforestation data using PRODES as source

Objective

In this use case we shift our focus from forest cover to deforestation. We will query annual deforestation data for Acre, Brazil from 2010 to 2019, using data from PRODES — the official Brazilian deforestation monitoring system operated by INPE.

This example also demonstrates an important aspect of the GJD API: filtering by data source, since multiple sources may report on the same topic for the same jurisdiction.

Step 1: Import Libraries

import os
import requests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from dotenv import load_dotenv

load_dotenv(dotenv_path="../.env")
load_dotenv(dotenv_path="../.Renviron")

API_KEY = os.getenv("GJD_API_KEY")
BASE_URL = "https://api.greenjurisdictions.org/api/v1/dataPlaces/false/true/false"
HEADERS = {
    "X-API-TOKEN": API_KEY,
    "Accept": "application/json",
    "Content-Type": "application/json",
}

Step 2: Query Parameters

Parameter Value Meaning
ID_Topic 11 Deforestation
ID_Countries ["BR"] Brazil
ID_Jurisdictions ["BR-AC"] Acre
ID_years ["2010", ..., "2019"] Years 2010–2019
ID_sources [12] PRODES Amazonia, Brazil

Why filter by source? The GJD API may return data from multiple sources for the same topic (PRODES, Hansen/UMD, MapBiomas). Each uses different methodologies. By specifying ID_sources=[12], we ensure we only get PRODES data.

Step 3: Send the Request (PRODES only)

years = [str(y) for y in range(2010, 2020)]

params_prodes = {
    "ID_Topic": 11,
    "ID_Countries": '["BR"]',
    "ID_Jurisdictions": '["BR-AC"]',
    "ID_Municipalities": '[]',
    "ID_years": str(years).replace("'", '"'),
    "ID_sources": '[12]',
}

response = requests.get(BASE_URL, params=params_prodes, headers=HEADERS)
print(f"HTTP status: {response.status_code}")

data = response.json()
print(f"Message: {data['message']}")
print(f"Total records: {data['data']['total_data']}")

Step 4: Parse into a DataFrame

df = pd.DataFrame(data["data"]["data"])
df["value"] = df["value"].astype(float)
df["year"] = df["year"].astype(int)
df = df[["year", "jurisdiction", "value", "unit", "source"]].sort_values("year")

df

Step 5: Bar Chart — PRODES Deforestation

fig, ax = plt.subplots(figsize=(10, 5))

bars = ax.bar(df["year"].astype(str), df["value"] / 1e3, color="#c0392b", width=0.7)

for bar, val in zip(bars, df["value"]):
    ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.3,
            f"{val:,.0f}", ha="center", va="bottom", fontsize=8)

ax.set_title("Annual Deforestation in Acre, Brazil (2010–2019)", fontweight="bold", fontsize=14)
ax.set_xlabel("Year")
ax.set_ylabel("Deforestation (thousand hectares)")
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f"{x:.0f}K"))
ax.spines[["top", "right"]].set_visible(False)
plt.figtext(0.5, -0.02, "Source: PRODES Amazonia (INPE) — via GJD API", ha="center", fontsize=9, color="gray")
plt.tight_layout()
plt.show()

Step 6: Comparing Data Sources

To illustrate why source filtering matters, let’s make the same request without the ID_sources filter.

params_all = {
    "ID_Topic": 11,
    "ID_Countries": '["BR"]',
    "ID_Jurisdictions": '["BR-AC"]',
    "ID_Municipalities": '[]',
    "ID_years": str(years).replace("'", '"'),
}

response_all = requests.get(BASE_URL, params=params_all, headers=HEADERS)
data_all = response_all.json()

df_all = pd.DataFrame(data_all["data"]["data"])
df_all["value"] = df_all["value"].astype(float)
df_all["year"] = df_all["year"].astype(int)
df_all = df_all[["year", "jurisdiction", "value", "unit", "source"]].sort_values(["year", "source"])

print(f"Records with all sources: {len(df_all)}")
print(f"Sources found: {df_all['source'].unique().tolist()}")
source_colors = {
    "PRODES Amazonia, Brazil": "#c0392b",
    "Hansen et al./UMD/Google/USGS/NASA, post-processing by EII": "#2980b9",
    "MapBiomas - Brasil": "#27ae60",
}

sources = df_all["source"].unique()
n_sources = len(sources)
year_labels = sorted(df_all["year"].unique())
x = np.arange(len(year_labels))
total_width = 0.75
bar_width = total_width / n_sources

fig, ax = plt.subplots(figsize=(12, 6))

for i, src in enumerate(sources):
    subset = df_all[df_all["source"] == src].set_index("year").reindex(year_labels)
    offset = (i - n_sources / 2 + 0.5) * bar_width
    color = source_colors.get(src, f"C{i}")
    label = src if len(src) < 30 else src[:27] + "..."
    ax.bar(x + offset, subset["value"] / 1e3, bar_width, label=label, color=color)

ax.set_xticks(x)
ax.set_xticklabels([str(y) for y in year_labels])
ax.set_title("Deforestation in Acre: Comparing Data Sources (2010–2019)", fontweight="bold", fontsize=14)
ax.set_xlabel("Year")
ax.set_ylabel("Deforestation (thousand hectares)")
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f"{x:.0f}K"))
ax.legend(loc="upper left", fontsize=8)
ax.spines[["top", "right"]].set_visible(False)
plt.figtext(0.5, -0.02, "Three sources report different values for the same jurisdiction and topic", ha="center", fontsize=9, color="gray")
plt.tight_layout()
plt.show()

Key Takeaway: Different data sources use different methodologies (satellite imagery resolution, classification algorithms, reference periods). Always be explicit about which source you are using in your analysis and document your choice.

Summary

In this use case we learned how to:

  1. Query deforestation data (Topic 11) from the GJD API using requests.
  2. Filter by a specific data source (PRODES) to ensure consistency.
  3. Visualize annual deforestation as a bar chart with matplotlib.
  4. Compare multiple data sources for the same jurisdiction to understand methodological differences.

Next Steps

  • Combine deforestation and forest cover data to calculate deforestation rates.
  • Compare deforestation across multiple jurisdictions in the Brazilian Amazon.
  • Explore how deforestation trends relate to specific policy interventions.