Practical Course 04

Forest Loss Mapping
with Hansen GFC + DeepSeek AI

Detect deforestation, compute annual loss, map canopy cover -- all via AI-generated GEE scripts
North Morocco (Rif & Middle Atlas) ~2 hours Master - Climate Change & GeoAI
Key Datasets:
Hansen GFC v1.11 (2000-2023)
NASA ORNL Biomass Carbon Density
Dynamic World (Land Cover)
FIRMS MODIS Fire Alerts

Contents

  1. Key Terms for Beginners
  2. Hansen GFC Dataset Overview
  3. Setup: GEE + DeepSeek
  4. Understanding Forest Loss Bands
  5. Crafting the Perfect Prompt
  6. Generate GEE Code with DeepSeek
  7. Annual Forest Loss Visualization
  8. Canopy Cover & Tree Height
  9. Forest Loss Area by Year
  10. Fire & Deforestation Linkage
  11. Exercises: Drivers & Hotspots
  12. Complete Tested Script

πŸ“– Key Terms for Beginners

Before starting, make sure you are comfortable with these terms. They appear throughout all code blocks in this course.

TermFull NameWhat it means in simple words
GEEGoogle Earth EngineA free cloud platform where you write JavaScript code to analyze satellite images without downloading anything to your computer
GFCGlobal Forest ChangeThe name of the Hansen dataset -- a global map of where forests existed in 2000 and where they were lost or gained since then
LandsatLand Remote Sensing SatelliteA family of NASA/USGS satellites that have been photographing Earth since 1972 at 30m resolution. The Hansen GFC dataset is built entirely from Landsat images
MODISModerate Resolution Imaging SpectroradiometerA sensor on NASA's Terra and Aqua satellites that scans the entire Earth every 1-2 days at 250m-1km resolution. Used here for fire detection
FIRMSFire Information for Resource Management SystemA NASA system that processes MODIS and VIIRS data daily to detect active fires anywhere on Earth. Each fire detection is called a "hotspot"
VIIRSVisible Infrared Imaging Radiometer SuiteA newer NASA sensor (on Suomi-NPP satellite) that detects fires with higher resolution than MODIS. FIRMS combines both MODIS and VIIRS data
GEDIGlobal Ecosystem Dynamics InvestigationA NASA lidar instrument on the International Space Station that measures tree height by firing laser pulses at the forest and timing the return signal
ETHETH Zurich (EidgenΓΆssische Technische Hochschule)A leading Swiss university. Their canopy height dataset (Lang et al. 2023) was produced by training a deep learning model on Sentinel-2 images + GEDI measurements
WDPAWorld Database on Protected AreasA global database of national parks, nature reserves, and other protected zones, maintained by IUCN and UNEP. Available as a vector dataset in GEE
FAO GAULFood & Agriculture Organization Global Administrative Unit LayersOfficial country and province boundary polygons from the UN. Used in GEE to filter analysis by country or region
Band--A single layer of data in a satellite image. Like a channel -- a raster image can have many bands, each storing a different variable (e.g. loss year, canopy cover)
Pixel--The smallest unit of a raster image. In the Hansen dataset, each pixel covers 30x30 meters on the ground
Mask--Hiding certain pixels from analysis. A masked pixel is treated as "no data" and does not appear on the map or in calculations
Reducer--A GEE operation that summarizes many pixel values into one number -- for example, summing all pixel areas to get total hectares
Scale--The pixel size (in meters) used for a calculation. Larger scale = faster but less precise. Smaller scale = slower but more accurate
Confidence--In FIRMS, a 0-100 score indicating how certain the algorithm is that a pixel is actually on fire. Values β‰₯50 are considered reliable detections
1
Hansen Global Forest Change Dataset
10 min

The Hansen Global Forest Change (GFC) dataset, produced by the University of Maryland and available free in GEE, is the world's most widely used annual deforestation product. It covers 2000 to 2023 at 30m resolution using Landsat imagery -- a family of NASA/USGS satellites that have been photographing Earth continuously since 1972.

Think of it as a global before-and-after photo album: the dataset records exactly where forest existed in the year 2000, and tracks pixel by pixel where trees were lost or gained in each subsequent year up to 2023.

30m
Spatial Resolution
2000-23
Temporal Coverage
Free
Access via GEE
GEE Asset ID
UMD/hansen/global_forest_change_2023_v1_11
Source Institution
University of Maryland (Hansen et al.)
Base Year
2000 (tree cover reference)
Loss Years
2001 to 2023 (band: lossyear)
🌍
Why this dataset? It is analysis-ready, no preprocessing needed, globally consistent, and ideal for prompting DeepSeek to generate scripts that immediately produce compelling results -- perfect for teaching GeoAI workflows.

