EGS 2403 · Jomo Kenyatta University of Agriculture and Technology

Remote Sensing
Applications

A course reader for fourth-year Geomatic Engineering and Geospatial Information Systems.

11 teaching weeks 2 h lecture + 2 h laboratory Google Earth Engine · QGIS · SNAP Assessment 30 / 70 Edition 1.0 · Sept 2026

How to use this reader

You have already done a principles unit: electromagnetic radiation, sensors, orbits, resolution. This unit is about getting a defensible answer out of an archive — choosing the sensor, masking the cloud, classifying honestly, validating with a probability sample, and knowing what the resulting map can and cannot support.

Each chapter covers one week with the same parts: learning outcomes, the reading, a two-tier practical, self-test questions, and further reading. The Core task is what everyone completes; the Extension is for those who finish early.

The Earth Engine Code Editor is JavaScript

If you have taken Web Mapping, you have a head start — it is the same language. If you have not, the subset Earth Engine needs is small: variables, functions, objects, and one unfamiliar idea called method chaining. Appendix A is a self-check that will tell you privately where you stand.

What trips up everyone, regardless of background, is not the syntax. It is that your code does not run on your computer — it builds a description of a computation which Google's servers execute. Week 2 is entirely about that distinction, and until it clicks, nothing else will.

One habit to form now. Every map you produce in this unit must be able to answer: how accurate is it, and how do you know? A classified image without a validation sample is a coloured picture, not a map. Week 6 is the centre of this unit, and everything before it is preparation.

Accounts and setup

  1. Register for Earth Engine at earthengine.google.com with a Google account, selecting non-commercial or academic use. Registration approval can take a few days — do this in week 1, not the night before practical 2.
  2. Open the Code Editor at code.earthengine.google.com. Nothing to install. It runs in the browser and does the computation on Google's infrastructure, which is why this unit works on a modest laptop.
  3. Install QGIS (long-term release) for local work, cartographic output, and the weeks where you need to look at pixels properly.
  4. Install ESA SNAP before week 8, for the SAR material that Earth Engine does not cover.

Why Earth Engine, given the bandwidth here. A single Sentinel-2 tile is roughly 600 MB. A ten-year time series for one county, downloaded and processed locally, is hundreds of gigabytes of transfer and days of processing on a lab machine.

Earth Engine inverts this: the archive stays where it is, your code goes to it, and what comes back is a small answer — a chart, a statistic, a modest export. The transfer cost is measured in kilobytes. This is not a convenience; it is what makes continental-scale analysis possible from a university lab in Juja.

One caveat belongs here rather than at the end. Earth Engine is a product of a single company, offered under terms that company can change — and has changed before. Appendix D states that risk plainly, shows the same analysis written in the open Python stack, and sets the exercise that makes you independent of the platform you are about to learn. Read it in week 2, not week 11.

Have a fallback ready. Earth Engine needs a working connection, and the lab's is not guaranteed on any given afternoon. Keep pre-exported GeoTIFFs of the key datasets on local disk so that a practical can proceed in QGIS when the network does not cooperate. The unit's shared folder holds these.

01

Week one

From principles to applications

By the end of this week you should be able to
  • Choose a sensor from the requirements of an application, and defend the trade-off
  • Read a spectral signature and say which bands separate which surfaces
  • State the current constellation and what each platform is for
  • Distinguish processing levels and say what analysis-ready data already guarantees
  • Explain what changes when the computation moves to the archive

The four resolutions, as a set of trade-offs

You know the definitions. What matters now is that you cannot have all four at once, because they trade against each other through the physics of the instrument. A detector element collects a fixed amount of energy; make the ground footprint smaller and less energy arrives, so to keep the signal-to-noise ratio usable you must widen the spectral bands or lengthen the dwell time. Every satellite is one settled compromise, and choosing among them is the first professional decision in any project.

If the application needs…You are buying it with…
Fine spatial detail — individual buildings, field boundariesNarrower swath, so less frequent revisit; and usually cost
Frequent revisit — crop phenology, flood responseCoarse pixels, because a wide swath means a large footprint
Many narrow spectral bands — mineralogy, water qualityCoarser pixels and a lower signal-to-noise ratio
Fine radiometric sensitivity — subtle water or thermal differencesWider bands or larger pixels

State the requirement before you choose the sensor. "Map crop types across Trans Nzoia" needs enough revisit to catch the phenological sequence and enough spatial detail to resolve fields — Sentinel-2 at 10 m every five days. "Map informal settlement structures" needs metres, not tens of metres, and revisit hardly matters — commercial very-high-resolution imagery. "Track a flood over three days under cloud" needs radar, and nothing else will do.

Spectral signatures — the foundation of everything that follows

visible vegetation bare soil water B2B3B4 B8B11B12 5001000 15002000 wavelength (nm) 020 4060 reflectance (%)
Three surfaces, and where Sentinel-2 samples them. Vegetation absorbs red (B4) for photosynthesis and scatters near-infrared (B8) strongly from leaf mesophyll — that jump is the red edge, and every vegetation index exploits it. Water absorbs almost everything beyond 700 nm, which is why it appears black in NIR and why NIR is the cheapest water detector there is. Soil rises gently and steadily with no red edge, which is what separates bare ground from senesced crop.

Read that figure as a set of practical rules. Vegetation is separated from soil in B8 far more strongly than in any visible band. Water is separated from everything in B8 and B11. Two vegetation types that look identical in the visible may differ in the SWIR because of moisture content. When a classification confuses two classes, the first question is which band would separate them? — and the answer usually comes from this figure.

It also explains the standard false-colour composite. Put B8 in the red channel, B4 in green, B3 in blue, and healthy vegetation — with its high NIR — renders bright red. This is the display convention you will use all semester, and once you have read the figure it is not arbitrary at all.

The constellation you will actually use

PlatformSpatialRevisitWhat it is for
Landsat 8/9 OLI/TIRS30 m · 15 m pan
100 m thermal
8 days
(two satellites)
The long record — continuous since 1972. Any question about change over decades starts here. Also the accessible thermal band.
Sentinel-2 A/B/C MSI10 m · 20 m · 60 m5 daysThe workhorse. Best free combination of resolution and revisit. Red-edge bands are genuinely useful for crops.
Sentinel-1 SAR10 m (IW GRD)6–12 daysCloud-penetrating radar. Floods, crop structure, deformation. Week 8.
MODIS / VIIRS250–1000 mdailyRegional and continental monitoring where daily coverage beats detail. Drought, fire, phenology.
Sentinel-3 OLCI/SLSTR300 m · 500–1000 m1–2 daysOcean and land colour, sea and land surface temperature.
Sentinel-5P TROPOMI~5.5 × 3.5 kmdailyAtmospheric composition — NO₂, CO, SO₂, aerosol. Week 9.
PlanetScope~3 mdailyCommercial. Free education tier available and worth applying for.

Processing levels

Imagery is distributed at defined levels, and knowing which you have determines what you must do before analysis.

Free is not the same as free of judgement

Level-2 products have already had substantial processing applied by someone who was not thinking about your question. The cloud mask was tuned to a global average and is systematically weaker over bright soils — which is most of northern Kenya. The atmospheric correction assumes an aerosol model that may not hold over the Rift during a dust event.

Use the products; they are excellent and you could not do better yourself. But know what has been done, and check the result over your own area rather than trusting it. Week 3 is about exactly this.

What changes when compute moves to the data

Until recently, remote sensing meant downloading scenes and processing them locally. That model imposes hard limits: a single Sentinel-2 tile is around 600 MB, so a decade of imagery for one county is hundreds of gigabytes before you have computed anything. On a university connection this is not a delay; it is a wall.

Earth Engine, and platforms like it, invert the arrangement. The archive stays in place, your algorithm is sent to it, and only the result returns. The consequences are worth stating plainly because they reshape what questions are askable:

Practical 1 — Register, and look at your own county
Core everyone
  1. Register for Earth Engine today. Approval takes time and week 2 assumes you have access.
  2. Open the Code Editor. Take the interface tour: script panel, console, inspector, map, asset manager, task manager.
  3. Load one Sentinel-2 scene over your home county. Display it in true colour (B4-B3-B2) and false colour (B8-B4-B3), side by side.
  4. Use the Inspector to click on: healthy vegetation, bare soil, water, and a built-up area. Record the reflectance in B2, B3, B4, B8, B11 for each, and plot the four signatures. Compare with the figure above — where does your data match, and where not?
  5. Print the image metadata. Find and record: acquisition date, cloud cover percentage, tile ID, and the processing level.
  6. For a stated application — pick one of crop mapping, flood response, or informal settlement mapping — write half a page choosing a sensor and defending the trade-off you accepted.
Extension if you have time
  1. Plot spectral signatures for five crop types from a supplied field-boundary dataset. Which band pairs separate them best, and which pairs are hopeless?
  2. Compare the same location in Landsat 9 and Sentinel-2 acquired within two days. Where do the reflectance values differ, and how much is sensor and how much is atmosphere?
Self-test
  1. Why can a sensor not have fine spatial, spectral and temporal resolution simultaneously?
  2. Why does vegetation appear red in a standard false-colour composite?
  3. Which single band most cheaply separates water from land, and why?
  4. When would you use Level-1 TOA rather than Level-2 surface reflectance?
  5. Two crops are indistinguishable in the visible bands. Where would you look next, and why?

Reading. Jensen, Remote Sensing of the Environment, ch. 1–2 for the applications framing. Lillesand, Kiefer & Chipman, ch. 1 and 5. Gorelick et al. (2017), "Google Earth Engine: Planetary-scale geospatial analysis for everyone", RSE 202 — the platform's own statement of what it is for.

02

Week two

Earth Engine fundamentals

By the end of this week you should be able to
  • Explain the client/server distinction and predict which operations are which
  • Filter an ImageCollection by date, bounds and metadata
  • Apply a cloud mask and build a composite
  • Use map() and the reducers instead of loops
  • Export a result and know which export to choose

The one concept that matters

Your script does not process imagery. It builds a description of a computation, sends that description to Google's servers, and receives a result. The objects in your code — ee.Image, ee.Number, ee.List — are handles to things that live on the server. They are not JavaScript values, and treating them as though they were is the source of nearly every problem students have in this unit.

