Dalekie dojazdy do pracy (która odbywa się w godzinach 9-17) spowodowały że zacząłem zastanawiać się jak zoptymalizwać powroty do domu. Problem: wrócić z Gliwic do Mysłowic w jak najkrótszym czasie. Rozwiązanie: Monitorowanie opóźnień miejskich autobusów żeby wyznaczyć optymalne okno czasowe na powrót do domu.
Początkowe pomysły
Monitorowanie ruchu na drodze - pierwszym pomysłem była próba wyciągnięcia danych z map TomTom lub Google, na szczęście zanim zdążyłem otworzyć okno przeglądarki przypomniałem sobie o górnośląskim ZTM (dawniej KZK GOP) które około rok temu zaczęło stawiać tablicę z dynamiczną informacją o kursowaniu autobusów.
Okazało się że w ramach tego systemu istnieje też strona System Dynamicznej Informacji Pasażerskiej - Portal Pasażera z której można wyciągnąć informacje o opóźnieniu autobusów.
Plan działania
Znalezienie wszystkich linii autobusowych do monitorowaniaWyznaczenie przystanków do monitorowaniaPobranie rozkładów jazdyi zapisanie w BD- Pobranie danych o opóżnieniu
- Parsowanie danych, zapis do BD
- Analiza i Wizualizacja
- Wnioski
Monitorowane przystanki i linie autobusowe
Najbardziej interesuje mnie odcinek od Katowic do Mysłowic, ze szczególnym uwzględnieniem węzła Bagienna (rejon Wilhelminy) Żeby zmniejszyć liczbę generowanych zapytań będę pobierał dane o wszystkich autobusach zatrzymujących się na przystanku Mysłowice Katowicka
Z tego przystanku odjezdzają autobusy linii: 35, 44, 66, 77, 77N, 106, 149, 219, 292, 536, 788, 931, 995. Wstępnie do monitorowania wybrałem trzy linie, jadące od strony Katowic:
Pobranie informacji o wszystkich autobusach zatrzymujących się na przystanku Mysłowice Katowicka (post_id: 103750
)
import requests
URL = 'http://sdip.metropoliaztm.pl/web/map/vehicles/gj/A?interval=00%3A05%3A00&post_id=103750'
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
r = requests.get(URL, headers=headers, verify=False)
print(r.json())
Analiza (pobranego pliku JSON) pozwoliła wyciągnąć kilka interesujących informacji:
- można ustalić koordynaty każdego autobusu ZTM:
"geometry":{
"type":"Point",
"coordinates":[
2130007.73094,
6479933.6062
]
},
Sa one zapisane w układzie EPSG:3857 (csr - coordinate projection systems). Konwersja do innych formatów będzie odbywać się z pomocą pyproj
# conda install -c conda-forge pyproj
Różnica w stosunku do planowanego rozkładu jest opisana jako difference
, oznaczenie linii jest zapisane w polu get_line_id
stop_id
post_id
Ponieważ przystanki w pliku JSON są zapisane jako ID dla własnej wygody potrzebowałem znać ich nazwy.Zrobiłem to parsując podstrony z listą przystanków
def get_stops():
bus_stops = {}
page = 1
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
while True:
URL = f"http://sdip.metropoliaztm.pl/web/ml/stop/page/{page}"
r = requests.get(URL, headers=headers, verify=False)
if r.status_code == 200:
soup = BeautifulSoup(r.content, 'html.parser')
page += 1
time.sleep(.400)
else:
break
li = soup.find_all('li')
for i in li:
# <li><a data-ajax="false" data-mini="true" href="/web/ml/route/100939">Grodziec Boleradz n/ż</a></li>
# 4 groups: [0] href=" ; [1] /web/ml/route/100939 ; [2] "> ; [3] Grodziec Boleradz n/ż
results = re.search(r'(href=")(.*)(">)([\w\s.\/]*)', str(i)).groups()
if results[1].lstrip('/').split('/')[2] == 'route':
key = results[1].lstrip('/').split('/')[3]
bus_stops[key] = results[3]
return bus_stops
Przejrzałem na spokojnie informacje w pliku JSON z autobusami przejeżdżającymi przez dany przystanek i okazało się że nie ma tam informacji o przystanku na którym ma sie zatrzymać autobus :/ Potrzebne będzie parsowanie HTML,przykładowa podstrona. Na szczęście ten adres jest w pliku JSON.
Pobranie rozkładu jazdy na dany dzień
Sprawdzenie rozkładu jazdy dla linii A66 pokazało że jego trasa może przebiegać w różnych wariantach. Na szczęście na stronie ZTM pokazywany jest rozkład wszystkich kursów na dany dzień dla danej linii. Postanowiłem ze będę pobierał codziennie o północy rozkład na dany dzień ze strony ZTM
daily_timetable = {}
soup = BeautifulSoup(html, 'html.parser')
section = soup.find('section')
containers = section.find_all('div', {'class': 'container'})
for entry in containers:
bus_line = entry.find('a', {'class':'btn btn-danger btn-lg'}) # <a style="min-width:60px;" href="/rozklady/1-106/" class="btn btn-danger btn-lg" title="Zobacz szczegółowy rozkład jazdy dla linii 106">
bus_no = str(bus_line).split()[-2]
print(bus_no)
arrivals = entry.find_all('div', {'class':'panel-body'}) # <div class="panel-body rundaycalendar" >
today_arrivals = arrivals[2].find_all('div', {'class':'arrival-time'})
bus_arrivals = {}
for item in today_arrivals:
txt = re.sub(r'[\ \n]{2,}', '', str(item)) # remowe double spaces and newlines
no_tags = re.sub("<.*?>", "", txt) # remove HTML tags
arrival_list = no_tags.split()
hour = int(arrival_list[0])
minutes = arrival_list[1:]
bus_arrivals[hour] = minutes
daily_timetable[bus_no] = bus_arrivals
Dane o rozkładzie jazdy przetwarzam do słownika o strukturze:
daily_timetable = {
'149' : { 8: [15, 30],
9: [20, 40] }
'931' : { 18: [25, 45],
19: [10, 50] }
}
- Sprawdzenie odstępów czasowych w ciągu dnia
- Sprawdzenie opóźnień z zadanym interwałem czasowym (30s)
Porównanie z rzeczywistym rozkładem jazdy
Zapytanie o wszystkie autobusy dla przystanku o
- post_id = 103750 (Przystanek: Mysłowice Katowicka)
- interwale: 00%3A05%3A00 - %3A 00:05:00