2
Setup: GEE + DeepSeek
5 min
1
Open chat.deepseek.com (free account, no API key needed)
2
Open code.earthengine.google.com in a second tab
3
Create a new GEE script: Course04_Forest_Hansen_DeepSeek
4
Choose your study region (Congo Basin, Morocco Cedar forests, Amazon, Southeast Asia)
πŸ’‘
Study area: This course uses North Morocco, covering the Rif Mountains (cedar and cork oak forests near Chefchaouen and Tetouan) and the northern Middle Atlas (Azrou, Ifrane). Coordinates: [-6.5, 33.0, -1.5, 36.0]. You can also narrow to just the Rif: [-6.0, 34.5, -4.5, 35.8], or expand to the full Atlas range.

3
Understanding the Hansen GFC Bands
10 min

The Hansen dataset is a single multi-band image. Before prompting DeepSeek, students must understand what each band represents:

Band NameTypeValuesUse Case
treecover2000Continuous0-100 (%)Canopy cover in year 2000
lossBinary0 or 1Pixel experienced forest loss (2001-2023)
lossyearInteger1-23Year of loss (1=2001, 23=2023)
gainBinary0 or 1Forest gain between 2000-2012
datamaskInteger0,1,2Valid land (1), water (2)
first_b30Reflectance0-255Landsat band (for visualization)
🌲
Key logic: Forest is defined as pixels with treecover2000 >= 10% for North Morocco (sparse semi-arid forests). Loss pixels where lossyear == 20 means forest loss occurred in 2020 (the encoding is: value 1 = 2001, value 2 = 2002 ... value 23 = 2023). This integer encoding makes filtering by year very simple.
πŸ”‘
Beginner note -- what is datamask? This band tells GEE which pixels are usable. Value 0 = no data (outside coverage), value 1 = valid land pixel, value 2 = water body. We always filter to datamask == 1 so that ocean and river pixels are excluded from our forest calculations.

4
Crafting the Perfect Prompt for DeepSeek
15 min

The prompt is the core skill of this course. Write it precisely: include the exact dataset ID, band names, threshold, study area, desired outputs.

πŸ“‹ Starter Prompt (copy-paste to DeepSeek)
Write Google Earth Engine JavaScript code to map and analyze forest loss using the Hansen Global Forest Change dataset. Dataset: 'UMD/hansen/global_forest_change_2023_v1_11' Study area: North Morocco rectangle [-6.5, 33.0, -1.5, 36.0] covering the Rif Mountains and northern Middle Atlas. Map center: longitude -4.0, latitude 34.5, zoom 8. Tasks: 1. Load the Hansen GFC image and select bands: treecover2000, loss, lossyear, gain. 2. Define forest as pixels where treecover2000 >= 10 (use 10% threshold for semi-arid Moroccan forests). 3. Show the year-2000 canopy cover layer (green palette). 4. Show total forest loss 2001-2023 (red layer using the 'loss' band). 5. Show forest gain (blue layer using the 'gain' band). 6. Compute total forest loss area in hectares (sum of loss pixels x pixel area). 7. Create a bar chart of annual forest loss area (hectares) by year (2001-2023) using ui.Chart.feature.byFeature. 8. Add a legend panel with color swatches for: forest cover, loss, gain. 9. Add comments in the code explaining each step. 10. Export the annual loss map to Google Drive (EPSG:4326, scale 30m).
✨
Advanced additions you can request: "intersect loss with protected areas (WDPA -- the global database of national parks)", "compare loss before/after 2015 (Paris Agreement)", "show loss by country using FAO GAUL boundaries (official UN country/province polygons)", "overlay with fire hotspots from FIRMS (NASA fire detection system)".

5
Generate GEE Code with DeepSeek
10 min
β–Ά
Live demo: Go to DeepSeek chat, paste the prompt above, press Enter. In about 20-30 seconds you will get a complete, working GEE script. Copy the entire code block and paste it into your GEE editor.
What DeepSeek will generate
  • Hansen image loading and band selection
  • Forest mask definition (treecover2000.gte(30))
  • Annual loss year loop or image.eq(year) filtering
  • Area calculation with ee.Image.pixelArea()
  • Bar chart with ui.Chart.image.byClass
  • Legend panel with color swatches
  • Export block to Google Drive

If DeepSeek output is cut short, type: "Continue the code from the legend panel". If a chart is missing, type: "Add the annual loss bar chart by year 2001 to 2023".


6
Annual Forest Loss Visualization
10 min

The lossyear band encodes the year of loss as an integer (1=2001 ... 23=2023). To visualize a gradient of loss years, use a sequential palette:

