UC03: Spatial Deforestation in Caquetá & Putumayo (Municipalities)

Fetching municipal-level GeoJSON data from the GJD API and rendering interactive maps with Folium

Objective

In this use case we fetch deforestation data with municipality-level geometry for Caquetá and Putumayo (Colombia) from the GJD API. The API returns paginated GeoJSON, so we collect all pages. We render two interactive maps:

  1. Basic map — Display all 29 municipalities on a Leaflet map.
  2. Choropleth map — Color each municipality by its deforestation value and add a tooltip.

Prerequisites

pip install requests folium branca python-dotenv

Step 1: Load Libraries and API Key

import os
import json
import requests
import folium
import branca.colormap as cm

# --- Load API key ---
# Option A: Running locally with .env / .Renviron file
try:
    from dotenv import load_dotenv
    load_dotenv("../.env")
    load_dotenv("../.Renviron")
except ImportError:
    pass

API_KEY = os.getenv("GJD_API_KEY", "")

# Option B: Running in Google Colab — uncomment and paste your key:
# API_KEY = "YOUR_API_KEY_HERE"

print("API key loaded:", "Yes" if API_KEY else "No")

Step 2: Fetch Municipal GeoJSON from the API

We request deforestation data (topic_id 11) for all 29 municipalities of Caquetá (16) and Putumayo (13) for the year 2019, using IDEAM as source, with geojson=true to include polygon geometries.

Note: For GeoJSON requests, the API uses the parameter topic_id instead of ID_Topic. The API returns paginated results (10 features per page), so we loop through all pages.

BASE_URL = "https://api.greenjurisdictions.org/api/v1/dataPlaces/false/false/false"

MUNICIPALITIES = [
    "CO-CAQ-18029", "CO-CAQ-18094", "CO-CAQ-18150", "CO-CAQ-18205",
    "CO-CAQ-18247", "CO-CAQ-18256", "CO-CAQ-18001", "CO-CAQ-18460",
    "CO-CAQ-18410", "CO-CAQ-18479", "CO-CAQ-18592", "CO-CAQ-18610",
    "CO-CAQ-18753", "CO-CAQ-18756", "CO-CAQ-18785", "CO-CAQ-18860",
    "CO-PUT-86219", "CO-PUT-86001", "CO-PUT-86320", "CO-PUT-86568",
    "CO-PUT-86569", "CO-PUT-86571", "CO-PUT-86573", "CO-PUT-86755",
    "CO-PUT-86757", "CO-PUT-86760", "CO-PUT-86749", "CO-PUT-86865",
    "CO-PUT-86885",
]

params = {
    "topic_id": 11,
    "ID_Countries": '["CO"]',
    "ID_Jurisdictions": '["CO-CAQ","CO-PUT"]',
    "ID_Municipalities": json.dumps(MUNICIPALITIES),
    "ID_years": '["2019"]',
    "ID_sources": '[14]',
    "ID_Classes": '[]',
    "api": "true",
    "lang": "en",
    "type": "api",
    "geojson": "true",
}

headers = {
    "X-API-TOKEN": API_KEY,
    "Accept": "application/json",
}

all_features = []
page = 1

while True:
    params["page"] = page
    response = requests.get(BASE_URL, params=params, headers=headers, timeout=120)
    data = response.json()
    pag = data["data"]["data"]
    features = pag["data"]["features"]
    all_features.extend(features)
    print(f"Page {page}: {len(features)} features (total: {pag['total']})")
    if page >= pag["last_page"]:
        break
    page += 1

print(f"\nTotal features collected: {len(all_features)}")

Step 3: Build the GeoJSON FeatureCollection

We assemble all paginated features into a single GeoJSON FeatureCollection and add human-readable municipality names.

geojson = {"type": "FeatureCollection", "features": all_features}

for f in geojson["features"]:
    props = f["properties"]
    code = props["place_code"]
    props["name"] = code
    props["value"] = float(props["value"])
    props["jurisdiction"] = "Caquetá" if code.startswith("CO-CAQ") else "Putumayo"

print(f"GeoJSON FeatureCollection with {len(geojson['features'])} municipalities")
for f in geojson["features"]:
    p = f["properties"]
    print(f"  {p['name']}: {p['value']:,.2f} ha ({p['jurisdiction']})")

Step 4: Basic Map

Display all 29 municipalities on a Leaflet map with a simple outline.

m1 = folium.Map(location=[0.5, -75.5], zoom_start=7, tiles="CartoDB positron")

folium.GeoJson(
    geojson,
    name="Municipalities",
    style_function=lambda x: {
        "color": "#2b7a78",
        "weight": 2,
        "fillOpacity": 0.15,
    },
    tooltip=folium.GeoJsonTooltip(
        fields=["name", "jurisdiction"],
        aliases=["Municipality", "Jurisdiction"],
    ),
).add_to(m1)

m1.fit_bounds(m1.get_bounds())
m1

Step 5: Choropleth Map — Color by Deforestation

We create a color scale from light yellow to dark red based on deforestation values, and add a hover tooltip showing the municipality name, jurisdiction, and deforestation value.

values = [f["properties"]["value"] for f in geojson["features"]]
vmin, vmax = min(values), max(values)

colormap = cm.LinearColormap(
    colors=["#fee5d9", "#fb6a4a", "#a50f15"],
    vmin=vmin,
    vmax=vmax,
    caption="Deforestation (hectares) — 2019",
)

m2 = folium.Map(location=[0.5, -75.5], zoom_start=7, tiles="CartoDB positron")

folium.GeoJson(
    geojson,
    name="Deforestation",
    style_function=lambda x: {
        "fillColor": colormap(x["properties"]["value"]),
        "color": "#333",
        "weight": 1.5,
        "fillOpacity": 0.7,
    },
    highlight_function=lambda x: {
        "weight": 3,
        "color": "#000",
        "fillOpacity": 0.85,
    },
    tooltip=folium.GeoJsonTooltip(
        fields=["name", "jurisdiction", "value"],
        aliases=["Municipality", "Jurisdiction", "Deforestation (ha)"],
        localize=True,
    ),
).add_to(m2)

colormap.add_to(m2)
m2.fit_bounds(m2.get_bounds())
m2

Summary

In this use case we learned how to:

  1. Request municipal-level GeoJSON from the GJD API by adding geojson=true and specifying ID_Municipalities.
  2. Handle paginated responses — the API returns 10 features per page; we loop through all pages.
  3. Display 29 municipalities on an interactive Folium/Leaflet map.
  4. Create a choropleth with a color scale, hover tooltips, and a legend.

Next Steps

  • Extend to more jurisdictions across the Colombian or Brazilian Amazon.
  • Add a temporal slider to compare deforestation across years.
  • Overlay additional layers (e.g., protected areas, indigenous territories).