JavaScript · Earth Engine
// WRONG — collection.size() is an ee.Number, not a JavaScript number.
// The comparison is meaningless and this loop never runs correctly.
var total = 0;
for (var i = 0; i < collection.size(); i++) {
  total += collection.get(i);
}

// RIGHT — describe the whole operation and let the server do it.
var total = collection.size();
print(total);          // print() is one of the few calls that fetches to the client

// WRONG — a JavaScript conditional on a server-side value
if (image.get('CLOUD_COVER') < 10) { ... }

// RIGHT — express the condition server-side, as a filter
var clear = collection.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));

The rule. If a value came from an ee. object, it is on the server. You cannot compare it with <, you cannot loop over it with for, and you cannot print it with anything but print().

Anything you want done to many images is done with .map(); anything you want reduced across images or pixels is done with a reducer. Once you stop trying to write loops, Earth Engine becomes straightforward — and the code becomes shorter than the local-processing equivalent by an order of magnitude.

Filter, mask, composite

Almost every Earth Engine script has the same three-stage skeleton, and once you can write it from memory you can write most of this unit.

JavaScript · Earth Engine
var aoi = ee.FeatureCollection('FAO/GAUL/2015/level1')
            .filter(ee.Filter.eq('ADM1_NAME', 'Kiambu'));

// ---- 1. FILTER: narrow the archive to what you need -------------------
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
           .filterBounds(aoi)
           .filterDate('2025-01-01', '2025-12-31')
           .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40));

print('scenes found:', s2.size());

// ---- 2. MASK: remove cloud, shadow and cirrus, pixel by pixel ---------
function maskS2(image) {
  var scl = image.select('SCL');        // scene classification layer
  var clear = scl.neq(3)                  // 3  cloud shadow
                 .and(scl.neq(8))         // 8  cloud, medium probability
                 .and(scl.neq(9))         // 9  cloud, high probability
                 .and(scl.neq(10))        // 10 thin cirrus
                 .and(scl.neq(11));       // 11 snow / ice
  return image.updateMask(clear)
              .divide(10000)              // SCALE FACTOR — see the warning below
              .copyProperties(image, ['system:time_start']);
}

var masked = s2.map(maskS2);              // map(), never a for loop

// ---- 3. COMPOSITE: reduce many images to one --------------------------
var composite = masked.median().clip(aoi);

Map.centerObject(aoi, 10);
Map.addLayer(composite, {bands: ['B8', 'B4', 'B3'], min: 0, max: 0.4},
             'false colour — vegetation is red');

Three notes on that script. median() is the standard compositing reducer because it is robust to the cloud that survived masking — a residual bright cloud is an outlier and the median ignores it, where mean() would not. copyProperties preserves the timestamp, without which any later time-series operation silently fails. And clip() is for display: it does not make the computation faster, and clipping early in a long chain can actually slow things down.

Scale factors — the silent error

Satellite products store reflectance as integers to save space, with a documented scale and offset. Forget them and everything downstream is wrong by a constant factor, with no error message.

Sentinel-2 L2A: reflectance = DN / 10000.
Landsat Collection 2 Level-2 surface reflectance: reflectance = DN × 0.0000275 − 0.2. Note the offset — this one is not a simple division, and applying only the multiplier gives values that look plausible and are wrong.
Landsat Collection 2 surface temperature: kelvin = DN × 0.00341802 + 149.0.

The diagnostic is simple: surface reflectance must lie between 0 and 1. If your NDVI comes out as 3000, or your reflectance histogram peaks at 2400, you have skipped a scale factor. Check this on every new dataset, every time.

Reducers

A reducer collapses many values into fewer. The same objects work in three contexts, which is what makes them worth learning properly:

Exporting

Nothing leaves Earth Engine until you export, and exports run as background tasks you must start from the Tasks tab.

Export the smallest thing that answers your question. A CSV of per-ward means is a few kilobytes; the raster it came from is gigabytes. Students routinely export a full-resolution image when they needed a table of ten numbers — and then wait an hour on a lab connection for it.

Practical 2 — Build a clean annual composite
Core everyone
  1. Define your county as the area of interest from the GAUL boundaries.
  2. Filter Sentinel-2 L2A to 2025 over that area. Print how many scenes you found, and print the same figure with the cloud filter removed. Report both.
  3. Display a single scene with heavy cloud. Then apply the SCL mask and display it again. Screenshot both.
  4. Build a median composite for the year. Display in true and false colour.
  5. Build a second composite from the dry season only. Compare them and explain, in terms of phenology, why they differ.
  6. Verify your scale factor: use reduceRegion to print the mean of B8 over the composite. If it is not between 0 and 1, find your error before going further.
  7. Export the false-colour composite to Drive at 10 m, and open it in QGIS.
  8. Compute mean NDVI per sub-county with reduceRegions and export as CSV.
Extension if you have time
  1. Replace the SCL mask with the s2cloudless probability layer joined to the collection. Compare the two masks over a bright bare-soil area — which one over-masks?
  2. Build a "greenest pixel" composite using qualityMosaic('NDVI') and compare it with the median composite. When would each be the right choice?
Self-test
  1. Why does for (var i = 0; i < collection.size(); i++) not work?
  2. Your NDVI values range from −2000 to 8000. What went wrong?
  3. Why is median() preferred over mean() for compositing?
  4. What happens if you omit scale in a reduceRegion call?
  5. When would you export to Asset rather than to Drive?

Reading. Cardille et al., Cloud-Based Remote Sensing with Google Earth Engine (2023, open access), parts F1–F3. The Earth Engine "Client vs Server" guide — short, and it is the concept the whole platform rests on.

03

Week three

Getting the data right

By the end of this week you should be able to
  • Say what a Level-2 product has corrected and what it has not
  • Recognise topographic effects and apply a correction
  • Harmonise Landsat and Sentinel-2 into one usable series
  • Smooth a noisy time series without inventing signal
  • Diagnose residual artefacts before they reach your analysis

What is already done, and what is not

Level-2 surface reflectance has had the dominant atmospheric effects removed: Rayleigh scattering, aerosol scattering and absorption, and water vapour absorption, using an atmospheric model driven by ancillary data. This is a substantial and well-validated piece of work, and you should use it.

Three things it does not handle, all of which matter in Kenya:

Topographic correction

This matters as soon as your study area has relief — the Aberdares, Mount Kenya, the Rift escarpment, the Taita Hills. The illumination condition for a sloped pixel is

cos i = cos θs cos θn + sin θs sin θn cos(φs − φn)

θs is solar zenith, φs solar azimuth, θn terrain slope and φn terrain aspect, all obtainable from the image metadata and a DEM. The result, cos i, is the illumination — 1 for a surface facing the sun directly, falling toward 0 as the surface turns away.

The simplest correction divides by cos i, which over-corrects steep shaded slopes badly. The C-correction adds an empirical term fitted per band from the regression of reflectance on cos i, and is the standard practical choice. SCS+C is better again for forestry, because it preserves the geotropic orientation of tree canopies rather than assuming a Lambertian surface.

Diagnose the need by displaying a false-colour composite over a mountain: if the sunlit and shaded flanks of the same forest look like different cover types, you need correction. Diagnose success by plotting reflectance against cos i before and after — a good correction flattens the slope of that relationship toward zero.

Harmonising sensors

Landsat 8/9 and Sentinel-2 have similar but not identical bands: different centre wavelengths, different bandwidths, different spatial resolutions. Combining them roughly triples your usable observations, which for a cloudy tropical site is often the difference between a workable time series and no time series at all. But you cannot simply concatenate them.

  1. Resample to a common grid — usually Sentinel-2's 10 m or a shared 30 m.
  2. Rename bands to a common scheme so that "NIR" means the same thing regardless of source.
  3. Apply cross-calibration coefficients. Published band-by-band linear transformations (Roy et al., 2016) bring one sensor onto the other's scale. The adjustments are small — a few per cent — but they are systematic, and a systematic step in a time series looks exactly like real change.
  4. Verify on near-coincident acquisitions: find dates where both sensors imaged the same clear scene within a day or two, and check that the harmonised values agree.

Smoothing a time series

A per-pixel NDVI series from real imagery is ragged: gaps where cloud was masked, dips where thin cloud or shadow survived the mask, and noise. Three responses, in increasing sophistication:

Smoothing can invent signal

Every smoother imposes an assumption about how the underlying series behaves. If the truth contains an abrupt event — a fire, a clearance, a flood — the smoother will round it off, and you will report a gradual decline where a sudden loss occurred. Worse, an aggressive smoother across a long cloud gap will draw a confident curve through a period when you had no observations at all.

Always keep and plot the raw observations alongside the smoothed curve, and mark which points are real. If a feature exists only in the smoothed line, it does not exist.

Practical 3 — Build a clean, harmonised series
Core everyone
  1. Build a combined Landsat 8/9 and Sentinel-2 collection over your area for 2020–2025. Rename bands to a common scheme and apply the correct scale factors to each — they differ, and one has an offset.
  2. Verify the harmonisation: find a near-coincident clear pair and compare band values at 50 random points. Plot one against the other with a 1:1 line and report the bias.
  3. Extract an NDVI time series for a single agricultural pixel. Plot the raw points. Count how many observations survived masking, and what fraction of the period that represents.
  4. Apply a Savitzky–Golay smoother. Plot raw and smoothed on the same axes with the real observations marked.
  5. Fit a harmonic model with annual and semi-annual terms. Overlay it. Does it capture the bimodal rainfall pattern?
  6. Over a hilly area, display a false-colour composite and identify illumination effects. Compute cos i from a DEM and plot NIR reflectance against it. Report the correlation.
  7. Apply a C-correction and re-plot. Report the correlation before and after.
Extension if you have time
  1. Quantify BRDF: for one date, plot reflectance against view zenith angle across a wide scene. How large is the across-swath gradient relative to the between-class differences you care about?
  2. Take a pixel with a known abrupt change, smooth it three ways, and measure how much each smoother delays and softens the detected break.
Self-test
  1. Name three things Level-2 surface reflectance does not correct.
  2. How would you recognise, from a false-colour image alone, that topographic correction is needed?
  3. Why is a simple cos i division a poor correction, and what does C-correction add?
  4. Why prefer Savitzky–Golay over a moving average for phenology?
  5. Why does harmonic regression suit Kenyan rainfall particularly well?