Lossyear Visualization (DeepSeek will generate similar)
// Study area: North Morocco (Rif Mountains & Middle Atlas)
var studyArea = ee.Geometry.Rectangle([-6.5, 33.0, -1.5, 36.0]);
Map.setCenter(-4.0, 34.5, 8);

var hansen = ee.Image('UMD/hansen/global_forest_change_2023_v1_11');
var lossYear = hansen.select('lossyear');
var loss = hansen.select('loss');
var treeCover = hansen.select('treecover2000');

// Define forest mask (>= 10% canopy cover -- semi-arid Moroccan threshold)
var forestMask = treeCover.gte(10);

// Mask loss to forest pixels only
var forestLoss = loss.updateMask(loss).updateMask(forestMask);
var lossYearMasked = lossYear.updateMask(loss).updateMask(forestMask);

// Visualize loss year with temporal gradient
Map.addLayer(treeCover.updateMask(forestMask).clip(studyArea), 
  {min: 10, max: 100, palette: ['#A8D5A2','#2D6A4F','#1B4332']}, 
  'Tree Cover 2000');

Map.addLayer(lossYearMasked.clip(studyArea), 
  {min: 1, max: 23, palette: ['#FFFFB2','#FED976','#FEB24C','#FD8D3C','#FC4E2A','#E31A1C','#B10026']}, 
  'Forest Loss Year (2001-2023)');
Forest 2000 (treecover ≥30%)
Loss early years (2001-2008)
Loss mid years (2009-2016)
Recent loss (2017-2023)
Forest gain (2000-2012)
πŸ”‘
Beginner note -- GEE masking functions:

updateMask(condition) -- hides all pixels where the condition is false. For example, updateMask(forestMask) keeps only pixels that were forest in 2000 and removes everything else from the map and calculations.

selfMask() -- a shortcut that hides pixels with a value of 0. Instead of writing updateMask(image.gt(0)), you can just write .selfMask(). Used here so that non-loss pixels (value 0) do not appear as a colored layer on the map.
⚠
For North Morocco's semi-arid forests, the canopy threshold is set to 10% (not the tropical standard of 30%). This captures cedar (Cedrus atlantica), cork oak (Quercus suber), and holm oak (Quercus ilex) stands in the Rif and Atlas. Ask DeepSeek: "Show only pixels above 20% canopy to focus on dense cedar stands near Azrou and Ifrane."

7
Canopy Cover & Tree Height Analysis
10 min

Beyond loss detection, you can analyze forest structure. Ask DeepSeek to add the Global Canopy Height Model (Lang et al. 2023) available in GEE:

πŸ“‹ Follow-up Prompt
"Modify the script to add the ETH Global Canopy Height Model (dataset: 'users/nlang/ETH_GlobalCanopyHeight_2020_10m_v1', band: 'b1'). Show the tree height layer clipped to the study area, with a palette from light to dark green (0-40m). Then compute the mean canopy height inside forest loss areas to understand what kind of forest was destroyed."
Canopy Height snippet
// ETH Global Canopy Height Model (Lang et al. 2023, Sentinel-2 based, 10m)
// Correct GEE asset: users/nlang/ETH_GlobalCanopyHeight_2020_10m_v1
var canopyHeight = ee.Image('users/nlang/ETH_GlobalCanopyHeight_2020_10m_v1').select('b1');

Map.addLayer(canopyHeight.clip(studyArea), 
  {min: 0, max: 40, palette: ['#F7FCF0','#A8D5B5','#52B788','#2D6A4F','#1B4332']}, 
  'Canopy Height (m) 2020');

// Mean height of lost forest pixels
var heightInLoss = canopyHeight.updateMask(forestLoss);
var meanHeight = heightInLoss.reduceRegion({
  reducer: ee.Reducer.mean(),
  geometry: studyArea,
  scale: 100,
  maxPixels: 1e9
});
print('Mean canopy height in lost forest (m):', meanHeight.get('b1'));
πŸ”‘
Beginner note -- ETH dataset & reduceRegion:

ETH Canopy Height -- this dataset was created by ETH Zurich by training a deep learning model on Sentinel-2 optical images alongside GEDI lidar height measurements. It gives tree height in meters at 10m resolution for the entire Earth in 2020.

GEDI (Global Ecosystem Dynamics Investigation) -- a NASA laser instrument on the International Space Station that measures exact tree height by firing laser pulses and timing the return. GEDI data was used as ground truth to train the ETH model.

reduceRegion() -- a GEE function that takes all pixel values inside a geometry and computes a summary statistic (here: mean). Think of it as: "calculate the average height of all pixels inside my study area". The result is a dictionary you read with .get('band_name').

