openstreetmap-tools/web/templates/api.html
Edward Betts a79b0a7098 Make public transport routes discoverable with shareable search
Add one-click examples and route search by number or metadata, with transport type, location and map-area filters. Preserve search filters and bounds in shareable URLs and restore searches on reload and browser navigation.

Validate with 91 passing tests, including desktop and mobile browser coverage, and strict mypy checks.

Closes #2
2026-09-30 22:26:52 +01:00

295 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>API Documentation – OSM Public Transport → GeoJSON</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<style>
pre { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 1rem; font-size: 0.85rem; }
.endpoint-url { font-family: monospace; font-size: 1rem; }
.badge-get { background: #0d6efd; }
h2 { margin-top: 2.5rem; }
h3 { margin-top: 2rem; font-size: 1.1rem; }
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark px-3" style="height:56px">
<a class="navbar-brand" href="{{ url_for('index') }}">OSM Public Transport → GeoJSON</a>
<a class="nav-link text-white" href="{{ url_for('docs') }}">API docs</a>
</nav>
<div class="container py-5" style="max-width:860px">
<h1 class="mb-1">API Documentation</h1>
<p class="text-muted mb-4">JSON API for fetching OSM public transport routes as GeoJSON.</p>
<p>All endpoints are read-only and require no authentication. Data is fetched
live from the <a href="https://wiki.openstreetmap.org/wiki/API_v0.6">OSM API v0.6</a>.
Route search uses <a href="https://wiki.openstreetmap.org/wiki/Overpass_API">Overpass</a>
to find relation IDs by route metadata.</p>
<h2><span class="badge bg-primary me-2">GET</span><code>/api/search?q=TEXT&amp;mode=TYPE</code></h2>
<p>Search worldwide for public transport <code>route</code> and <code>route_master</code>
relations. Required <code>q</code> is 2–100 characters. Route numbers (<code>ref</code>)
match exactly; names, termini, operators and networks match literal substrings,
case-insensitively. Optional <code>mode</code> filters by a supported transport type.</p>
<p>Optional <code>south</code>, <code>west</code>, <code>north</code> and <code>east</code>
coordinates restrict the search to routes with mapped members within a bounding box,
including their parent route masters. All four coordinates are required together.
Routes may extend outside the box. Bounds must be finite and ordered, span at most
20 degrees in either direction, and must not cross the date line.</p>
<p>Returns <code>{"routes": [...], "truncated": false}</code>. Each result has
<code>id</code>, <code>name</code>, <code>ref</code>, <code>from</code>, <code>to</code>,
<code>operator</code>, <code>network</code>, <code>route</code> (transport type) and
<code>type</code> (relation type). At most 50 results are returned; <code>truncated</code>
indicates more matches. No matches returns an empty list. Invalid input returns
400 <code>invalid_query</code>; upstream failures return 502 <code>osm_error</code>.</p>
<pre>GET /api/search?q=M11&amp;mode=subway</pre>
<pre>GET /api/search?q=24&amp;mode=bus&amp;south=51.39&amp;west=-2.73&amp;north=51.55&amp;east=-2.51</pre>
<h2><span class="badge bg-primary me-2">GET</span><code>/api/locations?q=PLACE</code></h2>
<p>Find up to five candidate places using Nominatim. Required <code>q</code> is 2–100
characters. Returns <code>{"locations": [{"label": "...", "bounds": [south, west, north, east]}]}</code>.
An empty list means no matching location. Invalid input returns 400 <code>invalid_query</code>;
upstream failures return 502 <code>osm_error</code>. Location lookups are submitted explicitly,
cached and limited to one upstream request per second per application process.</p>
<pre>GET /api/locations?q=Bristol%2C+UK</pre>
<p>All error responses return JSON with at least <code>error</code> (machine-readable
code) and <code>message</code> (human-readable description) fields.</p>
<!-- ── GET /api/route/<relation_id> ──────────────────────────────────── -->
<h2>
<span class="badge badge-get me-2" style="background:#0d6efd;font-size:0.75rem;vertical-align:middle">GET</span>
<span class="endpoint-url">/api/route/<em>&lt;relation_id&gt;</em></span>
</h2>
<p>Returns the full route as a GeoJSON <code>FeatureCollection</code>, the
ordered list of stops, and links to sibling routes in the same
<code>route_master</code> (if any).</p>
<h3>Path parameters</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>relation_id</code></td><td>integer</td><td>OSM relation ID of a public transport route.</td></tr>
</tbody>
</table>
<h3>Success response <span class="badge bg-success">200 OK</span></h3>
<pre>{
"name": "M11: Arnavutköy Hastane → Gayrettepe",
"ref": "M11",
"stops": [
{ "name": "Arnavutköy Hastane", "lat": 41.1234, "lon": 28.7654 },
{ "name": "İstanbul Havalimanı", "lat": 41.2701, "lon": 28.7519 },
...
],
"geojson": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[28.765, 41.123], ...] },
"properties": { "name": "M11: Arnavutköy Hastane → Gayrettepe",
"ref": "M11", "from": "Arnavutköy Hastane",
"to": "Gayrettepe", "route": "subway" }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [28.765, 41.123] },
"properties": { "name": "Arnavutköy Hastane" }
},
...
]
},
"other_directions": [
{ "id": 15083964, "name": "M11: Gayrettepe → Arnavutköy Hastane", "ref": "M11" }
]
}</pre>
<p>The <code>geojson</code> field is a <code>FeatureCollection</code> containing
one <code>LineString</code> for the route geometry followed by one
<code>Point</code> per stop. The <code>LineString</code> carries the route tags
as properties (<code>name</code>, <code>ref</code>, <code>from</code>,
<code>to</code>, <code>route</code>). Each <code>Point</code> has a
<code>name</code> property.</p>
<p><code>other_directions</code> lists sibling routes from the same
<code>route_master</code> relation. It is an empty array if the route has no
parent <code>route_master</code>.</p>
<h3>Error responses</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Status</th><th><code>error</code></th><th>When</th></tr></thead>
<tbody>
<tr><td>404</td><td><code>osm_error</code></td><td>Relation not found on OpenStreetMap.</td></tr>
<tr><td>422</td><td><code>is_route_master</code></td><td>Relation is a <code>route_master</code>. Use <code>/api/route_master/&lt;id&gt;</code> instead.</td></tr>
<tr><td>422</td><td><code>not_public_transport</code></td><td>Relation exists but is not a supported public transport route.</td></tr>
<tr><td>502</td><td><code>osm_error</code></td><td>OSM API returned an unexpected error.</td></tr>
</tbody>
</table>
<h3>Example</h3>
<pre>GET /api/route/15083963</pre>
<!-- ── GET /api/segment/<relation_id> ────────────────────────────────── -->
<h2>
<span class="badge me-2" style="background:#0d6efd;font-size:0.75rem;vertical-align:middle">GET</span>
<span class="endpoint-url">/api/segment/<em>&lt;relation_id&gt;</em></span>
</h2>
<p>Returns a GeoJSON <code>FeatureCollection</code> for the portion of the
route between two named stops. Stops are matched case-insensitively.</p>
<h3>Path parameters</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>relation_id</code></td><td>integer</td><td>OSM relation ID of a public transport route.</td></tr>
</tbody>
</table>
<h3>Query parameters</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Parameter</th><th>Required</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>from</code></td><td>Yes</td><td>Name of the start stop (case-insensitive).</td></tr>
<tr><td><code>to</code></td><td>Yes</td><td>Name of the end stop (case-insensitive).</td></tr>
<tr><td><code>stops</code></td><td>No</td><td>Include stop <code>Point</code> features. <code>1</code> (default) to include, <code>0</code> to omit.</td></tr>
</tbody>
</table>
<h3>Success response <span class="badge bg-success">200 OK</span></h3>
<pre>{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[28.800, 41.259], ...] },
"properties": { "name": "M11: Arnavutköy Hastane → Gayrettepe",
"ref": "M11", "from": "Arnavutköy Hastane",
"to": "Gayrettepe", "route": "subway" }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [28.800, 41.259] },
"properties": { "name": "İstanbul Havalimanı" }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [28.820, 41.247] },
"properties": { "name": "Hasdal" }
}
]
}</pre>
<p>The <code>LineString</code> is trimmed to the shortest path between the
two stops along the route geometry. The <code>from</code> stop is treated as
the start regardless of the order the names are supplied — the segment always
follows the route direction.</p>
<h3>Error responses</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Status</th><th><code>error</code></th><th>When</th></tr></thead>
<tbody>
<tr><td>400</td><td><code>missing_params</code></td><td><code>from</code> or <code>to</code> parameter is absent.</td></tr>
<tr><td>404</td><td><code>osm_error</code></td><td>Relation not found on OpenStreetMap.</td></tr>
<tr><td>404</td><td><code>station_not_found</code></td><td>One or both stop names were not found. The response includes an <code>available</code> array listing valid stop names.</td></tr>
<tr><td>422</td><td><code>not_public_transport</code></td><td>Relation is not a supported public transport route.</td></tr>
<tr><td>502</td><td><code>osm_error</code></td><td>OSM API returned an unexpected error.</td></tr>
</tbody>
</table>
<h3>Example</h3>
<pre>GET /api/segment/15083963?from=İstanbul+Havalimanı&to=Hasdal</pre>
<pre>GET /api/segment/15083963?from=İstanbul+Havalimanı&to=Hasdal&stops=0</pre>
<!-- ── GET /api/route_master/<relation_id> ────────────────────────────── -->
<h2>
<span class="badge me-2" style="background:#0d6efd;font-size:0.75rem;vertical-align:middle">GET</span>
<span class="endpoint-url">/api/route_master/<em>&lt;relation_id&gt;</em></span>
</h2>
<p>Returns all member routes of an OSM <code>route_master</code> relation,
each with its GeoJSON geometry (stops omitted). Use this to display all
directions of a line on a map.</p>
<h3>Path parameters</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>relation_id</code></td><td>integer</td><td>OSM relation ID of a <code>route_master</code> relation.</td></tr>
</tbody>
</table>
<h3>Success response <span class="badge bg-success">200 OK</span></h3>
<pre>{
"name": "M11",
"ref": "M11",
"routes": [
{
"id": 15083963,
"name": "M11: Arnavutköy Hastane → Gayrettepe",
"ref": "M11",
"from": "Arnavutköy Hastane",
"to": "Gayrettepe",
"geojson": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[28.765, 41.123], ...] },
"properties": { "name": "M11: Arnavutköy Hastane → Gayrettepe", ... }
}
]
}
},
{
"id": 15083964,
"name": "M11: Gayrettepe → Arnavutköy Hastane",
"ref": "M11",
"from": "Gayrettepe",
"to": "Arnavutköy Hastane",
"geojson": { ... }
}
]
}</pre>
<p>Each entry in <code>routes</code> contains the individual route's
<code>id</code>, <code>name</code>, <code>ref</code>, <code>from</code>,
<code>to</code> tags, and a <code>geojson</code> <code>FeatureCollection</code>
with only the <code>LineString</code> geometry (no stop points). If a member
route cannot be fetched, it is included with <code>"geojson": null</code>.</p>
<h3>Error responses</h3>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr><th>Status</th><th><code>error</code></th><th>When</th></tr></thead>
<tbody>
<tr><td>404</td><td><code>osm_error</code></td><td>Relation not found on OpenStreetMap.</td></tr>
<tr><td>422</td><td><code>osm_error</code></td><td>Relation exists but is not a <code>route_master</code>.</td></tr>
<tr><td>502</td><td><code>osm_error</code></td><td>OSM API returned an unexpected error.</td></tr>
</tbody>
</table>
<h3>Example</h3>
<pre>GET /api/route_master/15083966</pre>
<!-- ── Supported route types ─────────────────────────────────────────── -->
<h2>Supported route types</h2>
<p>The following OSM <code>route</code> tag values are accepted:</p>
<p>
<code>bus</code> &nbsp;
<code>ferry</code> &nbsp;
<code>funicular</code> &nbsp;
<code>light_rail</code> &nbsp;
<code>monorail</code> &nbsp;
<code>subway</code> &nbsp;
<code>train</code> &nbsp;
<code>tram</code> &nbsp;
<code>trolleybus</code>
</p>
</div>
</body>
</html>