Reading. Roy et al. (2016), "Characterization of Landsat-7 to Landsat-8 reflective wavelength and normalized difference vegetation index continuity", RSE 185, and the companion Sentinel-2 harmonisation paper. Cardille et al., part F4. Riaño et al. (2003) on topographic correction methods, IEEE TGRS 41(5).

04

Week four

Vegetation, phenology and drought

By the end of this week you should be able to
  • Choose an index from the question rather than from habit
  • State where NDVI fails and what replaces it
  • Extract phenological metrics from a time series
  • Compute a Vegetation Condition Index against a proper baseline
  • Interpret a drought product the way an operational agency does

Indices, and what each one is for

An index is a band ratio designed to amplify one signal and suppress others. There are hundreds; six will cover almost everything you do.

IndexSentinel-2 formFor, and where it fails
NDVI(B8−B4)/(B8+B4)General greenness. Saturates in dense canopy — above about LAI 3 it stops responding. Also sensitive to soil background when cover is sparse.
EVI2.5(B8−B4)/
(B8+6B4−7.5B2+1)
Dense vegetation, where NDVI has saturated. The blue term corrects for residual aerosol.
SAVI1.5(B8−B4)/
(B8+B4+0.5)
Sparse cover over bright soil — rangelands, ASALs. The soil adjustment factor is what NDVI lacks.
NDMI(B8−B11)/(B8+B11)Canopy water content. Detects water stress before it shows as browning, which makes it valuable for early warning.
MNDWI(B3−B11)/(B3+B11)Open water. Outperforms NDWI in built-up areas, where concrete otherwise reads as water.
NBR(B8−B12)/(B8+B12)Burn severity, via the difference between pre- and post-fire values.
There are two indices called NDWI, and they do different things

McFeeters (1996) NDWI = (Green − NIR)/(Green + NIR) maps open water bodies.
Gao (1996) NDWI = (NIR − SWIR)/(NIR + SWIR) maps vegetation water content, and is usually called NDMI to avoid exactly this collision.

They share a name, use different bands, and answer different questions. Papers are frequently ambiguous about which they used. Always write the band formula in your methods rather than the name alone — and when reading someone else's work, check which one they meant before believing their result.

Phenology

A single date tells you little. The shape of the curve through a season tells you a great deal: when growth started, how fast, how high it peaked, when it senesced, and how much total greenness accumulated. From a smoothed series you can extract:

Crop types differ in phenology even when their spectra on any single date are identical. This is why a time-series feature stack classifies crops far better than a single image, and it is the basis of practically all operational crop mapping. Week 5 uses it.

From index to decision: the Vegetation Condition Index

NDVI is not comparable between places — a value of 0.4 is poor for Kericho and remarkable for Marsabit. To ask "is this area unusually stressed?", you must compare a location against its own history in the same season. That is what VCI does.

VCI = 100 × NDVIcurrent − NDVIminNDVImax − NDVImin

where min and max are taken over the historical record for the same calendar window at the same location. VCI is a percentile-like position within local history: 0 means the worst on record, 100 the best.

Worked example — a March assessment in Marsabit

For one ward, the record of mean March NDVI over the past twenty years runs from a minimum of 0.18 to a maximum of 0.52. March 2026 comes in at 0.24.

VCI = 100 × (0.24 − 0.18) / (0.52 − 0.18) = 100 × 0.06 / 0.34 = 17.6

Against the categories used in Kenya's drought early-warning bulletins:

VCIVegetation condition
≥ 50Normal or above
35 – 50Moderate vegetation deficit
21 – 34Severe vegetation deficit
≤ 20Extreme vegetation deficit

17.6 falls in the extreme band. Note what has happened: an NDVI of 0.24 is unremarkable in absolute terms and would mean nothing on its own. Placed against twenty years of the same ward in the same month, it is a warning that feeds into resource allocation decisions.

Two ways to get this wrong. If you build the baseline from annual maxima rather than the same month, you are comparing March against the historical peak of the growing season, and every March looks like a drought. If you build it from a short record — five years, say — a single unusual year sets the min or the max and distorts every subsequent value. The baseline is the whole method; treat its construction with more care than the arithmetic.

JavaScript · Earth Engine
// NDVI over a long baseline, restricted to the same calendar window
var withNDVI = function(img) {
  return img.addBands(img.normalizedDifference(['B8', 'B4']).rename('NDVI'));
};

var march = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(ward)
  .filter(ee.Filter.calendarRange(3, 3, 'month'))   // same window, every year
  .map(maskS2).map(withNDVI).select('NDVI');

var ndviMin = march.min();
var ndviMax = march.max();
var current = march.filterDate('2026-03-01', '2026-04-01').median();

var vci = current.subtract(ndviMin)
                 .divide(ndviMax.subtract(ndviMin))
                 .multiply(100)
                 .rename('VCI');

Map.addLayer(vci, {min: 0, max: 100, palette: ['8B0000', 'FF8C00', 'FFFF00', 'ADFF2F', '006400']},
             'VCI, March 2026');
Practical 4 — A drought assessment
Core everyone
  1. For an agricultural pixel, compute NDVI, EVI and SAVI across one year and plot all three. Where do they diverge, and which would you trust in the dense-canopy peak?
  2. Repeat for a rangeland pixel in an ASAL county. Which index behaves best over sparse cover on bright soil, and does the ranking match what you predicted?
  3. Build a twenty-year NDVI record for one ASAL ward using harmonised Landsat and Sentinel-2, restricted to a single calendar month.
  4. Compute the historical minimum and maximum, then the VCI for the most recent year. Map it with the category palette above.
  5. Compute VCI for every year in the record and plot the time series. Identify the drought years, and check them against the published record of Kenyan droughts. Do they match? Explain any that do not.
  6. Test the baseline. Recompute VCI using a five-year baseline instead of twenty. Report how much the values move and what that implies about published VCI products.
  7. Extract start of season, peak date and integrated NDVI for one cropland pixel across three years. Relate the variation to rainfall records.
Extension if you have time
  1. Compute NDMI alongside NDVI through a drought onset. Does moisture stress appear before greenness declines, and by how many weeks?
  2. Build a combined index using both vegetation and rainfall anomaly, and compare its drought classification with VCI alone.
Self-test
  1. Where does NDVI saturate, and which index would you use instead?
  2. Distinguish the two indices called NDWI. Which maps open water?
  3. Why is an absolute NDVI value not comparable between Kericho and Marsabit?
  4. A colleague computes VCI against a five-year baseline. What is your concern?
  5. Why does a time-series feature stack classify crops better than a single date?

Reading. Huete et al. (2002) on MODIS vegetation indices, RSE 83. Kogan (1995) on VCI, Advances in Space Research 15(11). Kenya's NDMA drought bulletins — read three consecutive months and see how the remote sensing feeds an operational decision.

05

Week five · CAT 1 this week

Supervised classification

By the end of this week you should be able to
  • Design a class scheme whose classes are actually separable
  • Collect training data that represents the variation in each class
  • Build a feature stack combining spectral, temporal and terrain information
  • Train and apply a classifier in Earth Engine
  • Recognise overfitting before the accuracy assessment tells you

The class scheme comes first

Before any code, decide what you are mapping. Two constraints bind, and beginners routinely violate both.

Classes must be spectrally separable. "Maize" and "sorghum" may be indistinguishable on any single date. "Smallholder farm" and "commercial farm" differ by ownership, not reflectance, and no classifier can see ownership. If two classes cannot be told apart by the sensor, merge them or find a feature that does separate them — usually a temporal one.

Classes must be exhaustive and mutually exclusive. Every pixel must belong to exactly one class. A scheme with "forest", "cropland" and "wetland" has nowhere to put a road. The classifier will assign it somewhere regardless, silently, and your accuracy assessment will not tell you which of your classes absorbed it.

Start from an established scheme — the FAO Land Cover Classification System, or Kenya's national land-cover classes — rather than inventing one. It costs nothing and makes your product comparable with everyone else's.

Training data

This is where classification projects succeed or fail, and it takes longer than everything else combined.

Training data must never come from the map you are validating

It is tempting to draw training polygons, classify, look at the result, and then add more polygons where it looks wrong. That is legitimate iterative refinement of the training set — but the moment you validate against points chosen the same way, your accuracy is meaningless, because you have measured how well the classifier reproduces your own opinion.

Validation data must be an independent probability sample drawn after the map exists, by a design that does not depend on the map's appearance. This is not a formality; it is the difference between a reported accuracy that means something and one that does not. Week 6 is entirely about doing it properly.

Feature stacks

You are not limited to the spectral bands of one date. A classifier can use any co-registered layer, and adding the right ones usually helps more than changing the algorithm:

JavaScript · Earth Engine
// ---- build a feature stack: two seasons, indices, terrain -------------
var dry = buildComposite('2025-01-01', '2025-03-31');
var wet = buildComposite('2025-04-01', '2025-06-30');
var dem = ee.Image('USGS/SRTMGL1_003');

var stack = dry.select(['B2','B3','B4','B8','B11','B12'])
                 .rename(['d_B2','d_B3','d_B4','d_B8','d_B11','d_B12'])
  .addBands(wet.select(['B2','B3','B4','B8','B11','B12'])
                 .rename(['w_B2','w_B3','w_B4','w_B8','w_B11','w_B12']))
  .addBands(dry.normalizedDifference(['B8','B4']).rename('d_NDVI'))
  .addBands(wet.normalizedDifference(['B8','B4']).rename('w_NDVI'))
  .addBands(dem.rename('elev'))
  .addBands(ee.Terrain.slope(dem).rename('slope'));

// ---- sample the stack at the training polygons ------------------------
var training = stack.sampleRegions({
  collection: trainingPolygons,   // must carry a 'class' property
  properties: ['class'],
  scale: 10,
  tileScale: 4                    // raise this if you hit memory limits
});

// ---- train, and hold back nothing here — validation is a SEPARATE sample
var classifier = ee.Classifier.smileRandomForest({
  numberOfTrees: 300,
  minLeafPopulation: 3
}).train({
  features: training,
  classProperty: 'class',
  inputProperties: stack.bandNames()
});

var classified = stack.classify(classifier);

// ---- which features are actually doing the work? ----------------------
print('variable importance:', classifier.explain().get('importance'));

Choosing a classifier