8
Forest Loss Area by Year (Chart)
15 min

A key deliverable: an annual bar chart of hectares of forest lost per year. This is what policy-makers and NGOs want to see. Ask DeepSeek to generate it with a loop over years 2001-2023:

Annual Loss Area Calculation (single-pass histogram)
// Single-pass approach: count pixels per loss year using frequencyHistogram
// Much faster -- avoids "Too many concurrent aggregations" error

var lossPixelCount = lossYearMasked.reduceRegion({
  reducer: ee.Reducer.frequencyHistogram(),
  geometry: studyArea,
  scale: 100,           // use 100m for speed; change to 30 for exact area
  maxPixels: 1e10,
  bestEffort: true
});

// Each pixel at 100m = 1 ha (100x100m = 10,000 m2 = 1 ha)
// At scale 30m: divide pixel count by ~11.11 to get ha (900m2/10000)
var pixelAreaHa = ee.Number(100).multiply(100).divide(10000); // = 1 ha at scale 100

var histogram = ee.Dictionary(lossPixelCount.get('lossyear'));

// Convert pixel counts to hectares and build FeatureCollection for chart
var years = ee.List.sequence(1, 23);
var annualLossFc = ee.FeatureCollection(years.map(function(y) {
  y = ee.Number(y);
  var yearKey = y.int().format('%d');
  var count = ee.Number(histogram.get(yearKey, 0));
  var ha = count.multiply(pixelAreaHa);
  return ee.Feature(null, {year: y.add(2000), area_ha: ha});
}));

var chart = ui.Chart.feature.byFeature({
  features: annualLossFc,
  xProperty: 'year',
  yProperties: ['area_ha']
}).setChartType('ColumnChart')
  .setOptions({
    title: 'Annual Forest Loss - North Morocco (2001-2023)',
    hAxis: {title: 'Year', format: '####'},
    vAxis: {title: 'Forest Loss (ha)'},
    colors: ['#B10026'],
    bar: {groupWidth: '80%'},
    legend: {position: 'none'},
    backgroundColor: '#FFFFFF'
  });
print(chart);
πŸ“Š
Why frequencyHistogram? The previous loop-based approach fired 23 simultaneous reduceRegion calls, triggering GEE's "Too many concurrent aggregations" error. The histogram approach computes all 23 years in a single pass -- faster, cheaper, and error-free. Scale is set to 100m for speed; areas are still in hectares since 100x100m = 1 ha exactly.
πŸ”‘
Beginner note -- key concepts in this block:

frequencyHistogram() -- a special GEE reducer that counts how many pixels have each unique value. For example, applied to the lossyear band it returns a dictionary like {1: 3200, 2: 4100, ..., 23: 5800} where each key is a year code and each value is the pixel count for that year. One calculation instead of 23.

bestEffort: true -- tells GEE "if the calculation is too heavy at scale 100m, automatically use a coarser scale to avoid a timeout error". This is useful for large study areas.

scale: 100 -- each pixel in this calculation is 100x100 meters = 10,000 mΒ² = exactly 1 hectare. This makes the area conversion trivial: pixel count = hectares. At scale 30m (Hansen native resolution) each pixel is only 900 mΒ², so you would need to divide by 11.11 to get hectares.

ee.Image.pixelArea() -- an alternative approach that creates an image where every pixel's value equals its area in square meters. Multiplying it by a binary mask then summing gives total area. Here we avoid it by using scale 100m directly.

10
Fire & Deforestation Linkage
10 min

A key driver of forest loss is fire. Ask DeepSeek to overlay FIRMS MODIS fire alerts with Hansen loss to identify fire-driven deforestation:

πŸ“‹ Fire Overlay Prompt
"Add FIRMS MODIS active fire data to the script. Load the collection 'FIRMS' and filter by date '2020-01-01' to '2023-12-31' and the study area. Convert fire points to a rasterized layer. Intersect fire pixels with Hansen loss pixels from 2020-2023 (lossyear >= 20). Compute the percentage of recent forest loss that is fire-related. Add a red fire layer on the map."
πŸ”₯
Context: Fire is the primary driver of forest loss in North Morocco. The Rif Mountains experienced major fire events in 2021 (over 30,000 ha burned in Larache and Chefchaouen provinces) and again in 2022-2023 during drought years. Overlaying FIRMS with Hansen loss reveals exactly which fires caused permanent canopy loss vs. temporary damage.
πŸ”‘
Beginner note -- FIRMS, MODIS, and confidence:

FIRMS (Fire Information for Resource Management System) -- a NASA service that processes satellite data daily to detect active fires. In GEE it is available as an ImageCollection where each image represents one day of global fire detections.

