diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b6e2008
--- /dev/null
+++ b/index.html
@@ -0,0 +1,240 @@
+
+
+
+
+ Display a map
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vehic_pos_processor.py b/vehic_pos_processor.py
new file mode 100644
index 0000000..2f220ce
--- /dev/null
+++ b/vehic_pos_processor.py
@@ -0,0 +1,255 @@
+import argparse
+import time
+import json
+import logging
+import os
+
+from pathlib import Path
+from collections import deque
+from urllib.error import URLError
+from urllib.request import Request, urlopen
+
+
+# geojson indexes names
+class GI:
+ type = "type"
+ trip = "trip"
+ shapes = "shapes"
+ feature = "feature"
+ features = "features"
+ geometry = "geometry"
+ properties = "properties"
+ lineString = "LineString"
+ coordinates = "coordinates"
+ gtfs_trip_id = "gtfs_trip_id"
+ featureCollection = "FeatureCollection"
+ shape_dist_traveled = "shape_dist_traveled"
+ gtfs_route_short_name = "gtfs_route_short_name"
+
+
+# logging messages
+class Log:
+ start = "Program has started."
+ net_err = "Network error: "
+ sleep = "Sleep failed, "
+ io_err = "Write to file failed: "
+ vpf_up = "Vehicle positions file updated"
+
+ @staticmethod
+ def trip_updater(trip_id):
+ return "Trip " + trip_id + " positions file updated"
+
+ @staticmethod
+ def del_files(trip):
+ return "Shape of trip " + trip + " file removed"
+
+ @staticmethod
+ def deL_fail(trip):
+ return "Some file for trip " + trip + " not found."
+
+ @staticmethod
+ def new_shape(trip):
+ return "New shape of trip " + trip + " file exported"
+
+
+def update_json_file(json_data, path, mode, log_message):
+ try:
+ with open(path, mode) as f:
+ f.seek(0)
+ f.write(json.dumps(json_data))
+ if log_message != "":
+ logging.info(log_message)
+
+ except Exception as e:
+ raise IOError(e)
+
+ finally:
+ f.close()
+
+
+def downloadURL(request):
+ webURL = urlopen(request)
+ response_body = webURL.read()
+ encoding = webURL.info().get_content_charset('utf-8')
+ return json.loads(response_body.decode(encoding))
+
+
+def prepare_geojson_lfms():
+ geojson_lfms = {}
+ geojson_lfms[GI.type] = GI.featureCollection
+ geojson_lfms[GI.features] = [{}]
+ geojson_lfms[GI.features][0][GI.type] = GI.feature
+ geojson_lfms[GI.features][0][GI.properties] = {}
+ geojson_lfms[GI.features][0][GI.geometry] = {}
+ geojson_lfms[GI.features][0][GI.geometry][GI.type] = GI.lineString
+ geojson_lfms[GI.features][0][GI.geometry][GI.coordinates] = []
+ return geojson_lfms
+
+
+def transform_bus_list(bus_input_list):
+ bus_properties = bus_input_list[GI.properties][GI.trip]
+ current_trip_gtfs_id = bus_properties[GI.gtfs_trip_id]
+ bus_output_list = {}
+ bus_output_list[GI.type] = GI.feature
+ bus_output_list[GI.properties] = {}
+ bus_output_list[GI.properties][GI.gtfs_trip_id] = current_trip_gtfs_id
+ bus_output_list[GI.properties][GI.gtfs_route_short_name] = bus_properties[GI.gtfs_route_short_name]
+ bus_output_list[GI.geometry] = {}
+ bus_output_list[GI.geometry][GI.coordinates] = bus_input_list[GI.geometry][GI.coordinates]
+ bus_output_list[GI.geometry][GI.type] = "Point"
+ return bus_output_list
+
+
+def transform_shape_json_file(old_json_data):
+ new_json_data = {}
+ new_json_data[GI.type] = GI.featureCollection
+ new_json_data[GI.features] = [None]
+ new_json_data[GI.features][0] = {}
+ new_json_data[GI.features][0][GI.type] = GI.feature
+ new_json_data[GI.features][0][GI.geometry] = {}
+ new_json_data[GI.features][0][GI.geometry][GI.type] = GI.lineString
+ new_json_data[GI.features][0][GI.geometry][GI.properties] = {}
+ new_json_data[GI.features][0][GI.geometry][GI.coordinates] = []
+ for feature in old_json_data[GI.shapes]:
+ new_json_data[GI.features][0][GI.geometry][GI.coordinates].append(feature[GI.geometry][GI.coordinates])
+ return new_json_data
+
+
+def is_old(coordinates):
+ from datetime import datetime
+ fmt = '%H:%M:%S'
+ coordinates_time = coordinates[1]
+ now = datetime.now().strftime(fmt)
+ tdelta = datetime.strptime(now, fmt) - datetime.strptime(coordinates_time, fmt)
+ seconds = tdelta.total_seconds()
+ if seconds > 10*60:
+ return True
+ return False
+
+
+def parse_arguments():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--file_name", default="../data/last_positions", type=str, help="The last generated output file")
+ parser.add_argument("--update_time", default=20, type=int, help="Time to next request")
+ parser.add_argument("--update_error", default=20, type=int, help="Update time if network error occurred")
+ parser.add_argument("--log", default="../veh_pos_proc.log", type=str, help="Name of logging file")
+ parser.add_argument("--trips_folder", default="../data/trips", type=str, help="Name of trips folder")
+ return parser.parse_args()
+
+"""
+ Following header is necessary for requesting golemio api.
+ code copied from https://golemioapi.docs.apiary.io/#reference/public-transport/vehicle-positions/get-all-vehicle-positions
+ access token generated by https://api.golemio.cz/api-keys/auth/sign-in
+ Get your own token!
+"""
+headers = {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'x-access-token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImNpem1hcmZpbGlwQGdtYWlsLmNvbSIsImlkIjo3NiwibmFtZSI6bnVsbCwic3VybmFtZSI6bnVsbCwiaWF0IjoxNTcwNTQ2MTU2LCJleHAiOjExNTcwNTQ2MTU2LCJpc3MiOiJnb2xlbWlvIiwianRpIjoiMzAxYWNhNDUtNGRlNC00ZDRmLWI4NzAtMzQwMDQ5OTM1MzBhIn0.4rCELzCNY8XOSvjqQA7cKocPGJ8D2ezhXiWUkIRUNjg'
+}
+
+if __name__ == "__main__":
+
+ args = parse_arguments()
+ logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO, filename=args.log, filemode='w')
+ logging.info(Log.start)
+
+ active_trips = {key[:-6]: deque() for key in os.listdir(args.trips_folder)}
+ active_trips_set = {key[:-6] for key in os.listdir(args.trips_folder)}
+
+ while True:
+
+ # Download the updated information about buses and store them in the json_data array.
+ req_start = time.time()
+ try:
+ json_vehiclepositions = downloadURL(Request('https://api.golemio.cz/v1/vehiclepositions', headers=headers))
+ except URLError as e:
+ logging.error(Log.net_err + str(e))
+ time.sleep(args.update_error - (time.time() - req_start))
+ continue
+
+ # Process json data and generate geojson file
+ geojson_vehiclepositions = {}
+ geojson_vehiclepositions[GI.type] = GI.featureCollection
+ geojson_vehiclepositions["timestamp"] = time.strftime("%Y-%m-%d-%H:%M:%S")
+ geojson_vehiclepositions[GI.features] = []
+
+ current_trips_set = set()
+ for bus_input_list in json_vehiclepositions[GI.features]:
+ geojson_vehiclepositions[GI.features].append(transform_bus_list(bus_input_list))
+ current_trip_gtfs_id = bus_input_list[GI.properties][GI.trip][GI.gtfs_trip_id]
+ current_trips_set.add(current_trip_gtfs_id)
+
+ """
+ active_trips variable holds all positions of each trip for last n seconds (defined in function is_old).
+ The following code update the bus positions information in the variable.
+ """
+
+
+ if current_trip_gtfs_id not in active_trips or len(active_trips[current_trip_gtfs_id]) == 0:
+ active_trips[current_trip_gtfs_id] = deque()
+ active_trips[current_trip_gtfs_id].append([bus_input_list[GI.geometry][GI.coordinates], bus_input_list[GI.properties]["last_position"]["origin_time"], bus_input_list[GI.properties]["last_position"]["gtfs_shape_dist_traveled"]])
+ else:
+ active_trips[current_trip_gtfs_id].append([bus_input_list[GI.geometry][GI.coordinates], bus_input_list[GI.properties]["last_position"]["origin_time"], bus_input_list[GI.properties]["last_position"]["gtfs_shape_dist_traveled"]])
+ while len(active_trips[current_trip_gtfs_id]) > 0 and is_old(active_trips[current_trip_gtfs_id][0]):
+ active_trips[current_trip_gtfs_id].popleft()
+ logging.info(Log.trip_updater(current_trip_gtfs_id))
+
+ try:
+ update_json_file(geojson_vehiclepositions, Path(args.file_name), "w+", Log.vpf_up)
+ except IOError as e:
+ logging.error(Log.io_err + str(e))
+ continue
+
+ for trip in active_trips_set - current_trips_set:
+ active_trips.pop(trip)
+ try:
+ os.remove(Path(args.trips_folder) / (trip + ".shape"))
+ os.remove(Path(args.trips_folder) / (trip + ".lfms"))
+ logging.info(Log.del_files(trip))
+ except FileNotFoundError as e:
+ logging.warning(Log.deL_fail(trip))
+
+ for trip in current_trips_set - active_trips_set:
+ try:
+ json_data_trip = downloadURL(Request('https://api.golemio.cz/v1/gtfs/trips/' + trip + '?includeShapes=true', headers=headers))
+ geojson_shape = transform_shape_json_file(json_data_trip)
+ update_json_file(geojson_shape, Path(args.trips_folder) / (trip + ".shape"), "w+", Log.new_shape(trip))
+ except URLError as e:
+ logging.error(Log.net_err + str(e))
+ current_trips_set -= trip
+ continue
+ except IOError as e:
+ logging.error(Log.io_err + str(e))
+ current_trips_set -= trip
+ continue
+
+ active_trips_set = current_trips_set
+
+ for trip in current_trips_set:
+ try:
+ json_data_trip = downloadURL(Request('https://api.golemio.cz/v1/gtfs/trips/' + trip + '?includeShapes=true', headers=headers))
+ geojson_lfms = prepare_geojson_lfms()
+ if len(active_trips[trip]) > 0:
+ geojson_lfms[GI.features][0][GI.geometry][GI.coordinates].append(active_trips[trip][0][0])
+
+ for shape_fault in json_data_trip[GI.shapes]:
+ if float(shape_fault[GI.properties][GI.shape_dist_traveled]) > float(active_trips[trip][0][2]) and float(shape_fault[GI.properties][GI.shape_dist_traveled]) < float(active_trips[trip][len(active_trips[trip]) - 1][2]):
+ geojson_lfms[GI.features][0][GI.geometry][GI.coordinates].append(shape_fault[GI.geometry][GI.coordinates])
+
+ geojson_lfms[GI.features][0][GI.geometry][GI.coordinates].append(active_trips[trip][len(active_trips[trip]) - 1][0])
+
+ update_json_file(geojson_lfms, Path(args.trips_folder) / (trip + '.lfms'), "w+", "")
+ except URLError as e:
+ logging.error(Log.net_err + str(e))
+ current_trips_set -= trip
+ continue
+ except IOError as e:
+ logging.error(Log.io_err + str(e))
+ current_trips_set -= trip
+ continue
+
+ try:
+ time.sleep(args.update_time - (time.time() - req_start))
+ except Exception as e:
+ logging.warning(Log.sleep + str(e))
+ continue