Wingfoilscout · Methodology

How Wingfoilscout calculates

From eight data sources to three destinations: every threshold, every weighting and every exclusion that is actually in the code — and the places where the tool estimates instead of knowing.

Version 2.1.0 278 spots in the catalogue Values from config.example.yaml Same numbers as SCORING.md
00

The path of an hour

Wingfoilscout does not rate spots, it rates hours. Every hour at every spot gets a score between 0 and 1; hard exclusions set it to 0 and record a reason. Everything else — sessions, destinations, ranking — is built from these hourly values by aggregating them. None of it is a black box: every hour knows why it is what it is.

SOURCES spots.yaml OSRM (drive time) Open-Meteo ×3 Regional model Open-Meteo Marine OSM / Overpass ICON-EPS (40) Park4Night Prefilter Drive time per spot Hourly wind series Direction & water state Hourly score 0 … 1 Sessions per day Destinations, ranked Radius · season · water type · drive time (+35%) OSRM, else straight line × 1.22 · then exact 3 models + regional · wind factor · thermal Fetch rose, else catalogue sectors 5 sub-scores × model agreement ≥ 0.55 · ≥ 2 h · daylight · one day Rating × drive × duration · Plan B INTERVENTIONS Vetoes → score 0 past · night · water below 7 °C tide window · no wing · offshore thunderstorm · air temperature Ensemble damping 40 runs of the same weather factor 0.5 … 1.0 on the score for the top 20 destinations thermal: the spot’s reliability Any source may fail: if one is missing, Wingfoilscout carries on without it and notes it in the log.
The whole run in one picture. On the left the sources, in the middle the seven stages, on the right the two places where something is subtracted rather than added. The green stage is the only one where real calculating happens — everything above it collects numbers, everything below it aggregates them.
01

Where the numbers come from

Eight sources, all without an API key or an account. None of them is load-bearing: if one fails, the report runs without it and says in the log what is missing. That is deliberate — a tool that delivers nothing at all when someone else’s server goes down is worthless at the decisive moment.

Sourceprovidesresolutionif it fails
Open-Meteo Forecastwind, gust, direction, temperature, rain, CAPE, weather code, cloud cover, solar radiation, sunrise and sunset7–25 kmno report
Regional modelsthe same wind series, finer — overrides it hour by hour1–2.5 kmthe coarse model stays
ICON-EPS ensemble40 runs of the same situation → probabilitycoarserno damping, raw score only
Open-Meteo Marinewater temperature, wave height, water level (sea and lagoon)8 kmno tide, no wetsuit hint, no cold-water veto
OpenStreetMap / Overpassshoreline → fetch rose per spot, protected areas36 directions, 25 kmsectors from the catalogue
OSRMreal drive time and road distance—straight line × 1.22 at 85 km/h, marked “(estimated)”
Park4Nightovernight spots, dogs must be allowedtop 8 destinationsdestinations without a list of overnight spots
MeteoAlarmofficial severe-weather warnings per region—no warning badges

The prefilter runs before any network request and throws out whatever is out of the question anyway, in this order: reference spot only, disabled in the catalogue, excluded water type, heavy seagrass, standing depth throughout, dog ban and wingfoiling ban (both only if you have switched that on), out of season, outside the radius, too far to drive. It checks radius and drive time with the estimate (straight line × 1.22 at 85 km/h) plus a 35% allowance, so that a spot does not fail because of an overly pessimistic rule of thumb. Only after that are the real drive times fetched, and with them radius and upper limit apply exactly. Spots with an unknown water type deliberately stay in; the shoreline geometry later clarifies what kind of water the point lies in.

02

Which model provides the wind

Three global models always run: dwd_icon_seamless as the primary model, with ncep_gfs_seamless and ecmwf_ifs alongside. The primary model provides the value, the others fill gaps — and above all, they measure the agreement. Both requests, coarse and fine, explicitly use the nearest grid cell: otherwise Open-Meteo would move a coordinate to a cell of similar elevation on land, and over land the wind is systematically weaker because of surface roughness.

Before anything is scored, the spot’s wind factor (wind_factor in the catalogue) scales wind and gusts. It is meant for spots where experience shows it blows more or less than the models say; the logbook suggests it from the sessions you have logged. 1.15 means: it blows 15% more there. Currently it is set to 1.0 at all spots.

Spot (lat, lon) + country code Covered by a regional model? no global models only yes Model of the spot’s country? code, else home country yes The country’s model if several: the finest no Neighbouring model comes second, even if finer Why this order: at the edge of its domain, a model is not the better source, even with a finer grid — there, the provider lacks the weather stations its calculations rely on.
The first test run gave Lake Garda the Swiss 1 km model and Zeeland the French one: both respond there because their domains extend far beyond the national border. Since 1.4.1, responsibility decides first, then resolution; since 1.8.1, the spot’s country code counts first, and only without a code the rectangle — Fehmarn lies in both, the German and the Danish one, and gets ICON-D2. The regional models reach at most three days ahead.
Regional modelShort nameGridHome country
meteoswiss_icon_ch1CH11.0 kmSwitzerland
meteofrance_arome_france_hdAROME-HD1.5 kmFrance
italia_meteo_arpae_icon_2iICON-2I2.0 kmItaly
dwd_icon_d2ICON-D22.0 kmGermany
knmi_harmonie_arome_netherlandsHARMONIE2.0 kmNetherlands
dmi_harmonie_arome_europeDMI2.0 kmDenmark
geosphere_arome_austriaAROME-AT2.5 kmAustria

Which model covers which point is documented nowhere by Open-Meteo — the docs just say “Central Europe”. And a point outside the domain is answered with HTTP 400 instead of empty values, which in a batch request for twenty coordinates takes all the others down with it. Wingfoilscout therefore halves rejected groups until the outliers are pinned down, and remembers the result for each spot. This dance happens once, not on every run.

1.0 0.4 0 0 3 9 14 kn Agreement vs. model spread
The spread is the maximum minus the minimum of the models in that hour, including the regional model when it provides the wind. The agreement multiplies the finished hourly score by 0.7 + 0.3 · agreement — you don’t plan a drive around an outlier. Since the agreement never drops below 0.4, an hour on which the models disagree completely still keeps 82% of its score.
03

The hourly score

Five sub-scores, each between 0 and 1, summed with their weights and divided by the sum of the weights. Then the model agreement as a factor. The weights are in the configuration and are an opinion, not a measurement — wind and direction together carry 58%, because without those two nothing works at all.

Wind Direction Water Weather Gusts 0.34 0.24 0.16 0.14 0.12 × model agreement: total score × (0.7 + 0.3 · agreement) × thunderstorm shadow: up to 2 h before and after a thunderstorm hour, an extra × 0.4 A veto sets the score to 0 — the weighting never even comes into play.
The weights from config.example.yaml. You can change them without having to adjust anything anywhere else — the sum is always normalised.

Wind: wing centre times comfort band

Two questions in one number. First: is the wind in the middle of a wing’s range or at its edge? Second: is it within the comfort band you have set? The sawtooth in the chart is not a bug, it is the quiver — with every change of wing, the centrality starts over.

1.0 0.5 0 10 14 28 43 kn comfort band 14–28 kn Wind score · quiver 6.5 / 5.0 / 4.2 / 3.5 / 2.5 m² below 10 and above 43 kn: no suitable wing → veto
Outside the band the score tapers off gently (to no less than 0.6 of the centrality value), not to zero — 12 knots is not a great day, but not a reason for exclusion either. The hard cut only comes when no wing fits any more.

Gustiness: gust divided by mean wind

1.0 0.3 0 1.0 1.25 1.60 2.2 Gustiness score vs. gust factor
Additionally capped at 0.25 if the gust is more than 6 knots above the top of the quiver’s range — then even a good mean wind no longer helps.

Vetoes: the cascade before the calculation

A veto is not a downgrade but a stop: score 0, reason recorded, next hour. The order in the code is chosen deliberately — whatever makes the hour impossible in principle is checked first.

#CheckThresholdEffect
1Hour already pasttime of the runVeto
2Water temperature — sea and lagoon only, with the “Water temperature and wave model” box ticked< 7 °CVeto
3Daylightsunrise + 30 min … sunset − 30 minVeto
4Tide window — only where the catalogue sets onee.g. HW ± 2 hVeto
5Suitable wing in the quiver10 … 43 knVeto
6Offshore with a lot of water downwind — only with offshore_veto_km above 0, off by defaultkm of open water downwindVeto
7Thunderstorm reported or air temperature out of rangeWMO 95 / 96 / 99 · 7 … 35 °CVeto
—Close to a temperature limit< 3 °C away× 0.8
—Rainfrom 0.6 mm/h · from 3.0 mm/hlinear … × 0.2
—Thunderstorm potential (CAPE)from 1200 · from 2000 J/kg× 0.7 · × 0.35
—Thunderstorm shadow± 2 h around a thunderstorm hour× 0.4

The thunderstorm shadow is where the tool goes beyond a forecast portal: the model only flags the hour in which it calculates a thunderstorm at that grid point — out on the water, the window around it matters just as much.

04

Direction and water from the shoreline geometry

Maintaining wind sectors by hand does not scale to 278 spots — currently 29 have them. Wingfoilscout therefore measures the wind angle from OpenStreetMap: from the spot’s coordinate, in 36 directions, up to 25 km each, until it hits land. That gives the fetch rose — how far the wind has travelled over water, from every direction. Hand-maintained sectors beat the geometry when both exist.

Everything else follows from two numbers of the rose: the fetch upwind and the room downwind.

Ratio: upwind fetch ÷ downwind room offshore side-on side-shore side-on onshore 0.50 0.88 1.00 0.88 0.55 0.15 0.35 2.9 6.7 flat, but you drift out waves and shore break right in front of you First, two hard limits from the rose: Upwind ≤ 400 m → offshore · downwind ≤ 500 m → onshore · both blocked → “no open water”, score 0.10 The number under each field is the direction score that goes into the weighted sum.
The same rose answers both questions that matter at a spot: is the wind coming from a good direction, and how much water is in front of me if something goes wrong?

The upwind fetch also yields the wave height, via the fetch-limited engineering approximation from the Shore Protection Manual:

H_s = 0.0016 · U · √(F / g) U in m/s, F in m, g = 9.81 m/s² H_s < 0.25 m → flat water score 1.00 H_s < 0.60 m → chop water score 0.80 otherwise → wave water score 0.59

The water scores come from the chop aversion in the configuration (default 0.7): at 0 the water state does not matter, at 1 only flat water counts. Above 22 knots, everything except flat loses another 10%.

This is explicitly an approximation. It assumes that the wind has been blowing from the same direction for long enough, and it uses the 10-metre wind instead of the wind stress factor. At the sea it is therefore replaced by Open-Meteo’s wave model wherever that provides a value — with swell, tide and seabed profile; the fetch is still used for the wind angle. On inland lakes the approximation remains. Where hand-maintained sectors apply, the water state comes from the sector and the wave model is shown for information only.

If a spot has neither a rose nor sectors, the direction is scored 0.6 — neutral. If it has sectors but none of them matches, it is 0.2: that is a statement, not a gap. The difference matters, because otherwise every imported spot without maintained sectors would become invisible.

05

Thermal wind: where the models fail

Ora, Maloja wind, Breva, Maestral: at these spots the circulation is smaller than the model grid. The model sees 5 knots where there are actually 18. For 71 spots, the catalogue therefore records which thermal wind blows there, when, from which direction, how strongly and how reliably — with a source. From that, Wingfoilscout calculates whether it will blow today.

Three quantities decide that, and all three are backed by sources rather than guessed:

1.151.00 036 912 kn Opposing-wind factor vs. offshore gradient wind light opposing wind boosts · no breeze from 9 kn 1.00.40 12:0013:4516:5419:00 Daily cycle in the stored window (here 12:00–19:00) rises from 0.4, plateau 25–70%, fades to 0.25
The kink at 3 knots is not a cosmetic flaw: a weak offshore gradient wind strengthens the sea breeze instead of weakening it (Arritt 1993). Only above that does it tip over, and from about 7–8 knots offshore, according to the Centro Meteo Ligure, nothing is left at all.

The third quantity is the solar energy received since sunrise, not the sunshine in that hour: the thermal lives on how much heat the morning has already put into the ground. From 1800 Wh/m² it counts as full; if the model has no radiation data, cloud cover is the fallback.

Hourly potential = daily cycle · energy · opposing wind (0 … 1.15) Daily potential = Σ hourly potential ÷ Σ daily cycle · reliability Assumed wind = typical strength · trust · hourly potential — only if that is above what the model shows anyway

Reliability is no longer part of the assumed wind: up to 1.6.1, 16 knots at 70% gave 11 knots, a number that never occurs on any day — if the Ora blows, it is 16, otherwise 0. Reliability is a probability, and it now goes where probabilities belong: into the ranking of destinations, in place of the ensemble (section 07).

In the daily potential, the daily cycle cancels out. What remains is what actually differs today: solar radiation and opposing wind, times the spot’s reliability. Up to 1.5.1 this was the maximum over the session — and on every sunny day, that is the one hour in which all three ingredients happen to line up. In the run of 16 September, all five thermal destinations were therefore between 96 and 100%.

And one rule that is not a curve: some thermals are killed off by a particular background flow. For the Maloja wind, according to SRF Meteo, an upper-level flow from north to south-east is enough, and then it practically never blows. Such sectors are recorded for 28 spots in the catalogue (suppressed_by) and set the potential hard to zero from 6 knots out of that direction. Below 8 knots of background wind, the thermal also sets the direction; above that, the model’s direction stays.

Wingfoilscout does not invent thermals. It only calculates them at spots where the catalogue says there is one. Where no source gives a strength — Chiemsee, Lake Constance, Lake Thun — none is assumed; the spot only appears as a candidate. Assumed wind is always marked as an assumption in the report, never as a measurement.

06

Tides: always shown, decisive only on request

At the sea and in lagoons, Wingfoilscout fetches the water level from Open-Meteo’s marine model and calculates high and low tide from it to the minute: a parabola through the three hours around each turning point. The model itself is not accurate to the minute — it works on an 8 km grid, combines tide and wind set-up, and is not an official tide table. How far it deviates from a tide gauge has not yet been checked against a table.

Whether the tide applies at a spot is decided by the catalogue: tidal: true or false, otherwise automatically — sea or lagoon with a modelled tidal range of at least 0.5 m (tide.auto_hub_min). Currently nine spots are set to “yes” by hand, and two lagoons behind dams to “no”, because there the 8 km grid sees the open sea in front of them.

Without a tide window, the tide does not change any score. It is shown at the destination and in the hourly grid, and the report recalculates the tide state when you open it: “Now rising · high tide at 14:50”. Only a window in the catalogue turns it into a veto:

tide: fahrbarrideable duringExample
hochwasserthe hours around each high tideHW ± 2 h
niedrigwasserthe hours around each low tideLW ± 2 h
auflaufendfrom low to high tide, turning point to turning pointLW → HW
ablaufendfrom high to low tideHW → LW

What counts is the middle of the hour: “13:00” stands for 13:00 to 14:00 and, with high tide at 14:25 and ± 2 h, is included. Wingfoilscout does not ship any windows — which one a spot needs is known only to those who know the spot. Currently no spot has one set.

07

From hours to destinations

Scored hours become consecutive blocks, blocks become destinations, destinations become a ranking. Every stage throws information away — and exactly at these points arise the errors that you can no longer see in the report.

HOURS green = score ≥ 0.55 and no veto Session at least 2 consecutive hours, in daylight, within a single day; a single near-miss hour in between is bridged Destination all sessions of a spot · hours on the water per day capped at 5 h (“no wishful thinking”) Ranking Rank = rating × drive × min(1, 0.55 + 0.15 · hours) Rating = hour-weighted mean of the session scores Drive = 1.0 within the rule, otherwise falling to 0 Drive rule 3 h of driving per 1 h on the water a day + 50% per night away, hard cap at 12 h beyond that the rank falls, to zero at +125%
The 5-hour cap per day is the most important of the three simplifications: without it, every spot where the wind blows moderately for twelve hours would beat the one where it is perfect for four. Bridging applies to a single hour up to 0.1 below the threshold between two qualifying hours, without a veto — up to 1.6.1, a single 0.54 in mid-afternoon cut five hours into two blocks of two. The drive factor is 1 − 0.8 × excess ÷ allowed time: one hour too many with four allowed costs 20%, and at 125% over the allowed time (2.25 times the allowed drive) the factor is zero.

The ensemble damps the ranking

The same weather, calculated forty times with slightly perturbed initial conditions (ICON-EPS). The share of runs that reach the lower end of your quiver in at least two hours of the session is a more honest number than any single value — it tells you how stable the situation is. For this, the threshold is converted back for the regional model and the wind factor, so that the ensemble judges the same number that is shown in the report. Since 1.5.0 the share is not only shown in the report, it also has a say:

Factor = (1 − weight) + weight · share default: weight = 0.5 Share 100% → factor 1.00 score unchanged Share 50% → factor 0.75 Share 0% → factor 0.50 half the score, but not gone

Multiplying by the raw share would empty the report whenever the weather is uncertain — it would show nothing, even though there is something to decide. weight: 0.0 restores the behaviour up to 1.4.3: the badge is shown and changes nothing.

There is a limitation: the probability is only fetched for the top 20 destinations. Anything ranked below that is neither rewarded nor penalised — and can therefore overtake a damped destination.

For thermal sessions, the spot’s reliability from the catalogue (reliability, 0.6 if not specified) takes the place of the ensemble, with the same damped factor and for all thermal sessions, not just the top twenty. There, the ensemble sees the model wind of 5 knots and reported 0% — up to 1.6.1, that halved precisely the sessions the tool had specifically corrected.

Finally, each destination comes with a Plan B: the best other destination within 120 km of estimated road distance (drive.plan_b_km, straight line × 1.22) with at least one day in common.

08

What the tool cannot do

This list is not small print but part of the method. Each item is a place where a number in the report looks more confident than it is.

All numbers on this page come from the code and from config.example.yaml, as of version 2.1.0; SCORING.md in the repository says the same in text form, step by step. Anyone who changes the weights, thresholds or the comfort band has to change this page as well — it describes the defaults, not a law of nature. A test in the repository will remind you when a number is no longer right.