MODIS (Moderate Resolution Imaging Spectroradiometer) -- the satellite sensor used by FIRMS. It scans the entire Earth every 1-2 days and can detect the thermal signature (heat) of fires even through smoke, unlike optical sensors. Each fire pixel covers roughly 1 kmΒ².

Confidence band (0-100) -- each FIRMS fire detection comes with a confidence score. A value of 100 means the algorithm is certain it detected a real fire. Values below 30 often indicate false alarms (e.g. hot bare soil, industrial heat). We use >= 50 as a balanced threshold to keep reliable detections while removing noise.

Fire frequency raster -- by summing the daily fire masks over 4 years, we create a raster where each pixel's value tells us how many days it was detected as burning. A pixel with value 5 burned on 5 different days -- likely a large persistent fire or a repeatedly burned area.
Fire & Deforestation Linkage -- add after Step 7 in the main script
// ---- Fire & Deforestation Linkage ----
// Requires: studyArea, forestMask, lossYear, loss (defined in main script)

// Recent loss: 2020-2023 = lossyear values 20-23
var recentLoss = lossYear.gte(20).and(loss.eq(1)).updateMask(forestMask);

// Load FIRMS MODIS fire detections (confidence >= 50)
var fireMask = ee.ImageCollection('FIRMS')
  .filterBounds(studyArea)
  .filterDate('2020-01-01', '2023-12-31')
  .select('confidence')
  .map(function(img) { return img.gte(50).selfMask(); });

// Fire frequency raster: how many days each pixel had a detection
var fireFrequency = fireMask.sum().rename('fire_frequency').clip(studyArea);
var fireBinary = fireFrequency.gt(0).rename('fire_detected');

// Intersect: pixels that are both recently lost AND had fire
var fireLoss = recentLoss.updateMask(fireBinary).rename('fire_driven_loss');

// Visualise
Map.addLayer(recentLoss.selfMask().clip(studyArea),
  {palette: ['#FD8D3C']}, 'Recent Forest Loss (2020-2023)');
Map.addLayer(fireFrequency.selfMask(),
  {min: 1, max: 20, palette: ['#FFEDA0','#FC4E2A','#800026']},
  'Fire Frequency 2020-2023');
Map.addLayer(fireLoss.selfMask().clip(studyArea),
  {palette: ['#FF0000']}, 'Fire-Driven Forest Loss');

// Compute areas and percentage (scale 100m = 1 ha per pixel)
var pixelAreaHa = ee.Number(100).multiply(100).divide(10000);

var recentLossHa = ee.Number(
  recentLoss.reduceRegion({reducer: ee.Reducer.sum(), geometry: studyArea,
    scale: 100, maxPixels: 1e10, bestEffort: true}).get('lossyear')
).multiply(pixelAreaHa);

var fireLossHa = ee.Number(
  fireLoss.reduceRegion({reducer: ee.Reducer.sum(), geometry: studyArea,
    scale: 100, maxPixels: 1e10, bestEffort: true}).get('fire_driven_loss')
).multiply(pixelAreaHa);

print('Recent forest loss 2020-2023 (ha):', recentLossHa);
print('Fire-driven loss (ha):', fireLossHa);
print('Fire-driven loss (%):', fireLossHa.divide(recentLossHa).multiply(100));

// Bar chart: fire detections per year
var fireByYear = ee.FeatureCollection([2020,2021,2022,2023].map(function(yr) {
  var count = ee.ImageCollection('FIRMS').filterBounds(studyArea)
    .filterDate(yr+'-01-01', yr+'-12-31').select('confidence')
    .map(function(img){ return img.gte(50).selfMask(); }).sum()
    .reduceRegion({reducer: ee.Reducer.sum(), geometry: studyArea,
      scale: 1000, maxPixels: 1e9, bestEffort: true});
  return ee.Feature(null, {year: yr, fire_detections: count.get('confidence')});
}));

print(ui.Chart.feature.byFeature({
  features: fireByYear, xProperty: 'year', yProperties: ['fire_detections']
}).setChartType('ColumnChart').setOptions({
  title: 'MODIS Fire Detections per Year -- North Morocco',
  hAxis: {title: 'Year', format: '####'},
  vAxis: {title: 'Fire Pixel Count'},
  colors: ['#E31A1C'], bar: {groupWidth: '70%'}, legend: {position: 'none'}
}));
⚠
The FIRMS collection can be large over 4 years. If GEE is slow, narrow the date range to a single fire season, e.g. '2021-07-01' to '2021-10-31' to focus on the major 2021 Rif fires. The bar chart uses scale: 1000m to stay within computation limits.
Legend -- add after the fire block
// ---- Legend ----
// Covers all map layers: forest cover, annual loss, gain, fire
var legend = ui.Panel({style: {position: 'bottom-left', padding: '10px', width: '240px'}});
legend.add(ui.Label('Forest Change & Fire Legend', {fontWeight: 'bold', fontSize: '13px', margin: '0 0 6px 0'}));

