diff --git a/Dockerfile b/Dockerfile index 4f6c7d2..d55be7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,6 @@ EXPOSE 3000 ENV HOSTNAME v5.db.transport.rest ENV PORT 3000 +RUN npm run build + CMD ["node", "index.js"] diff --git a/api-docs.js b/api-docs.js new file mode 100644 index 0000000..4b3047a --- /dev/null +++ b/api-docs.js @@ -0,0 +1,455 @@ +'use strict' + +const generateApiDocs = require('hafas-rest-api/tools/generate-docs') +const {api} = require('./api') + +const HEAD = `\ +# \`v5.db.transport.rest\` API documentation + +[\`v5.db.transport.rest\`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net). Data is being returned as [JSON](https://www.json.org/). + +You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 request/minute (burst 200 requests/minute) set up. + +*Note:* For [URL-encoding](https://de.wikipedia.org/wiki/URL-Encoding), this documentation uses the \`url-encode\` tool of the [\`url-decode-encode-cli\` package](https://www.npmjs.com/package/url-decode-encode-cli). +` + +const order = [ + '/locations', + '/stops/nearby', + '/stops/reachable-from', + '/stops/:id', + '/stops/:id/departures', + '/stops/:id/arrivals', + '/journeys', + '/journeys/:ref', + '/trips/:id', + '/stations', + '/stations/:id', + '/radar', +] + +const descriptions = { + '/locations': `\ +Uses [\`hafasClient.locations()\`](https://github.com/public-transport/hafas-client/blob/5/docs/locations.md) to **find stops/stations, POIs and addresses matching \`query\`**. +`, + '/stops/nearby': `\ +Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client/blob/5/docs/nearby.md) to **find stops/stations close to the given geolocation**. +`, + '/stops/reachable-from': `\ +Uses [\`hafasClient.reachableFrom()\`](https://github.com/public-transport/hafas-client/blob/5/docs/reachable-from.md) to **find stops/stations reachable within a certain time from an address**. +`, + '/stops/:id': `\ +Uses [\`hafasClient.stop()\`](https://github.com/public-transport/hafas-client/blob/5/docs/stop.md) to **find a stop/station by ID**. +`, + '/stops/:id/departures': `\ +Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-client/blob/5/docs/departures.md) to **get departures at a stop/station**. +`, + '/stops/:id/arrivals': `\ +Works like [\`/stops/:id/departures\`](#get-stopsiddepartures), except that it uses [\`hafasClient.arrivals()\`](https://github.com/public-transport/hafas-client/blob/5/docs/arrivals.md) to **arrivals at a stop/station**. +`, + '/stations': `\ +If the \`query\` parameter is used, it will use [\`db-stations-autocomplete\`](https://npmjs.com/package/db-stations-autocomplete) to autocomplete *Deutsche Bahn*-operated stops/stations. Otherwise, it will filter the stops/stations in [\`db-stations\`](https://npmjs.com/package/db-stations). + +Instead of receiving a JSON response, you can request [newline-delimited JSON](http://ndjson.org) by sending \`Accept: application/x-ndjson\`. +`, + '/stations/:id': `\ +Returns a stop/station from [\`db-stations\`](https://npmjs.com/package/db-stations). +`, + '/journeys': `\ +Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) to **find journeys from A (\`from\`) to B (\`to\`)**. + +\`from\` (A), \`to\` (B), and the optional \`via\` must each have one of these formats: + +- as stop/station ID (e.g. \`from=8010159\` for *Halle (Saale) Hbf*) +- as a POI (e.g. \`from.id=991561765&from.latitude=51.48364&from.longitude=11.98084\` for *Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)*) +- as an address (e.g. \`from.latitude=51.25639&from.longitude=7.46685&from.address=Hansestadt+Breckerfeld,+Hansering+3\` for *Hansestadt Breckerfeld, Hansering 3*) + +### Pagination + +Given a response, you can also fetch more journeys matching the same criteria. Instead of \`from*\`, \`to*\` & \`departure\`/\`arrival\`, pass \`earlierRef\` from the first response as \`earlierThan\` to get journeys "before", or \`laterRef\` as \`laterThan\` to get journeys "after". + +Check the [\`hafasClient.journeys()\` docs](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) for more details. +`, + '/journeys/:ref': `\ +Uses [\`hafasClient.refreshJourney()\`](https://github.com/public-transport/hafas-client/blob/5/docs/refresh-journey.md) to **"refresh" a journey, using its \`refreshToken\`**. + +The journey will be the same (equal \`from\`, \`to\`, \`via\`, date/time & vehicles used), but you can get up-to-date realtime data, like delays & cancellations. +`, + '/trips/:id': `\ +Uses [\`hafasClient.trip()\`](https://github.com/public-transport/hafas-client/blob/5/docs/trip.md) to **fetch a trip by ID**. + +A trip is a specific vehicle, stopping at a series of stops at specific points in time. Departures, arrivals & journey legs reference trips by their ID. +`, + '/radar': `\ +Uses [\`hafasClient.radar()\`](https://github.com/public-transport/hafas-client/blob/5/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements. +`, +} + +const examples = { + '/locations': `\ +### Example + +\`\`\`shell +curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq +\`\`\` + +\`\`\`js +[ + { + "type": "stop", + "id": "8010159", + "name": "Halle (Saale) Hbf", + "location": { + "type": "location", + "id": "8010159", + "latitude": 51.477079, + "longitude": 11.98699 + }, + "products": { + "nationalExpress": true, + "national": true, + // … + } + } +] +\`\`\` +`, + '/stops/nearby': `\ +### Example + +\`\`\`shell +curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq +\`\`\` + +\`\`\`js +[ + { + "type": "stop", + "id": "694800", + "name": "Böttgerstraße, Hamburg", + "location": { + "type": "location", + "id": "694800", + "latitude": 53.568356, + "longitude": 9.995528 + }, + "products": { /* … */ }, + "distance": 498, + }, + // … + { + "type": "stop", + "id": "694802", + "name": "Bahnhof Dammtor, Hamburg", + "location": { + "type": "location", + "id": "694802", + "latitude": 53.561048, + "longitude": 9.990315 + }, + "products": { /* … */ }, + "distance": 1340, + }, + // … +] +\`\`\` +`, + '/stops/reachable-from': `\ +### Example + +\`\`\`shell +curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq +\`\`\` + +\`\`\`js +[ + { + "duration": 1, + "stations": [ + { + "type": "stop", + "id": "694815", + "name": "Handwerkskammer, Hamburg", + "location": { /* … */ }, + "products": { /* … */ }, + }, + ] + }, + // … + { + "duration": 5, + "stations": [ + { + "type": "stop", + "id": "694807", + "name": "Feldstraße (U), Hamburg", + "location": { /* … */ }, + "products": { /* … */ }, + // … + }, + // … + ] + }, + // … +] +\`\`\` +`, + '/stops/:id': `\ +### Example + +\`\`\`shell +curl 'https://v5.db.transport.rest/stops/8010159' -s | jq +\`\`\` + +\`\`\`js +{ + "type": "stop", + "id": "8010159", + "ids": { + "dhid": "de:15002:8010159", + "MDV": "8010159", + "NASA": "8010159" + }, + "name": "Halle (Saale) Hbf", + "location": { + "type": "location", + "id": "8010159", + "latitude": 51.477079, + "longitude": 11.98699 + }, + "products": { /* … */ }, + // … +} +\`\`\` +`, + '/stations': `\ +### Examples + +\`\`\`shell +# autocomplete using db-stations-autocomplete +curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq +\`\`\` + +\`\`\`js +{ + "8002548": { + "id": "8002548", + "relevance": 0.8572361756428573, + "score": 9.175313823998414, + "weight": 1212, + "type": "station", + "ril100": "ADF", + "name": "Hamburg Dammtor", + "location": { + "type": "location", + "latitude": 53.560751, + "longitude": 9.989566 + }, + "operator": { + "type": "operator", + "id": "hamburger-verkehrsverbund-gmbh", + "name": "BWVI" + }, + "address": { + "city": "Hamburg", + "zipcode": "20354", + "street": "Dag-Hammarskjöld-Platz 15" + }, + // … + }, + // … +} +\`\`\` + +\`\`\`shell +# filter db-stations by \`hasParking\` property +curl 'https://v5.db.transport.rest/stations?hasParking=true' -s | jq +\`\`\` + +\`\`\`js +{ + "8000001": { + "type": "station", + "id": "8000001", + "ril100": "KA", + "name": "Aachen Hbf", + "weight": 653.75, + "location": { /* … */ }, + "operator": { /* … */ }, + "address": { /* … */ }, + // … + }, + // … +} +\`\`\` + +\`\`\`shell +# filter db-stations by \`hasDBLounge\` property, get newline-delimited JSON +curl 'https://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq +\`\`\` +`, + '/stations/:id': `\ +### Example + +\`\`\`shell +# lookup Halle (Saale) Hbf +curl 'https://v5.db.transport.rest/stations/8010159' -s | jq +curl 'https://v5.db.transport.rest/stations/LH' -s | jq # RIL100/DS100 +curl 'https://v5.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100 +\`\`\` + +\`\`\`js +{ + "type": "station", + "id": "8010159", + "additionalIds": ["8098159"], + "ril100": "LH", + "nr": 2498, + "name": "Halle (Saale) Hbf", + "weight": 815.6, + "location": { /* … */ }, + "operator": { /* … */ }, + "address": { /* … */ }, + "ril100Identifiers": [ + { + "rilIdentifier": "LH", + // … + }, + // … + ], + // … +} +\`\`\` +`, + '/stops/:id/departures': `\ +### Example + +\`\`\`shell +# at Halle (Saale) Hbf, in direction Berlin Südkreuz +curl 'https://v5.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq +\`\`\` + +\`\`\`js +[ + { + "tripId": "1|317591|0|80|1052020", + "direction": "Berlin Hbf (tief)", + "line": { + "type": "line", + "id": "ice-702", + "name": "ICE 702", + "mode": "train", + "product": "nationalExpress", + // … + }, + + "when": "2020-05-01T21:06:00+02:00", + "plannedWhen": "2020-05-01T21:06:00+02:00", + "delay": 0, + "platform": "8", + "plannedPlatform": "8", + + "stop": { + "type": "stop", + "id": "8010159", + "name": "Halle (Saale) Hbf", + "location": { /* … */ }, + "products": { /* … */ }, + }, + + "remarks": [], + // … + } +] +\`\`\` +`, + '/stops/:id/arrivals': `\ +### Example + +\`\`\`shell +# at Halle (Saale) Hbf, 10 minutes +curl 'https://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq +\`\`\` +`, + '/journeys': `\ +### Examples + +\`\`\`shell +# stop/station to POI +curl 'https://v5.db.transport.rest/journeys?from=8010159&to.id=991561765&to.latitude=51.483641&to.longitude=11.980841' -s | jq +# without buses, with ticket info +curl 'https://v5.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq +\`\`\` +`, + '/journeys/:ref': `\ +### Example + +\`\`\`shell +# get the refreshToken of a journey +journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') +refresh_token=$(echo $journey | jq -r '.refreshToken') + +# refresh the journey +curl "https://v5.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq +\`\`\` +`, + '/trips/:id': `\ +### Example + +\`\`\`shell +# get the trip ID of a journey leg +journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') +journey_leg=$(echo $journey | jq -r '.legs[0]') +trip_id=$(echo $journey_leg | jq -r '.tripId') + +# fetch the trip +curl "https://v5.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq +\`\`\` +`, + '/radar': `\ +### Example + +\`\`\`shell +bbox='north=53.555&west=9.989&south=53.55&east=10.001' +curl "https://v5.db.transport.rest/radar?$bbox&results=10" -s | jq +\`\`\` +`, +} + +const { + listOfRoutes, + routes, + tail, +} = generateApiDocs(api.routes) + +const sortedRoutes = Object.entries(routes) +.sort(([routeA], [routeB]) => { + const iA = order.indexOf(routeA) + const iB = order.indexOf(routeB) + if (iA >= 0 && iB >= 0) return iA - iB + if (iA < 0 && iB >= 0) return 1 + if (iB < 0 && iA >= 0) return -1 + return 0 +}) + +const write = process.stdout.write.bind(process.stdout) + +write(HEAD) +write(`\n\n`) + +write(listOfRoutes) +write(`\n\n`) + +for (const [route, params] of sortedRoutes) { + write(`## \`GET ${route}\`\n\n`) + write(descriptions[route] || '') + write(` +### Query Parameters +`) + write(params) + if (examples[route]) { + write('\n' + examples[route]) + } + write(`\n\n`) +} +write(tail) diff --git a/api.js b/api.js new file mode 100644 index 0000000..3d44f86 --- /dev/null +++ b/api.js @@ -0,0 +1,42 @@ +'use strict' + +const createHafas = require('db-hafas') +const createApi = require('hafas-rest-api') +const createHealthCheck = require('hafas-client-health-check') + +const pkg = require('./package.json') +const stations = require('./routes/stations') +const station = require('./routes/station') + +const hafas = createHafas(pkg.name) + +const berlinHbf = '8011160' +const healthCheck = createHealthCheck(hafas, berlinHbf) + +const modifyRoutes = (routes) => { + routes['/stations/:id'] = station + routes['/stations'] = stations + return routes +} + +const config = { + hostname: process.env.HOSTNAME || 'localhost', + port: process.env.PORT ? parseInt(process.env.PORT) : 3000, + name: pkg.name, + description: pkg.description, + homepage: pkg.homepage, + version: pkg.version, + docsLink: 'https://github.com/derhuerst/db-rest/blob/5/docs/readme.md', + logging: true, + aboutPage: true, + etags: 'strong', + healthCheck, + modifyRoutes, +} + +const api = createApi(hafas, config, () => {}) + +module.exports = { + config, + api, +} diff --git a/docs/api.md b/docs/api.md index 167069c..528a5a9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,228 +1,610 @@ -# [*Deutsche Bahn*](https://en.wikipedia.org/wiki/Deutsche_Bahn) Public Transport API +# `v5.db.transport.rest` API documentation -**The public endpoint is [`2.db.transport.rest`](`https://2.db.transport.rest`).** This API returns data in the [*Friendly Public Transport Format* `1.2.0`](https://github.com/public-transport/friendly-public-transport-format/blob/1.2.0/spec/readme.md). +[`v5.db.transport.rest`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net). Data is being returned as [JSON](https://www.json.org/). -*Note:* In order to improve this API, I would like to know which software projects use it. Please send an **`X-Identifier` header (e.g. `my-awesome-tool`) to let me know who you are**. I you don't provide it, a hash of the client IP will be logged. +You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 request/minute (burst 200 requests/minute) set up. -## all routes +*Note:* For [URL-encoding](https://de.wikipedia.org/wiki/URL-Encoding), this documentation uses the `url-encode` tool of the [`url-decode-encode-cli` package](https://www.npmjs.com/package/url-decode-encode-cli). -- [`GET /stations?query=…`](#get-stationsquery) -- [`GET /stations`](#get-stations) -- [`GET /stations/:id/departures`](#get-stationsiddepartures) + +## Routes + +*Note:* These routes only wrap [`hafas-client@5` methods](https://github.com/public-transport/hafas-client/blob/5/docs/readme.md), check their docs for more details. + + +- [`GET /stops/nearby`](#get-stopsnearby) +- [`GET /stops/reachable-from`](#get-stopsreachable-from) +- [`GET /stops/:id`](#get-stopsid) +- [`GET /stops/:id/departures`](#get-stopsiddepartures) +- [`GET /stops/:id/arrivals`](#get-stopsidarrivals) - [`GET /journeys`](#get-journeys) -- [`GET /journeys/:ref`](#get-journeysref) - [`GET /trips/:id`](#get-tripsid) - [`GET /locations`](#get-locations) +- [`GET /radar`](#get-radar) +- [`GET /journeys/:ref`](#get-journeysref) +- [`GET /stations/:id`](#get-stationsid) +- [`GET /stations`](#get-stations) +- [date/time parameters](#datetime-parameters) -## `GET /stations?query=…` -Passes all parameters into [`db-stations-autocomplete`](https://github.com/derhuerst/db-stations-autocomplete). +## `GET /locations` -- `query`: **Required.** -- `completion`: `true`/`false` – Default is `true` -- `fuzzy`: `true`/`false` – Default is `false` +Uses [`hafasClient.locations()`](https://github.com/public-transport/hafas-client/blob/5/docs/locations.md) to **find stops/stations, POIs and addresses matching `query`**. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`query` | **Required.** | string | – +`fuzzy` | Find more than exact matches? | boolean | `true` +`results` | How many stations shall be shown? | number | `10` +`stops` | Show stops/stations? | boolean | `true` +`addresses` | Show points of interest? | boolean | `true` +`poi` | Show addresses? | boolean | `true` +`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false` +`language` | Language of the results. | string | `en` + +### Example ```shell -curl 'https://2.db.transport.rest/stations?query=jungfernheide' -# note the typo -curl 'https://2.db.transport.rest/stations?query=jungfernhiede&fuzzy=true' +curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq +``` + +```js +[ + { + "type": "stop", + "id": "8010159", + "name": "Halle (Saale) Hbf", + "location": { + "type": "location", + "id": "8010159", + "latitude": 51.477079, + "longitude": 11.98699 + }, + "products": { + "nationalExpress": true, + "national": true, + // … + } + } +] ``` -## `GET /stations` +## `GET /stops/nearby` -Passes all parameters into [`db-stations`](https://github.com/derhuerst/db-stations). +Uses [`hafasClient.nearby()`](https://github.com/public-transport/hafas-client/blob/5/docs/nearby.md) to **find stops/stations close to the given geolocation**. -- `id`: Filter by ID. -- `name`: Filter by name. -- `coordinates.latitude`: Filter by latitude. -- `coordinates.longitude`: Filter by longitude. -- `weight`: Filter by weight. +### Query Parameters -`Content-Type`: [`application/x-ndjson`](http://ndjson.org/) +parameter | description | type | default value +----------|-------------|------|-------------- +`latitude` | **Required.** | number | – +`longitude` | **Required.** | number | – +`results` | maximum number of results | number | `8` +`distance` | maximum walking distance in meters | number | – +`stops` | Return stops/stations? | boolean | `true` +`poi` | Return points of interest? | boolean | `false` +`linesOfStops` | Parse & expose lines at each stop/station? | boolean | `false` +`language` | Language of the results. | string | `en` -### examples +### Example ```shell -curl 'https://2.db.transport.rest/stations?name=hannover&coordinates.latitude=52.3765387' +curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq +``` + +```js +[ + { + "type": "stop", + "id": "694800", + "name": "Böttgerstraße, Hamburg", + "location": { + "type": "location", + "id": "694800", + "latitude": 53.568356, + "longitude": 9.995528 + }, + "products": { /* … */ }, + "distance": 498, + }, + // … + { + "type": "stop", + "id": "694802", + "name": "Bahnhof Dammtor, Hamburg", + "location": { + "type": "location", + "id": "694802", + "latitude": 53.561048, + "longitude": 9.990315 + }, + "products": { /* … */ }, + "distance": 1340, + }, + // … +] ``` -## `GET /stations/all` +## `GET /stops/reachable-from` -Dumps `full.json` from [`db-stations`](https://github.com/derhuerst/db-stations). +Uses [`hafasClient.reachableFrom()`](https://github.com/public-transport/hafas-client/blob/5/docs/reachable-from.md) to **find stops/stations reachable within a certain time from an address**. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`latitude` | **Required.** | number | – +`longitude` | **Required.** | number | – +`address` | **Required.** | string | – +`when` | Date & time to compute the reachability for. See [date/time parameters](#datetime-parameters). | date+time | *now* +`maxTransfers` | Maximum number of transfers. | number | `5` +`maxDuration` | Maximum travel duration, in minutes. | number | *infinite* +`language` | Language of the results. | string | `en` +`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true` +`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true` +`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true` +`regional` | Include Regio (RB)? | boolean | `true` +`suburban` | Include S-Bahn (S)? | boolean | `true` +`bus` | Include Bus (B)? | boolean | `true` +`ferry` | Include Ferry (F)? | boolean | `true` +`subway` | Include U-Bahn (U)? | boolean | `true` +`tram` | Include Tram (T)? | boolean | `true` +`taxi` | Include Group Taxi (Taxi)? | boolean | `true` + +### Example ```shell -curl 'https://2.db.transport.rest/stations/all' +curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq +``` + +```js +[ + { + "duration": 1, + "stations": [ + { + "type": "stop", + "id": "694815", + "name": "Handwerkskammer, Hamburg", + "location": { /* … */ }, + "products": { /* … */ }, + }, + ] + }, + // … + { + "duration": 5, + "stations": [ + { + "type": "stop", + "id": "694807", + "name": "Feldstraße (U), Hamburg", + "location": { /* … */ }, + "products": { /* … */ }, + // … + }, + // … + ] + }, + // … +] ``` -## `GET /stations/:id/departures` +## `GET /stops/:id` -Returns departures at a station. +Uses [`hafasClient.stop()`](https://github.com/public-transport/hafas-client/blob/5/docs/stop.md) to **find a stop/station by ID**. -*Note:* As stated in the [*Friendly Public Transport Format* `1.2.0`](https://github.com/public-transport/friendly-public-transport-format/tree/1.2.0), the returned `departure` and `arrival` times include the current delay. +### Query Parameters -Passes all parameters into [`departures(…)` from `db-hafas`](https://github.com/public-transport/db-hafas/blob/master/docs/departures.md). +parameter | description | type | default value +----------|-------------|------|-------------- +`linesOfStops` | Parse & expose lines at each stop/station? | boolean | `false` +`language` | Language of the results. | string | `en` -- `when`: A [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) or anything parsable by [`parse-messy-time`](https://github.com/substack/parse-messy-time#example). Default: now. -- `duration`: Show departures for the next `n` minutes. Default: `10`. - -`Content-Type`: `application/json` - -### examples +### Example ```shell -curl 'https://2.db.transport.rest/stations/008011160/departures?when=tomorrow%206pm' +curl 'https://v5.db.transport.rest/stops/8010159' -s | jq +``` + +```js +{ + "type": "stop", + "id": "8010159", + "ids": { + "dhid": "de:15002:8010159", + "MDV": "8010159", + "NASA": "8010159" + }, + "name": "Halle (Saale) Hbf", + "location": { + "type": "location", + "id": "8010159", + "latitude": 51.477079, + "longitude": 11.98699 + }, + "products": { /* … */ }, + // … +} +``` + + +## `GET /stops/:id/departures` + +Uses [`hafasClient.departures()`](https://github.com/public-transport/hafas-client/blob/5/docs/departures.md) to **get departures at a stop/station**. + +### Query Parameters + +parameter | description | type | default value +----------|-------------|------|-------------- +`when` | Date & time to get departures for. See [date/time parameters](#datetime-parameters). | date+time | *now* +`direction` | Filter departures by direction. | string |   +`duration` | Show departures for how many minutes? | number | `10` +`results` | Max. number of departures. | number | *whatever HAFAS wants +`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false` +`remarks` | Parse & return hints & warnings? | boolean | `true` +`language` | Language of the results. | string | `en` +`includeRelatedStations` | Fetch departures at related stops, e.g. those that belong together on the metro map? | boolean | `true` +`stopovers` | Fetch & parse next stopovers of each departure? | boolean | `false` +`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true` +`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true` +`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true` +`regional` | Include Regio (RB)? | boolean | `true` +`suburban` | Include S-Bahn (S)? | boolean | `true` +`bus` | Include Bus (B)? | boolean | `true` +`ferry` | Include Ferry (F)? | boolean | `true` +`subway` | Include U-Bahn (U)? | boolean | `true` +`tram` | Include Tram (T)? | boolean | `true` +`taxi` | Include Group Taxi (Taxi)? | boolean | `true` + +### Example + +```shell +# at Halle (Saale) Hbf, in direction Berlin Südkreuz +curl 'https://v5.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq +``` + +```js +[ + { + "tripId": "1|317591|0|80|1052020", + "direction": "Berlin Hbf (tief)", + "line": { + "type": "line", + "id": "ice-702", + "name": "ICE 702", + "mode": "train", + "product": "nationalExpress", + // … + }, + + "when": "2020-05-01T21:06:00+02:00", + "plannedWhen": "2020-05-01T21:06:00+02:00", + "delay": 0, + "platform": "8", + "plannedPlatform": "8", + + "stop": { + "type": "stop", + "id": "8010159", + "name": "Halle (Saale) Hbf", + "location": { /* … */ }, + "products": { /* … */ }, + }, + + "remarks": [], + // … + } +] +``` + + +## `GET /stops/:id/arrivals` + +Works like [`/stops/:id/departures`](#get-stopsiddepartures), except that it uses [`hafasClient.arrivals()`](https://github.com/public-transport/hafas-client/blob/5/docs/arrivals.md) to **arrivals at a stop/station**. + +### Query Parameters + +parameter | description | type | default value +----------|-------------|------|-------------- +`when` | Date & time to get departures for. See [date/time parameters](#datetime-parameters). | date+time | *now* +`direction` | Filter departures by direction. | string |   +`duration` | Show departures for how many minutes? | number | `10` +`results` | Max. number of departures. | number | *whatever HAFAS wants* +`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false` +`remarks` | Parse & return hints & warnings? | boolean | `true` +`language` | Language of the results. | string | `en` +`includeRelatedStations` | Fetch departures at related stops, e.g. those that belong together on the metro map? | boolean | `true` +`stopovers` | Fetch & parse next stopovers of each departure? | boolean | `false` +`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true` +`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true` +`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true` +`regional` | Include Regio (RB)? | boolean | `true` +`suburban` | Include S-Bahn (S)? | boolean | `true` +`bus` | Include Bus (B)? | boolean | `true` +`ferry` | Include Ferry (F)? | boolean | `true` +`subway` | Include U-Bahn (U)? | boolean | `true` +`tram` | Include Tram (T)? | boolean | `true` +`taxi` | Include Group Taxi (Taxi)? | boolean | `true` + +### Example + +```shell +# at Halle (Saale) Hbf, 10 minutes +curl 'https://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq ``` ## `GET /journeys` -Output from [`require('db-hafas').journeys(…)`](https://github.com/public-transport/db-hafas#getting-started). Start location and end location must be either in [station format](#station-format) or in [POI/address format](#poiaddress-format) (you can mix them). +Uses [`hafasClient.journeys()`](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) to **find journeys from A (`from`) to B (`to`)**. -*Note:* As stated in the [*Friendly Public Transport Format* `1.2.0`](https://github.com/public-transport/friendly-public-transport-format/tree/1.2.0), the returned `departure` and `arrival` times include the current delay. +`from` (A), `to` (B), and the optional `via` must each have one of these formats: -## station format +- as stop/station ID (e.g. `from=8010159` for *Halle (Saale) Hbf*) +- as a POI (e.g. `from.id=991561765&from.latitude=51.48364&from.longitude=11.98084` for *Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)*) +- as an address (e.g. `from.latitude=51.25639&from.longitude=7.46685&from.address=Hansestadt+Breckerfeld,+Hansering+3` for *Hansestadt Breckerfeld, Hansering 3*) -- `from`: **Required.** Station ID (e.g. `008011162`). -- `to`: **Required.** Station ID (e.g. `008011162`). +### Pagination -## POI format +Given a response, you can also fetch more journeys matching the same criteria. Instead of `from*`, `to*` & `departure`/`arrival`, pass `earlierRef` from the first response as `earlierThan` to get journeys "before", or `laterRef` as `laterThan` to get journeys "after". -- `from.latitude`/`to.latitude`: **Required.** Latitude (e.g. `52.543333`). -- `from.longitude`/`to.longitude`: **Required.** Longitude (e.g. `13.351686`). -- `from.name`/`to.name`: Name of the locality (e.g. `Atze Musiktheater`). -- `from.id`/`to.id`: **Required.** POI ID (e.g. `991598902`). +Check the [`hafasClient.journeys()` docs](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) for more details. -## address format +### Query Parameters -- `from.latitude`/`to.latitude`: **Required.** Latitude (e.g. `52.543333`). -- `from.longitude`/`to.longitude`: **Required.** Longitude (e.g. `13.351686`). -- `from.address`/`to.address`: **Required.** Address (e.g. `Voltastr. 17`). +parameter | description | type | default value +----------|-------------|------|-------------- +`departure` | Compute journeys departing at this date/time. Mutually exclusive with `arrival`. See [date/time parameters](#datetime-parameters). | date+time | *now* +`arrival` | Compute journeys arriving at this date/time. Mutually exclusive with `departure`. See [date/time parameters](#datetime-parameters). | date+time | *now* +`earlierThan` | Compute journeys "before" an `ealierRef`. | string |   +`laterThan` | Compute journeys "after" an `laterRef`. | string |   +`results` | Max. number of journeys. | number | `3` +`stopovers` | Fetch & parse stopovers on the way? | boolean | `false` +`transfers` | Maximum number of transfers. | number | *let HAFAS decide* +`transferTime` | Minimum time in minutes for a single transfer. | number | `0` +`accessibility` | `partial` or `complete`. | string | *not accessible* +`bike` | Compute only bike-friendly journeys? | boolean | `false` +`startWithWalking` | Consider walking to nearby stations at the beginning of a journey? | boolean | `true` +`walkingSpeed` | `slow`, `normal` or `fast`. | string | `normal` +`tickets` | Return information about available tickets? | boolean | `false` +`polylines` | Fetch & parse a shape for each journey leg? | boolean | `false` +`remarks` | Parse & return hints & warnings? | boolean | `true` +`scheduledDays` | Parse & return dates each journey is valid on? | boolean | `false` +`language` | Language of the results. | string | `en` +`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true` +`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true` +`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true` +`regional` | Include Regio (RB)? | boolean | `true` +`suburban` | Include S-Bahn (S)? | boolean | `true` +`bus` | Include Bus (B)? | boolean | `true` +`ferry` | Include Ferry (F)? | boolean | `true` +`subway` | Include U-Bahn (U)? | boolean | `true` +`tram` | Include Tram (T)? | boolean | `true` +`taxi` | Include Group Taxi (Taxi)? | boolean | `true` -## other parameters - -- `departure`/`arrival`: A [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) or anything parsable by [`parse-messy-time`](https://github.com/substack/parse-messy-time#example). Default: now. Use either `departure` or `arrival`. -- `earlierThan`: By passing an identifier from another query, get earlier journeys than before. Mutually exlusive with `laterThan`, `departure` & `arrival`. -- `laterThan`: By passing an identifier from another query, get later journeys than before. Mutually exclusive with `earlierThan`, `departure` & `arrival`. -- `results`: Maximum number of results. Default: `5`. -- `via`: Station ID. Default: `null`. -- `stopovers`: Return stations on the way? Default: `false`. -- `transfers`: Maximum number of transfers. Default: `5`. -- `transferTime`: Minimum time in minutes for a single transfer. Default: `0`. -- `accessibility`: Possible values: `partial`, `complete`. Default: `none`. -- `bike`: Return only bike-friendly journeys. Default: `false`. -- `tickets`: Return information about available tickets. Default: `false`. -- `language`: Language to get results in. Default: `en`. -- `polylines`: Return a shape for each leg? Default: `false`. -- `remarks`: Parse & expose hints & warnings? Default: `true`. -- `startWithWalking`: Consider walking to nearby stations at the beginning of a journey? Default: `true`. -- `scheduledDays`: Parse which days each journey is valid on? Default: `false`. - -You can filter by means of transportation using these parameters: - -- `taxi`: Include taxis? Default: `false`. -- `tram`: Include [trams](https://en.wikipedia.org/wiki/Tram)? Default: `true`. -- `ferry`: Include [ferries](https://en.wikipedia.org/wiki/Ferry)? Default: `true`. -- `bus`: Include [buses](https://en.wikipedia.org/wiki/Bus)? Default: `true`. -- `subway`: Include [U-Bahn trains](https://en.wikipedia.org/wiki/Rapid_transit)? Default: `true`. -- `suburban`: Include [S-Bahn trains](https://en.wikipedia.org/wiki/S-train)? Default: `true`. -- `regional`: Include RB/ODEG trains? Default: `true`. -- `regionalExp`: Include RE/ODEG trains? Default: `true`. -- `national`: Include [IC/EC trains](https://en.wikipedia.org/wiki/Intercity_(Deutsche_Bahn)? Default: `true`. -- `nationalExp`: Include [ICE trains](https://en.wikipedia.org/wiki/Intercity-Express)? Default: `true`. - -`Content-Type`: `application/json` - -### examples +### Examples ```shell -curl 'https://2.db.transport.rest/journeys?from=008011162&to=008000281' -curl 'https://2.db.transport.rest/journeys?from=008004158&to.id=991598902&to.name=Atze%20Musiktheater&to.latitude=52.543333&to.longitude=13.351686' -curl 'https://2.db.transport.rest/journeys?from=…&to=…&results=3&bus=false&tickets=true' +# stop/station to POI +curl 'https://v5.db.transport.rest/journeys?from=8010159&to.id=991561765&to.latitude=51.483641&to.longitude=11.980841' -s | jq +# without buses, with ticket info +curl 'https://v5.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq ``` ## `GET /journeys/:ref` -Output from [`hafas.refreshJourney(…)`](https://github.com/public-transport/hafas-client/blob/4/docs/refresh-journey.md). +Uses [`hafasClient.refreshJourney()`](https://github.com/public-transport/hafas-client/blob/5/docs/refresh-journey.md) to **"refresh" a journey, using its `refreshToken`**. -- `stopovers`: Return stations on the way? Default: `false`. -- `polylines`: Return shape of each journey leg? Default: `false`. -- `remarks`: Parse & expose hints & warnings? Default: `true`. -- `language`: Language of the results. Default: `en`. +The journey will be the same (equal `from`, `to`, `via`, date/time & vehicles used), but you can get up-to-date realtime data, like delays & cancellations. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`stopovers` | Fetch & parse stopovers on the way? | boolean | `false` +`tickets` | Fetch & parse a shape for each journey leg? | boolean | `false` +`polylines` | Return information about available tickets? | boolean | `false` +`remarks` | Parse & return hints & warnings? | boolean | `true` +`language` | Language of the results. | string | `en` + +### Example ```shell -curl 'https://2.db.transport.rest/journeys/%C2%B6HKI%C2%B6T%24A%3D1%40O%3DBerlin%20Hbf%20(tief)%40L%3D8098160%40a%3D128%40%24A%3D1%40O%3DHamburg%20Hbf%40L%3D8002549%40a%3D128%40%24201910062138%24201910062333%24ICE%20%20502%24%241%24%C2%A7T%24A%3D1%40O%3DHamburg%20Hbf%40L%3D8002549%40a%3D128%40%24A%3D1%40O%3DMannheim%20Hbf%40L%3D8000244%40a%3D128%40%24201910062345%24201910070625%24ICE%201271%24%241%24%C2%A7T%24A%3D1%40O%3DMannheim%20Hbf%40L%3D8000244%40a%3D128%40%24A%3D1%40O%3DStuttgart%20Hbf%40L%3D8000096%40a%3D128%40%24201910070629%24201910070707%24ICE%20%20991%24%241%24%C2%A7T%24A%3D1%40O%3DStuttgart%20Hbf%40L%3D8000096%40a%3D128%40%24A%3D1%40O%3DT%C3%BCbingen%20Hbf%40L%3D8000141%40a%3D128%40%24201910070722%24201910070823%24RE%2022011%24%241%24%0A' +# get the refreshToken of a journey +journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') +refresh_token=$(echo $journey | jq -r '.refreshToken') + +# refresh the journey +curl "https://v5.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq ``` ## `GET /trips/:id` -Output from [`hafas.trip(…)`](https://github.com/public-transport/hafas-client/blob/4/docs/trip.md). +Uses [`hafasClient.trip()`](https://github.com/public-transport/hafas-client/blob/5/docs/trip.md) to **fetch a trip by ID**. -- `lineName`: **Required.** Line name of the part's mode of transport, e.g. `RE 7`. -- `stopovers`: Return stations on the way? Default: `true`. -- `remarks`: Parse & expose hints & warnings? Default: `true`. -- `polyline`: Return a shape for the trip? Default: `false`. -- `language`: Language of the results. Default: `en`. +A trip is a specific vehicle, stopping at a series of stops at specific points in time. Departures, arrivals & journey legs reference trips by their ID. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`lineName` | **Required.** Line name of the part's mode of transport, e.g. `RE7`. | string | – +`stopovers` | Fetch & parse stopovers on the way? | boolean | `true` +`remarks` | Parse & return hints & warnings? | boolean | `true` +`polyline` | Fetch & parse the geographic shape of the trip? | boolean | `false` +`language` | Language of the results. | string | `en` + +### Example ```shell -curl 'https://your-api-endpoint/trips/1|229086|0|80|6102019?lineName=ICE+993' +# get the trip ID of a journey leg +journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') +journey_leg=$(echo $journey | jq -r '.legs[0]') +trip_id=$(echo $journey_leg | jq -r '.tripId') + +# fetch the trip +curl "https://v5.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq ``` -## `GET /locations` +## `GET /stations` -Output from [`require('db-hafas').locations(…)`](https://github.com/public-transport/db-hafas/blob/master/docs/locations.md) +If the `query` parameter is used, it will use [`db-stations-autocomplete`](https://npmjs.com/package/db-stations-autocomplete) to autocomplete *Deutsche Bahn*-operated stops/stations. Otherwise, it will filter the stops/stations in [`db-stations`](https://npmjs.com/package/db-stations). -- `query`: **Required.** (e.g. `Alexanderplatz`) -- `results`: How many stations shall be shown? Default: `10`. -- `stations`: Show stations? Default: `true`. -- `poi`: Show points of interest? Default: `true`. -- `addresses`: Show addresses? Default: `true`. +Instead of receiving a JSON response, you can request [newline-delimited JSON](http://ndjson.org) by sending `Accept: application/x-ndjson`. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`query` | Find stations by name using [`db-stations-autocomplete`](https://npmjs.com/package/db-stations-autocomplete). | string | – +`limit` | *If `query` is used:* Return at most `n` stations. | number | `3` +`fuzzy` | *If `query` is used:* Find stations despite typos. | boolean | `false` +`completion` | *If `query` is used:* Autocomplete stations. | boolean | `true` + +### Examples ```shell -curl 'https://2.db.transport.rest/locations?query=Alexanderplatz' -curl 'https://2.db.transport.rest/locations?query=Pestalozzistra%C3%9Fe%2082%2C%20Berlin&poi=false&stations=false' +# autocomplete using db-stations-autocomplete +curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq +``` + +```js +{ + "8002548": { + "id": "8002548", + "relevance": 0.8572361756428573, + "score": 9.175313823998414, + "weight": 1212, + "type": "station", + "ril100": "ADF", + "name": "Hamburg Dammtor", + "location": { + "type": "location", + "latitude": 53.560751, + "longitude": 9.989566 + }, + "operator": { + "type": "operator", + "id": "hamburger-verkehrsverbund-gmbh", + "name": "BWVI" + }, + "address": { + "city": "Hamburg", + "zipcode": "20354", + "street": "Dag-Hammarskjöld-Platz 15" + }, + // … + }, + // … +} +``` + +```shell +# filter db-stations by `hasParking` property +curl 'https://v5.db.transport.rest/stations?hasParking=true' -s | jq +``` + +```js +{ + "8000001": { + "type": "station", + "id": "8000001", + "ril100": "KA", + "name": "Aachen Hbf", + "weight": 653.75, + "location": { /* … */ }, + "operator": { /* … */ }, + "address": { /* … */ }, + // … + }, + // … +} +``` + +```shell +# filter db-stations by `hasDBLounge` property, get newline-delimited JSON +curl 'https://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq +``` + + +## `GET /stations/:id` + +Returns a stop/station from [`db-stations`](https://npmjs.com/package/db-stations). + +### Query Parameters + +### Example + +```shell +# lookup Halle (Saale) Hbf +curl 'https://v5.db.transport.rest/stations/8010159' -s | jq +curl 'https://v5.db.transport.rest/stations/LH' -s | jq # RIL100/DS100 +curl 'https://v5.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100 +``` + +```js +{ + "type": "station", + "id": "8010159", + "additionalIds": ["8098159"], + "ril100": "LH", + "nr": 2498, + "name": "Halle (Saale) Hbf", + "weight": 815.6, + "location": { /* … */ }, + "operator": { /* … */ }, + "address": { /* … */ }, + "ril100Identifiers": [ + { + "rilIdentifier": "LH", + // … + }, + // … + ], + // … +} ``` ## `GET /radar` -- `north`: **Required.** Northern latitude. -- `west`: **Required.** Western longtidue. -- `south`: **Required.** Southern latitude. -- `east`: **Required.** Eastern longtidue. -- `results`: How many vehicles shall be shown? Default: `256`. -- `duration`: Compute frames for how many seconds? Default: `30`. -- `frames`: Number of frames to compute. Default: `3`. +Uses [`hafasClient.radar()`](https://github.com/public-transport/hafas-client/blob/5/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements. -`Content-Type`: `application/json` +### Query Parameters -### examples +parameter | description | type | default value +----------|-------------|------|-------------- +`north` | **Required.** Northern latitude. | number | – +`west` | **Required.** Western longitude. | number | – +`south` | **Required.** Southern latitude. | number | – +`east` | **Required.** Eastern longitude. | number | – +`results` | Max. number of vehicles. | number | `256` +`duration` | Compute frames for the next `n` seconds. | number | `30` +`frames` | Number of frames to compute. | number | `3` +`polylines` | Fetch & parse a geographic shape for the movement of each vehicle? | boolean | `true` +`language` | Language of the results. | string | `en` + +### Example ```shell -curl 'https://2.db.transport.rest/radar?north=52.52411&west=13.41002&south=52.51942&east=13.41709' +bbox='north=53.555&west=9.989&south=53.55&east=10.001' +curl "https://v5.db.transport.rest/radar?$bbox&results=10" -s | jq ``` + + +## Date/Time Parameters + +Possible formats: + +- anything that [`parse-human-relative-time`](https://npmjs.com/package/parse-human-relative-time) can parse (e.g. `tomorrow 2pm`) +- [ISO 8601 date/time string](https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations) (e.g. `2020-04-26T22:43+02:00`) +- [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) (e.g. `1587933780`) diff --git a/index.js b/index.js index 23bd293..37c336f 100644 --- a/index.js +++ b/index.js @@ -1,40 +1,6 @@ 'use strict' -const createHafas = require('db-hafas') -const createApi = require('hafas-rest-api') -const createHealthCheck = require('hafas-client-health-check') - -const pkg = require('./package.json') -const stations = require('./routes/stations') -const station = require('./routes/station') - -const hafas = createHafas(pkg.name) - -const berlinHbf = '8011160' -const healthCheck = createHealthCheck(hafas, berlinHbf) - -const modifyRoutes = (routes) => { - routes['/stations'] = stations - routes['/stations/:id'] = station - return routes -} - -const config = { - hostname: process.env.HOSTNAME || 'localhost', - port: process.env.PORT ? parseInt(process.env.PORT) : 3000, - name: pkg.name, - description: pkg.description, - homepage: pkg.homepage, - version: pkg.version, - docsLink: 'https://github.com/derhuerst/db-rest/blob/5/docs/readme.md', - logging: true, - aboutPage: true, - etags: 'strong', - healthCheck, - modifyRoutes, -} - -const api = createApi(hafas, config, () => {}) +const {api, config} = require('./api') api.listen(config.port, (err) => { const {logger} = api.locals diff --git a/package.json b/package.json index 1674b9b..5775165 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "pino-pretty": "^4.0.0" }, "scripts": { + "docs": "node api-docs.js >docs/api.md", + "build": "npm run docs", "start": "node index.js" } }