Main Roads & Railways API
Access & limitsPublic, no key needed · Heavy▾
Open to everyone. A free API key from your profile raises the limits: send it in an X-API-Key header, or as api_key= in the address.
| 5 minutes | Hour | Day | |
|---|---|---|---|
| Without a key | 20 | 120 | 500 |
| With a key | 60 | 400 | 2,000 |
Each 100 square km of area counts as one request, so a 10 km square is 1 and a 30 km square is 9.
Log in to create a free key on your profile page.
Every answer carries X-RateLimit-Remaining and X-RateLimit-Reset. Over a limit, the answer is error 429 with retry_after in seconds. Refused requests do not count.
Where the main roads and railways are over a square around a point, and what each one is: every cell of about 10 metres comes back as one of seven classes, from motorways to heritage railways, the same data the Main Roads and Railways Map draws. It is meant as a landing hazard layer beside the Inland Water API, so a landing tool can keep a wider margin from a motorway than from a quiet road: ask for it as JSON for a web page or script, or as a small binary file that a flight computer or ground station can carry.
lat lon size_kmParameters
| Parameter | Meaning |
|---|---|
lat, lon | The middle of the square, decimal degrees (lng also accepted) |
size_km | The square's side in km, more than 0 and up to 30. Left out, 1. Squares over 10 km come as format=bin only. |
format | Optional. json (the default, for squares up to 10 km) or bin: a 32 byte header and the cells, as a file, for any size. |
compress | Optional. none (the default), zlib or lzma, as on the Inland Water API. |
classes | Optional. The classes to keep, as a list such as 1,2,5; every other class comes back as 0. Left out, all seven. |
download | Optional. 1 makes a browser save a format=bin answer as a file, named for the square. |
The classes
| Class | What | OpenStreetMap types |
|---|---|---|
| 0 | Nothing | |
| 1 | Motorway and trunk road | motorway, trunk, and their slip roads |
| 2 | A road | primary and its slip roads |
| 3 | B road | secondary and its slip roads |
| 4 | Tertiary and minor road | tertiary and its slip roads; unclassified (the public minor roads and lanes between villages and farms) |
| 5 | Heavy railway | rail |
| 6 | Medium railway | light rail, tram, subway, monorail |
| 7 | Light and heritage railway | narrow gauge, miniature railway, funicular, rack railway |
Any cell a road or railway crosses at all takes its class, so each comes back as an unbroken line of cells however it runs. Where two share a cell the worse hazard is kept, in this order, worst first: 1 motorway and trunk, 5 heavy railway, 2 A road, 6 medium railway, 3 B road, 7 light and heritage railway, 4 tertiary road. Anything in a tunnel is left out, and so are disused railways; the minor roads and lanes between villages and farms (OpenStreetMap's unclassified roads) count with tertiary as class 4, while town side streets, driveways, car parks and farm tracks are never included. Heavy railway is every standard gauge line, so standard gauge heritage lines count as heavy.
The cells
The grid is the Inland Water API's: cells of 1/12000 of a degree each way (about 9.3 m north to south), row 0 the north edge and column 0 the west edge, rows running south and columns east. Each cell is three bits, its class, the cells laid end to end along the row with the first cell in the highest bits of the first byte, so eight cells fill exactly three bytes. Every row is padded to a whole number of bytes, so row j starts at byte j * row_bytes.
Ground not yet in the database is 0 as well; the JSON answer's coverage_pct says how much of the square the database holds, road_pct how much of it is any road or railway, and class_pct how much is each class kept.
Sizes
At UK latitudes (54°N), before compression:
| Square | Cells | File |
|---|---|---|
| 1 km | 108 × 186 | 7.4 KB |
| 3 km | 324 × 556 | 66 KB |
| 5 km | 540 × 926 | 183 KB |
| 10 km | 1,078 × 1,850 | 731 KB |
| 20 km | 2,156 × 3,700 | 2.9 MB |
| 30 km | 3,234 × 5,550 | 6.4 MB |
Roads and railways cover a tiny share of the ground, so compression takes these down to a small fraction of their raw size.
The binary file
The same 32 byte header as the water and tree canopy files, with version 2 and three bits a cell.
| Byte | Type | Value |
|---|---|---|
| 0 | char[4] | ACRD |
| 4 | uint8 | Version, 2 |
| 5 | uint8 | Bits per cell, 3 |
| 6 | uint16 | Cells per degree, 12000 |
| 8 | int32 | North edge in cells: its latitude × 12000 |
| 12 | int32 | West edge in cells: its longitude × 12000 |
| 16 | uint32 | Rows |
| 20 | uint32 | Columns |
| 24 | uint32 | Bytes per row |
| 28 | uint32 | 0, reserved |
A position maps to its cell exactly: row north_edge - 1 - floor(lat * 12000), column floor(lon * 12000) - west_edge.
Examples
https://www.altimetercloud.com/api/roads/?lat=54.35&lon=-2.95&size_km=3
https://www.altimetercloud.com/api/roads/?lat=54.35&lon=-2.95&size_km=5&classes=1,2,5&format=bin&compress=zlib
https://www.altimetercloud.com/api/roads/?lat=54.35&lon=-2.95&size_km=10&format=bin&compress=lzma&download=1
Reading the cells
On a device
The header is the same structure as the water file's (see the Inland Water API); cells is the bytes straight after it. A cell's three bits can straddle two bytes, so read them as a bit position along the row:
// C: the class at a position, 0 to 7, or -1 outside the square
int roadClassAt(const GridHeader *h, const uint8_t *cells, double lat, double lon) {
int32_t j = h->north_edge - 1 - (int32_t)floor(lat * h->cells_per_deg);
int32_t i = (int32_t)floor(lon * h->cells_per_deg) - h->west_edge;
if (j < 0 || i < 0 || j >= (int32_t)h->rows || i >= (int32_t)h->cols) return -1;
const uint8_t *row = cells + (uint32_t)j * h->row_bytes;
uint32_t bit = (uint32_t)i * 3; // first bit of the cell along the row
uint32_t two = ((uint32_t)row[bit >> 3] << 8)
| ((bit >> 3) + 1 < h->row_bytes ? row[(bit >> 3) + 1] : 0);
return (two >> (13 - (bit & 7))) & 7; // the 3 bits, wherever they start
}
On a computer
# Python
import requests, struct, zlib, numpy as np
r = requests.get('https://www.altimetercloud.com/api/roads/',
params={'lat': 54.35, 'lon': -2.95, 'size_km': 3, 'format': 'bin', 'compress': 'zlib'})
f = zlib.decompress(r.content) # or lzma.decompress(r.content, format=lzma.FORMAT_ALONE)
magic, ver, bits, cpd, north, west, rows, cols, rb, _ = struct.unpack('<4sBBHiiIIII', f[:32])
b = np.unpackbits(np.frombuffer(f[32:], np.uint8).reshape(rows, rb), axis=1)[:, :cols * 3]
classes = (b[:, 0::3] << 2) | (b[:, 1::3] << 1) | b[:, 2::3] # rows x cols, 0 to 7
The JSON answer
{
"success": true,
"lat", "lon", "size_km", "bits", bits is 3
"rows", "cols", "cells", "row_bytes", the grid, as above
"cells_per_degree", "north_edge_cell", "west_edge_cell",
"north", "south", "west", "east", the square's edges, degrees
"cell_deg", "cell_m_north_south", "cell_m_east_west",
"road_pct", cells with any road or railway kept
"class_pct": { "1": ..., "5": ... }, each class kept
"classes": { "1": "motorway and trunk", ... },
"kept": [1, 2, 3, 4, 5, 6, 7], the classes asked for
"coverage_pct", cells the database holds
"meaning", "order", "compress", "bytes",
"data_b64", the rows, row 0 first, compressed if asked
"file_name", "seconds",
"credits": { "roads", "source", "licence", "full" }
}
Errors
| Code | Meaning |
|---|---|
412 | A parameter is missing or not valid, the square is more than 30 km, JSON was asked for over 10 km, or classes= is not a list of 1 to 7 |
413 | The square has too many cells, which only happens very close to the poles |
429 | Too many requests from your address, or one still being answered |
501 | The compression asked for is not available just now |
Try it
The Main Roads and Railways Map has a download panel: move the map so the cross sits on your launch site, choose the size and compression, and it saves the file from this API.
Road and railway data credits
Contains OpenStreetMap data © OpenStreetMap contributors, available under the Open Database Licence (ODbL), through the regional extracts published by Geofabrik. The main roads and railways are reduced here to a class a cell, which changes them: this is not the original data.
Licence of this data. Because it contains OpenStreetMap data, the data this API gives out is made available under the Open Database License (ODbL) 1.0. You may use it freely, commercially too, as long as you credit it as above and share any database you make from it under the same licence.
Crediting AltimeterCloud
Free to use. If you use this API, or anything made from the answers, in a commercial product or service, credit AltimeterCloud with a link to www.altimetercloud.com at the point of use: on the screen, page or printout where the data appears, for example “Data from AltimeterCloud.com”. Any credits the data's own sources ask for, listed on this page, apply as well.



