ClassifierCharacter
CARTA single decision tree. Interpretable — you can read the rules — but unstable: a small change in training data gives a different tree. Use it to understand, not to publish.
Random forestHundreds of trees on bootstrapped samples, voting. Robust, handles many correlated features, needs almost no tuning, gives variable importance. The default choice, and it is hard to beat.
Gradient boostingOften marginally better than RF, at the cost of real tuning sensitivity. Worth trying once you have a working RF baseline.
SVMStrong with limited training data and high-dimensional features. Slow on large areas, and kernel and cost parameters matter a great deal.
Minimum distance, maximum likelihoodThe classical parametric methods. Still worth knowing — they appear throughout the older literature and in ENVI-based workflows you may inherit.

Do not spend the semester comparing classifiers. On most real problems the difference between random forest and gradient boosting is a percentage point or two, while the difference between poor and good training data is ten or twenty. Spend your effort where the leverage is.

Overfitting, and how to see it early

A classifier that has memorised its training data will report near-perfect accuracy on that data and fail on anything else. Three signals, available before you run a formal assessment:

Remedies: more and better-distributed training data; fewer, less correlated features; a larger minLeafPopulation; and — the one that actually settles the question — an honest validation sample, next week.

Practical 5 — Build a land-cover map
Core everyone
  1. Define a six-class scheme for your study area. Write one sentence per class defining it, and justify in two sentences why you expect each pair to be separable.
  2. Collect at least 80 training polygons per class, spread across the area, and deliberately including the internal variation of each class.
  3. Build a feature stack with dry and wet season composites, at least two indices, and terrain.
  4. Train a random forest. Map the result with a sensible legend.
  5. Print the variable importance. Which five features carry the model? Does that make physical sense?
  6. Retrain using only the dry-season bands. Compare the maps. Quantify what the temporal information bought you.
  7. Look for salt-and-pepper and for classes bleeding into one another. Name the two classes most confused, and predict — from week 1's spectral figure — which band would separate them.
  8. Retrain with minLeafPopulation of 1 and of 10. Describe the difference in map texture.
Extension if you have time
  1. Compute Jeffries–Matusita separability between every class pair from your training data. Which pair is weakest, and does it match what the map shows?
  2. Add Sentinel-1 VV and VH to the stack. Which classes improve, and why would radar help those particularly?
Self-test
  1. Give two properties a class scheme must have, and an example of a scheme that fails each.
  2. Why must training data capture within-class variation? Give a concrete failure.
  3. Why is 99% training accuracy not good news?
  4. Why does a time-series stack usually beat a better classifier on a single date?
  5. You see isolated single-pixel patches scattered through a homogeneous area. What is happening and what are your options?

Reading. Cardille et al., part F2 on classification. Belgiu & Drăguţ (2016), "Random forest in remote sensing: A review", ISPRS Journal 114. Foody (2002) on classification accuracy, RSE 80 — read it before week 6.

06

Week six · the centre of this unit

Accuracy assessment done properly

By the end of this week you should be able to
  • Explain why training accuracy and holdout accuracy are both inadequate
  • Design a stratified random validation sample and compute its size
  • Build a confusion matrix and compute overall, producer's and user's accuracy
  • State the case against kappa
  • Produce a bias-adjusted area estimate with a confidence interval

The number you computed last week is not an accuracy

Random forest reports an accuracy on its training data. It is typically above 95% and it is worthless — the model has seen those pixels. Splitting your training polygons into train and test is better but still inadequate, for a reason specific to this field: your training polygons were not a probability sample. You drew them where you were confident, which is where the classes are pure and obvious. The map's errors live at boundaries, in mixed pixels, and in the ambiguous cases you avoided digitising. A holdout from your training set contains none of them.

An accuracy figure is only meaningful if the validation points were selected by a probability sampling design — every pixel in the map having a known, non-zero chance of selection. That is the requirement, and everything in this chapter follows from it.

Designing the sample

Simple random is valid but inefficient: rare classes get few points. If built-up land is 3% of your area, a 200-point random sample gives you about six built-up points, from which you can say almost nothing about that class.

Stratified random — strata being the map classes — is the standard choice. Allocate points to each class deliberately, ensuring even rare classes get enough (a common floor is 50 per class), and weight the results by class area afterwards. That weighting is the step that students omit, and it is what makes the arithmetic below different from a plain confusion matrix.

For sample size, Cochran's formula for stratified estimation of overall accuracy:

n = ( ∑ Wi SiS(Ô) )2 ,    Si = √( Ui(1 − Ui) )

Wi is the area proportion of map class i, Ui your guess at that class's user's accuracy, and S(Ô) the standard error you are willing to accept on overall accuracy — 0.01 is a common target. In practice this lands most projects between 200 and 800 points.

Then interpret each sample point against the best reference data you can obtain — high-resolution imagery, field visits, or both — without looking at the map's classification of that point. If you know what the map said, you will agree with it more often, and that bias is not recoverable afterwards.

Worked example — a full assessment, end to end

A three-class map of a 900 km² area. Mapped areas: forest 270 km² (30%), cropland 540 km² (60%), built-up 90 km² (10%). A stratified random sample of 200 points is allocated 60 / 100 / 40 and interpreted against high-resolution imagery.

The confusion matrix, in sample counts. Rows are what the map said; columns are what the reference says.

Ref forestRef cropRef builtTotal
Map forest508260
Map cropland12826100
Map built283040
Total649838200

The naive overall accuracy is (50 + 82 + 30) / 200 = 81.0%. This figure is wrong, because the sample was stratified: built-up land is 10% of the map but 20% of the sample, so it is over-represented. We must weight by area.

Step 1 — convert counts to area proportions. For each cell, p̂ij = Wi × nij / ni·, with W = 0.30, 0.60, 0.10:

ForestCropBuiltWi
Map forest0.2500.0400.0100.300
Map cropland0.0720.4920.0360.600
Map built0.0050.0200.0750.100
Reference0.3270.5520.1211.000

Step 2 — the accuracies. Overall accuracy is the sum of the diagonal: 0.250 + 0.492 + 0.075 = 81.7%.

ClassUser's accuracyProducer's accuracy
Forest0.250/0.300 = 83.3%0.250/0.327 = 76.5%
Cropland0.492/0.600 = 82.0%0.492/0.552 = 89.1%
Built-up0.075/0.100 = 75.0%0.075/0.121 = 62.0%

Read those two columns as answers to different questions. User's accuracy answers "the map says built-up here — how often is that right?" — 75%, the reliability a user experiences. Producer's accuracy answers "of the land that really is built-up, how much did the map find?" — only 62%. The map is missing more than a third of the built-up land. That asymmetry is invisible in the overall figure.

Step 3 — the bias-adjusted area. The reference column totals estimate the true area proportions, so:

ClassMapped areaAdjusted estimateDifference
Forest270 km²294.3 km²+9%
Cropland540 km²496.8 km²−8%
Built-up90 km²108.9 km²+21%

Step 4 — the confidence interval. The standard error of an estimated area proportion under stratified sampling is

S(p̂·k) = √( ∑i Wi2 (nik/ni·)(1 − nik/ni·)ni· − 1 )

For built-up: the three strata contribute 0.30²×(2/60)(58/60)/59 = 0.0000492, 0.60²×(6/100)(94/100)/99 = 0.0002051, and 0.10²×(30/40)(10/40)/39 = 0.0000481. The sum is 0.0003023, so S = 0.0174. At 95% confidence that is ±1.96 × 0.0174 = ±0.0341 in proportion, or ±30.7 km².

The reportable result: built-up area = 109 ± 31 km² (95% CI).

Compare that with what a student would otherwise have written: "built-up area is 90 km², overall accuracy 81%." Both numbers in that sentence are wrong — the area by 21%, and the accuracy by a bias whose direction you could not have guessed. And the honest version carries an interval nearly a third as wide as the estimate, which is itself the most important thing the reader needs to know.

Do not report kappa

Kappa purports to correct overall accuracy for agreement "expected by chance". Pontius & Millones (2011) showed the correction is not useful: kappa is very highly correlated with overall accuracy, so it adds no information, and its chance-agreement baseline — that a classifier assigns classes at random in proportion to their marginals — describes no classifier anyone builds.

It persists because reviewers expect it. Report overall, producer's and user's accuracy with confidence intervals, plus quantity and allocation disagreement if you want a summary of error structure. If a reviewer insists on kappa, give it, and cite Pontius & Millones in the same sentence.

Practical 6 — Validate your map honestly
Core everyone
  1. Compute the area of each class in your week 5 map, and the proportions Wi.
  2. Use Cochran's formula to determine sample size for S(Ô) = 0.01. State your assumed user's accuracies and justify them.
  3. Draw a stratified random sample with at least 50 points per class, using Earth Engine's stratifiedSample.
  4. Interpret every point against high-resolution imagery without looking at the classification. Hide the map layer while you work. Record uncertain points rather than guessing, and report how many there were.
  5. Build the confusion matrix. Compute the naive overall accuracy first, then the area-weighted version. Report both and explain the gap.
  6. Compute user's and producer's accuracy for every class. Identify the class the map handles worst, and say whether it is an omission or a commission problem.
  7. Compute bias-adjusted areas with 95% confidence intervals for every class. Compare with the mapped areas.
  8. Write the accuracy statement as it would appear in a paper — three sentences, no jargon beyond the standard terms.
Extension if you have time
  1. Have a classmate independently interpret 50 of your validation points. Compute inter-interpreter agreement. What does the disagreement rate imply about the ceiling on your reported accuracy?
  2. Compute quantity and allocation disagreement following Pontius & Millones, and compare what they tell you against what kappa would have.
Self-test
  1. Why is a holdout from your training polygons not an adequate validation set?
  2. Distinguish user's from producer's accuracy in one sentence each, using a class from your own map.
  3. Why must a stratified sample be area-weighted before computing overall accuracy?
  4. Your map says a class covers 90 km² and the adjusted estimate is 109 km². Which do you report, and why?
  5. Give the two-part argument against kappa.

Reading. Olofsson et al. (2014), "Good practices for estimating area and assessing accuracy of land change", RSE 148 — read this one properly; it is the standard the field works to. Pontius & Millones (2011), IJRS 32(15). Stehman & Foody (2019), "Key issues in rigorous accuracy assessment", RSE 231.

07

Week seven

Change detection

By the end of this week you should be able to
  • Choose a change-detection method appropriate to the change you are looking for
  • Explain why post-classification comparison compounds error, and quantify it
  • Set and defend a change threshold
  • Describe the time-series algorithms and say when each applies
  • Distinguish detection from attribution

