Lab 6: Classifying the Salish Sea

Supervised and unsupervised land cover mapping with CART, Random Forest, and k-means

Author

Dr. Francisco Laso, ESCI 442/542 — Introduction to Remote Sensing

Lab 6 Module 6 — Machine Learning Classification
Readings due before lab: Campbell Ch. 12 — Image Classification  |  Cardille Ch. F6 — Interpreting an Image: Classification  |  Latifovic, Pouliot & Olthof (2017) — Remote Sensing 9:1098

Background

At the end of Lab 5 we produced a map of stressed land across the Salish Sea bioregion during the June 2021 heat dome — roughly 2,132 km² where both NDVI and NDWI dropped relative to the 2020 baseline. The map answered one question and immediately raised a sharper one. The threshold rule we wrote — dNDVI < t1 AND dNDWI < t2 — told us whether a pixel was stressed, but it told us nothing about what kind of land the stress fell on. Was it old-growth conifer forest in the Snoqualmie? Hayfields in the Skagit Valley? Suburban tree canopy in metropolitan Vancouver? The threshold rule cannot say. To answer questions like that, we need a different kind of map — one whose pixels carry category labels rather than magnitudes. That is the work of image classification.

Campbell Ch. 12.1 reframes a multispectral pixel as a vector in feature space — six reflective Landsat bands giving each pixel a position in six-dimensional space. The threshold rule from Lab 5 was, in that language, a hand-tuned two-feature axis-aligned classifier: a rule we wrote ourselves by inspecting two coastal-versus-inland points. Lab 6 generalizes that move, but does so in two stages. The walkthrough demonstrations (Parts 2–6) build a simple two-class classifier — land versus water — across the full Salish Sea bioregion. Two classes is the simplest non-trivial supervised problem, and land/water is spectrally so separable that it teaches the entire workflow cleanly, without the added noise of hard class boundaries. The Independent Exercise then scales the same workflow up to five labeled classes — Forest, Developed, Water, Herbaceous, and Snow/Ice — where the additional point-collection labor and the harder class boundaries make the cost of doing classification honestly observable in your own hands.

We have a real reference to compare our work against. The Salish Sea Atlas land cover map (Western Washington University) draws its land-cover layer from the North American Land Change Monitoring System (NALCMS) — a 30 m harmonized product produced collaboratively by Natural Resources Canada, the U.S. Geological Survey, and Mexican federal institutions. The Canadian methodology is documented in Latifovic, Pouliot & Olthof (2017), the paper assigned for this lab, who trained a Random Forest classifier — the same algorithm we will train here, several orders of magnitude smaller — on Landsat TM/ETM+ imagery and reported 76.6 % overall accuracy across 2,811 reference samples. That number gives us both a benchmark and a humility check: classification at continental scale by a national mapping agency, with Random Forest, lands in the mid-70s. We should not expect a five-class lab classifier built in two weeks to clear that bar — and Campbell Ch. 12.4 tells us why. The dominant factor in classification accuracy is not the algorithm but the quality and representativeness of the training data.

Following Cardille Ch. F6, the lab compares two supervised classifiers — Classification and Regression Trees (CART) and Random Forest — and one unsupervised classifier — k-means clustering. The comparison in Lab 6 is visual; rigorous accuracy assessment is reserved for Lab 7 (Cardille Ch. F7), which is its own conceptual chapter and its own lab. What we do here that prepares for Lab 7 is collect at least 50 training points per class across the five EX1 classes — at least 250 points total — and split them randomly into a 70 % training subset (used now) and a 30 % held-out reference subset that stays untouched until Lab 7. That reserved set becomes our internal validation reference in Lab 7 — the convenient, easy thing to do, and what most published student projects and many production reports actually do. Lab 7 will also have us collect a second reference dataset: an independent stratified random sample drawn over the classified map and labeled point-by-point against high-resolution imagery. The gap between the two accuracy numbers is part of what Lab 7 quantifies.


Note

Lab Objectives

By the end of this lab you will be able to:

  1. Distinguish informational classes from spectral classes and explain why one informational class typically maps to multiple spectral classes (Campbell Ch. 12.2)
  2. Decide when supervised and unsupervised classification are each appropriate, given a research question and the availability of labeled reference data (Campbell Ch. 12.3–12.4)
  3. Collect labeled training data using the Earth Engine Geometry Tools, attach a class property to each point, and merge per-class FeatureCollections into a single training set (Cardille Ch. F6 §6.2.1)
  4. Build a per-pixel feature table from imagery using sampleRegions, split it into training and reserved-holdout subsets, and articulate what each row of the table represents
  5. Train and apply CART and Random Forest classifiers and reason about why an ensemble (Random Forest) typically smooths the decision boundaries that a single tree (CART) commits to
  6. Apply k-means clustering and confront the cluster-relabeling problem — that unsupervised classes come out unlabeled by construction
  7. Scale a validated two-class workflow up to a five-class land-cover scheme, collecting representative training data for each class and critically interpreting the resulting maps

