Lab 6: Classifying the Salish Sea
Supervised and unsupervised land cover mapping with CART, Random Forest, and k-means
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.
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.
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.
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.
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.
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.
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.
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.
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.
.remap() step assigns them.
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.
Guiding Questions
ESCI 442/542 — Introduction to Remote Sensing | Western Washington University