Two images are not enough on their own

The naive approach — classify 2000, classify 2025, compare — is the most common method in the literature and the most error-prone. Before anything else, three things must be true of your two images, and if they are not, everything you find is artefact:

The error compounding problem

Worked example — why post-classification comparison usually fails

A 1,000 km² area. Suppose 5% of it — 50 km² — genuinely changed. You have two land-cover maps, each with 85% overall accuracy, which is a respectable figure.

A pixel is flagged as changed when the two maps disagree. For a pixel to be classified correctly on both dates: 0.85 × 0.85 = 0.7225. So roughly 28% of pixels have at least one wrong label.

Of the 950 km² that did not change, most of those disagreements show up as spurious change — on the order of 250 km² falsely flagged.

Of the 50 km² that did change, only about 72% is caught on both dates — roughly 36 km² correctly flagged.

So the change map flags around 285 km², of which about 36 km² is real. Roughly one flagged pixel in eight is genuine change.

Two caveats keep this honest. It assumes the two maps' errors are independent, and in practice they correlate — same classifier, same training data, same confusions — which reduces the spurious rate somewhat. And the arithmetic is illustrative, not a substitute for measuring your own maps. But the structural point survives every refinement: when real change is rare, classification error dominates the change signal. This is why direct methods, which compare the imagery rather than the classifications, are usually preferable — and why any change map must be validated as a change map, with its own sample, not inferred from the accuracies of its inputs.

The methods

MethodHow, and when
Image differencingSubtract one date's index from the other and threshold. Simple, robust, and it avoids the compounding problem entirely because no classification is involved. Best when the change has a clear spectral direction — deforestation, burn, flood.
Image ratioingDivide rather than subtract. Less sensitive to multiplicative differences in illumination between dates.
Change vector analysisTreat the multi-band difference as a vector: its magnitude says how much changed, its direction hints at what kind of change. More informative than a single index difference.
Post-classification comparisonThe only method that names both the from-class and the to-class, which is what a land-cover transition matrix needs. Use it when you need that, and validate the change map independently.
Time-series algorithmsUse every available observation rather than two. Far more robust. See below.

Setting a threshold

Differencing gives you a continuous surface; you must decide what counts as change. Three approaches, in increasing defensibility:

Whichever you use, state it and show the histogram with the threshold marked. And recognise the choice is not purely technical: a low threshold catches more real change and more false alarms. Which error is worse depends on what happens next — a forest-loss alert that triggers a ranger patrol tolerates false alarms far better than one that triggers a prosecution.

Time-series algorithms

Using two dates throws away everything in between. The modern approach fits a model to a pixel's whole history and looks for departures.

All three share one advantage: because they use the seasonal cycle as their baseline, a dry year does not read as deforestation. Bi-temporal methods have no way to make that distinction.

Detection is not attribution

Your algorithm found a break. It does not know whether the cause was logging, fire, charcoal burning, drought, disease, or a cloud your mask missed. Attribution requires additional evidence — the shape and edge geometry of the patch, the spectral direction of the change, the season, the recovery trajectory afterwards, ancillary data, and often a field visit or a conversation with someone local.

Reporting "500 ha of deforestation" when you have detected 500 ha of negative NBR change is a claim your data does not support, and in a context where such claims affect land-use policy and individual liability, the distinction matters a great deal.

Practical 7 — Urban expansion around Nairobi and Thika
Core everyone
  1. Build same-season composites for 2000, 2010 and 2025 from harmonised Landsat and Sentinel-2. Verify the dates and confirm co-registration.
  2. Compute NDBI and NDVI for each date. Difference 2000→2025 for both.
  3. Plot the histogram of the NDBI difference. Is it bimodal? Set a threshold by two different methods and map both results.
  4. Take an independent sample of 100 points, interpret each as changed or unchanged against high-resolution imagery, and use it to choose a defensible threshold. Report how much it differs from the statistical one.
  5. Classify all three dates and perform post-classification comparison. Build the full transition matrix.
  6. Compare the direct-differencing and post-classification change maps. Quantify the disagreement and explain it in terms of the compounding argument above.
  7. Validate the change map itself with its own stratified sample — changed and unchanged strata — and report user's and producer's accuracy for the change class.
  8. Report the area of new built-up land with a confidence interval, using week 6's method.
Extension if you have time
  1. Run LandTrendr over the Mau forest complex and map the year of disturbance. Compare against the published record.
  2. Implement change vector analysis and use the direction of the vector to separate two different kinds of change.
Self-test
  1. Name three conditions two images must satisfy before you difference them.
  2. Two maps at 85% accuracy, 5% real change. Roughly what fraction of flagged change is genuine, and why?
  3. When is post-classification comparison the right method despite its weakness?
  4. Why do time-series algorithms avoid mistaking a dry year for deforestation?
  5. Distinguish detection from attribution, with an example where the difference has consequences.

Reading. Kennedy et al. (2010) on LandTrendr, RSE 114. Zhu & Woodcock (2014) on CCDC, RSE 144. Verbesselt et al. (2010) on BFAST, RSE 114. Olofsson et al. (2014) again — its area-estimation guidance applies with particular force to change.

08

Week eight

Radar — seeing through cloud

By the end of this week you should be able to
  • Explain why SAR works when optical sensing does not
  • State what controls backscatter and read a backscatter image
  • Recognise layover, foreshortening and shadow
  • Interpret polarisation and the VV/VH ratio
  • Map flood extent from Sentinel-1

The problem SAR solves

Optical remote sensing fails under cloud, and much of Kenya is cloudy exactly when the interesting things happen. During the long rains, a whole month can pass without a usable Sentinel-2 observation over the highlands — and floods, planting, and rapid vegetation change all occur in precisely that window. Optical sensing is systematically blind to the events it is most needed for.

Synthetic Aperture Radar supplies its own illumination at microwave wavelengths that pass through cloud unimpeded. Sentinel-1 operates in C-band at 5.405 GHz — a wavelength of about 5.6 cm — and images regardless of cloud, day or night, every six to twelve days.

What backscatter measures

SAR does not measure reflected sunlight. It transmits a pulse and measures how much energy returns, reported as the backscatter coefficient σ⁰ in decibels. Three surface properties dominate:

Geometry, and its three distortions

SAR slant range layover front slope shadow no return at all back slope stretched summit returns first Radar orders returns by distance from the sensor, not by position on the ground
Everything odd about SAR imagery follows from one fact: the sensor looks sideways and sorts returns by slant range. Where a slope faces the sensor more steeply than the incidence angle, the summit is nearer than the base, so the mountain top appears to fall over toward the sensor — layover. A gentler facing slope is compressed rather than inverted — foreshortening. Behind the hill, nothing is illuminated at all — shadow, which contains no information whatever and must never be interpreted as a dark surface.

The practical consequence: in mountainous terrain — the Aberdares, Mount Kenya, the Rift escarpments — parts of every SAR scene are unusable, and a shadow region is not water even though it is as dark as water. Terrain-corrected products handle the geometry, but they cannot invent data where none was collected. Mask layover and shadow explicitly; Earth Engine's Sentinel-1 collection is terrain-corrected but does not mask them for you.

Polarisation

Sentinel-1 in its standard mode transmits vertically and receives both vertically (VV) and horizontally (VH). The pair carries structural information:

Speckle

SAR images have a grainy salt-and-pepper texture that is not noise in the ordinary sense. It arises from coherent interference among the many scatterers within a resolution cell, and it is deterministic — image the same scene twice under identical geometry and you get the same speckle pattern.

It still has to be reduced before most analysis. Multi-looking averages several independent looks, trading resolution for radiometric quality. Adaptive filters — Lee, refined Lee, Frost — smooth homogeneous areas while preserving edges. For a threshold-based flood map, a modest speckle filter makes the difference between a clean water mask and a shredded one. Do not over-filter: aggressive smoothing removes small water bodies along with the speckle.

JavaScript · Earth Engine
// Sentinel-1 GRD in Earth Engine arrives terrain-corrected and in dB.
var s1 = ee.ImageCollection('COPERNICUS/S1_GRD')
  .filterBounds(aoi)
  .filter(ee.Filter.eq('instrumentMode', 'IW'))
  .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
  .filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING'))  // keep geometry constant
  .select('VV');

var before = s1.filterDate('2026-03-01', '2026-03-25').median();
var after  = s1.filterDate('2026-04-20', '2026-04-30').median();

// speckle reduction — enough to stabilise the threshold, not enough to erase ponds
var smooth = function(img) {
  return img.focalMedian(50, 'circle', 'meters');
};

// water is specular: very low backscatter. Threshold, then remove pre-existing water.
var THRESH = -16;                          // dB — CHECK THIS against your own histogram
var waterAfter  = smooth(after ).lt(THRESH);
var waterBefore = smooth(before).lt(THRESH);
var flooded = waterAfter.and(waterBefore.not());

// exclude permanent water and steep terrain, where shadow mimics water
var jrc   = ee.Image('JRC/GSW1_4/GlobalSurfaceWater').select('seasonality');
var slope = ee.Terrain.slope(ee.Image('USGS/SRTMGL1_003'));
flooded = flooded.updateMask(jrc.lt(5)).updateMask(slope.lt(5));

var areaKm2 = flooded.multiply(ee.Image.pixelArea()).divide(1e6)
  .reduceRegion({reducer: ee.Reducer.sum(), geometry: aoi, scale: 20, maxPixels: 1e10});
print('flooded area, km²:', areaKm2);
Do not accept a threshold from a textbook

The −16 dB above is a starting point, not a constant. The right threshold depends on the incidence angle, the wind — a roughened water surface backscatters far more than a calm one and can disappear from your mask entirely — the season, and your speckle filtering. Plot the histogram of your own scene: open water should appear as a distinct low mode, and the threshold belongs in the trough.

And mask the confusers explicitly, as the code above does. Radar shadow, dry smooth sand, and asphalt all produce low backscatter and will otherwise be mapped as flood. A flood map that includes the runway at Wilson Airport has not been checked.

