Aneta Derková & Andrea Janatová: Analýza hromadné dopravy Jihomoravského kraje

Analýza hromadné dopravy Jihomoravského kraje


Kdo jsme?

Jsme Anet a Andrea. Obě jsme studovaly/studujeme ESF na Masarykově univerzitě. Obě jsme byly na Erasmu ve Finsku. Obě pracujeme v IT firmě. Obě jsme chtěly zpracovávat projekt na téma cestovaní (doprava) v Pythonu.

Kdo nám pomáhal s projektem?

Na meet your mentor jsme prezentovaly představu, že chceme použít data o dopravě v Brně a pomocí nich vytvořit aplikaci (pro setkávání), do které dva uživatelé zadají zastávku hromadné dopravy, která je jim nejbližší, a poté jim aplikace určí nejbližší místo, ve kterém by se mohli uživatelé setkat. Při prezentování tohoto tématu jsme slyšely spoustu názorů – někteří nám tvrdili, že je to lehké, další, že je to až moc těžké. Nakonec se pro naše téma nadchl Martin Zelený, který pracuje v Red Hat a zajímá se o data o veřejné dopravě v Brně.

Jaká data zpracováváme?

Martin nám poradil několik webových stránek, které by mohly být užitečné při zpracovávání našeho projektu. Nakonec jsme se rozhodly použít data, která jsou dostupná na webové stránce https://iris.bmhd.cz, na které jsou zobrazeny reálné polohy vozidel aktuálně v provozu, jež patří pod správu DPMB, ať už jsou to tramvaje, trolejbusy, autobusy nebo vlaky, odjezdy ze zastávek a další informace, a https://iris.bmhd.cz/api/data.json, kde jsou tyto data ve formátu JSON.

Na obrázku níže je ukázka dat z
https://iris.bmhd.cz/api/data.json


Klíče jsou evidenční čísla vozidel. Každý klíč má několik detailů (podklíčů). Vzhledem k chybějící dokumentaci jsme musely samy vysledovat, co jednotlivé podklíče znamenají. V přápadě StartStop, LastStop, EndStop se jedná pouze o identifikační čísla zastávek, ne jejich názvy. Pro účely našeho projektu ale nebylo potřeba tyto čísla rozklíčovat do přesných názvů. Jediný podklíč, Route, se nám vysledovat nepodařilo. 

Popis jednotlivých podklíčů je v následující tabulce:

Podklíč
Popis
Formát
Lat
zeměspisná šířka
double
Lng
zeměpisná délka
double
Line
číslo linky
int
LineX
číslo linky, v případě, že má v názvu písmeno
string
Delay
zpoždění v minutách
int
Route
– 
int
Course
kurzové číslo
string
Bear
azimut směru vozidla (ve stupních)
int, <0;360>
StartStop
identifikační číslo počáteční zastávky
int
LastStop
identifikační číslo poslední zastávky, kterou vozidlo projelo
int
EndStop
identifikační číslo konečná zastávky
int
LF
jestli je vozidlo nízkopodlažní
bool


Data bychom tedy měly, ale co s nimi?

Již na naší první schůzce s Martinem jsme zjistily, že téma projektu budeme muset trochu upravit, protože vytvořit aplikaci podle našich původních představ bychom v tak krátkém čase a s našimi zkušenosti nezvládly. Postupně jsme dospěly k rozhodnutí, že tyto data zpracujeme v Pythonu a poté v PowerBI vytvoříme vizualizace a odpovíme si na následující otázky:
  1. Kolik je aktuálně na silnicích a kolejích vozů DPMB?
  2. Jaké je momentální zpoždění podle jednotlivých linek?
  3. Ve kterých místech dochází ke shlukování vozidel stejných linek?

Stahujeme data!

Nejprve jsme stahovaly vždy aktuální data o poloze vozidel pomocí knihovny requests v Pythonu. 

# stáhnutí dat z iris
r = requests.get('https://iris.bmhd.cz/api/data.json')
j = json.loads(r.text)
data = j['Data']

