diff --git a/README.md b/README.md index bf652f5..ca233ab 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,10 @@ This is a set of libraries to gather data from TBM. -They all work around [the dynamic map](http://plandynamique.infotbm.com). - -# What's TBM? +## What's TBM? TBM is a french compagny that operates Bordeaux's public transportation. -# Licence +## Licence -These libraries are under GNU GPL v3. See [license](LICENSE) for more details. \ No newline at end of file +These libraries are under GNU GPL v3. See [license](LICENSE) for more details. diff --git a/libs.py b/libs.py deleted file mode 100644 index e823917..0000000 --- a/libs.py +++ /dev/null @@ -1,30 +0,0 @@ -''' -Common libraries -''' - -from json import loads as read_json -from urllib import request -from urllib.error import HTTPError - - -def get_data_from_json (url): - ''' - gets data from json at url - ''' - opener = request.build_opener () - try: - return (read_json (opener.open (url).read ().decode ('utf8'))) - except HTTPError: - return (None) - - -def hms2seconds (hhmmss): - ''' - Convert H:M:S string to time in seconds - ''' - try: - cut_string = hhmmss.split (':') - cut_time = (int (cut_string [0]), int (cut_string [1]), int (cut_string [2])) - return (3600 * cut_time [0] + 60 * cut_time [1] + cut_time [2]) - except (IndexError, ValueError, TypeError): - return (0) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f38a923 --- /dev/null +++ b/main.py @@ -0,0 +1,30 @@ +from src.stop import * +import sys +from datetime import datetime +from gmplot import gmplot +import imgkit + + +if __name__ == "__main__": + for word in sys.argv[1:]: + for area in search_stop_by_name(word): + for stop in show_stops_from_ref(area["ref"])["stop_points"]: + for route in stop["routes"]: + if "Tram" in route["line_human"]: + sr = StopRoute(stop["id"], route["line_id"]) + line = sr.get_line() + v = line.get_vehicle(0) + if v.is_realtime: + print( + str(v.wait_time_text) + + " (" + + datetime.fromtimestamp(v.arrival).strftime("%H:%M") + + ") → " + + v.destination + + " " + + str(v.location) + ) + gmap = gmplot.GoogleMapPlotter(v.location[0], v.location[1], 13) + gmap.marker(v.location[0], v.location[1], "cornflowerblue") + gmap.draw("map.html") + imgkit.from_file("map.html", "map.pdf") diff --git a/map.html b/map.html new file mode 100644 index 0000000..96f4b8a --- /dev/null +++ b/map.html @@ -0,0 +1,31 @@ + + + + +Google Maps - gmplot + + + + +
+ + diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..07d40c1 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,126 @@ +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "gmplot" +version = "1.4.1" +description = "A matplotlib-like interface to plot data with Google Maps." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = "*" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "imgkit" +version = "1.2.2" +description = "Wkhtmltopdf python wrapper to convert html to image using the webkit rendering engine and qt" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.26.9" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "38d635e600115509aba955aaee5bd0c5d91d0a621f114d3b1723ca3471bb2900" + +[metadata.files] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +gmplot = [ + {file = "gmplot-1.4.1-py3-none-any.whl", hash = "sha256:873bcecd90f0e63a73b2876e15a2225145d726f3b7cfb47a363a3a5459fc778d"}, + {file = "gmplot-1.4.1.tar.gz", hash = "sha256:cfe72d251c17b5c05043169d121a9728554bf65b8c96760ce9fabb6d269c6667"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +imgkit = [ + {file = "imgkit-1.2.2-py3-none-any.whl", hash = "sha256:304de76b83fe2dbb1f70194f5f88e27e46bdc352bdc9c9ec5cf961e986300fc8"}, + {file = "imgkit-1.2.2.tar.gz", hash = "sha256:382e7b3c280de4a472d0d786d9cea7bb9d6d8df2c2310ad07d4fe440ad1cb218"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +urllib3 = [ + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9ab5494 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "info_tbm" +version = "0.1.0" +description = "" +authors = ["mdeboute "] + +[tool.poetry.dependencies] +python = "^3.9" +gmplot = "^1.4.1" +imgkit = "^1.2.2" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/src/libs.py b/src/libs.py new file mode 100644 index 0000000..9f2da9f --- /dev/null +++ b/src/libs.py @@ -0,0 +1,32 @@ +""" +Common libraries +""" + +from json import loads as read_json +from urllib import request +from urllib.error import HTTPError + + +def get_data_from_json(url): + """ + Gets data from json at url + """ + + opener = request.build_opener() + try: + return read_json(opener.open(url).read().decode("utf8")) + except HTTPError: + return None + + +def hms2seconds(hhmmss): + """ + Convert H:M:S string to time in seconds + """ + + try: + cut_string = hhmmss.split(":") + cut_time = (int(cut_string[0]), int(cut_string[1]), int(cut_string[2])) + return 3600 * cut_time[0] + 60 * cut_time[1] + cut_time[2] + except (IndexError, ValueError, TypeError): + return None diff --git a/src/stop.py b/src/stop.py new file mode 100644 index 0000000..a226a2c --- /dev/null +++ b/src/stop.py @@ -0,0 +1,245 @@ +""" +Provides stop information +""" + +from src.libs import get_data_from_json, hms2seconds +from time import time +from urllib.parse import quote +from re import search + + +STOP_URL = "https://ws.infotbm.com/ws/1.0/get-schedule/%s" +INFO_URL = "https://ws.infotbm.com/ws/1.0/network/stoparea-informations/%s" +SCHEDULE_URL = "https://ws.infotbm.com/ws/1.0/get-realtime-pass/%d/%s" + +line_types = ( + "Tram", + "Corol", + "Lianes", + "Ligne", + "Bus Relais", + "Citéis", +) +line_translate = { + "Tram A": "A", + "Tram B": "B", + "Tram C": "C", + "Tram D": "D", + "TBNight": "58", + "BAT3": "69", +} + + +def search_stop_by_name(keyword): + """ + Finds the reference of a stop name + + Format of data returned by the site + [ + { + id: str, named ref thereafter + name: str + type: str, but we only manage "stop_area" + city: str + }, + ] + """ + + d = get_data_from_json(STOP_URL % quote(keyword)) + r = [] + for i in d: + if i["type"] == "stop_area": + r.append( + { + "name": i["name"], + "city": i["city"], + "ref": i["id"], + } + ) + return r + + +def show_stops_from_ref(ref): + """ + Displays the list of stops of a reference given by search_stop_name + + Format of data returned by the site + { + id: str, content of given ref variable + name: str + latitude: str, convertible in float + longitude: str, convertible in float + city: str + hasWheelchairBoarding: bool, wheelchair accessibility + stopPoints: [ + id: str + name: str, once again the name of the stop + routes: [ + { + id: str + name: str, name of the terminus + line: { + name: str, human readable + } + } + ] + ] + } + """ + + d = get_data_from_json(INFO_URL % quote(ref)) + r = { + "ref": d["id"], + "name": d["name"], + "latitude": float(d["latitude"]), + "longitude": float(d["longitude"]), + "city": d["city"], + "stop_points": [], + } + for i in d["stopPoints"]: + s = { + "name": i["name"], + "routes": [], + } + s["id"] = int(search("[0-9]+$", i["id"]).group()) + for j in i["routes"]: + rte = { + "terminus": j["name"], + "line_human": j["line"]["name"], + } + add = False + if rte["line_human"] in line_translate: + line_id = line_translate[rte["line_human"]] + add = True + else: + try: + line_id = search("[0-9]+$", rte["line_human"]).group() + except AttributeError: + continue + line_id = "%02d" % int(line_id) + for i in line_types: + if rte["line_human"][0 : len(i)] == i: + add = True + break + if add: + rte["line_id"] = line_id + s["routes"].append(rte) + if s["routes"] != []: + r["stop_points"].append(s) + return r + + +class StopRoute: + """ + Retrieves information about a stop + + Format of data returned by the site + { + destinations: { + : [ + { + destination_name: str + realtime: 1 if followed, 0 otherwise + vehicle_id: str + vehicle_lattitude: float + vehicle_longitude: float + waittime: HH:MM:SS + waittime_text: str, human readable + }, + ] + } + } + """ + + def __init__( + self, + number, + line, + autoupdate_at_creation=True, + autoupdate=False, + autoupdate_delay=-1, + ): + self.number = number + self.line = line + self.last_update = 0 + self.data = None + if autoupdate_at_creation: + self.update() + + def update(self, auto=False): + """ + Update data + """ + + d = get_data_from_json(SCHEDULE_URL % (self.number, self.line)) + if "destinations" in d: + d = d["destinations"] + else: + return () + self.last_update = time() + if type(d) == dict: + self.data = [] + # let's simplify the data + for i in d: + for j in d[i]: + loc = None + try: + loc = ( + float(j["vehicle_lattitude"]), + float(j["vehicle_longitude"]), + ) + except TypeError: + pass + vehicle = { + "id": j["vehicle_id"], + "destination": j["destination_name"], + "realtime": j["realtime"] == "1", + "location": loc, + "wait_time": hms2seconds(j["waittime"]), + "wait_time_human": j["waittime_text"], + "arrival": int(self.last_update + hms2seconds(j["waittime"])), + } + self.data.append(vehicle) + self.data = sorted(self.data, key=lambda item: item["arrival"]) + else: + self.last_update = 0 + + def data_age(self): + """ + Returns the age of the data + """ + + return time() - self.last_update + + def get_line(self): + class Line: + """ + Information on the line served at a stop + """ + + def __init__(self, data): + self.ve = data + + def vehicles(self): + if self.ve is not None: + return list(range(0, len(self.ve))) + return [] + + def get_vehicle(self, vehicle): + class Vehicle: + """ + Information on a vehicle passage + """ + + def __init__(self, data): + self.id = data["id"] + self.location = data["location"] + self.destination = data["destination"] + self.is_realtime = data["realtime"] + self.wait_time = data["wait_time"] + self.wait_time_text = data["wait_time_human"] + self.arrival = data["arrival"] + + return Vehicle(self.ve[vehicle]) + + return Line(self.data) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/src/vcub.py b/src/vcub.py new file mode 100644 index 0000000..44424cd --- /dev/null +++ b/src/vcub.py @@ -0,0 +1,132 @@ +""" +Provides all info about V³ stations +""" + +from src.libs import get_data_from_json +from time import time + +vcub_url = "https://ws.infotbm.com/ws/1.0/vcubs" + + +class Vcub: + """ + Retrieves information from V³ stations + + Data format, as returned by the infotbm website: + { + lists: [ + { + 'id': station number, + 'name': str, + 'connexionState': 'CONNECTED' if in service 'DISCONNECTED' otherwise, + 'typeVlsPlus': 'VLS_PLUS' if V³+ 'PAS_VLS_PLUS' otherwise, + 'nbPlaceAvailable': 33, + 'nbBikeAvailable': 0 + 'nbElectricBikeAvailable': 6 + 'latitude': float, + 'longitude': float, + }, + ] + } + """ + + def __init__( + self, + autoupdate_at_creation=None, + autoupdate=False, + autoupdate_delay=-1, + data=None, + ): + self.last_update = 0 + if type(data) == dict: + self.data = self.update(data=data) + else: + self.data = None + if autoupdate_at_creation or ( + autoupdate_at_creation is None and self.data is None + ): + self.update() + + def update(self, auto=False, data=None): + """ + Updates data + auto optionnal param is to set if a update is a automatic one, + and must be performed as defined in autoupdate_delay variable + """ + + if data is None or type(data) != dict: + d = get_data_from_json(vcub_url) + else: + d = data + # the original format is awfull, so I change it a little + if type(d) == dict: + self.data = {} + d = d["lists"] + for i in d: + e = { + "name": i["name"], + "online": i["connexionState"] == "CONNECTEE", + "plus": i["typeVlsPlus"] == "VLS_PLUS", + "empty": int(i["nbPlaceAvailable"]), + "bikes": int(i["nbBikeAvailable"]), + "ebikes": int(i["nbElectricBikeAvailable"]), + "location": (float(i["latitude"]), float(i["longitude"])), + } + self.data[int(i["id"])] = e + self.last_update = time() + + def data_age(self): + """ + Computes the data's age + """ + + return time() - self.last_update + + def get_names(self): + """ + Returns all names in a dict with id as data + """ + + r = {} + for i in self.data: + r[self.data[i]["name"]] = i + return r + + def get_locations(self): + """ + Returns all locations in a dict with id as data + """ + + r = {} + for i in self.data: + r[self.data[i]["location"]] = i + return r + + def get_by_id(self, id): + """ + Returns a station by its id + """ + + class Station: + """ + A V³ station + """ + + def __init__(self, data, id): + self.data = data + self.id = id + self.name = self.data["name"] + self.location = self.data["location"] + self.online = self.data["online"] + self.isplus = self.data["plus"] + self.bikes = self.data["bikes"] + self.ebikes = self.data["ebikes"] + self.empty = self.data["empty"] + + def __int__(self): + return self.id + + return Station(self.data[id], id) + + def get_all_ids(self): + return tuple(self.data.keys()) diff --git a/stop.py b/stop.py deleted file mode 100644 index c0b35cf..0000000 --- a/stop.py +++ /dev/null @@ -1,246 +0,0 @@ -''' -Fourni les informations sur les arrêts -''' - -from libs import get_data_from_json, hms2seconds -from time import time -from urllib.parse import quote -from re import search - -search_stop_url = 'https://ws.infotbm.com/ws/1.0/get-schedule/%s' -stop_info_url = 'https://ws.infotbm.com/ws/1.0/network/stoparea-informations/%s' -stop_schedule_url = 'https://ws.infotbm.com/ws/1.0/get-realtime-pass/%d/%s' - -line_types = ( - 'Tram', - 'Corol', - 'Lianes', - 'Ligne', - 'Bus Relais', - 'Citéis', -) -line_translate = { - 'Tram A': 'A', - 'Tram B': 'B', - 'Tram C': 'C', - 'Tram D': 'D', - 'TBNight': '58', - 'BAT3': '69', -} - - -def search_stop_by_name (keyword): - ''' - Recherche la référence d'un nom d'arrêt - - Format des données retournées par le site - [ - { - id: str, nommé ref par la suite - name: str - type: str, mais je ne gère que "stop_area" - city: str - }, - ] - ''' - d = get_data_from_json (search_stop_url % quote (keyword)) - r = [] - for i in d: - if i ['type'] == 'stop_area': - r.append ({ - 'name': i ['name'], - 'city': i ['city'], - 'ref': i ['id'], - }) - return (r) - - -def show_stops_from_ref (ref): - ''' - Affiche la liste des arrêts d'une référence donnée par search_stop_name - - Format des données retournées par le site - { - id: str, contenu de la variable ref donnée - name: str - latitude: str, convertible en float - longitude: str, convertible en float - city: str - hasWheelchairBoarding: bool, accessibilité en fauteuil roulant - stopPoints: [ - id: str - name: str, encore le nom - routes: [ - { - id: str - name: str, nom du terminus - line: { - name: str, nom pour les humains - } - } - ] - ] - } - ''' - d = get_data_from_json (stop_info_url % quote (ref)) - r = { - 'ref': d ['id'], - 'name': d ['name'], - 'latitude': float (d ['latitude']), - 'longitude': float (d ['longitude']), - 'city': d ['city'], - 'stop_points': [], - } - for i in d ['stopPoints']: - s = { - 'name': i ['name'], - 'routes': [], - } - s ['id'] = int (search ('[0-9]+$', i ['id']).group ()) - for j in i ['routes']: - rte = { - 'terminus': j ['name'], - 'line_human': j ['line'] ['name'], - } - add = False - if rte ['line_human'] in line_translate: - line_id = line_translate [rte ['line_human']] - add = True - else: - try: - line_id = search ('[0-9]+$', rte ['line_human']).group () - except AttributeError: - continue - line_id = '%02d' % int (line_id) - for i in line_types: - if rte ['line_human'] [0:len (i)] == i: - add = True - break - if add: - rte ['line_id'] = line_id - s ['routes'].append (rte) - if s ['routes'] != []: - r ['stop_points'].append (s) - return (r) - - -class StopRoute (): - ''' - Récupère les informations sur un arrêt - - Format des données retournées pas le site - { - destinations: { - : [ - { - destination_name: str - realtime: 1 si suivi, 0 sinon - vehicle_id: str - vehicle_lattitude: float - vehicle_longitude: float - waittime: HH:MM:SS - waittime_text: str, lisible pas un humain - }, - ] - } - } - ''' - - def __init__ (self, number, line, autoupdate_at_creation = True, autoupdate = False, autoupdate_delay = -1): - self.number = number - self.line = line - self.last_update = 0 - self.data = None - if autoupdate_at_creation: - self.update () - - def update (self, auto = False): - ''' - Met à jour les données - ''' - d = get_data_from_json (stop_schedule_url % (self.number, self.line)) - if 'destinations' in d: - d = d ['destinations'] - else: - return () - self.last_update = time () - if type (d) == dict: - self.data = [] - # let's simplify the data - for i in d: - for j in d [i]: - loc = None - try: - loc = (float (j ['vehicle_lattitude']), float (j ['vehicle_longitude'])) - except TypeError: - pass - vehicle = { - 'id': j ['vehicle_id'], - 'destination': j ['destination_name'], - 'realtime': j ['realtime'] == '1', - 'location': loc, - 'wait_time': hms2seconds (j ['waittime']), - 'wait_time_human': j ['waittime_text'], - 'arrival': int (self.last_update + hms2seconds (j ['waittime'])), - } - self.data.append (vehicle) - self.data = sorted (self.data, key = lambda item: item ['arrival']) - else: - self.last_update = 0 - - def data_age (self): - ''' - Retourne l'âge des données - ''' - return (time () - self.last_update) - - def get_line (self): - class Line (): - ''' - Information sur la ligne déservie à un arrêt - ''' - def __init__ (self, data): - self.ve = data - - def vehicles (self): - if self.ve is not None: - return (list (range (0, len (self.ve)))) - return ([]) - - def get_vehicle (self, vehicle): - class Vehicle (): - ''' - Information sur un passage de véhicule - ''' - def __init__ (self, data): - self.id = data ['id'] - self.location = data ['location'] - self.destination = data ['destination'] - self.is_realtime = data ['realtime'] - self.wait_time = data ['wait_time'] - self.wait_time_text = data ['wait_time_human'] - self.arrival = data ['arrival'] - - return (Vehicle (self.ve [vehicle])) - - return (Line (self.data)) - - -if __name__ == '__main__': - from datetime import datetime - for word in ('Gravière', 'Gare Saint Jean', 'Quinconces', 'Zorbut'): - print (word + ':') - for area in search_stop_by_name (word): - print ('\t' + area ['name'] + ' (' + area ['city'] + '):') - for stop in show_stops_from_ref (area ['ref']) ['stop_points']: - print ('\t\t' + stop ['name'] + ' (' + str (stop ['id']) + '):') - for route in stop ['routes']: - print ('\t\t\t' + route ['line_human'] + ' terminus ' + route ['terminus'] + ':') - sr = StopRoute (stop ['id'], route ['line_id']) - line = sr.get_line () - for vehicle in line.vehicles (): - v = line.get_vehicle (vehicle) - if v.is_realtime: - print ('\t\t\t\t' + str (v.wait_time) + ' (' + datetime.fromtimestamp (v.arrival).strftime ('%H:%M') + ') → ' + v.destination) - else: - print ('\t\t\t\t~' + str (v.wait_time) + ' (' + datetime.fromtimestamp (v.arrival).strftime ('%H:%M') + ') → ' + v.destination) \ No newline at end of file diff --git a/vcub.py b/vcub.py deleted file mode 100644 index 9d814e9..0000000 --- a/vcub.py +++ /dev/null @@ -1,126 +0,0 @@ -''' -Provides all info about V³ stations -''' - -from libs import get_data_from_json -from time import time - -vcub_url = 'https://ws.infotbm.com/ws/1.0/vcubs' - - -class Vcub (): - ''' - Récupère les informations des stations V³ - - Format de données, tel que retourné par le site infotbm : - { - lists: [ - { - 'id': numéro de la station, - 'name': str, - 'connexionState': 'CONNECTEE' si en service 'DECONNECTEE' sinon, - 'typeVlsPlus': 'VLS_PLUS' si V³+ 'PAS_VLS_PLUS' sinon, - 'nbPlaceAvailable': 33, - 'nbBikeAvailable': 0 - 'nbElectricBikeAvailable': 6 - 'latitude': float, - 'longitude': float, - }, - ] - } - ''' - def __init__ (self, autoupdate_at_creation = None, autoupdate = False, autoupdate_delay = -1, data = None): - self.last_update = 0 - if type (data) == dict: - self.data = self.update (data = data) - else: - self.data = None - if autoupdate_at_creation or (autoupdate_at_creation is None and self.data is None): - self.update () - - def update (self, auto = False, data = None): - ''' - Updates data - auto optionnal param is to set if a update is a automatic one, and must be performed as defined in autoupdate_delay variable - ''' - if data is None or type (data) != dict: - d = get_data_from_json (vcub_url) - else: - d = data - # the original format is awfull, so I change it a little - if type (d) == dict: - self.data = {} - d = d ['lists'] - for i in d: - e = { - 'name': i ['name'], - 'online': i ['connexionState'] == 'CONNECTEE', - 'plus': i ['typeVlsPlus'] == 'VLS_PLUS', - 'empty': int (i ['nbPlaceAvailable']), - 'bikes': int (i ['nbBikeAvailable']), - 'ebikes': int (i ['nbElectricBikeAvailable']), - 'location': (float (i ['latitude']), float (i ['longitude'])) - } - self.data [int (i ['id'])] = e - self.last_update = time () - - def data_age (self): - ''' - Computes the data's age - ''' - return (time () - self.last_update) - - def get_names (self): - ''' - Returns all names in a dict with id as data - ''' - r = {} - for i in self.data: - r [self.data [i] ['name']] = i - return (r) - - def get_locations (self): - ''' - Returns all locations in a dict with id as data - ''' - r = {} - for i in self.data: - r [self.data [i] ['location']] = i - return (r) - - def get_by_id (self, id): - ''' - Returns a station by its id - ''' - class Station (): - ''' - A V³ station - ''' - def __init__ (self, data, id): - self.data = data - self.id = id - self.name = self.data ['name'] - self.location = self.data ['location'] - self.online = self.data ['online'] - self.isplus = self.data ['plus'] - self.bikes = self.data ['bikes'] - self.ebikes = self.data ['ebikes'] - self.empty = self.data ['empty'] - - def __int__ (self): - return (self.id) - - return (Station (self.data [id], id)) - - def get_all_ids (self): - return (tuple (self.data.keys ())) - - -if __name__ == '__main__': - v = Vcub () - for i in (v.get_by_id (149), v.get_by_id (v.get_names () ['Buttiniere']), ): - print ('%s (%d) (%f, %f)%s%s\n\tbikes: %d\n\te-bikes: %d\n\tfree: %d\n\t' % (i.name, i, i.location [0], i.location [1], i.isplus and ' (VCUB+)' or '', i.online and ' ' or ' OFFLINE', i.bikes, i.ebikes, i.empty)) - v = Vcub (data = get_data_from_json (vcub_url)) - for i in (v.get_by_id (v.get_locations () [(44.8875, -0.51763)]), ): - print ('%s (%d) (%f, %f)%s%s\n\tbikes: %d\n\te-bikes: %d\n\tfree: %d\n\t' % (i.name, i, i.location [0], i.location [1], i.isplus and ' (VCUB+)' or '', i.online and ' ' or ' OFFLINE', i.bikes, i.ebikes, i.empty)) - print ('stations :', v.get_all_ids ()) \ No newline at end of file