Warning

Before You Begin


Tip

First time using the Earth Engine Geometry Tools?

Part 2 of this lab introduces the Geometry Tools — the small toolbar at the upper-left of the map view, just above the basemap. We use them to drop labeled point markers across the bioregion. The relevant Earth Engine documentation is at the Geometry Tools guide; Cardille Ch. F6 §6.2.1 walks through the workflow one click at a time. When in doubt about the land-cover identity of a pixel, switch the basemap to satellite view (Map.setOptions('SATELLITE')) and zoom in.


Part 1 — From Threshold to Learned Rule

The Lab 5 threshold rule worked, but it required us to write the rule by hand. We picked two indices, picked two thresholds by visual inspection, and combined them with a logical AND. The classifier we will build in Lab 6 is structurally similar — it partitions the same kind of feature space into class regions — but the partitioning is learned from data. We hand the classifier a list of labeled examples (“this pixel is land, that pixel is water”) and the algorithm finds the partition that best separates the classes.

Campbell Ch. 12.1 introduces the conceptual move that makes this possible: a multispectral pixel is a vector. Six Landsat reflective bands place each pixel at a single point in a six-dimensional reflectance space. Lab 5’s NDVI and NDWI added two more dimensions. A classifier is, formally, a function from that eight-dimensional feature space to a discrete set of class labels. The simplest classifier (Lab 5’s threshold rule) cuts the space into rectangles aligned with the axes. CART cuts it into rectangles too, but the cuts are learned from the data instead of guessed. Random Forest averages many such CART partitions to get a smoother result. K-means doesn’t use labels at all — it finds clusters in the feature space and lets us decide later what they mean.

The walkthrough runs this workflow at its simplest: two classes — land and water. The conceptual plumbing is identical to the five-class case, but land/water separation is spectrally extreme (NDWI alone carries most of the signal), so every step in the workflow produces a readable result with a small number of training points. Once we have seen the pipeline succeed at binary scale, the Independent Exercise in Part 7 scales it to a five-class scheme where the harder class boundaries and the additional point-collection work become the lesson.

The other framing Campbell hammers in Ch. 12.2 is the distinction between informational classes (the categories we care about) and spectral classes (groups of pixels whose reflectance values cluster together). The two do not align one-to-one. A single informational class — Forest — typically contains multiple spectral classes: shaded canopy, sunlit canopy, mixed deciduous–conifer stands, recently burned regrowth. That one-to-many relationship is fine; classifiers handle it routinely. The dangerous case is the reverse — a single spectral class containing pixels from multiple informational classes (bare urban that looks identical to bare soil, or shaded water that looks identical to dark forest). That reverse case shows up as confusion in our final maps. Watching for it is one of the things Lab 7 will quantify; recognizing it now, while we are still collecting training data, is what gives us a chance to mitigate it.

Note

Script L6.0 covers:

The L6.0 introduction script orients us to the Lab 6 arc, prints the class table, and confirms that the Lab 5 baseline composite loads correctly as the classification target.


Part 2 — Collecting Walkthrough Training Data (Land and Water)

This part introduces the Earth Engine Geometry Tools — a new workflow that has not appeared in any prior ESCI 442 lab. The mechanics are walked through one click at a time. Once we have used the tool here, every subsequent lab will assume it.

How the Geometry Tools work

The Geometry Tools toolbar sits at the upper-left of the map panel in the Code Editor, just above the basemap. The marker icon (a teardrop pin) lets us click to drop point geometries onto the map. Each click produces one Feature — a point with no attributes attached. To turn that group of clicks into a FeatureCollection with class labels attached, we do two things in the import configuration dialog: rename the import to a meaningful variable name (e.g., land), set the import type to FeatureCollection, and add a + property called class with the integer class code. Every point dropped into that import then carries that class value automatically.

What we collect for the walkthrough

Script L6.1 sets up two class-specific imports — land (class = 0) and water (class = 1) — and asks us to drop 15–20 points into each. The points should be distributed across the bioregion, not concentrated in one area. Drop them well inside homogeneous regions: avoid shorelines, cloud-shadow remnants, and any pixel where the satellite basemap looks ambiguous.

