Guide
Everything you need to use Windfield, plus the full public API for anyone writing code or an AI agent against it.
Quick start
- Draw an area. Click the draw-area tool (or press D), then drag a box on the map — that's the ground WindNinja will model. You can also search for a place first and use its area, or "use current view".
- Choose how the wind starts. Pick one of the three big cards: Simulate (you set the wind by hand), Current conditions (live weather fills it in automatically), or Forecast (a weather model drives it over several hours, with a time player).
- Set options. Speed/direction/height (Simulate), vegetation, mesh detail (how fine the grid is), and anything else the mode needs. Every control has a short (i) explanation next to it.
- Run. Press the Run button (or R). The app shows a live estimate (cells, seconds, terrain resolution) before you run, and progress while it runs.
- Read the result. A colour speed raster drapes over the terrain, with arrows, optional animated particles, a legend and stats. Click anywhere to probe the exact speed/direction/elevation at that point.
What runs where
All of the modelling happens on the server: downloading and building the terrain, running WindNinja, and producing the speed raster, arrows and grid. Your browser never runs the wind model — it only draws the area you pick, sends the request, polls for progress, and renders the images and GeoJSON the server sends back. This is why a run keeps progressing even on a slow phone, and why the same job id gives the same result to anyone who opens the link while it's still alive.
Reading the wind
- Speed drape — a colour-mapped raster of wind speed over the terrain, with an opacity slider so you can see the map underneath. Colours follow the legend shown with each result.
- Streamlines — lines traced through the wind field showing the path air would follow; good for seeing how flow wraps around a ridge or a structure.
- Particles — small animated dots drifting with local speed and direction, a quick, intuitive read of where the wind is fast, slow, or turning.
- Arrows — a decimated grid of direction/speed arrows, useful when you want exact values at a glance rather than an animation.
- Rising / sinking air — where terrain forces air up or down (windward slopes push air up, lee slopes let it sink), shown as part of the probe readout and, where available, a vertical component in the data.
- Heights selector — if the run included more than one output height, switch between them to see how the wind field changes close to the ground versus higher up.
- Cross-section — draw a line and see a side-on profile of the terrain (and, when available, wind speed) along it.
- Probe — click any point on a finished result for a popup with speed, direction and elevation at that exact spot.
Structures
Draw a building, wall, barn or any polygon/rectangle/circle/ellipse shape on the map, give it a height (or mark it infinitely tall), and the server raises the terrain under it before solving. This is a good way to see how wind deflects around one specific object rather than just open terrain.
Two things matter for getting a useful result:
- Use a mesh finer than the object. The fast mass-conserving solver represents a structure as raised terrain on its grid, so a structure smaller than one grid cell barely shows up. Pick a mesh resolution clearly smaller than the structure's own size (for example, a 10 m-wide shed needs a mesh well under 10 m, not the "coarse" 100+ m default).
- Use the high-fidelity solver for buildings. The fast solver doesn't model separated flow, wakes or turbulence behind sharp-edged objects — it's a smoothed, mass-conserving approximation. For a realistic building wake, switch to the opt-in high-fidelity solver (OpenFOAM momentum solver), which is slower (minutes, not seconds) and capped to a smaller cell count, but actually resolves flow separation around corners and roofs.
Live weather and data sources
| Source | What it provides | Availability |
|---|---|---|
| Open-Meteo | Current conditions and short-range hourly forecast (speed, direction, gust, temperature, humidity, cloud, pressure) for any point on Earth. | Worldwide |
| NOAA NWS stations | Real station observations and active weather alerts. | United States only |
| METAR airports | Aviation weather station observations — wind, temperature, visibility. | Worldwide |
| NDBC buoys | Coastal and offshore marine wind observations. | Coastal/ocean only, 0 results inland |
| Radiosondes | Upper-air soundings used to build a vertical wind profile with height. | Near ~55 North American launch sites, depends on the latest flight having reported |
| GFS / HRRR / NAM (NOAA NOMADS) | Forecast-model wind fields that drive Forecast mode, one WindNinja run per forecast hour. | GFS worldwide (28 km); HRRR and NAM are CONUS (continental US) only, at 3 km and 12 km |
| OSM Nominatim | Place search and reverse lookup (addresses, zip/postal codes, cities, mountains, buildings, countries). | Worldwide |
Some sources depend on optional server configuration (for example a RAWS/mesonet network needs a free API token) and report themselves as unavailable with a reason, rather than silently returning nothing.
Limits and fair use
There's no fixed area-size cutoff. Instead every run is budgeted by
cells, time and memory, reported live by GET /api/v1/capabilities:
- Domain up to about 400 km on a side (a soft, informational figure — the real limits below are what's enforced).
- Up to 1.5 million mesh cells per run.
- Up to 15 minutes of run time.
- Up to 2 runs processing at once, 20 queued, 2 in flight per visitor.
- If a custom mesh resolution would be too fine for the area you've drawn, the app (and the API) offers the coarsest resolution that would actually fit.
- Results are kept only while someone is actively watching them — roughly 5 minutes after you close the page or move on, they're deleted. A share link doesn't point at a file kept forever; it rebuilds and re-runs the original request.
Please keep automated/API request rates light. See the full policy in /ai.txt and /agents.txt.
Keyboard shortcuts
| D | Draw area |
| S | Add structure |
| P | Probe point |
| M | Measure |
| X | Cross-section |
| V | Select / edit |
| L | Toggle layers panel |
| R | Run |
| / | Search |
| Esc | Exit the current tool / mode |
| Delete | Remove the selected object |
| Alt+1…9 | Jump to window 1–9 |
Privacy
Windfield has no login and no accounts. Its privacy policy and terms are SummitFlux's own: Privacy policy · Terms.
API (for developers and AI agents)
Base URL https://windfield.summitflux.com/api/v1. JSON,
CORS open, no auth, no key. Attribution is requested, not required
— see /ai.txt. Full machine-readable
references: /agents.txt,
/agents.xml, /llms.txt,
/llms-full.txt.
GET /api/v1/health
{
"ok": true,
"windninja": "4.0.0",
"queue": { "running": 1, "queued": 0 },
"limits": { "max_domain_km": 400, "max_cells": 1500000, "max_run_seconds": 900 }
}
GET /api/v1/capabilities
{
"mesh_choices": ["coarse", "medium", "fine"],
"mesh_choice_cells": { "coarse": 4000, "medium": 10000, "fine": 20000 },
"mesh_resolution_m": { "min": 10, "max": 2000 },
"vegetation": ["grass", "brush", "trees"],
"max_domain_km": 400,
"max_cells": 1500000,
"max_run_seconds": 900,
"initialization": ["domain_average", "point", "weather_model", "profile"],
"wx_models": [
{ "id": "NOMADS-GFS-GLOBAL-0.25-DEG", "label": "GFS Global 0.25°", "coverage": "global" },
{ "id": "NOMADS-HRRR-CONUS-3-KM", "label": "HRRR CONUS 3 km", "coverage": "conus" },
{ "id": "NOMADS-NAM-CONUS-12-KM", "label": "NAM CONUS 12 km", "coverage": "conus" }
],
"solvers": [
{ "id": "mass", "label": "Fast (mass-conserving)", "eta": "seconds", "available": true },
{ "id": "momentum", "label": "High fidelity (momentum / OpenFOAM) -- models wakes behind buildings",
"eta": "2-10 min", "available": false, "max_cells": 60000 }
]
}
POST /api/v1/jobs
Domain-average Simulate run, with output heights and one structure:
{
"bbox": [-105.72, 39.54, -105.58, 39.64],
"initialization": "domain_average",
"input_speed": 15, "input_speed_units": "mph",
"input_direction": 270,
"input_wind_height": 10, "input_wind_height_units": "m",
"output_wind_height": 10, "output_wind_height_units": "m",
"output_heights": [2, 10, 30],
"vegetation": "grass",
"mesh_choice": "medium",
"structures": [
{ "id": "s1", "label": "Barn", "height_m": 12, "infinite": false, "mode": "raise",
"geometry": { "type": "Polygon", "coordinates": [[[-105.651,39.601],[-105.650,39.601],[-105.650,39.602],[-105.651,39.602],[-105.651,39.601]]] } }
],
"outputs": ["geojson", "png", "geotiff"]
}
Point initialization (station-fed wind):
{
"bbox": [-105.72, 39.54, -105.58, 39.64],
"initialization": "point",
"mesh_choice": "coarse",
"points": [
{ "lat": 39.60, "lon": -105.65, "speed": 12, "direction": 260, "height": 10 }
]
}
Forecast run (weather model):
{
"bbox": [-105.72, 39.54, -105.58, 39.64],
"initialization": "weather_model",
"wx_model": "NOMADS-HRRR-CONUS-3-KM",
"forecast_hours": 6,
"mesh_choice": "coarse"
}
Vertical-profile run:
{
"bbox": [-105.72, 39.54, -105.58, 39.64],
"initialization": "profile",
"profile": [
{ "height_m": 2, "speed": 5, "direction": 270 },
{ "height_m": 100, "speed": 25, "direction": 300 }
],
"output_heights": [2, 50, 100],
"mesh_choice": "coarse"
}
Response:
201 { "id": "01J...ULID", "status": "queued", "status_url": "/api/v1/jobs/01J...ULID" }
GET /api/v1/jobs/{id}
{
"id": "01J...ULID", "status": "done", "progress": 100,
"message": null,
"created_at": "2026-10-06T14:00:00Z",
"started_at": "2026-10-06T14:00:01Z",
"finished_at": "2026-10-06T14:00:29Z",
"expires_in_s": 298,
"params": { "...": "echo of the request" },
"dem": { "source": "AWS Terrarium (Mapzen/SRTM/...)", "resolution_m": 100, "cells": [629, 694], "bbox": [-105.72, 39.54, -105.58, 39.64] },
"results": {
"speed_units": "mph", "height_m": 10,
"bounds": [[39.54, -105.72], [39.64, -105.58]],
"png": "/api/v1/jobs/01J.../files/speed.png",
"legend": [ { "min": 0, "max": 5, "color": "#2b83ba" } ],
"arrows": "/api/v1/jobs/01J.../files/arrows.geojson",
"grid": "/api/v1/jobs/01J.../files/grid.json",
"structures": "/api/v1/jobs/01J.../files/structures.geojson",
"downloads": {
"speed_geotiff": "/api/v1/jobs/01J.../files/speed.tif",
"dir_geotiff": "/api/v1/jobs/01J.../files/dir.tif",
"kmz": "/api/v1/jobs/01J.../files/result.kmz",
"shapefile_zip": "/api/v1/jobs/01J.../files/shapefile.zip",
"ascii_zip": "/api/v1/jobs/01J.../files/ascii.zip",
"dem_geotiff": "/api/v1/jobs/01J.../files/dem.tif",
"grid_csv": "/api/v1/jobs/01J.../files/grid.csv",
"cfg": "/api/v1/jobs/01J.../files/job.cfg",
"log": "/api/v1/jobs/01J.../log"
},
"stats": { "speed_min": 1.5, "speed_max": 22.3, "speed_mean": 9.8, "run_seconds": 27.4 }
}
}
GET /api/v1/jobs/{id}/files/{name}
Returns the file with the correct Content-Type and
Cache-Control: public, max-age=31536000, immutable.
Nested paths are supported for per-timestep/per-height files, e.g.
files/t00/speed.png or files/h30/speed.png.
GET /api/v1/jobs/{id}/log
text/plain
WindNinja_cli ...
Run 1
...
GET /api/v1/jobs/{id}/recipe
{
"bbox": [-105.72, 39.54, -105.58, 39.64],
"initialization": "domain_average",
"...": "the exact params this job was created with",
"_debug": { "dem_resolution_m": 100, "dem_cells": [629, 694] }
}
POST this object straight back to /api/v1/jobs to
recreate the same run — unknown fields like _debug
are ignored.
POST /api/v1/jobs/{id}/keepalive
204 No Content
Empty body. Bumps the job's "someone is still watching" timer so it isn't cleaned up while you're reading it.
GET /api/v1/dem/elevation?lat=&lon=
{ "elevation_m": 1823.4 }
GET /api/v1/geocode?q=
[
{ "label": "Mount Evans, Clear Creek County, Colorado, USA",
"lat": 39.5883, "lon": -105.6438,
"bbox": [-105.68, 39.56, -105.60, 39.62],
"type": "peak", "importance": 0.52 }
]
GET /api/v1/geocode/reverse?lat=&lon=
{ "label": "Idaho Springs, Clear Creek County, Colorado, USA", "lat": 39.7428, "lon": -105.5172 }
GET /api/v1/weather/current?lat=&lon=
{
"source": "open-meteo", "lat": 39.60, "lon": -105.65, "timezone": "America/Denver",
"current": { "temperature_2m": 12.4, "wind_speed_10m": 9.1, "wind_direction_10m": 284,
"wind_gusts_10m": 15.2, "relative_humidity_2m": 38, "cloud_cover": 20, "pressure_msl": 1016.2 },
"hourly": { "...": "48h series: wind_speed_10m, wind_direction_10m, wind_gusts_10m, ..." },
"fetched_at": "2026-10-06T14:00:00Z", "cache": "miss"
}
GET /api/v1/weather/stations?bbox=&sources=nws,metar,ndbc,raws
{
"stations": [
{ "id": "KBJC", "name": "Rocky Mountain Metro Airport", "network": "nws",
"lat": 39.909, "lon": -105.117, "elevation_m": 1725, "anemometer_height_m": 10,
"wind_speed": 8.1, "wind_gust": 13.8, "wind_direction": 300, "temperature_c": 14.2,
"observed_at": "2026-10-06T13:51:00Z", "source_url": "https://api.weather.gov/stations/KBJC" }
],
"networks": {
"nws": { "available": true, "count": 8 },
"metar": { "available": true, "count": 4 },
"ndbc": { "available": true, "count": 0 },
"raws": { "available": false, "count": 0, "reason": "needs SYNOPTIC_TOKEN in /etc/windninja/windninja.env (free token at synopticdata.com)" }
}
}
GET /api/v1/weather/profile?lat=&lon=&sources=model,sounding,surface
{
"lat": 39.60, "lon": -105.65, "units": "mph", "fetched_at": "2026-10-06T14:00:00Z",
"levels": [
{ "height_m": 10, "speed": 4.3, "direction": 327, "source": "model" },
{ "height_m": 10, "speed": 3.4, "direction": 220, "source": "surface", "station": "KCCU", "observed_at": "2026-10-06T13:51:00Z" },
{ "height_m": 436.5, "speed": 7.2, "direction": 46, "pressure_hpa": 600, "source": "model" }
],
"sources_available": {
"model": true, "sounding": true, "surface": true,
"aircraft_amdar": { "available": false, "reason": "NOAA MADIS requires an account" },
"satellite_winds": { "available": false, "reason": "assimilated into the model analysis; raw AMVs are not publicly served" }
}
}
All endpoints are also reachable over a browser's normal
fetch() — CORS is open and there is no API key to
manage. Please keep automated request rates light; see
Limits and fair use above.