This commit is contained in:
mdeboute 2022-04-26 22:25:40 +02:00
parent dd82e9afe9
commit 6a2c514265
12 changed files with 615 additions and 407 deletions

View File

@ -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.
These libraries are under GNU GPL v3. See [license](LICENSE) for more details.

30
libs.py
View File

@ -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)

30
main.py Normal file
View File

@ -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")

31
map.html Normal file
View File

@ -0,0 +1,31 @@
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Google Maps - gmplot</title>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=visualization"></script>
<script type="text/javascript">
function initialize() {
var map = new google.maps.Map(document.getElementById("map_canvas"), {
zoom: 13,
center: new google.maps.LatLng(44.840843, -0.577247)
});
var marker_icon_6495ED = {
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAiCAYAAACwaJKDAAAABmJLR0QA/wD/AP+gvaeTAAADFUlEQVRIia2VW0gUYRiGn91lrGwj0qCsbS9qpZN5UaFlUQmF1IUURN0FkVFeSEQgdBcZBN10hEqipLoxjKjoXG5ZFh6y8tBKanmazLRd0t1Wx935u3AG121tx9X35me+/32f4eP//hkT/9dyYD0wHzADKiADb4CGKNl/tBmoBIQ1wSbsKVnCkbZL2FOyhDXBJgCh7W+OFDZFeD6JyZS/aOUOU+qWPBJtK8A02varo5baZ+dpqbkjEOIUcFR70T9QE3Bammo9lLm3EHtKVtR22use4yw6wNCA9yxwWAdbQjyHLNKUY1vzbjN/8caoQICZcxwkJWfQUl2yRqhBD1ARCp0H3E3bfkxauGqHIaAu6ywbUlw8na7SDUAR0G/W9nKtCQvil2/aPy6grmUbc5iRaI8HcmF4TACyk9N3Y7ZIMUHNFglH+m6AbB1qAZYkJa+LCagryZEBsASwmAEJiJs+yzYhqJaPAyTzSFmMYTeqkbwZUADF6+6YEFLLK4Ci32dXV1P5hKBa3gWoevv3miqKUQNKTEA1OERTRTHAPRgZqYs+j+ytd16KCdrgvIzPI3uBizByo7xAf1fzu21zHWuZkWg3DPzR/JayG3kINXgEKA2FAlQKNZjQ+vF+um1pJvEz50YF9nbU8ujCTgLKn3PAcb1uCfM9UQNKcnv90xWLVu9EmmodEzjoc/PgTDYD3t5bQE7onjnMK4CcP79/1JXdzAMx9uyWF+fj88hfgH2EDXk4FMAP7On8/CLQXF0SESg3vuLr+zsA+xk+j1GKBAX4CFyqultAMDAY1oug+v4JgBKgLFJ4LChAgc8j+5srb40qdjWV09NaIwg5mPFAfwLXG15eGVV0vb4Gw6NTFwsUoNAt1+OWh//Gir+PtrpHAFf/F4oGrQG+tH16CECnq5Tg0KAf7TrGCgV4IDe+BEB2OQGcRDjx8UKdPW0fUAOK/iV6Hi1gBFoVDAzS/a2Kvt5WgHcGMobUk5J5UAABYNpkQatmL0gVQIsRs5H2AXq11tsmE+pW/H0A/ZMJ7dbW75MJ7dLWViPmv887IG8sMF4/AAAAAElFTkSuQmCC",
labelOrigin: new google.maps.Point(10, 11)
};
new google.maps.Marker({
position: new google.maps.LatLng(44.840843, -0.577247),
icon: marker_icon_6495ED,
map: map
});
}
</script>
</head>
<body style="margin:0px; padding:0px;" onload="initialize()">
<div id="map_canvas" style="width: 100%; height: 100%;" />
</body>
</html>

126
poetry.lock generated Normal file
View File

@ -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"},
]

16
pyproject.toml Normal file
View File

@ -0,0 +1,16 @@
[tool.poetry]
name = "info_tbm"
version = "0.1.0"
description = ""
authors = ["mdeboute <martin.deboute@gmail.com>"]
[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"

32
src/libs.py Normal file
View File

@ -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

245
src/stop.py Normal file
View File

@ -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_stop_id>: [
{
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)

0
src/utils.py Normal file
View File

132
src/vcub.py Normal file
View File

@ -0,0 +1,132 @@
"""
Provides all info about 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 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 + '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 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())

246
stop.py
View File

@ -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_stop_id>: [
{
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)

126
vcub.py
View File

@ -1,126 +0,0 @@
'''
Provides all info about 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
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 + '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 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 ())