var items = [
  {color: '#2D6A4F', label: 'Forest cover 2000 (β‰₯10% canopy)'},
  {color: '#FFFFB2', label: 'Forest loss: early (2001-2007)'},
  {color: '#FD8D3C', label: 'Forest loss: mid (2008-2015)'},
  {color: '#B10026', label: 'Forest loss: recent (2016-2023)'},
  {color: '#3B82F6', label: 'Forest gain (2000-2012)'},
  {color: '#FC4E2A', label: 'Fire detections (FIRMS)'},
  {color: '#FF0000', label: 'Fire-driven loss (overlap)'}
];

items.forEach(function(item) {
  var row = ui.Panel({layout: ui.Panel.Layout.Flow('horizontal'), style: {margin: '2px 0'}});
  row.add(ui.Label({style: {backgroundColor: item.color, padding: '7px',
    margin: '0 6px 0 0', borderRadius: '3px'}}));
  row.add(ui.Label(item.label, {fontSize: '11px', color: '#374151'}));
  legend.add(row);
});

Map.add(legend);
πŸ”‘
Beginner note -- ui.Panel and Map widgets:

ui.Panel -- a GEE interface element that creates a floating box on the map. You can add text labels, color swatches, and other widgets inside it. The position style property places it at a corner of the map (e.g. 'bottom-left', 'top-right').

ui.Label -- adds a text or colored box element inside a panel. When you set backgroundColor without text, it renders as a colored square -- this is how the legend color swatches are made.

Map.add(legend) -- attaches the panel to the GEE map. Without this line the legend exists in memory but is never displayed.
Export -- add after the legend block
// ---- Export Maps to Google Drive ----

// Export 1: Annual loss year map (full time series 2001-2023)
Export.image.toDrive({
  image: lossYearMasked.rename('loss_year'),
  description: 'Hansen_LossYear_NorthMorocco',
  folder: 'GEE_ForestLoss',
  region: studyArea,
  scale: 30,
  crs: 'EPSG:4326',
  maxPixels: 1e13
});

// Export 2: Recent fire-driven loss (2020-2023)
Export.image.toDrive({
  image: fireLoss.rename('fire_driven_loss'),
  description: 'FireDrivenLoss_NorthMorocco_2020_2023',
  folder: 'GEE_ForestLoss',
  region: studyArea,
  scale: 100,
  crs: 'EPSG:4326',
  maxPixels: 1e10
});

// Export 3: Fire frequency raster
Export.image.toDrive({
  image: fireFrequency.rename('fire_frequency'),
  description: 'FIRMS_FireFrequency_NorthMorocco_2020_2023',
  folder: 'GEE_ForestLoss',
  region: studyArea,
  scale: 500,
  crs: 'EPSG:4326',
  maxPixels: 1e10
});
πŸ”‘
Beginner note -- exporting from GEE:

Export.image.toDrive() -- saves a GEE image as a GeoTIFF file to your Google Drive. Running this line does not export immediately -- it creates a task.

Tasks tab -- after clicking Run in GEE, go to the Tasks tab (top right of the code editor). You will see your export jobs listed there. Click the blue Run button next to each task to actually start the export. Files appear in your Google Drive folder once complete.

CRS (Coordinate Reference System) -- EPSG:4326 is geographic coordinates (latitude/longitude in degrees), the standard for GIS files that need to work in any software (QGIS, ArcGIS, etc.).

Scale tradeoff -- Export 1 uses scale 30m (native Hansen resolution, large file), Export 2 uses 100m (fire data is coarser), Export 3 uses 500m (FIRMS detection pixels are ~1 km so 500m is sufficient and keeps the file small).

11
Exercises (Using DeepSeek Prompts)
Homework

A - Province-Level Forest Loss Analysis

  1. Prompt DeepSeek: "Load FAO GAUL Level 2 boundaries and filter to Morocco. Intersect with Hansen loss. Show which province (e.g., Chefchaouen, Al Hoceima, Taounate, Ifrane) has lost the most forest hectares between 2001 and 2023."
  2. Discuss which provinces are most vulnerable and why (fire, agriculture, urbanization).

B - Before/After Paris Agreement Comparison

  1. Ask DeepSeek: "Compute total forest loss for 2001-2015 vs 2016-2023 in the study area. Show a side-by-side bar chart. Did loss increase or decrease after the Paris Agreement?"
  2. Discuss policy implications in your report.