Practical 8 — Map a real flood
Core everyone
  1. Choose a documented Kenyan flood event — Budalangi, Garissa on the Tana, or Tana River county. Find its dates.
  2. Attempt an optical assessment first: how many usable cloud-free Sentinel-2 scenes exist in the two weeks after onset? Record the answer. It is the argument for this entire week.
  3. Build before and after Sentinel-1 VV composites. Keep the orbit direction constant and say why that matters.
  4. Display both. Identify permanent water, and describe what the flooded area looks like relative to its surroundings.
  5. Plot the VV histogram for both dates. Identify the water mode and choose your threshold from the trough, not from these notes.
  6. Map flood extent as newly-water. Mask permanent water using the JRC dataset and steep slopes.
  7. Report the flooded area in km². Overlay settlements and estimate exposed population, stating your assumptions.
  8. Validate against any available optical imagery, news reports or official figures. Where does your map disagree, and why?
Extension if you have time
  1. Add VH and the VH/VV ratio and attempt to separate flooded vegetation — which double-bounces and appears bright, not dark — from open water.
  2. Process a Sentinel-1 SLC pair in SNAP through to an interferogram, and interpret the coherence image over the flooded area.
Self-test
  1. Why is open water dark in SAR?
  2. Explain layover in one sentence, and say why it cannot be corrected by better processing.
  3. What does VH respond to that VV does not?
  4. Why is speckle not noise in the usual sense, and why must it still be reduced?
  5. Name three surfaces that could be falsely mapped as flood by a simple threshold, and how you would exclude each.

Reading. Richards, Remote Sensing with Imaging Radar (2009), ch. 1–3. The ESA Sentinel-1 user guide. Cardille et al., part F3 on SAR in Earth Engine. Moreira et al. (2013), "A tutorial on synthetic aperture radar", IEEE GRSM 1(1) — the best single introduction available.

09

Week nine

Thermal, water and atmosphere

By the end of this week you should be able to
  • Retrieve land surface temperature and state its assumptions
  • Quantify an urban heat island against a defensible rural reference
  • Describe how evapotranspiration is estimated from imagery
  • Use the JRC surface water dataset to analyse water body change
  • Read a Sentinel-5P atmospheric product and state its limitations

Land surface temperature

A thermal sensor measures emitted radiance, from which brightness temperature follows by inverting the Planck function. Converting that to the actual surface temperature requires emissivity — how efficiently a surface radiates compared with a perfect black body. Water is near 0.99, dense vegetation about 0.98, bare soil 0.92–0.96, and concrete and metal roofing lower still and highly variable.

Get emissivity wrong and your temperature is wrong: an error of 0.01 in emissivity translates to roughly 0.5–0.7 K. Most operational approaches estimate emissivity from NDVI via a vegetation-cover fraction, which works reasonably over vegetated land and poorly over the mixed urban surfaces where you most want the answer.

Note also what LST is and is not. It is the radiometric temperature of the surface — the roof, the road, the leaf canopy — not the air temperature a person experiences. On a sunny afternoon a tarmac surface can read 20 K above the air temperature two metres above it. Reporting LST as "temperature" without qualification is a real misrepresentation, and it appears in published work.

Worked example — LST from Landsat Collection 2

Landsat Collection 2 Level-2 provides a surface temperature band, ST_B10, as scaled integers. The documented conversion is kelvin = DN × 0.00341802 + 149.0.

A pixel over the Nairobi CBD reads DN = 44,000.

44,000 × 0.00341802 = 150.39
150.39 + 149.0 = 299.39 K
299.39 − 273.15 = 26.2 °C

Now the two ways students get this wrong. Forget the scale entirely and you report 44,000 — obviously absurd, and therefore harmless. Subtract 273.15 from the raw DN and you report 43,727 °C, equally absurd. But apply the multiplier and forget the 149.0 offset and you report 150.4 K, or −122.8 °C — a number that is wrong by 150 degrees and yet looks like a temperature. That third error is the dangerous one, because it survives a careless glance.

Always sanity-check against physical reality. Land surface temperature in Kenya should fall roughly between 10 °C and 60 °C. Anything outside that means a scaling error, not a discovery.

Urban heat islands

Cities are hotter than their surroundings: dark impervious surfaces absorb more shortwave radiation, there is little vegetation to cool by transpiration, and the urban geometry traps longwave radiation. The measurement is straightforward and the design decisions are where the difficulty lies.

Evapotranspiration

ET is the largest term in the water balance of most Kenyan catchments and the one hardest to measure directly. Satellite methods exploit the surface energy balance: net radiation is partitioned between ground heat flux, sensible heat and latent heat, and latent heat is evapotranspiration. Because a well-watered surface stays cool through transpiration while a water-stressed one heats up, surface temperature carries the signal.

The applications are concrete and Kenyan: irrigation scheduling in the Rift Valley horticulture schemes, water allocation in the Ewaso Ng'iro basin, and crop water productivity assessment. FAO's WaPOR portal publishes ET for Africa and is worth knowing.

Surface water

The JRC Global Surface Water dataset is a finished product built from 40 years of Landsat, and it saves you an enormous amount of work. It provides, at 30 m: occurrence (the percentage of observations in which each pixel was water), change intensity, seasonality, recurrence, and transitions between permanent, seasonal and no water.

Kenya offers unusually good material here. The Rift Valley lakes — Baringo, Nakuru, Naivasha, Turkana — have undergone dramatic and well-documented level changes, with substantial rises since about 2010 that displaced settlements and flooded infrastructure. The occurrence layer distinguishes the historically permanent lake from the recently inundated margin immediately, and that distinction is exactly what a planning question needs.

Atmospheric composition

Sentinel-5P's TROPOMI instrument measures NO₂, CO, SO₂, CH₄, O₃ and aerosol index daily at roughly 5.5 × 3.5 km. Three limitations to state whenever you use it:

Within those limits it is genuinely useful: NO₂ over Nairobi shows a clear weekday–weekend cycle that tracks traffic, and the aerosol index picks up biomass burning across the region during the dry season.

Practical 9 — Nairobi's heat island, and a Rift Valley lake
Core everyone
  1. Retrieve LST over Nairobi from Landsat Collection 2 for a cloud-free dry-season date. Apply the scale and offset correctly and sanity-check the range before doing anything else.
  2. Map LST with a sensible palette. Identify the hottest and coolest areas and relate them to what is on the ground.
  3. Compute NDVI for the same date. Plot LST against NDVI for 1,000 random points, fit a line, and report the slope in °C per unit NDVI.
  4. Define an urban core and a rural reference. Justify your reference in three sentences. Compute the heat island magnitude.
  5. Test the sensitivity: recompute with two different rural references and report how much the answer moves. This is the honest part of the exercise.
  6. Load the JRC surface water dataset for a Rift Valley lake. Map occurrence and seasonality, and quantify the area that changed from land to water since 2010.
  7. Overlay settlements and roads on the newly inundated area and estimate what was affected.
  8. Load Sentinel-5P NO₂ over Nairobi. Compare weekday and weekend means over one year and report the difference with its uncertainty.
Extension if you have time
  1. Compute LST for the same date across five years and test whether the heat island is intensifying. State clearly why five mid-morning observations are weak evidence either way.
  2. Compute an SSEBop-style ET estimate for an irrigation scheme and compare with published crop water requirements.
Self-test
  1. Why does surface temperature retrieval need emissivity, and what does a 0.01 error cost you?
  2. Distinguish land surface temperature from air temperature, with a case where confusing them matters.
  3. What makes a rural reference defensible in a heat island study?
  4. Why does Landsat overpass timing limit what you can say about urban heat?
  5. Give three limitations of Sentinel-5P NO₂ that belong in any methods section using it.

Reading. The USGS Landsat Collection 2 Level-2 Science Product guide, for the scale factors and the surface temperature algorithm. Pekel et al. (2016), "High-resolution mapping of global surface water and its long-term changes", Nature 540. Senay et al. (2013) on SSEBop, JAWRA 49(3).

10

Week ten · CAT 2 this week

Ground truth, uncertainty and ethics

By the end of this week you should be able to
  • Design a field campaign that actually supports your analysis
  • Choose a positioning method matched to your pixel size
  • Handle the mismatch between a field plot and a pixel
  • Communicate uncertainty to someone who must act on it
  • Identify the ethical questions your project raises, and answer them

"Ground truth" is a misleading phrase

Field data is not truth. It is another observation, with its own errors: the GPS fix, the observer's judgement, the date it was collected, the definition of the class in the observer's head. Two competent field workers will disagree on perhaps 5–15% of land-cover calls at boundaries and in mixed cover — and that disagreement rate is a ceiling on any accuracy you can demonstrate. If your interpreters agree only 90% of the time, you cannot demonstrate 95% map accuracy, because you have no instrument capable of measuring it.

The honest term is reference data, and the honest practice is to report how it was collected, by whom, when, and with what agreement between interpreters.

Designing a field campaign

Positioning

MethodAccuracyAdequate for
Smartphone GNSS3–5 m30 m Landsat pixels, and 10 m Sentinel-2 in homogeneous cover
Mapping-grade receiver0.5–2 m10 m pixels near boundaries; small features
RTK / PPK~2 cmVery-high-resolution validation; individual trees; structures

Match the method to the pixel. A 3 m smartphone error on a 30 m pixel is a tenth of a pixel and irrelevant; the same error on 3 m PlanetScope imagery puts you in the wrong pixel entirely. And record the accuracy the device reports for each point — under canopy or between tall buildings it degrades sharply, and knowing which points are weak lets you weight or exclude them later.

Scale mismatch

A Landsat pixel is 30 × 30 m — 900 m². A typical field plot might be 10 × 10 m, about one ninth of that. You are comparing a measurement of part of a pixel against a value integrated over all of it, and in a smallholder landscape with fields smaller than a pixel, most pixels are mixtures.

This is not a solved problem, but it is a manageable one. Sample in patches large enough to contain a full pixel with margin; record the fractional composition of a plot rather than a single label where you can; or move to a finer sensor when the landscape demands it. Above all, state the mismatch in your methods — a reader needs to know that your 30 m map was validated with 10 m plots in a landscape of half-hectare fields.

Communicating uncertainty

A county planner does not want a confusion matrix. They want to know whether they can act on the map. Translate:

Ethics

Earth observation watches people who did not consent and mostly do not know. That is worth sitting with rather than passing over, and four questions arise directly in the projects you will build.

Who is being observed, and who benefits?

Mapping informal settlements can direct services to people who need them, and it can support eviction. The imagery is identical; the difference is who commissioned the map and what happens next. You are entitled to ask that question, and in professional practice you should.

