← All projectsKylan Thomson

Geospatial ML · Species Distribution Modeling

SporeCast

Geospatial machine learning that predicts where edible mushrooms are fruiting across Washington State.

Role
Sole engineer — data, modeling, backend, and frontend
Status
In production
Period
2025 – present
20
Species modeled
40+
Engineered features
7
Learners per ensemble
13
Public data sources

Overview

What it does

SporeCast forecasts habitat suitability for 20 gourmet mushroom species across Washington, combining species distribution modeling, satellite imagery analysis, weather-aware phenology, and real-time web mapping into a single system.

The hard part is not fitting a classifier. Presence-only biodiversity data is spatially biased toward roads and trailheads, absence data does not exist, and a naive model learns to separate forest from desert rather than good habitat from ordinary habitat. Most of the engineering here is about making an honest model out of dishonest data.

Every data source is a keyless public API or a server-side reduction, so there are no raster downloads, no vendor lock-in, and the whole pipeline redeploys from scratch.

Architecture

Six-stage pipeline

01Ingest02Enrich03Model04Phenology05Bake06Serve
  1. 01

    Ingest

    Pool presence records from four independent biodiversity archives, then de-duplicate and filter by coordinate accuracy and habitat plausibility.

  2. 02

    Enrich

    Attach 40+ covariates to every observation and prediction cell across climate, terrain, hydrology, land cover, and satellite embeddings.

  3. 03

    Model

    Train a per-species ensemble under spatially blocked cross-validation, with effort-corrected background weighting and isotonic calibration.

  4. 04

    Phenology

    Build a debiased monthly fruiting curve per species, then shift it using the current season's regional soil-moisture anomaly.

  5. 05

    Bake

    Freeze suitability, uncertainty, habitat masks, and metadata into versioned artifacts so serving never depends on a live model.

  6. 06

    Serve

    Render the suitability raster in the browser and apply elevation, host, and accessibility gates client-side for instant interaction.

Features

A multi-resolution stack

Every prediction cell carries covariates drawn from sources that disagree about resolution by three orders of magnitude. Reconciling them — without pretending coarse data is fine-grained — is most of the feature engineering.

FINERCOARSERSatellite embeddings64-D habitat texture10 mTerrain (DEM)slope, aspect, relief~13 mLand cover & hostforest type, canopy30 mHydrologydistance to watervectorSoiloptional, self-pruning250 mClimate normalslapse-rate downscaled~20 km

Approach

How it works

Ensemble with a stacked meta-learner

Seven learners with deliberately different inductive biases — bagged trees, boosted trees, and linear models including a MaxEnt-style formulation — are combined by a meta-learner trained on out-of-fold predictions. When a species has too little data to train the stacker honestly, it degrades to a performance-weighted average rather than overfitting the blend.

Spatially blocked cross-validation

Random k-fold leaks badly on spatial data: a held-out point sitting ▓▓▓ from its training neighbor is not a real test. Block size is derived from the empirical variogram of elevation, soil moisture, and water distance together, rather than assumed or tuned by hand.

Hard background stratum

Roughly ▓▓▓ of background weight is concentrated on forested cells within ▓▓▓ of known finds. This is the single highest-leverage decision in the system — it forces the model to discriminate within habitat instead of learning the trivial forest-versus-desert boundary.

Observer-effort debiasing

Presence-only data reflects where people look, not where mushrooms grow. Background is reweighted by a distance-decayed count of all target-group observations, and presences are thinned so no two retained points sit within ▓▓▓ of each other — which strips out roadside and trailhead clustering.

Lapse-rate climate downscaling

Climate normals arrive on a coarse grid far wider than the prediction cells. Monthly normals are shifted by the elevation difference between each point and its grid cell at ▓▓▓, then temperature aggregates are recomputed — a large effective resolution gain for almost no compute.

Satellite embeddings as habitat texture

