GrapeExpectations

Precision-viticulture pipeline modeling vine canopy health across Washington State wine country at 10 m resolution — nine years of Sentinel-2 imagery, terrain, soil, and climate fused into a gradient-boosted model, then pushed through three different tools to see what each one is actually built for.

32,978
hex cells, ~100 m² each
9 years
Sentinel-2, 2016–2025
R² = 0.8886
GBT, harvest-window NDVI anomaly

1. The pipeline — Python + Google Earth Engine

Vineyard blocks digitized from KML are retiled into 32,978 equal-area hexagonal cells (~10 m across, one Sentinel-2 pixel each). Each cell gets a terrain profile from the USGS 1 m DEM (elevation, slope, aspect, curvature), soil properties from USGS gSSURGO, daily weather from gridMET, and nine years of Sentinel-2 NDVI pulled through Google Earth Engine — then a stacked gradient-boosted model (PySpark MLlib) predicts each cell's harvest-window NDVI anomaly from terrain, soil, and climate alone.

Real problems the GEE pull actually hit

Payload limit. Sending all 32,978 cell boundaries inline in a FeatureCollection blew past GEE's 10 MB request cap. Fixed by uploading the tile centroids as a GEE asset once, then referencing that asset ID in every export task instead of re-sending geometry each time.
Silent empty results. filterBounds() against the uploaded asset returned zero images — GEE didn't recognize a spatial extent on an asset built from bare points. Fixed by computing an explicit ee.Geometry.BBox from the centroid CSV's own lon/lat min/max instead of trusting the asset's implicit bounds.
Point-in-polygon at the wrong scale. A 5 m buffer around each centroid at 10 m pixel scale often contained no pixel center at all, so reduceRegion() returned null. sampleRegions() against the polygon set, not a buffered point, was the fix.

Full run order and every notebook: RegressionRidge/spark_pipeline/.

2. GIS branch — QGIS

The same 32,978-cell grid that crashed a browser BI tool's map chart (see below) is exactly what a desktop GIS tool is built for. export_qgis.py joins the real hex polygon geometry to terrain and a 9-year NDVI trend and writes one GeoPackage — no aggregation, no row-count wall.

Download grapeexpectations.gpkg Loading + styling walkthrough
IN PROGRESS
The authored QGIS map — graduated symbology on the 9-year trend, styled and screenshotted from an actual QGIS session — isn't built yet. The export above is real and ready to open; this callout gets replaced with the real screenshot once that session happens.

What the full-resolution surface looks like

These are the matplotlib fallback — not QGIS output, but the same underlying grid rendered at full density, which is the point: neither of these figures aggregates or samples down from 32,978 cells.

2024 canopy build-up map, bud break to pre-harvest peak
2024 canopy build-up: bud break to pre-harvest peak, all 32,978 cells, diverging red/blue by NDVI Δ.
2024 harvest-window NDVI decline map
Peak to harvest decline — expected for wine grapes (deficit irrigation after veraison).
Animated NDVI through the 2024 growing season
One growing season, real Sentinel-2 composite dates, cloud-contaminated dates filtered.

3. BI branch — Looker Studio

Power BI Service rejected personal-email signup outright, and its "free" workaround wanted a phone number and a subscription plan. Looker Studio works with a plain Gmail account — but its map chart draws one marker per row client-side and crashed the browser tab past a few thousand points, which this grid clears by a wide margin. The star-schema CSVs below are the real fix: same data, same tool, joined by hand in Looker Studio's Blend editor instead of trying to force 32,978 markers onto one map.

dim_cells.csv dim_years.csv fact_ndvi_yearly.csv Loading + blend walkthrough
IN PROGRESS
The published Looker Studio report isn't live yet. The CSVs and walkthrough above are the real, working export — the report itself slots in here once it's built.