C - Rif vs. Middle Atlas Comparison

  1. Split the study area into two sub-regions: Rif Mountains ([-6.0, 34.5, -4.5, 35.8]) and Middle Atlas ([-5.5, 32.5, -3.5, 34.5]).
  2. Prompt: "Compute total forest loss 2001-2023 for each sub-region separately and compare. Which zone has lost more forest? Show a side-by-side bar chart."

D - Deforestation Frontier Detection

  1. Ask DeepSeek: "Compute distance from each forest loss pixel to the forest edge in year 2000, using ee.Image.distance(). Show a heatmap of loss proximity to forest edge. Distinguish frontier deforestation from interior clearings."

12
Complete Tested Script (Generated by DeepSeek)
Ready to use
πŸ“Œ
The script below was generated using the prompt in Section 4, tested in GEE, and adapted for North Morocco (Rif Mountains and Middle Atlas). The canopy threshold is set to 10% to capture sparse semi-arid forest types including cedar, cork oak, and holm oak.
Forest Loss Mapping - Hansen GFC | North Morocco (Rif & Middle Atlas)
// =====================================================
// Course 04: Forest Loss Mapping with Hansen GFC
// Generated by DeepSeek AI (March 2026)
// Study area: North Morocco (Rif Mountains & Middle Atlas)
// Dataset: UMD/hansen/global_forest_change_2023_v1_11
// =====================================================

// ---- 1. Study Area ----
var studyArea = ee.Geometry.Rectangle([-6.5, 33.0, -1.5, 36.0]);
Map.setCenter(-4.0, 34.5, 8);
Map.setOptions('SATELLITE');

// ---- 2. Load Hansen GFC Dataset ----
var hansen = ee.Image('UMD/hansen/global_forest_change_2023_v1_11');

var treeCover  = hansen.select('treecover2000');
var loss       = hansen.select('loss');
var lossYear   = hansen.select('lossyear');
var gain       = hansen.select('gain');
var dataMask   = hansen.select('datamask');

// ---- 3. Define Forest Mask (>= 10% canopy cover -- semi-arid Moroccan threshold) ----
// Use 30% for dense tropical forests (Congo, Amazon)
var CANOPY_THRESHOLD = 10;
var forestMask = treeCover.gte(CANOPY_THRESHOLD).and(dataMask.eq(1));

// ---- 4. Forest Loss (masked to forest pixels) ----
var forestLoss     = loss.updateMask(loss).updateMask(forestMask);
var lossYearMasked = lossYear.updateMask(loss).updateMask(forestMask);
var forestGain     = gain.updateMask(gain);

// ---- 5. Visualisation Layers ----
// Tree cover 2000
Map.addLayer(treeCover.updateMask(forestMask).clip(studyArea),
  {min: CANOPY_THRESHOLD, max: 100, palette: ['#A8D5A2','#52B788','#2D6A4F','#1B4332']},
  'Tree Cover 2000');

// Annual loss year (2001-2023) -- yellow to dark red temporal gradient
Map.addLayer(lossYearMasked.clip(studyArea),
  {min: 1, max: 23, palette: ['#FFFFB2','#FED976','#FEB24C','#FD8D3C','#FC4E2A','#E31A1C','#B10026']},
  'Forest Loss Year (2001-2023)');

// Gain layer (blue)
Map.addLayer(forestGain.clip(studyArea),
  {palette: ['#3B82F6']},
  'Forest Gain 2000-2012');

// ---- 6. Total Forest Loss Area (hectares) ----
var totalLossArea = forestLoss.multiply(ee.Image.pixelArea())
  .reduceRegion({
    reducer: ee.Reducer.sum(),
    geometry: studyArea,
    scale: 30,
    maxPixels: 1e13
  });
var totalHa = ee.Number(totalLossArea.get('loss')).divide(10000);
print('Total forest loss 2001-2023 (ha):', totalHa);

// ---- 7. Annual Loss by Year (single-pass histogram chart) ----
// frequencyHistogram counts pixels per lossyear value in one reduceRegion call
var lossPixelCount = lossYearMasked.reduceRegion({
  reducer: ee.Reducer.frequencyHistogram(),
  geometry: studyArea,
  scale: 100,
  maxPixels: 1e10,
  bestEffort: true
});

var pixelAreaHa = ee.Number(100).multiply(100).divide(10000); // 1 ha at scale 100m
var histogram = ee.Dictionary(lossPixelCount.get('lossyear'));

var years = ee.List.sequence(1, 23);
var annualLossFc = ee.FeatureCollection(years.map(function(y) {
  y = ee.Number(y);
  var yearKey = y.int().format('%d');
  var count = ee.Number(histogram.get(yearKey, 0));
  var ha = count.multiply(pixelAreaHa);
  return ee.Feature(null, {year: y.add(2000), area_ha: ha});
}));

