Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How ZTM data were parsed. #3

Open
robert-skarzycki opened this issue May 28, 2017 · 2 comments
Open

How ZTM data were parsed. #3

robert-skarzycki opened this issue May 28, 2017 · 2 comments

Comments

@robert-skarzycki
Copy link

Hi,

I'm considering reusing your project to prepare similar visualization for other Polish cities that provides timetable data of public transportation. For instance, Wrocław provides timetable in XML and GTFS, but the ZTM data your project is based on seems to be formatted custom way. Could you share some info how those data were formatted or how other type data could be consumed by your engine?

@mccartney
Copy link

(I am not the author, but...)

You can see the scripts/transport_data.py script, which:

  • takes the data from ftp://rozklady.ztm.waw.pl/, the latest file is taken
  • and does the parsing - see ZtmFormatIterator
  • writes the .json files to src/includes

If you implement your own transport_data.py, and store the information in the same format - that should be enough for data, I guess. Next thing is the "outline of the city" - see scripts/shape_data.py.

@sdadas
Copy link
Owner

sdadas commented May 28, 2017

Warszawski ZTM udostępnia dane w wymyślonym przez siebie formacie, więc niestety musiałem od podstaw napisać parser dla tych plików. Oznacza to, że najprawdopodobniej żaden fragment tego kodu nie będzie mógł być użyty do generowania danych dla innych miast. Dobra wiadomość jest taka, że jeżeli inne miasta używają standardowych formatów typu XML, wygenerowanie danych na ich podstawie powinno być znacznie prostsze. To tyle słowem wstępu, a teraz specyfikacja plików wynikowych - tych, które są konsumowane przez klienta.

Wizualizacja potrzebuje trzech typów plików: danych geograficznych miasta (określających kształt, lokalizację na mapie, rozmiar warstwy itp.), danych dotyczących przystanków (kody, lokalizacja na mapie oraz informacje o możliwości przejścia piechotą pomiędzy przystankami) oraz połączeń komunikacyjnych pomiędzy przystankami (dla Warszawy jest 1.6 mln takich połączeń, więc zostały podzielone na wiele plików reprezentujących dni tygodnia oraz godziny). Interfejsy reprezentujące wewnętrzną strukturę tych plików zdefiniowane są w src/types.d.ts, natomiast same pliki generowane są przez dwa skrypty napisane w pythonie: scripts/shape_data.py oraz scripts/transport_data.py.

1. Dane geograficzne miasta

Pierwszy plik nie ma żadnego związku z danymi ZTM, informacje na temat samego miasta można znaleźć w Wikipedii, wyekstrahować z Google Maps lub Open Street Maps. Skrypt scripts/shape_data.py przyjmuje na wejściu obrazek reprezentujący kształt Warszawy (scripts/data/warsaw.png) i generuje plik json o następującej strukturze:

interface ShapeData {
    center?: L.LatLngTuple;
    box?: L.LatLngTuple[];
    hborder?: number[][];
    vborder?: number[][];
    size?: [number, number];
    river?: number[];
}
  • center - centralny punkt miasta jako długość i szerokość geograficzna, w tym punkcie zostanie zainicjalizowana wizualizacja po starcie
  • box - dwa punkty wyznaczające prostokąt, w granicach którego powinna być umieszczona warstwa wizualizacji (skrajny punkt południowo-zachodni oraz północno-wschodni jako długości i szerokości geograficzne)
  • hborder i vborder - wydobyte z obrazka granice poziomie i pionowe miasta, na ich podstawie rysowany jest kształt miasta po stronie klienta
  • size - rozmiar obrazka, warstwa po stronie klienta będzie miała taką samą rozdzielczość jak obraz źródłowy
  • river - dodatkowa tablica wyznaczająca linię Wisły (wykorzystywana wyłącznie do zapobiegania przechodzeniu "na pieszo" przez rzekę, w miastach bez rzeki można się jej trywialnie pozbyć, stosunkowo łatwe byłoby też zaimplementowania miasta z kilkoma takimi granicami)

Plan minimum do obsługi innego miasta to przygotowanie analogicznego obrazka wejściowego dla tegoż miasta oraz podmiana w skrypcie zapisanych na sztywno pozycji geograficznych center i box.

2. Dane komunikacyjne

Dane komunikacyjne można traktować jako graf skierowany, w którym wierzchołkami są przystanki a krawędziami połączenia pomiędzy przystankami. Wizualizacja wyróżnia dwa typy połączeń: proste oraz czasowe.

type SimpleRoute = number
type TimedRoute = [number, number]
type SimpleRoutes = {[key: string]: SimpleRoute}
type TimedRoutes = {[key: string]: TimedRoute[]}