Resolution is a policy choice

At 30 m you map settlement extent. At 30 cm you map individual structures and can count them. The finer product is more useful and more dangerous, and choosing the resolution is choosing what becomes possible. Default to the coarsest resolution that answers the question.

Accuracy has asymmetric consequences

A crop map that under-detects smallholder plots may exclude those farmers from an input subsidy. A forest-loss map with high commission error may implicate the wrong landholder. When errors fall unevenly on people with different power to contest them, the class-level accuracies in week 6 are not a technical annex — they are the fairness analysis.

Open data cuts both ways

Publishing your map openly is usually right: it enables verification, reuse, and accountability. But a map of water points during a resource conflict, or of a wildlife population under poaching pressure, is a targeting aid. Kenya has open data commitments and a Data Protection Act; know the framework, and know that legal compliance is a floor rather than the whole of the question.

Practical 10 — Field validation, and an ethics statement
Core everyone
  1. Design a field form in KoboToolbox or ODK: location, land cover class, homogeneity, confidence, photograph, observer, timestamp.
  2. Draw 40 stratified random points on or near campus. Plan an efficient route that does not degrade into a road transect, and say how you protected against that.
  3. Collect the data in the field. Record the GNSS accuracy at every point.
  4. Have a classmate independently classify 15 of your points. Compute your agreement rate. Report it, and state explicitly what ceiling it places on any accuracy you can claim.
  5. Compare your field observations against your week 5 classification. Which classes were confused, and does it match what the week 6 assessment predicted?
  6. Quantify the scale mismatch: for each point, record what fraction of the surrounding 10 m and 30 m cell shares its class. How many points are unambiguous at each resolution?
  7. Write a half-page summary of your map's accuracy addressed to a county planning officer. No confusion matrix, no jargon, and it must be actionable.
  8. Write a one-page ethics statement for your project: who is observed, who benefits, what resolution you chose and why, who could be harmed by an error or by the data leaking, and what you did about it.
Extension if you have time
  1. Repeat a subset of points with a mapping-grade receiver and quantify how much positional error changed the extracted pixel values.
  2. Analyse your class-level accuracies for asymmetric consequences: if this map allocated a subsidy, which group would be disadvantaged by its errors?
Self-test
  1. Why is "ground truth" a misleading term, and what limit does interpreter disagreement place on reported accuracy?
  2. Why is road-accessible sampling a problem, and what do you do if access forces it?
  3. A smartphone GNSS gives 4 m accuracy. Adequate for Sentinel-2? For PlanetScope? Explain.
  4. Rewrite "producer's accuracy for built-up is 62%" for a county planner.
  5. Give a case where the ethically correct choice is a coarser product than you could produce.

Reading. Stehman & Foody (2019), RSE 231, on reference data quality. Kenya's Data Protection Act (2019). Kwan (2016) on algorithmic geographies and the ethics of geospatial data, Annals of the AAG 106(2).

11

Week eleven

Applied projects and synthesis

By the end of this week you should be able to
  • Choose a method for an unfamiliar applied problem and defend the choice
  • State what an operational service needs beyond a good map
  • Present and defend an analysis under questioning

Where Earth observation actually gets used

Agriculture and food security. Crop type and area, condition monitoring, yield forecasting, drought early warning. In Kenya this runs through NDMA's bulletins and FEWS NET, and it is directly operational — it triggers resource allocation. The hard parts are smallholder field sizes below the pixel and persistent cloud during the growing season, which is why week 8 exists.

Forestry and carbon. Deforestation and degradation monitoring, REDD+ measurement, reporting and verification. Carbon finance requires accuracy assessments that meet published standards — which is why week 6 is the centre of this unit rather than an afterthought. A forest-change map without a probability-sample accuracy assessment and bias-adjusted areas has no financial value at all.

Urban planning. Growth monitoring, informal settlement mapping, heat islands, green space, infrastructure planning. Kenyan cities are growing faster than surveys can track, which makes EO the only practical instrument, and raises every question in week 10.

Disaster response. Flood extent, landslides, fire, drought. The International Charter "Space and Major Disasters" provides free tasking after a declared disaster; response timelines are hours to days, which shapes every methodological choice toward the robust and the fast.

Water resources. Surface water extent, lake levels, evapotranspiration, irrigation monitoring. Kenya's Rift Valley lake rises since 2010 are a well-documented case with real displacement consequences.

From a map to a service

Most student projects produce a map. Operational use needs several things a map does not have, and knowing this is part of what distinguishes a graduate:

Practical 11 — Project presentation and defence
Format

Ten minutes of presentation, five minutes of questions, in pairs. The reasoning behind your decisions is what is examined — not the appearance of your maps.

You must be able to answer
  • Why this sensor? What did you give up by choosing it?
  • Show me your validation sample. How was it drawn, and when was it interpreted relative to the map?
  • What is your worst class, and is it an omission or a commission problem?
  • Your map says this area is X km². What is the bias-adjusted estimate and its confidence interval?
  • Show me where on this map you would not trust the result.
  • Who could be harmed by an error in this map, and does the error fall evenly?
  • What would you do differently with another month?
Deliverable

A report of no more than eight pages: the question, the data and why, the preprocessing, the method, the validation design and full accuracy assessment with bias-adjusted areas and confidence intervals, the limitations, and the ethics statement. Submit your Earth Engine script — it must run from a clean account. Include the AI-use declaration: which tools, which prompts, what they produced, what you changed and why.

What to carry out of this unit

The platforms will change. Earth Engine is roughly a decade old; the sensors you have used will be superseded; the classifiers will be replaced by things that work better and explain themselves less. What does not change is the structure of the problem: an instrument measuring reflected or emitted radiation, an atmosphere in the way, a pixel that averages a piece of a world that does not respect pixel boundaries, and a person who will act on the map.

And one habit, which is the reason week 6 sits at the centre of this reader. A classified image is not a map until it has been validated with a probability sample. The classification is the easy part, and it now takes fifteen lines of code that a language model will write for you in seconds. The validation is the part that makes the result mean something — and it is the part nobody can do for you, because it requires going and looking.

Reading. Cardille et al., parts A1–A3 on applications. Olofsson et al. (2014) once more. The GFOI Methods and Guidance Documentation for the REDD+ reporting context.

A

Appendix A

Earth Engine self-check

The Code Editor is JavaScript. If you have taken Web Mapping you already have the language; if not, the subset you need is small. Work through these before week 2, without help of any kind — no notes, no search, no AI assistant. The point is to find out what you have.

Scoring yourself honestly
  • Comfortable with all ten — go straight to the Extension tasks.
  • Fine on 1–6, unsure on 7–10 — the usual position. Tasks 7–10 are the client/server concept, and week 2 teaches it properly. Read the Earth Engine "Client vs Server" guide before the practical.
  • Struggling with 1–6 — that is the JavaScript itself, and it is a weekend of work with the resources below. Do it before week 2 rather than during it, and come and see me in week 1.

The ten tasks

  1. Declare a variable holding the string "Kiambu" and print it to the console.
  2. Write a function that takes a number and returns it multiplied by 0.0001. Call it with 4500.
  3. Declare an object with properties name, zoom and bands (an array of three strings). Print the second band.
  4. Explain what method chaining does in collection.filterDate(a, b).filterBounds(g).median(), and in what order the operations apply.
  5. Write a template literal producing "Found 42 scenes" from a variable n.
  6. Explain the difference between var x = 5; and var x = ee.Number(5);.
  7. Why does the following not work, and what should you write instead?
    if (image.get('CLOUD_COVER') < 20) { print('clear'); }
  8. Why does this loop fail?
    for (var i = 0; i < collection.size(); i++) { ... }
  9. Rewrite this using map(): "add an NDVI band to every image in a collection".
  10. You call image.reduceRegion({reducer: ee.Reducer.mean(), geometry: aoi}) and get an unexpected number. What did you omit?

Tasks 1–5 are ordinary JavaScript. Tasks 6–10 are the Earth Engine concept, and getting them wrong now is completely normal — but getting them wrong in week 5 will cost you a practical.

Five things that will actually bite you

Where to go

On using AI assistants in this unit

They are permitted, and declared. They are genuinely good at Earth Engine boilerplate — a cloud mask, a composite, an export block — and using them for that is sensible.

Know how they fail here, because the failures are specific and they all produce output that looks completely normal. Generated Earth Engine code routinely: omits or misapplies scale factors, so NDVI comes out in the thousands; reports accuracy from training data; quotes kappa as the headline statistic; compares NDVI across years with no cloud masking; uses a fixed backscatter threshold copied from a tutorial written for a different climate; and computes an area from a classified map with no bias adjustment and no confidence interval.

Every one of those is an error this unit trains you to catch, and every one produces a convincing map. The tool is an asset only if you can catch them — and the way you learn to catch them is by doing week 6 by hand, once, properly.

B

Appendix B

Bands, scale factors, formulas

This sheet is supplied in the CATs and the final examination.

Sentinel-2 MSI

BandCentreRes.Use
B2490 nm10 mBlue — aerosol correction in EVI, true colour
B3560 nm10 mGreen — water indices, true colour
B4665 nm10 mRed — chlorophyll absorption
B5–B7705–783 nm20 mRed edge — crop stress, species discrimination
B8842 nm10 mNIR — the vegetation workhorse
B8A865 nm20 mNarrow NIR
B111610 nm20 mSWIR — moisture, built-up, water
B122190 nm20 mSWIR — burn severity, minerals
SCL—20 mScene classification: 3 shadow, 8/9 cloud, 10 cirrus, 11 snow

Landsat 8/9 equivalents: B2 blue, B3 green, B4 red, B5 NIR, B6 SWIR1, B7 SWIR2, B10 thermal. Note that NIR is B5 on Landsat and B8 on Sentinel-2 — the most common band-mixing error there is.

Scale factors — check these every time

ProductConversion
Sentinel-2 L2A reflectanceDN / 10000
Landsat C2 L2 reflectanceDN × 0.0000275 − 0.2
Landsat C2 L2 surface temperatureDN × 0.00341802 + 149.0 → K
Sentinel-1 GRD in Earth Enginealready dB — do not rescale

Sanity checks: surface reflectance ∈ [0, 1]; NDVI ∈ [−1, 1]; LST in Kenya ∈ roughly [10, 60] °C; Sentinel-1 VV σ⁰ ∈ roughly [−25, 0] dB.