Code Class What to look for in the basemap
0 Land Any non-water surface — forested slopes, urban areas, farmland, alpine terrain. Avoid the water’s edge
1 Water Open salt and fresh water. Strait of Georgia, Haro Strait, Puget Sound, larger lakes (Whatcom, Sammamish, Cowichan). Stay well away from shorelines

The figure below shows a completed walkthrough training set. Points are well-distributed across the bioregion — island interiors and mainland slopes for land, deep-channel pixels for water — and avoid mixed-pixel boundaries along coastlines.

Walkthrough training points for the two-class land/water classification — approximately 15–20 points per class, spread across the Salish Sea bioregion.

Merging the two imports

Once each class has 15–20 points, the script merges the two FeatureCollections into a single trainingPoints collection using .merge(). A print(trainingPoints) at the end of the script confirms the count and shows that every feature carries a class property with value 0 or 1.

Warning

Common mistakes when collecting training data

  • Dropping points on shoreline pixels. The 30 m Landsat pixel at a shoreline contains both water and land reflectance. A mixed-pixel point trains the classifier on a spectrum that matches neither pure class. Always drop points well inside the water body or well into the terrestrial interior.
  • Forgetting to set the class property on the import — the resulting points carry no label and will throw an error when sampleRegions looks for the property. The import config dialog must be set to FeatureCollection and must have a + property named class with an integer value.
  • Clustering all points in one area — points all drawn from the same inlet will not generalize to the rest of the bioregion. Spread them across the full extent of the Salish Sea.
Note

Script L6.1 covers:

Geometry Tools setup, two-class import configuration, point collection for land and water, and the .merge() operation that produces the unified trainingPoints FeatureCollection. Confirm Checkpoint L6-CP1 before continuing.


Part 3 — Building the Feature Table and Reserving the Holdout

A FeatureCollection of points alone is not what a classifier eats. The classifier needs a table whose rows are training examples and whose columns are the input features (six bands plus NDVI and NDWI) plus the class label. The operation that turns points + image into that table is sampleRegions — the same family of operation as reduceRegions from Lab 5, but extracting per-pixel band values at point locations instead of per-region summary statistics. Each row of the resulting FeatureCollection is one labeled training example, with eight feature columns and one class column.

The second job of Part 3 is to split that table. Script L6.2 splits 70 % into a trainingTable (used in L6.3, L6.4, and L6.5) and 30 % into a holdoutTable (reserved). The split is implemented by adding a random column with randomColumn(seed), then filtering on a 0.7 cutoff. The seed (42) is fixed for reproducibility. Both subsets are exported as GEE assets using Export.table.toAsset() so they persist for use downstream.

Note

Script L6.2 covers:

addBands to fold NDVI and NDWI into the input image, sampleRegions to extract the 8-feature training table, randomColumn and filter-based splitting into 70 % training and 30 % holdout, and two Export.table.toAsset calls. Confirm Checkpoint L6-CP2 and make sure both export tasks in the Tasks tab have actually been clicked Run before continuing.


Part 4 — CART: A Single Decision Tree

CART (Classification and Regression Trees) builds a binary decision tree by repeatedly splitting the feature space along one axis at a time, choosing each split to maximize the separation of class labels. The output is, in principle, a tree that can be drawn on a whiteboard: “if NDWI < x, go left; otherwise right; then if SR_B5 < y, go left; otherwise right…” and so on, until each leaf contains predominantly one class.

That transparency is also CART’s pedagogical role here. A single tree is opinionated: it commits to specific axis-aligned cuts, and any pixel that falls in a region the tree has decided is “Land” gets labeled Land, full stop. The result is a classification map with sharp linear boundaries that often follow the orientation of the dominant feature axis — blocky transitions where you might expect a smooth gradient. This is not necessarily wrong; it is what a single tree does. Random Forest in Part 5 will average across many such trees and produce a different visual signature.

Script L6.3 trains ee.Classifier.smileCart() on the 70 % training table from L6.2, applies it to the eight-band classification image, clips the result to the salishLand boundary, and adds it as a colored layer using the two-class palette.

Note

Script L6.3 covers:

The CART classifier — ee.Classifier.smileCart().train(...), .classify(image), and the two-class palette visualization. Confirm Checkpoint L6-CP3 before continuing.


Part 5 — Random Forest: An Ensemble of Trees

Random Forest builds many CART trees and averages them. Each tree is grown on a random subsample of the training data, and at each split each tree is allowed to consider only a random subset of the input features. The result is an ensemble of decision trees that disagree with each other in their individual predictions but, in aggregate, smooth out the idiosyncratic cuts that any single tree commits to. Cardille Ch. F6 §6.2.1 introduces the algorithm; Campbell Ch. 12.4 describes it in slightly more depth.

