# ALPE Centrale geographic terrain and climbing route

`alpe-terrain.json` is a self-contained static asset. It contains real, resampled
elevations and connected OpenStreetMap road geometry. Neither the JSON nor its
consumer needs an elevation service, map tiles, API key or route service at runtime.

Inputs were retrieved on **2 November 2026, Europe/Amsterdam** (1 November UTC).
The checked-in `sources/manifest.json` records exact input URLs, SHA-256 hashes,
file sizes, acquisition times and tile imagery-source headers. The PNGs and road
snapshots in `sources/` are build inputs; the webapp needs only `alpe-terrain.json`.

## JSON schema and grid orientation

- `bounds: {west: 6, east: 6.1, south: 45.045, north: 45.125}`: longitude and
  latitude in decimal degrees, WGS84 / EPSG:4326. Bounds include the grid edges.
- `width: 201`, `height: 201`: vertex counts, giving **200 by 200 cells**.
- `elevations: number[40401]`: metres above the source DEM's sea-level reference,
  rounded to 0.1 m. These are absolute heights, without vertical exaggeration or
  a subtracted baseline. Range: **704.3–2483.2 m**.
- `route: {lat: number, lon: number, elevation: number}[]`: **1506 points**, ordered
  uphill. Coordinates retain seven decimal places; this is storage precision,
  not a claim of centimetre mapping accuracy. Route elevation is in metres.
- `attribution: string`: attribution suitable for carrying into the renderer.
- `source: string`: source services and pointers to this provenance documentation.

`elevations[row * width + column]` is **row-major, north to south, west to east**:

```js
lon = bounds.west + column / (width - 1) * (bounds.east - bounds.west);
lat = bounds.north - row / (height - 1) * (bounds.north - bounds.south);
```

Index 0 is the northwest corner; index 200 is northeast; index 40200 is southwest;
index 40400 is southeast. Grid intervals are 0.0005 degrees longitude and 0.0004
degrees latitude, approximately **39.3 m east–west and 44.5 m north–south** here.
The geographic rectangle is approximately 7.85 km wide and 8.90 km tall; avoid
stretching it to a square when building a mesh.

For `THREE.PlaneGeometry`, use `width - 1` and `height - 1` segments. Its ordinary
vertex order matches this array. After rotating the plane by `-Math.PI / 2`, west
is negative X, north is negative Z, and height is positive Y. Route elevations
are interpolated on the delivered grid's southwest-to-northeast triangle diagonal,
matching the default PlaneGeometry cell triangulation. Use the same geographic
projection, baseline and vertical scale for the road and mesh. A small display
offset may be added to the road to avoid depth fighting; no offset is baked in.

## Terrain source and processing