Díky tomu, že jsme na projektu pracovaly převážně v dobách, kdy není doprava moc hustá (víkendy, pozdě večer) a skoro žádné shluky se nevytvářely, tak jsme kód rozšířily i o načtení dat od uživatele. Tedy pro účely testování dat, která jsme si již dříve uložili v době, kdy bylo v provozu spoustu vozidel a vytvářely se i shluky.

# načtení souboru od uživatele přes cmd
if len(sys.argv) == 2:
with open(sys.argv[1], 'r', encoding='utf-8') as f:
text = f.read()
j = json.loads(text)
# stáhnutí dat z iris
else:
r = requests.get('https://iris.bmhd.cz/api/data.json')
j = json.loads(r.text)
data = j['Data']

Pokus č. 1 o odpověď na otázku: „Kolik je aktuálně na silnicích a kolejích vozů DPMB“?

Náš první kód programu vypadal následovně:

# vytvoření seznamu linek [číslo linky]
linky = []
for v in data.values():
if 'LineX' in v.keys():
linky.append(v['LineX'])
else:
linky.append(str(v['Line']))
# vytvoření seznamu jedinečných linek
jedinecneLinky = []
for x in linky:
if x not in jedinecneLinky:
jedinecneLinky.append(x)
# vytvoření tabulky (linka: počet vozů)
tabulkaLinek = {}
for a in sorted(jedinecneLinky):
tabulkaLinek[a] = linky.count(a)
print(tabulkaLinek)
# uložení do souboru .json
with open('pocetVozuStare.json', 'w') as fp:
json.dump(tabulkaLinek, fp)

Program vytvořil seznam linek pomocí proměnné LineX (pokud byl uvedený) nebo Line. Tento seznam se pak upravil tak, aby tam vždy každá linka byla jenom jednou. Poté se vytvořil slovník, kde klíče byly jednotlivé linky a hodnoty počet vozů na trase.

Výsledek tohoto programu byl typu { "1": 24, "10": 1, "104": 6, "105": 8 …}.

Jak určit typ vozu?

Pro lepší a přehlednější vizualizace dat jsme si určily, co která linka je – tramvaj, trolejbus, autobus, vlak. Ve stahovaných datech nebylo toto určení blíže specifikované, na základě evidenčního čísla vozidel jsme ale jednotlivé typy rozklíčovaly.
  • Pokud evidenční číslo vozidla začíná 1 a má 4 místa, jedná se o tramvaj.
  • Pokud evidenční číslo vozidla začíná 3 a má 4 místa, jedná se o trolejbus.
  • Pokud evidenční číslo vozidla začíná 2 a má 5 míst, jedná se o vlak.
  • Zbylá vozidla jsou autobusy.
Jedná se o reálnou podobu prostředku. Pokud je tedy náhradní doprava za tramvaj (např. x3), v našem rozdělení ji bereme jako autobus, protože reálně je vozidlem autobus (i když jede za tramvaj).

Odpovědi na otázky: ,,Kolik je aktuálně na silnicích a kolejích vozů DPMB?“ a ,,Jaké je momentální zpoždění podle typu vozu a linek?“

Následující kód nám z originálních dat vytvoří soubor typu JSON, ve kterých jsou klíče čísla linek s hodnotami:
  1. pocet: počet vozů na trati jednotlivé linky
  2. typ: typ vozu (tramvaj, trolejbus, vlak, autobus)
  3. celkoveZpozdeni: celkové zpoždění všech vozů jednotlivé linky
  4. minZpozdeni: nejmenší zpoždění jednotlivé linky
  5. maxZpozdeni: největší zpoždení jednotlivé linky
# vytvoření slovníku {linka :{pocet, typ, celkoveZpozdeni, minZpozdeni, maxZpozdeni}}
linky = {}
for k, v in data.items():
# vytvoření klíče linka
linka = v['LineX'] if 'LineX' in v.keys() else str(v['Line'])
# vytvoření podklíčů pocet, typ, celkoveZpozdeni, minZpozdeni, maxZpozdeni
if linka not in linky:
linky[linka] = {
"pocet": 1,
"typ": "",
"route": [v["Route"]],
"celkoveZpozdeni": v["Delay"],
"minZpozdeni": v["Delay"],
"maxZpozdeni": v["Delay"]
}