Script L6.4 trains ee.Classifier.smileRandomForest(50) — 50 trees — on the same 70 % training table, classifies the same eight-band image, clips to the same boundary, and visualizes with the same palette. Holding everything else constant lets the visual comparison between Parts 4 and 5 isolate exactly what the ensemble buys us. At binary scale — land versus water — the difference between CART and RF is relatively subtle, because the land/water boundary is spectrally extreme and either method gets it mostly right. The more interesting comparison happens along coastlines and in the narrow inter-island channels of the San Juan Islands, where the two classifiers commit differently to ambiguous mixed pixels.

The zoomed view below compares the two classifiers over the San Juan Islands, where the channel widths approach the 30 m pixel scale and ambiguous shoreline pixels test the classifiers most directly.

CART Classification

RF Classification
Figure 1: CART (left) vs. Random Forest (right) over the San Juan Islands — zoomed to approximately 1:75,000. The RF ensemble tends to produce cleaner channel boundaries where CART commits to blocky axis-aligned cuts.

The Latifovic et al. (2017) paper used Random Forest at the national scale for the same fundamental reason: RF is fast to train, handles input features of mixed types, and degrades more gracefully than a single tree as training data quality drops.

Note

Script L6.4 covers:

The Random Forest classifier — ee.Classifier.smileRandomForest(50).train(...) — and side-by-side comparison with the CART map from L6.3. Confirm Checkpoint L6-CP4 before continuing.


Part 6 — Unsupervised Classification: K-means

CART and Random Forest are supervised classifiers — they cannot run without labeled training data. Unsupervised classification is the algorithmic move that drops the label requirement. We hand the algorithm a sample of pixels from the image with no class labels attached, and ask it to find groups of pixels that are spectrally similar to each other. K-means is the simplest such algorithm: pick k (we use 2 to match our two-class walkthrough), initialize k cluster centroids randomly in feature space, assign each sample pixel to its nearest centroid, recompute centroids as the means of their assigned pixels, and iterate until the assignments stop changing. Campbell Ch. 12.3 describes the algorithm; Cardille Ch. F6 §6.2.2 implements it in Earth Engine using the Weka library wrapper.

The output is a two-class map — but the classes come out unlabeled. Cluster 0 might be Water, or it might be Land; the algorithm has no way to know what the natural groupings it found correspond to in the real world. This is the cluster-relabeling problem, and it is the central teaching moment of unsupervised classification. Script L6.5 walks through the relabeling explicitly: visualize the raw clustering output, overlay the basemap, identify which cluster corresponds to which informational class by visual inspection, and then build a .remap() lookup that translates the cluster IDs to our standard class codes.

The teaching point is that unsupervised classification is not “supervised classification that doesn’t need training data” — it is a different question entirely. Supervised classification asks “what is each pixel’s land cover, given my two classes?” Unsupervised classification asks “what natural spectral groupings exist in this image?” The two questions sometimes have similar answers (when natural spectral groupings happen to align with our informational classes), and sometimes they don’t.

The figure below shows all three classifiers over the full Salish Sea bioregion, at the same color scale, for direct comparison.

CART Classification

RF Classification

K-means — raw clusters
Figure 2: Two-class land/water classification across the full Salish Sea bioregion: CART (left), Random Forest (center), and k-means before cluster relabeling (right). CART and RF use the standard green/blue palette; the k-means output displays raw cluster IDs — the colors carry no semantic meaning until the .remap() step assigns them.
Note

Script L6.5 covers:

image.sample() to extract an unlabeled sample, ee.Clusterer.wekaKMeans(2) to learn the cluster structure, .cluster() to apply it to the full image, and .remap() to translate the unlabeled cluster IDs back to our two-class scheme. Confirm Checkpoint L6-CP5 before continuing.


Part 7 — Independent Exercise: Five-Class Salish Sea Classification

The walkthrough in Parts 2–6 ran the complete supervised and unsupervised classification pipeline at binary scale, where every step was fast and every result was easy to read. The Independent Exercise runs the same pipeline at a harder scale: five land-cover classes across the same Salish Sea bioregion. The workflow is identical — collect labeled points, sample the feature table, split 70/30, train CART, train Random Forest, train k-means, relabel clusters — but the class boundaries are harder, the training-data collection takes substantially longer, and the visual differences between classifiers become more pronounced.

Class scheme