var chart = ui.Chart.feature.byFeature({
  features: annualLossFc,
  xProperty: 'year',
  yProperties: ['area_ha']
}).setChartType('ColumnChart')
  .setOptions({
    title: 'Annual Forest Loss - North Morocco (2001-2023)',
    hAxis: {title: 'Year', format: '####'},
    vAxis: {title: 'Forest Loss (ha)'},
    colors: ['#B10026'],
    bar: {groupWidth: '80%'},
    legend: {position: 'none'},
    backgroundColor: '#FFFFFF'
  });
print(chart);

// ---- 8. Legend Panel ----
var legend = ui.Panel({style: {position: 'bottom-left', padding: '10px', width: '220px'}});
legend.add(ui.Label('Forest Change Legend', {fontWeight: 'bold', fontSize: '13px', margin: '0 0 6px 0'}));

var items = [
  {color: '#2D6A4F', label: 'Forest cover 2000 (β‰₯30%)'},
  {color: '#FFFFB2', label: 'Loss: early (2001-2007)'},
  {color: '#FD8D3C', label: 'Loss: mid (2008-2015)'},
  {color: '#B10026', label: 'Loss: recent (2016-2023)'},
  {color: '#3B82F6', label: 'Forest gain (2000-2012)'}
];

items.forEach(function(item) {
  var row = ui.Panel({layout: ui.Panel.Layout.Flow('horizontal'), style: {margin: '2px 0'}});
  row.add(ui.Label({style: {backgroundColor: item.color, padding: '7px', margin: '0 6px 0 0', borderRadius: '3px'}}));
  row.add(ui.Label(item.label, {fontSize: '11px', color: '#374151'}));
  legend.add(row);
});
Map.add(legend);

// ---- 10. Click Inspector ----
var inspector = ui.Panel({style: {position: 'top-right', width: '240px', padding: '10px'}});
inspector.add(ui.Label('Click Inspector', {fontWeight: 'bold', fontSize: '13px'}));
var infoLabel = ui.Label('Click on the map to inspect', {fontSize: '11px', color: '#6B7280'});
inspector.add(infoLabel);
Map.add(inspector);

Map.onClick(function(coords) {
  var point = ee.Geometry.Point([coords.lon, coords.lat]);
  var vals = hansen.select(['treecover2000','loss','lossyear','gain']).reduceRegion({
    reducer: ee.Reducer.first(),
    geometry: point,
    scale: 30
  });
  vals.evaluate(function(v) {
    var ly = v.lossyear ? (2000 + v.lossyear) : 'None';
    infoLabel.setValue(
      'Canopy 2000: ' + (v.treecover2000 || 0) + '%\n' +
      'Loss: ' + (v.loss ? 'Yes (' + ly + ')' : 'No') + '\n' +
      'Gain: ' + (v.gain ? 'Yes' : 'No')
    );
  });
});

// ---- 11. Export Annual Loss Map ----
Export.image.toDrive({
  image: lossYearMasked.rename('loss_year'),
  description: 'Hansen_Forest_Loss_Year_NorthMorocco',
  folder: 'GEE_ForestLoss',
  region: studyArea,
  scale: 30,
  crs: 'EPSG:4326',
  maxPixels: 1e13
});
πŸ“‘
Scale note: The annual chart computation (scale: 30) over a large Congo Basin rectangle is computationally heavy. If GEE times out, reduce to scale: 100 or narrow the study area. Ask DeepSeek: "Reduce the computation scale to 100m for the chart only."

13
Prompt Engineering Tips for Hansen GFC
Reference
GoalWhat to add to your prompt
Semi-arid forests"Lower canopy threshold to 10% for sparse forest cover"
Country-level stats"Use FAO GAUL Level 1 to compute loss by country"
Protected areas"Mask to WDPA protected areas and compute % loss inside"
Deforestation drivers"Overlay fire (FIRMS), roads (OSM), and cropland (Dynamic World)"
Time animation"Create a GEE video export showing loss accumulation year by year"
Forest fragmentation"Compute patch size distribution using connectedPixelCount"
Regeneration"Show gain areas and compare with loss areas from 2001-2012"
🧠
Iteration is the key skill: Start with the base prompt, run the code, observe what is missing or wrong, then send a follow-up to DeepSeek. This reflects real-world GeoAI workflows where humans guide AI through domain knowledge.
Course 04 -- Forest Loss Mapping with Hansen GFC + DeepSeek AI | GeoAI for Climate Change | Dr. Ayoub Lahlouh | UM6P/CUS | 2025/2026