Indices, in Sentinel-2 bands

NDVI = (B8 − B4) / (B8 + B4) EVI = 2.5 (B8 − B4) / (B8 + 6·B4 − 7.5·B2 + 1) SAVI = 1.5 (B8 − B4) / (B8 + B4 + 0.5) NDMI = (B8 − B11) / (B8 + B11)  canopy moisture NDWI = (B3 − B8) / (B3 + B8)  open water — McFeeters MNDWI = (B3 − B11) / (B3 + B11)  water, better in cities NDBI = (B11 − B8) / (B11 + B8)  built-up NBR = (B8 − B12) / (B8 + B12)    dNBR = NBRpre − NBRpost

Accuracy and area

p̂ij = Wi · nij / ni·   area-weight a stratified matrix OA = ∑j p̂jj    UAi = p̂ii / p̂i·    PAj = p̂jj / p̂·j S(p̂·k) = √( ∑i Wi2 (nik/ni·)(1 − nik/ni·) / (ni· − 1) ) n = ( ∑ WiSi / S(Ô) )2,   Si = √(Ui(1−Ui))  sample size VCI = 100 (NDVI − NDVImin) / (NDVImax − NDVImin)

Earth Engine, minimum viable

JavaScript · Earth Engine
ee.ImageCollection(id).filterBounds(g).filterDate(a, b)
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
collection.map(fn) · collection.median() · collection.qualityMosaic('NDVI')
image.select(bands).rename(names) · image.updateMask(mask)
image.normalizedDifference(['B8','B4'])       // any normalised difference
image.addBands(other) · image.clip(geom)
image.reduceRegion({reducer, geometry, scale, maxPixels})   // scale is REQUIRED
image.reduceRegions({collection, reducer, scale})
image.sampleRegions({collection, properties, scale})
image.stratifiedSample({numPoints, classBand, region, scale})
ee.Classifier.smileRandomForest(n).train({features, classProperty, inputProperties})
classified.errorMatrix('reference', 'classification')
ee.Image.pixelArea()                          // for area sums
Export.image.toDrive({image, region, scale, crs, maxPixels})
Export.table.toDrive({collection, fileFormat: 'CSV'})
C

Appendix C

Datasets

CollectionEarth Engine IDUsed in
Sentinel-2 L2A, harmonisedCOPERNICUS/S2_SR_HARMONIZED1–7, 9
Sentinel-2 cloud probabilityCOPERNICUS/S2_CLOUD_PROBABILITY2
Landsat 9 C2 L2LANDSAT/LC09/C02/T1_L23, 7, 9
Landsat 8 C2 L2LANDSAT/LC08/C02/T1_L23, 7, 9
Landsat 5 TM C2 L2LANDSAT/LT05/C02/T1_L27
Sentinel-1 GRDCOPERNICUS/S1_GRD8
Sentinel-5P NO₂COPERNICUS/S5P/OFFL/L3_NO29
SRTM 30 m DEMUSGS/SRTMGL1_0033, 5, 8, 9
JRC Global Surface WaterJRC/GSW1_4/GlobalSurfaceWater8, 9
CHIRPS daily rainfallUCSB-CHG/CHIRPS/DAILY4
MODIS vegetation indicesMODIS/061/MOD13Q14
Administrative boundariesFAO/GAUL/2015/level2all
WorldPop populationWorldPop/GP/100m/pop8, 9

Supplied locally

Earth Engine collections carry the licence terms of their originating agency — Copernicus data is free and open with attribution; Landsat is public domain; CHIRPS and WorldPop have their own citation requirements. Cite the dataset, not just the platform, and cite Gorelick et al. (2017) for Earth Engine itself.

EGS 2403 Remote Sensing Applications · Course reader, edition 1.0 · Jomo Kenyatta University of Agriculture and Technology · September 2026. Corrections and suggestions are welcome and will be credited in the next edition.

D

Appendix D

The same analysis, without Google

This unit teaches Earth Engine. Earth Engine is a product, owned by one company, offered under terms that company sets and can change. It is reasonable to ask what happens to your skills if it does — and the honest answer is worth more to you than the platform is.

The risk is not hypothetical

Google has changed the terms of geospatial products before, with real consequences for people who had built on them. In 2018 the Google Maps Platform moved to a pay-as-you-go model with a monthly credit; a great deal of academic, NGO and small-business mapping that had run free for years suddenly carried a bill, and some of it simply stopped. Earth Engine itself moved to a commercial footing in 2023, with paid tiers for commercial users alongside continued free access for research, education and non-profit work. Free academic access remains, and there is no signal that it is going away — but it is a policy, not a guarantee, and policies are revised.

Set against that: the value is real. In a lab where the connection is unreliable and the machines are modest, a platform that transfers no imagery at all and does the computation elsewhere is not a convenience, it is the difference between doing this work and not doing it. That is why this unit teaches it.

What actually transfers

Nearly everything. Strip away the API and the workflow in every chapter of this reader is the same five operations:

  1. Search an archive by space, time and metadata
  2. Mask the pixels you cannot use
  3. Reduce many observations to one composite, or to a time series
  4. Compute band arithmetic and derived features
  5. Classify, validate, and estimate area with an interval

None of that is Google's. The sensor physics of week 1, the index choices of week 4, the training design of week 5, the whole of week 6, the change-detection logic of week 7, the radar geometry of week 8 — all of it is platform-independent, and all of it is the part that takes a semester to learn. What is Google-specific is the syntax, and syntax is the cheap part. A competent analyst moves between these environments in a week.

The open alternative, named concretely

The cloud-native geospatial stack does the same job with open standards and open tools:

Side by side

Week 2's central script, in both environments. Read them together — the operations line up one for one.

JavaScript · Earth Engine
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(aoi)
  .filterDate('2025-01-01', '2025-12-31')
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40));

var masked = s2.map(function(img) {
  var scl = img.select('SCL');
  var clear = scl.neq(3).and(scl.neq(8)).and(scl.neq(9))
                 .and(scl.neq(10)).and(scl.neq(11));
  return img.updateMask(clear).divide(10000);
});

var composite = masked.median();
var ndvi = composite.normalizedDifference(['B8', 'B4']);

Export.image.toDrive({image: ndvi, region: aoi, scale: 10});
Python · STAC + COG
import pystac_client, odc.stac, rioxarray

bbox = [36.6, -1.3, 37.2, -0.9]        # Kiambu

# 1. SEARCH — STAC replaces ee.ImageCollection().filter()
catalog = pystac_client.Client.open("https://earth-search.aws.element84.com/v1")
items = catalog.search(
    collections=["sentinel-2-l2a"],
    bbox=bbox,
    datetime="2025-01-01/2025-12-31",
    query={"eo:cloud_cover": {"lt": 40}},
).item_collection()
print(f"{len(items)} scenes found")

# 2. LOAD — lazily, into an xarray cube backed by dask.
#    Nothing is downloaded yet: COG range requests happen on demand.
ds = odc.stac.load(
    items,
    bands=["red", "nir", "scl"],
    resolution=10,
    bbox=bbox,
    chunks={"x": 2048, "y": 2048},
)

# 3. MASK — the same SCL classes as the Earth Engine version
clear = ~ds.scl.isin([3, 8, 9, 10, 11])
ds = ds.where(clear) / 10000          # the scale factor does not go away

# 4. COMPOSITE and 5. BAND MATH
composite = ds.median(dim="time")
ndvi = (composite.nir - composite.red) / (composite.nir + composite.red)

# only NOW does anything actually transfer or compute
ndvi.compute().rio.to_raster("ndvi_kiambu_2025.tif")

Two things to notice. The shape is identical — search, mask, composite, band maths, write — and both environments defer execution until you ask for a result, so the mental model transfers directly. And the scale factor is still there in the Python version, because it is a property of the Sentinel-2 product, not of the platform. Every trap in this reader is a trap in both stacks.

The honest comparison

Earth EngineSTAC + COG + Python
InstallNone — a browserA conda environment, and it will fight you once
ComputeGoogle's, free at academic scaleYours. A laptop handles a county; a country needs a cluster or a cloud bill
Data transferEffectively zeroRange requests — tens of MB for a county-year, not the gigabytes of full-scene download
CeilingContinental, casuallyBounded by your hardware or your budget
Reproducible in 2036If Google still offers it on these termsYes, if you pin your versions — the data and the tools are public
Who can revoke itGoogleNobody, in practice — though the hosting is still someone's cloud
Learning curveShallow, then the client/server wall in week 2Steeper at the start, no wall afterwards
EmployabilityWidely used in NGOs, agencies, researchIncreasingly the industry standard for production pipelines

Note the row that people forget: the open stack does not remove the dependency on someone's cloud, it moves it. Earth Search runs on AWS; the Planetary Computer is Microsoft's. What it removes is lock-in — your code is not written against a proprietary API, so switching provider is a changed URL rather than a rewrite.

The recommendation this unit follows. Learn Earth Engine, because in this environment it is what makes the work possible, and because a semester spent fighting installations is a semester not spent on accuracy assessment.

Then port one complete workflow out of it, deliberately, before you graduate. The port is what converts a platform skill into a transferable one — and having done it once, you will know that the answer to "what if Google changes the terms" is a week of work, not a lost career.

Reducing your exposure while you use it

Exercise D — Port it yourself

Do this once, at any point after week 4. It is the most valuable two hours in the unit for your career, and it is a legitimate project component.

  1. Install the open stack: conda install -c conda-forge pystac-client odc-stac xarray rioxarray dask matplotlib.
  2. Take your week 2 composite script and reproduce it in Python against Earth Search. Same area, same dates, same cloud filter, same SCL mask.
  3. Compute NDVI both ways and difference the two rasters. They will not be identical — record where and by how much, and work out why. Resampling, the exact masking, and reprojection all contribute.
  4. Record what each took: setup time, run time, and megabytes transferred. Report all three.
  5. Write half a page: which would you choose for a two-week consultancy mapping one county, and which for a five-year national monitoring programme that must still run after you leave? They are not the same answer, and the reasoning is the point.

Reading. The STAC specification and the pystac-client documentation. The Cloud-Native Geospatial Foundation's introductory material. The openEO API documentation and the Copernicus Data Space Ecosystem. Gorelick et al. (2017) for what Earth Engine set out to be, read alongside Google's current Earth Engine terms of service — the comparison is itself instructive.