UC01: Forest Cover in Caquetá, Colombia

Querying, visualizing, and comparing forest cover data from the GJD API

Objective

In this use case we will walk through a common research scenario: downloading forest cover data for a specific jurisdiction and time period from the GJD API, displaying the results in a table, visualizing them with a bar chart, and finally comparing two jurisdictions side by side.

We will query forest cover data for Caquetá and Putumayo (Colombia) from 2010 to 2019, using data published by IDEAM (Colombia’s national environmental authority).

Step 1: Import Libraries

import os
import requests
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: Build the API Request

The GJD API endpoint for place-level data follows this pattern:

https://api.greenjurisdictions.org/api/v1/dataPlaces/{groupByCountry}/{groupByJurisdiction}/{groupByMunicipality}

For our first query we focus on Caquetá:

Parameter Value Meaning
ID_Topic 120 Forest cover
ID_Countries ["CO"] Colombia
ID_Jurisdictions ["CO-CAQ"] Caquetá
ID_years ["2010", ..., "2019"] Years 2010–2019
ID_sources [14] IDEAM, Colombia
years = [str(y) for y in range(2010, 2020)]

params_caqueta = {
    "ID_Topic": 120,
    "ID_Countries": '["CO"]',
    "ID_Jurisdictions": '["CO-CAQ"]',
    "ID_Municipalities": '[]',
    "ID_years": str(years).replace("'", '"'),
    "ID_sources": '[14]',
}

Step 3: Send the Request

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

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

Step 4: Parse into a DataFrame

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

df_caq

Step 5: Bar Chart — Caquetá

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

bars = ax.bar(df_caq["year"].astype(str), df_caq["value"] / 1e6, color="#2b7a78", width=0.7)

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

ax.set_title("Forest Cover in Caquetá, Colombia (2010–2019)", fontweight="bold", fontsize=14)
ax.set_xlabel("Year")
ax.set_ylabel("Forest cover (million hectares)")
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f"{x:.1f}M"))
ax.spines[["top", "right"]].set_visible(False)
plt.figtext(0.5, -0.02, "Source: IDEAM, Colombia — via GJD API", ha="center", fontsize=9, color="gray")
plt.tight_layout()
plt.show()

The chart confirms a steady decline in forest cover throughout the decade.


Step 6: Compare with Putumayo

Our researcher now wants to compare Caquetá with a neighboring jurisdiction: Putumayo. We can query both in a single API call.

params_comparison = {
    "ID_Topic": 120,
    "ID_Countries": '["CO"]',
    "ID_Jurisdictions": '["CO-CAQ","CO-PUT"]',
    "ID_Municipalities": '[]',
    "ID_years": str(years).replace("'", '"'),
    "ID_sources": '[14]',
}

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

data_cmp = response_cmp.json()
print(f"Total records: {data_cmp['data']['total_data']}")
df_cmp = pd.DataFrame(data_cmp["data"]["data"])
df_cmp["value"] = df_cmp["value"].astype(float)
df_cmp["year"] = df_cmp["year"].astype(int)
df_cmp = df_cmp[["year", "jurisdiction", "value", "unit", "source"]].sort_values(["jurisdiction", "year"])

df_cmp

Step 7: Comparison Chart

import numpy as np

pivot = df_cmp.pivot(index="year", columns="jurisdiction", values="value") / 1e6
x = np.arange(len(pivot.index))
width = 0.35

fig, ax = plt.subplots(figsize=(11, 6))
ax.bar(x - width / 2, pivot["Caquetá"], width, label="Caquetá", color="#2b7a78")
ax.bar(x + width / 2, pivot["Putumayo"], width, label="Putumayo", color="#3aafa9")

ax.set_xticks(x)
ax.set_xticklabels(pivot.index.astype(str))
ax.set_title("Forest Cover: Caquetá vs Putumayo (2010–2019)", fontweight="bold", fontsize=14)
ax.set_xlabel("Year")
ax.set_ylabel("Forest cover (million hectares)")
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f"{x:.1f}M"))
ax.legend(loc="upper right")
ax.spines[["top", "right"]].set_visible(False)
plt.figtext(0.5, -0.02, "Source: IDEAM, Colombia — via GJD API", ha="center", fontsize=9, color="gray")
plt.tight_layout()
plt.show()

The comparison reveals that Caquetá has roughly 3.5 times more forest cover than Putumayo, but both jurisdictions follow a similar downward trend over the decade.

Summary

In this use case we learned how to:

  1. Build an API request with specific filters using requests.
  2. Parse the JSON response into a Pandas DataFrame.
  3. Visualize a single jurisdiction with a bar chart using matplotlib.
  4. Query multiple jurisdictions in a single API call.
  5. Compare jurisdictions visually with a grouped bar chart.

Next Steps

  • See UC02 for deforestation data and multi-source comparison.
  • Combine forest cover with deforestation data for a more complete picture.
  • Expand the comparison to more jurisdictions in the Colombian Amazon.