Połączenie proste (SimpleRoute) jest reprezentowane przez pojedynczą liczbę oznaczającą koszt tego połączenia w minutach. Połączenie czasowe (TimedRoute) reprezentowane jest przez dwie liczby: pierwsza mówi o której godzinie dane połączenie może zostać użyte (gdzie godzina jest zapisana jako liczba minut od północy tego samego dnia), drugie mówi o koszcie tego połączenia w minutach. Jak łatwo się domyślić, przejście na pieszo z przystanku do przystanku jest połączeniem prostym bo możemy to zrobić w dowolnej chwili a czas przejścia nie jest zależny od dnia ani godziny. Natomiast wszystkie połączenia środkami komunikacji miejskiej są połączeniami czasowymi - autobus odjeżdża o konkretnej godzinie i jeżeli chcemy z niego skorzystać, musimy poczekać na przystanku do tej godziny.

Koszt dojazdu do wszystkich punktów liczony jest po stronie klienta algorytmem Dijkstry. Algorytm może korzystać wymiennie z połączeń prostych oraz czasowych, przy czym połączenia proste są ładowane tylko raz przy starcie razem z danymi przystanków, natomiast połączenia czasowe są pobierane za każdym razem gdy użytkownik zmieni dzień lub godzinę wizualizacji.

Dane połączeń dla Warszawy generuje skrypt scripts/transport_data.py. W wyniku powstaje plik z danymi przystanków (points.json) oraz pliki z danymi połączeń (nazwy w formacie routes_[rodzaj_dnia_tygodnia]_[godzina].json).

2.1. Dane przystanków

Plik points.json ma następującą strukturę:

type Points = {[key: string]: Point}

Natomiast pojedynczy przystanek jest opisany interfejsem:

interface Point extends Site {
    routes: SimpleRoutes;
    visited?: boolean;
    cost?: number;
    code?: string;
    color?: number[];
    colorHex?: string;
    lon: number;
    lat: number;
}

Większość pól jest wypełnianych po stronie klienta podczas generowania wizualizacji, w pliku json wypełnione są tylko trzy z nich: lon (długość geograficzna punktu), lat (szerokość geograficzna punktu) oraz routes (połączenia proste z innymi punktami). Poniżej przykład poprawnego jsona, który może być odczytany przez klienta. Plik definiuje 2 punkty o identyfikatorach "A" i "B", dodatkowo z punktu "A" do punktu "B" oraz z punktu "B" do punktu "A" istnieje połączenie o koszcie 5 minut.

{
    "A": {"lon":21.00818,"lat":52.2084,"routes":{"B":5}},
    "B": {"lon":20.99212,"lat":52.1580,"routes":{"A":5}}
}

Skrypt scripts/transport_data.py generuje tylko połączenia pomiędzy najbliższymi przystankami - leżącymi w odległości do 1km od siebie. Jest to dosyć sensowna metodyka, którą można przyjąć przy tworzeniu danych dla innych miast. Po stronie klienta generowane są dodatkowe połączenia piesze na podstawie diagramu voronoia, ale nie będę opisywał tego procesu żeby nie komplikować sprawy.

2.2. Dane połączeń

Pliki z danymi połączeń mają następującą strukturę:

type Routes = {[key: string]: TimedRoutes}

Poniżej przykładowy plik json z definicjami połączeń. Plik zawiera połączenia z punktów "A" i "B". Punkt "A" ma po 2 połączenia z punktem "B" oraz 2 połączenia z punktem "C". Punkt "B" ma 2 połączenia z punktem "C". Połączenia zaczynają się od godziny 17 (bo 17 * 60 = 1020 minut od północy).

{
    "A":{"B":[[1020, 2], [1025, 2]],"C":[[1023, 4], [1030, 2]]},
    "B":{"C":[[1020, 2], [1022, 3]]}
}

W przypadku Warszawy istnieją osobne rozkłady dla dnia roboczego, soboty oraz niedzieli i świąt. Dodatkowo możliwe jest wybranie godziny wizualizacji, zatem ostatecznie dane połączeń podzielone zostały na pliki o nazwach z sufiksami zawierajacymi kod week, sat lub sun oraz liczbę od 0 do 23. Przykładowo plik routes_sat_19.json oznacza połączenia dla soboty rozpoczynające się od godziny 19. Domyślnie w każdym pliku zawarte są połączenia na najbliższe 120 minut, czyli dla powyższego przykładu plik będzie zawierał połączenia w godzinach 19:00-21:00 (120 minut jest też maksymalnym czasem obsługiwanym po stronie wizualizacji).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants