# Weather without a licence problem

Our 3D city demos now read live weather from MET Norway's Locationforecast 2.0 service: no API key, CC BY 4.0 with commercial use explicitly permitted, and global coverage, so one upstream serves both London and Manhattan. We moved because the keyless tier we had been using is licensed for non-commercial use only, and a demo on a commercial website is commercial use. The public routing API's own weather endpoint is a separate matter, and it is still switched off, on a self-hosted stack and on our own hosted gateway alike, for exactly the same reason.

## Why change a weather provider that worked?

Because the free tier we were on was licensed non-commercially, and reading the licence is cheaper than being told about it later. The previous provider serves excellent data and its keyless endpoint is genuinely free, but its free tier is for non-commercial use; commercial use needs a paid plan or a self-hosted instance of its open-source stack. A live demo on the site that sells our product is not a hobby project, whatever we would like to call it.

The alternatives we weighed were the paid plan on the same provider, and the UK Met Office's DataHub, which is credible and free at low volume but requires an account and a key. MET Norway's service needs neither, publishes under CC BY 4.0 with commercial use permitted, and covers the whole planet, which mattered because we render two cities on two continents and did not want two upstreams and two licence positions.

## What does the integration actually look like?

Two small route handlers on the website, each proxying one coordinate, and roughly ten lines of substance:

```ts
const UPSTREAM =
  "https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=51.5008&lon=-0.1246";

const res = await fetch(UPSTREAM, {
  next: { revalidate: 600 },
  headers: { "User-Agent": "MapMap-demo/1.0 hello@mapmap.ai" },
});
```

The response is cached hard on the way out with `public, s-maxage=600, stale-while-revalidate=1200`, so one upstream request per ten minutes per city serves every viewer regardless of traffic. We consume exactly two fields from the forecast: `cloud_area_fraction` from the instant details, divided by 100 and clamped to 0 to 1, and `precipitation_amount` from the next hour, which is a boolean rain flag as far as we are concerned.

Failure is designed to be boring. Any non-2xx, any thrown error, and the handler returns clear skies with a two-minute cache. Weather thickens the atmosphere in the scene; it never gates the first frame. In the demo the cloud fraction multiplies the time-of-day fog density, and rain adds a further fixed increment, so a grey afternoon in London looks like a grey afternoon rather than switching to a "rainy" preset.

## What does the licence ask of us?

Attribution and identification, and both are in the code rather than in a promise. Every response that carries forecast data carries the string `Weather data from MET Norway (CC BY 4.0)`, and the same credit is printed in the on-screen credits panel of both demos next to the OpenStreetMap attribution. The clear-skies fallback is the one response without it, because it carries no MET Norway data to attribute. Requests go out with a `User-Agent` naming the product and a contact address, which is what the service asks for so it can reach an operator whose client is misbehaving.

This is the same discipline we apply to the code we ship, where every distributable is permissive-licence only and checked in CI rather than trusted: the [licensing and SBOM write-up](/news/permissive-licensing-sbom) covers that side. Data licences deserve the same treatment, and they get less of it, because a licence breach in a dependency is caught by a scanner and a licence breach in an HTTP response is caught by nobody.

## Is there a weather endpoint in the API?

Yes, and it does not use MET Norway. `POST /v1/weather/along` samples a route and returns the forecast for each sample aligned to when you actually get there, not to now: a two-hour route reads the departure hour at its start and the hour two hours out at its end. It is a gateway endpoint with its own metering, its own caching and its own upstream, configured by `SN_OPEN_METEO_URL`. That variable is unset by default, so a stock deployment answers 501, and it is unset on our hosted gateway too, so `api.mapmap.ai` answers 501 as well.

That default is the licence position again, made explicit. The module carries the warning in its own source: the public keyless endpoint it talks to is non-commercial on the free tier, so a commercial deployment needs the provider's paid host or a self-hosted instance of its AGPL-licensed stack. Rather than ship a keyless URL that works immediately and quietly puts operators in breach, the endpoint requires the operator to name the upstream they are entitled to use. It costs one Standard call per request, never premium, and it rounds coordinates to a 0.05 degree grid and ETAs to the hour as both cache key and upstream query, which keeps the request rate low and the cache useful.

Two providers for two jobs is not an accident we are dressing up. The demos need one global, keyless, commercially clean source and nothing else. The API endpoint needs to be operator-configured anyway, because a self-hosted deployment behind an air gap has to point at whatever it is allowed to point at.

## What we have not done

- **We do not send conditional requests.** MET Norway asks callers to respect the `Expires` header and to use `If-Modified-Since` on repeat requests. We do neither: we ignore `Expires` and cache for a fixed ten minutes. In practice that is one request per city per ten minutes, well inside any reasonable rate, but it is not the letter of the guidance and we would rather write that down than let you assume otherwise. It is on the list.
- **No rate limiter in code.** The low request rate is a consequence of the cache, not of an enforced limit. If the cache were misconfigured, nothing else would stop it.
- **Weather is not in routing.** It thickens fog in the demos and it is available along a route as data. It does not influence costing, speeds or ETAs.
- **`/v1/weather/along` is off everywhere, ours included.** Unset upstream means 501, on a fresh self-hosted stack and on `api.mapmap.ai` alike. That is the correct behaviour, and it does mean the endpoint is not something anyone can call without an operator decision first.

## Try it

Load either 3D city demo and watch the credits panel: the weather line is live, the fog is driven by the number behind it, and the same cached response serves every visitor for ten minutes. For the API side, the [API reference](/docs/api-reference) lists `POST /v1/weather/along`, and the [analysis guide](/docs/analysis) documents its parameters, its billing, its licence position and the environment variable that enables it. If you are integrating any free data source into something you charge for, read its licence before you read its documentation. The endpoint being keyless tells you nothing about whether you are allowed to use it.

Map rendering derives from OpenStreetMap. Credit "© OpenStreetMap contributors" when you render or republish it, and credit MET Norway alongside it if you reuse the forecast data.