- Dataset: [Terrain Tiles, AWS Open Data Registry](https://registry.opendata.aws/terrain-tiles/).
- Encoding: [Tilezen/Joerd Terrarium specification](https://github.com/tilezen/joerd/blob/master/docs/formats.md#terrarium).
- Source and resolution documentation: [Tilezen/Joerd data sources](https://github.com/tilezen/joerd/blob/master/docs/data-sources.md).
- Attribution and upstream terms: [Tilezen/Joerd attribution](https://github.com/tilezen/joerd/blob/master/docs/attribution.md).

All twelve S3 objects report the imagery source
`eudem/eudem_dem_5deg_n45e005.tif` in their
`x-amz-meta-x-imagery-sources` response header. Their S3 modification dates are
15 November 2017; the acquisition date does not imply a newly surveyed DEM.

Exact tile URLs, XYZ zoom **13**, each 256 by 256 pixels:

- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4232/2942.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4232/2943.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4232/2944.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4232/2945.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4233/2942.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4233/2943.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4233/2944.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4233/2945.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4234/2942.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4234/2943.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4234/2944.png
- https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/4234/2945.png

Each RGB pixel is decoded to metres using
`R * 256 + G + B / 256 - 32768`. Numeric heights are bilinearly sampled across
tile boundaries in Web Mercator, with a half-pixel adjustment for pixel centres,
onto the regular geographic grid above. Encoded RGB values are never resized.
No synthetic terrain, gap filling, vertical exaggeration or decorative noise is
applied. Zoom 13 pixels are about 13.5 m apart here, but the upstream EU-DEM is
coarser, roughly 25–30 m. Neither interpolation nor decimal rounding increases
its surveyed resolution or vertical accuracy.

Terrain credit: **Mapzen/Tilezen, hosted through AWS Open Data. Produced using
Copernicus data and information funded by the European Union - EU-DEM layers.**
The upstream DEM terms linked above apply to terrain data.

## Road geometry and exact queries

The horizontal route is exclusively from actual OSM road ways and shared node
IDs. It is not hand traced, a synthetic switchback curve, or a straight-line
connection between disconnected roads. Directed shortest paths respect mapped
one-way roads and roundabout direction.

The unmodified `sources/d211-osm.json` response was obtained with an HTTPS POST to
https://overpass-api.de/api/interpreter using an
`application/x-www-form-urlencoded` body whose `data` field contained exactly:

```overpass
[out:json][timeout:40];way["highway"]["ref"~"^D ?211$"](45.05,6.00,45.105,6.10);out body geom;
```

This returned 47 road ways. Its OSM base timestamp is **2026-11-01T23:52:01Z**.

Resort streets were obtained by HTTPS GET from the official OSM API:
https://api.openstreetmap.org/api/0.6/map?bbox=6.058,45.089,6.075,45.098

The XML was converted into `sources/resort-osm.json`, retaining the 123 complete
ways with `highway=secondary|tertiary|unclassified|residential|living_street`, their
original node IDs, tags and coordinate geometry. No coordinates were edited.
This separate request was used after a resort-specific Overpass request timed out.

The selected route has two connected legs:

1. D211 only, from OSM node **251670698** to **340035221**: Bourg-d'Oisans valley
   approach, the 21-bend climbing road, La Garde, Huez, Vieil Alpe, Route de la
   Poste and Route du Signal. At divided sections, the uphill carriageway is used.
2. Avenue de l'Étendard, Place Jean Moulin, Rond-Point du Tour de France and Avenue
   du Rif Nel, finishing at OSM node **254500247**.

All **1026 original road vertices** on this path are preserved. Longer road
segments are linearly subdivided until every output segment is at most about
15 m. Densification adds rendering samples, not additional mapping accuracy.
There is no simplification or corner-cutting spline.

Road credit: **© OpenStreetMap contributors**. The OSM road snapshots and derived
horizontal route are available under [Open Database License 1.0](https://opendatacommons.org/licenses/odbl/1-0/).
See [OpenStreetMap copyright and attribution](https://www.openstreetmap.org/copyright).
Keep the road-data license distinct from the terrain's upstream terms.

## Endpoints, length and precision limits

- Start: **45.0622205, 6.0370259**, **718.0 m** on the delivered DEM mesh.
  This is a selected OSM vertex on the valley approach, around 330 m by road
  after the D1091 junction. It approximates the traditional timing-start area;
  it is not a surveyed timing line.
- Finish: **45.0950069, 6.0707335**, **1845.6 m**, on Avenue du Rif Nel.
- Horizontal route length: **14310.8 m**, summed by haversine distance; net DEM
  elevation difference: **1127.6 m**. The selected endpoints and current OSM
  centreline explain differences from commonly quoted climb distances.

The initially suggested start `45.066, 6.031` lies away from mapped D211, and the
suggested finish `45.09, 6.069` is not the conventional Tour finish. The selected
coordinates follow the actual road and finish avenue instead of forcing those
approximate coordinates into the route.

Endpoint context was checked against these official tourism sources:

- [Alpe d'Huez: The 21 bends](https://www.alpedhuez.com/en/activities/summer-activities/bike/the-21-bends/)
  describes the traditional start after the main-road junction and the finish
  above the final roundabout on Avenue du Rif Nel.
- [Alpes Isère / Oisans Tourisme: Alpe d'Huez, the mythical climb](https://www.alpes-isere.com/en/sit/alpe-dhuez-the-mythical-climb-233170/).
- The GPX linked by that page:
  https://static.apidae-tourisme.com/filestore/objets-touristiques/plans/25/241/10547481/04-alpe-d-huez-la-montee-mythique.gpx
  Its outbound turnaround is near `45.09497, 6.07068`, about 5.9 m from the
  selected OSM finish vertex. The GPX was used only for this cross-check; its
  coordinates and recorded elevations were not copied into the output route.

OSM is a community road centreline map, not a lane survey or official event
course certification. Route elevations are sampled from this coarse terrain
mesh; road cuts, bridges and the short Vieil Alpe tunnel are not modelled as
separate structures. They can produce local artificial dips or rises. In
particular, accumulated DEM ascent is **not** an authoritative climb-gain or
gradient measurement. Do not treat 0.1 m storage increments as vertical accuracy.

## Rebuilding and validation

Run from the project root:

```sh
node scripts/build-terrain.mjs
```

Requires Python 3 with Pillow and Node; `ALPE_PYTHON` can name another Python
executable. No npm installation or network access is required. The script verifies
every cached source SHA-256, decodes the PNGs with Pillow, regenerates the grid,
selects the connected directed road path, samples route heights on the delivered
mesh and writes `public/data/alpe-terrain.json`. Source files are read only.

Validation covers array dimensions, finite numeric elevations, plausible terrain
and endpoint heights, all route points inside bounds, a connected path without
loops, maximum segment spacing, retained OSM geometry and deterministic output.
An internal hillshade with the road overlay was visually checked for geographic
orientation and switchback continuity. No UI or package configuration is changed.