Per-pixel 64-dimensional AlphaEarth vectors encode vegetation texture, moisture signature, and seasonality at a resolution no land-cover class can express. These are reduced by PCA for the ensemble, and separately scored against multi-modal per-species centroids to produce a lookalike mask.

Host and substrate gating

Parasitic and mycorrhizal species are gated on the modeled suitability of their hosts rather than treated as independent. Each species carries an affinity profile over forest community types ▓▓▓, plus a soft substrate preference, so predictions respect the biology instead of only the correlations.

Weather-aware phenology

A static seasonal average is the obvious trap — it is right on median and wrong every actual year. Monthly curves are nudged by the current season's soil-moisture anomaly with a lag of ▓▓▓, computed per region so a dry east-Cascades autumn and a soaked Olympic one diverge on the same map.

Modeling

Seven learners, one surface

Random ForestExtra TreesHistGradientBoostingLightGBMXGBoostMaxEnt-styleLogistic RegressionSTACKMeta-learnerout-of-fold predsCALIBRATEIsotoniccross-species comparableSuitabilitysurface

Rigor

Measuring it honestly

Within-habitat AUC, not headline AUC

Full-background AUC reads about 0.99 for every species, which is meaningless — it measures forest versus desert. The reported metric compares presences against the forested serving grid, which is the question a forager actually asks.

Held-out by time and by platform

Two independent holdouts: train on older years and test on recent ones, and train on one archive while testing on others. Together they measure genuine forecasting skill rather than memorization of a single platform's sampling quirks.

Calibration as a first-class metric

Isotonic regression on balanced class weights makes scores comparable across species. Brier score and calibration curves are tracked alongside discrimination metrics, because a well-ranked but badly calibrated surface produces confident nonsense.

Uncertainty that respects geography

Confidence intervals come from a block bootstrap over spatial folds, and permutation importance is computed on held-out folds — in-sample permutation importance on tree ensembles is close to meaningless.

Trade-offs

Decisions worth defending

Train on the region, serve one state

Models train across the wider Pacific Northwest and predict only onto Washington. State lines are political, not ecological, and truncating training data at one multiplies the effective sample size for rare species.

No raster downloads anywhere

Every feature is either snapped from a keyless REST endpoint or reduced server-side before it crosses the network. The pipeline stays deployable and reproducible without a data-warehouse step.

Graceful degradation by default

Optional feature families self-prune when their upstream source is unreachable. A soil API outage costs a few features and some accuracy; it does not fail the build.

Accessibility screen at the end

Public-land polygons post-filter the surface so results only appear where harvesting is actually legal. A prediction the user cannot act on is not a feature.

Built with

Stack & data sources

Modeling

  • Python
  • scikit-learn
  • LightGBM
  • XGBoost
  • Optuna
  • NumPy / Pandas

Geospatial

  • Google Earth Engine
  • Shapely
  • pyosmium
  • scipy cKDTree
  • Pillow (DEM tiles)

Delivery

  • Leaflet
  • Vanilla JS
  • Canvas raster overlay
  • Cloud Run

Occurrence

  • iNaturalist
  • GBIF
  • Mushroom Observer
  • iDigBio

Climate & terrain

  • NASA POWER
  • Open-Meteo
  • Terrarium DEM tiles
  • USGS 3DEP

Habitat

  • USGS NLCD
  • LANDFIRE EVT
  • OpenStreetMap hydrology
  • Google AlphaEarth embeddings

Access

  • PAD-US public land
  • NIFC fire perimeters

What's withheld

This write-up names the techniques but not the tuning. The redacted values above, and the following, stay in the private repository:

  • Tuned thresholds, weights, and hyperparameters
  • Per-species host-affinity tables and gating rules
  • Calibration constants and phenology shift coefficients
  • Grid construction and densification heuristics
  • Artifact schemas and serving internals

Happy to walk through any of it in depth — get in touch.

← All projectsTalk about this work