# přidání hodnot do typu (tramvaj, trolejbus, vlak, bus)
if k.startswith('1') and len(k) == 4:
linky[linka]["typ"] = "tramvaj"
elif k.startswith('3') and len(k) == 4:
linky[linka]["typ"] = "trolejbus"
elif k.startswith('2') and len(k) == 5:
linky[linka]["typ"] = "vlak"
else:
linky[linka]["typ"] = "bus"
# přidání hodnot do pocet, celkoveZpozdeni, minZpozdeni, maxZpozdeni
else:
linky[linka]["pocet"] += 1
linky[linka]["route"].append(v["Route"])
linky[linka]["celkoveZpozdeni"] += v["Delay"]
linky[linka]["minZpozdeni"] = min(v["Delay"], linky[label]["minZpozdeni"])
linky[linka]["maxZpozdeni"] = max(v["Delay"], linky[label]["maxZpozdeni"])
# uložení do formátu .json
with open('pocetVozu.json', 'w') as fp:
json.dump(linky, fp, indent=4)

Výsledná data vypadala následovně:
"3": {
"pocet": 8,
"typ": "tramvaj",
"celkoveZpozdeni": 7,
"minZpozdeni": 0,
"maxZpozdeni": 3
}
V PowerBI jsme si vytvořily následující vizualizace, které nám odpověděly na naše otázky:

 

 

Ve kterých místech dochází ke shlukování vozidel stejných linek?

Při hledání odpovědi na poslední otázku jsme si data načetly stejným způsobem. Poté jsme si vytvořily slovník, jehož klíčem jsou opět linky. Načetly jsme si ale rozdílná data:
  1. souradnice: zeměpisná šířka a délka
  2. sirka: zeměpisná šířka
  3. delka: zeměpisná délka
  4. pocatecniZastavka: počáteční zastávka vozu
  5. posledniZastavka: poslední zastávka, kterou vozidlo projelo
  6. konecnaZastavka: konečná zastávka vozu

# vytvoření slovníku linky: {linka: {souradnice, sirka, delka, pocatecniZastavka, posledniZastavka,
konecnaZastavka}}

linky = {}
for k, v in data.items():
# vytvoření klíče linka
linka = v['LineX'] if 'LineX' in v.keys() else str(v['Line'])
if linka not in linky:
linky[linka] = {}
# vytvoření podklíčů souradnice, sirka, delka, pocatecniZastavka, posledniZastavka, konecnaZastavka
linky[linka][k] = {
"souradnice": (v["Lat"], v["Lng"]),
"sirka": v["Lat"],
"delka": v["Lng"],
"pocatecniZastavka": v["StartStop"],
"posledniZastavka": v["LastStop"],
"konecnaZastavka": v["EndStop"]
}

Poté program tento slovník prochází a vytváří dvojice vozů, které jsou ve shluku.  


listA = []
listB = []
# cyklus, který prochází slovník linky
for linka, detailyVozu in linky.items():
# definování slovníku, který slouží na meziukládání detailech o vozu
detaily = {}
# porovnávání dvou vozidel
for detailA in detailyVozu:
for detailB in detailyVozu:
# pokud se vozidla již porovnávali, nepokračuje se
if detailA == detailB:
break
# vytvoření detailů o dvou porovnávajích vozidlech
detaily["souradniceA"] = detailyVozu[detailA]["souradnice"]
detaily["souradniceB"] = detailyVozu[detailB]["souradnice"]
detaily["sirkaA"] = detailyVozu[detailA]["sirka"]
detaily["sirkaB"] = detailyVozu[detailB]["sirka"]
detaily["delkaA"] = detailyVozu[detailA]["delka"]
detaily["delkaB"] = detailyVozu[detailB]["delka"]
detaily["pocZastavkaA"] = detailyVozu[detailA]["pocatecniZastavka"]
detaily["pocZastavkaB"] = detailyVozu[detailB]["pocatecniZastavka"]
detaily["poslZastavkaA"] = detailyVozu[detailA]["posledniZastavka"]
detaily["poslZastavkaB"] = detailyVozu[detailB]["posledniZastavka"]
detaily["konZastavkaA"] = detailyVozu[detailA]["konecnaZastavka"]
detaily["konZastavkaB"] = detailyVozu[detailB]["konecnaZastavka"]
# podmínky, které zajistí, že vozy jedou ve stejném směru a nenachází se na začáteční
či konečné zastávce