Code Class What to look for in the basemap
0 Forest Continuous coniferous or mixed canopy. Olympic interior, Cascade foothills, Vancouver Island west coast
1 Developed Urban cores and built-up surfaces. Seattle, Tacoma, Bellingham, Vancouver, Victoria; large parking lots; industrial sites
2 Water Open salt and fresh water. Strait of Georgia, Haro Strait, Puget Sound, the larger lakes (Whatcom, Samish, Cowichan, Sammamish)
3 Herbaceous Active agriculture and grassland. Skagit Valley, Fraser Valley, Saanich Peninsula farmland; large playing fields and golf courses
4 Snow/Ice Persistent summer snow and glacial ice. Mt. Baker (Coleman, Roosevelt, Easton glaciers), Mt. Olympus (Hoh, Blue, Hubbard glaciers), Glacier Peak, ridgetop snowfields

Training-data collection

Script EX1 sets up five class-specific imports — forest, developed, water, herbaceous, snowIce — and asks us to collect at least 50 points per class (at least 250 total). The same quality controls from Part 2 apply here with more force: avoid mixed-pixel boundaries, spread points across the entire bioregion, and drop markers only in pixels that are clearly the class of interest at the 30 m Landsat resolution.

Snow/Ice is the smallest class spatially in a summer composite and requires extra care. Mt. Baker and Mt. Olympus are the most reliable sources; Glacier Peak and the Picket Range provide additional points in the eastern bioregion. Zoom in to zoom level 13–14 on the satellite basemap and place markers within visible glacier termini or persistent snowfields. Fifty points spread across three or four mountains is realistic; fifty points all clustered on Mt. Baker is not.

Workflow and deliverables

After collecting the five-class training set, the exercise follows the same sequence as the walkthrough: sample the feature table with sampleRegions, split 70/30 with randomColumn, train CART, train Random Forest, and train ee.Clusterer.wekaKMeans(5). The k-means relabeling step is more demanding at five classes than it was at two — there are more possible cluster-to-class assignments, and some assignments will be genuinely ambiguous. The script walks through the process explicitly.

EX1 also exports two GEE assets that Lab 7 will consume: the 70 % training table (EX1_TRAINING_ASSET_PATH) and the 30 % holdout table (EX1_HOLDOUT_ASSET_PATH). Both export tasks must be run in the Tasks tab before leaving this lab. Lab 7 opens by loading these two assets; if they are missing, Lab 7 cannot proceed.

The figure below shows example outputs from the five-class exercise — CART, Random Forest, and the raw (unlabeled) k-means result before cluster relabeling — over the full Salish Sea bioregion.

CART Classification

RF Classification

K-means — raw clusters
Figure 3: Five-class classification outputs from the Independent Exercise: CART (left), Random Forest (center), and k-means before cluster relabeling (right). The unlabeled k-means result illustrates the cluster-relabeling problem at five-class scale — clusters are numerically labeled 0–4 with no semantic meaning until manually assigned.
Note

Script EX1 covers:

The full five-class classification workflow — training-data collection across five classes, sampleRegions feature extraction, 70/30 split, CART, Random Forest, k-means (k = 5), cluster relabeling, and the two required Export.table.toAsset calls. Confirm Checkpoint L6-EX1 and verify that both asset exports have been submitted in the Tasks tab before closing the session.


Guiding Questions

Tip

Guiding Questions for Your Technical Analysis

Consider the following questions as you draft your technical analysis. Address those that best illustrate your engineering choices — these are not a checklist of required sections, and not every question will be equally relevant to every classification run.

  • Training data and spectral variability. Campbell Ch. 12.2 argues that one informational class typically maps to multiple spectral classes. Where did you encounter this in your own five-class point collection? Which classes required the most deliberate geographic spread, and why?
  • CART vs. Random Forest boundaries. The CART map tends to show sharp, axis-aligned discontinuities at gradual class boundaries; the RF map tends to smooth them. Where in your five-class map is that difference most visible, and what does it tell you about how each algorithm partitions feature space?
  • The cluster-relabeling problem. When you overlaid the raw k-means output on the satellite basemap, how did you assign clusters to informational classes? Were any assignments ambiguous? What does an ambiguous assignment tell you about the relationship between your five informational classes and the spectral structure of this image?
  • Training data as the dominant factor. Campbell Ch. 12.4 argues that training data quality dominates classifier choice. Looking at your EX1 maps, which classes appear most confused, and can you connect that confusion to something specific about how or where you collected training points?
  • Scale and the Latifovic benchmark. Latifovic et al. (2017) reported 76.6 % overall accuracy with nationally optimized training data and continental-scale resources. What would need to change about your EX1 workflow — in training data, in input features, or in the class scheme itself — to approach that benchmark?

ESCI 442/542 — Introduction to Remote Sensing | Western Washington University