changeset 55:1cbade934726

Add IC/ICE train collection script.
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 03 Dec 2020 22:34:25 +0100
parents 2d6770d7e8cf
children e47f25aef153
files examples/track_ICE/README.md examples/track_ICE/collect.py
diffstat 2 files changed, 121 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/track_ICE/README.md	Thu Dec 03 22:34:25 2020 +0100
@@ -0,0 +1,62 @@
+# `track_ICE`
+
+The German IC (InterCity) and ICE (InterCityExpress) trains have -- mostly --
+on-board Wi-Fi with entertainment and internet uplink.
+
+As any good nerd, we are not interested in some fringe German TV shows. We find
+that to implement the live map on the website, DB has thankfully built a little
+API:
+
+* `GET https://iceportal.de/api1/rs/status`
+  * Returns a JSON object. For example, in the last car of an ICE2:
+
+```json
+{
+  "connection": true,
+  "servicelevel": "AVAILABLE_SERVICE",
+  "internet": "HIGH",
+  "speed": 159,
+  "gpsStatus": "VALID",
+  "tzn": "Tz208",
+  "series": "807",
+  "latitude": 52.601715,
+  "longitude": 12.369854,
+  "serverTime": 1605641662281,
+  "wagonClass": "SECOND",
+  "navigationChange": "2020-11-17-16-04-20",
+  "trainType": "ICE"
+}
+
+```
+
+or a middle car of a doubledecker IC2:
+
+```json
+{
+  "connection": true,
+  "servicelevel": "AVAILABLE_SERVICE",
+  "internet": "MIDDLE",
+  "speed": 72,
+  "gpsStatus": "VALID",
+  "tzn": "ICD2871",
+  "series": "100",
+  "latitude": 50.96594,
+  "longitude": 7.019422,
+  "serverTime": 1606577933620,
+  "wagonClass": "SECOND",
+  "navigationChange": "2020-11-28-06-25-31",
+  "trainType": "IC"
+}
+```
+
+We can now directly store and evaluate position and speed. And because we are
+interested in the speed and internet quality of our train to evaluate at home,
+let's send them as metadata along the coordinates.
+
+The enclosed python script does just that. Once run, it will
+
+* Fetch the current train data at a regular interval (5 seconds)
+* Log it to a file consisting of JSON objects separated by newlines
+* Send it to a configured URL.
+
+Run it with `--help` to obtain more info.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/track_ICE/collect.py	Thu Dec 03 22:34:25 2020 +0100
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+# Collect speed data from a driving ICE/IC train.
+
+import requests
+
+import argparse
+import json
+import sys
+import time
+
+def fetch_current(api):
+    return requests.get(api).json()
+
+def format_server_time(servertime):
+    return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(servertime/1000))
+
+def send_point(args, info):
+    geohub_url = args.geohub.format(HOST=args.geohub_host, CLIENT=args.client or info.get('tzn', 'TRAIN'), SECRET=args.secret, PROTOCOL=args.geohub_scheme)
+    additional = '&lat={lat}&longitude={long}&spd={spd}&time={ts}'.format(
+            lat=info['latitude'], long=info['longitude'], spd=info['speed'], ts=format_server_time(info['serverTime']))
+    url = geohub_url + additional
+    requests.post(url, json=info)
+
+def parse_args():
+    parser = argparse.ArgumentParser(description='Fetch and send train data')
+    parser.add_argument('--api', default='https://iceportal.de/api1/rs/status', help='Location of train API')
+    parser.add_argument('--client', default='', help='Client name. By default, this will be the `tzn` (train number) of the current train.')
+    parser.add_argument('--secret', default='', help='Secret. This protects your current location; to share it, you have to share the secret. By default, the points will be made public on your GeoHub instance.')
+    parser.add_argument('--interval', default=5, help='Poll interval')
+    parser.add_argument('--outfile', default='traindata.jsonlines', help='Where to write the JSON data received from the train.')
+    parser.add_argument('--geohub_host', default='example.com', help='Host of your GeoHub. Use this if the URL --geohub works for you.')
+    parser.add_argument('--geohub_scheme', default='https', help='Protocol scheme of the GeoHub instance. Use this if you do not want to specify the entire --geohub URL')
+    parser.add_argument('--geohub', default='{PROTOCOL}://{HOST}/geo/{CLIENT}/log?secret={SECRET}', help='Base URL of Geohub instance. {PROTOCOL}, {CLIENT}, {HOST}, and {SECRET} will be replaced by the --geohub_scheme, --client, --geohub_host, and --secret values, respectively. This string must end in the URL query parameter section, ready to take more parameters.')
+
+    return parser.parse_args()
+
+def run(args):
+    info = fetch_current(args.api)
+    if not info:
+        print('Empty info received!')
+        return
+    tzn = info['tzn']
+    print('Running in train:', tzn)
+
+    with open(args.outfile, 'w') as outfile:
+        while True:
+            send_point(args, info)
+            outfile.write(json.dumps(info))
+            outfile.write('\n')
+            time.sleep(args.interval)
+
+def main():
+    args = parse_args()
+    run(args)
+
+
+if __name__ == '__main__':
+    main()