if ((detaily["pocZastavkaA"] == detaily["pocZastavkaB"]) \
or (detaily["konZastavkaA"] == detaily["konZastavkaB"])) \
and (detaily["poslZastavkaA"] != detaily["konZastavkaA"]) \
and (detaily["poslZastavkaB"] != detaily["konZastavkaB"]) \
and (detaily["poslZastavkaA"] != detaily["pocZastavkaA"]) \
and (detaily["poslZastavkaB"] != detaily["pocZastavkaB"]):
vzdalenost = gd.distance(detaily["souradniceA"], detaily["souradniceB"]).m
# podmínka, že vzdálenost dvou vozů musí být menší než 300m
if vzdalenost < 300:
podlistA = [linka, detaily["sirkaA"], detaily["delkaA"]]
listA.append(podlistA)
podlistB = [linka, detaily["sirkaB"], detaily["delkaB"]]
listB.append(podlistB)
# vytvoření csv souboru se souřadnicemi vozů, které jsou ve shluku (sloupce linka, zemepisna sirka
a zemepisna delka)

with open('shluky_souradnice.csv', 'w', encoding='utf-8', newline='\n') as csvfile:
csv_writer = csv.writer(csvfile, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
[csv_writer.writerow(r) for r in listA]
[csv_writer.writerow(r) for r in listB]

Výsledná zanalyzovaná data se ukládají do CSV souboru. Ten jsme následně nahrály do PowerBI, ve kterém jsme si zobrazily shluky vozidel.



Jako kontrolní data jsme použily data z pátku 23. listopadu 8:00 ráno. Na první obrázku jsou data ze stránek iris a na druhém pak vizualizace v PowerBI.




Další kontrolní data jsme použily z pondělí 26. listopadu v půl 8 ráno.





Co je možné vylepšit a jak program rozšířit?

Vzhledem k omezenému času je na programu možné spoustu věcí vylepšit a program o některé funkcionality rozšířit. 

Jako jedno z vylepšení je možné do kódu dodat funkce a tím kód zjednodušit, například nahradit jimi některé podmínky. 


Co se týče možného rozšíření, bylo by možné si vytvořit číselník pro identifikaci jmen zastávek na základě jejich identifikačních čísel a poté je zanést do vizualizice shluků, aby bylo možné analyzovat v okolí které zastávky se vozidla shlukují.


V rámci analýzy shluků jsme se zaměřily spíše na analýzu shluků vozidel přímo v Brně, tedy hlavně tramvaje, trolejbusy a městské autobusy. Program by mohl být rošířen, resp. upraven tak, aby se vzdálenost pro výpočet shluků lišila podle typu vozidla. Pro vozidla jezdící v Brně (tramvaje, trolejbusy a městské autobusy) by byla vzdálenost menší, pro vozidla jezdící mimo Brno (regionální autobusy, vlaky) pak větší.


Je možné také data dlouhodoběji stahovat, nasbírat dostatečné množství dat a poté sledovat trendy na trasách jednotlivých linek. Pokud jsou na mapě místa, kde se tvoří shluky pravidelně, tak program tyto místa odhalí a poté na to může město Brno zareagovat úpravou takových míst.


Komentáře

Populární příspěvky z tohoto blogu

Barbora Junová: Podpora začínajícího podnikání zaměřeného na prodej výrobků a poskytování služeb

Petra Havlínová: Automatické zpracování podkladů pro vyhodnocení vybraných KPI

Kateřina Kolouchová & Lenka Tomešová: Vliv počasí na kriminalitu v New Yorku a Brně