## Backend (Michał Matejuk)
\label{AplikacjaBackendowaPodrozdzial}
### Dobór technologii
Backend naszego systemu zrealizowaliśmy w oparciu o technologię ASP .NET (w najnowszej w tym momencie wersji 6) - ze względu na nasze doświadczenie w tej technologii, jak również prostotę tworzenia w niej RESTowych API.
Korzystając z ASP.NET Core, możemy skorzystać z bogatego ekosystemu narzędzi i bibliotek, co pozwala nam znacznie skrócić czas potrzebny na stworzenie i rozwój naszego projektu.
Połączyliśmy .NETowy serwis z bazą danych MongoDB, która oferuje nam ogromną elastyczność, kluczową przy początkowych fazach tworzenia projektu. Baza ta jest bazą dokumentową - NoSQL. Dane przechowywane są w kolekcjach, które oprócz posiadania unikalnego pola "Id" nie wymagają od dokumentów stałego schematu, tak jak ma to miejsce w bazach typowo SQLowych. To pozwala na elastyczne dodawanie i modyfikowanie danych, co jest szczególnie istotne na początkowych etapach projektu, gdy jeszcze nie wiemy, jakie dane będą nam potrzebne i w jakiej formie. Baza danych NoSQL jest również skalowalna - można łatwo dodawać nowe serwery i rozszerzać ją, aby zaspokoić rosnące wymagania systemu. Ponadto baza danych NoSQL jest odporna na awarie - dzięki replikacji danych i rozproszeniu ich na wielu serwerach, dane są zawsze bezpieczne i dostępne.
Dzięki takiemu połączeniu technologii i zaimplementowanego procesu CI/CD byliśmy w stanie w bardzo krótkim czasie dokonywać wszelakich zmian, dodawać nowe funkcjonalności, zmieniać założony schemat danych, na bieżąco dostosowywać aplikację do naszych potrzeb.
### Konteneryzacja
Aplikacja backendowa została przystosowana do konteneryzacji za pomocą narzędzia Docker. Konteneryzacja pozwala na zapakowanie aplikacji i jej zależności (takich jak biblioteki czy baza danych) w jedną jednostkę, która może być łatwo uruchamiana i skalowana w różnych środowiskach. Dzięki temu aplikacja może być łatwo przenoszona i uruchamiana na innych maszynach, co jest szczególnie przydatne w przypadku rozwoju i testowania oprogramowania.
Plik *Dockerfile* zawiera instrukcje potrzebne do utworzenia obrazu Dockera z naszej aplikacji. Pozwala on na określenie, jaki system operacyjny i jaki runtime będą użyte do uruchomienia aplikacji, jakie pliki i zależności mają zostać zainstalowane oraz jaki skrypt ma być uruchomiony, aby uruchomić aplikację.
Aplikacja backendowa używa następującego pliku *Dockerfile*:
\autoref{dockerfileBackend},
\begin{figure}[h!]
\begin{lstlisting}[language=Dockerfile]
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["city-traffic-simulator-backend/city-traffic-simulator-backend.csproj", "city-traffic-simulator-backend/"]
RUN dotnet restore "city-traffic-simulator-backend/city-traffic-simulator-backend.csproj"
COPY . .
WORKDIR "/src/city-traffic-simulator-backend"
RUN dotnet build "city-traffic-simulator-backend.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "city-traffic-simulator-backend.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "city-traffic-simulator-backend.dll"]
\end{lstlisting}
\caption{Plik Dockerfile \label{dockerfileBackend}}
\end{figure}
Plik zaczyna się od instrukcji **FROM**, która określa bazowy obraz Dockera, na którym oparta będzie nasza aplikacja. W tym przypadku używany jest obraz z .NETem i ASP.NETem w wersji 6.0. Następnie ustawiany jest katalog roboczy dla kontenera za pomocą instrukcji **WORKDIR**. Kolejne instrukcje **EXPOSE** określają porty, które będą dostępne dla aplikacji w kontenerze.
Kolejne sekcje pliku opisują proces budowania aplikacji. W sekcji *build* używana jest instrukcja **COPY** do skopiowania pliku projektu aplikacji oraz wykonania polecenia *dotnet restore*, które pobiera niezbędne zależności. Następnie skopiowane są wszystkie pliki z katalogu projektu i wykonywana jest instrukcja *dotnet build*, która buduje aplikację w trybie *Release* i zapisuje wynik w katalogu */app/build*.
Sekcja *publish* używa instrukcji *dotnet publish*, aby skompilować aplikację do postaci gotowej do uruchomienia i zapisać ją w katalogu */app/publish*.
Ostatnia sekcja final kopiuje skompilowaną aplikację do katalogu roboczego kontenera i ustawia polecenie dotnet z plikiem *city-traffic-simulator-backend.dll* jako punkt wejścia dla kontenera. To oznacza, że po uruchomieniu kontenera, aplikacja zostanie uruchomiona za pomocą polecenia *dotnet*.
### API
Jako że aplikacja udostępnia wiele endpointów, użyliśmy narzędzia Swagger, który ułatwia pracę z RESTowymi API. Dzięki temu wchodząc na stronę: https://ctsbackend.bieda.it/swagger/index.html możemy zobaczyć wszystkie wystawione na świat endpointy i dowiedzieć się jak się z nimi porozumieć. \autoref{swagger}
{ height=300px }
Aplikacja operuje na pięciu głównych typach danych:
- dane z symulatora (*SimulationData*),
- przeprocesowane dane z backendu obliczeniowego (*ProcessedData*),
- dane o drogach (*Map*),
- dane o ustawieniach (*Settings*),
- dane o znacznikach samochodów (*Tags*).
### Dane o drogach
Dokumenty Map przechowują w sobie listę dróg, każda z których składa się z 4 punktów (x,y), opisujących jej krzywą Beziera. Mapę identyfikujemy przez jej unikalny hash, nadany podczas tworzenia. Dzięki tej kolekcji aplikacja frontendowa może narysować takie same ulice jakie były użyte w symulatorze.
Backend umożliwia dodawanie nowych map poprzez przekazanie listy punktów (x,y) opisujących krzywą Beziera dla każdej drogi. Można także usunąć istniejące mapy, pobrać wszystkie dostępne mapy, oraz wyszukać mapę po jej hashu. Dokument mapy zawiera informacje o obiekcie z identyfikatorem i hashem. Zawiera również tablicę dróg, w której każda zawiera tablicę punktów, w której każdy zawiera współrzędne x i y. Te informacje mogą opisywać mapę z drogami i punktami na nich.
### Dane o znacznikach samochodów
Dokument typu Tag posiada dwa pola: nazwę oraz opis. Pozwala to na definiowanie opisów do samochodów w postaci krótkich znaczników, których dokładny opis trzymany jest w osobnej kolekcji. W ten sposób opisy oznaczeń aut otrzymuje od backendu aplikacja frontendowa.
Backend umożliwia dodawanie nowych encji znaczników poprzez podanie ich nazwy oraz opisu. Można także usunąć istniejące oznaczenia, pobrać wszystkie dostępne, oraz wyszukać znacznik po jego nazwie.
### Dane o ustawieniach
Dokumenty ustawień opisują wszystkie możliwe znaczniki dla samochodów w danej symulacji. Podobnie jak mapy, ustawienia identyfikowane są jednoznacznie przez swój hash.
Aplikacja umożliwia dodawanie nowych dokumentów ustawień przez podanie ich hasha oraz kolekcji znaczników w postaci (nazwa, opis).
Dokument ustawień zawiera informacje o obiekcie z identyfikatorem i hashem. Zawiera również tablicę etykiet, w której każda zawiera nazwę i opis etykiety.
### Dane o symulacji
Dane przesyłane z symulatora składają się z **identyfikatora** w postaci hash mapy + hash ustawień + numer symulacji. Hashe mapy i ustawień jednoznacznie identyfikują dane.
Obiekt przechowujący dane o symulacji zawiera identyfikator symulacji, hash ustawień i mapy oraz identyfikator bieżącego uruchomienia symulacji. Zawiera również ramki symulacji, w których każda zawiera informacje o samochodach w danej chwili. Każdy samochód zawiera identyfikator, etykiety, stan, prędkość, przyspieszenie, pozycję na mapie, odległość od poprzedzającego samochodu oraz identyfikator poprzedzającego samochodu.
### Przeprocesowane dane o symulacji
Dokumenty przeprocesowane zawierają wyniki obliczeń wykonanych na danych z symulatora przez backend obliczeniowy. Te dane są następnie wykorzystywane przez aplikację frontendową do wizualizacji wyników symulacji.
Backend umożliwia dodawanie, usuwanie, pobieranie oraz wyszukiwanie dokumentów przeprocesowanych po identyfikatorze symulacji.
### Ograniczenia
Sporym problemem dla aplikacji backendowej jest ciągły przypływ danych z symulatora. Wraz ze wzrostem ilości samochodów symulacji rośnie także rozmiar przekazywanego za każdym razem obiektu symulacji. Nie udałoby nam się przesłać całej symulacji za pomocą jednego requestu REST. Rosnąca liczba zapytań skutecznie spowalnia pracę aplikacji, która potrafiła szybko osiągnąć limit pamięci i zwyczajnie się wyłączyć.
### Rozwiązania
Aby rozwiązać ten problem, rozważaliśmy wprowadzenie kilku ograniczeń w przepływie danych. Jednym z rozwiązań mogło być ograniczenie liczby samochodów w symulacji oraz zmniejszenie ilości danych przekazywanych za każdym razem, jednak zmniejszyłoby to miarodajność symulacji. Innym pomysłem było wprowadzenie systemu kolejkowania zapytań, dzięki czemu aplikacja będzie mogłaby obsłużyć większą liczbę zapytań bez obciążania pamięci.
Inną opcją było skorzystanie z rozwiązań chmury obliczeniowej, takich jak Amazon Web Services (AWS) lub Google Cloud Platform (GCP). Dzięki temu można zapewnić większą skalowalność aplikacji i lepszą wydajność niż na używanym przez nas Mikr.usie, jednak odrzuciliśmy ją ze względu na znacznie niższe koszty dotychczasowej architektury.
W celu zlikwidowania problemów z przesyłem dużej ilości danych na początek symulator zaczął dzielić dane o psymulacje na części, i wysyłać je w tle w trakcie działania symulacji.
Aplikacja backendowa miała spore problemy z odbieraniem danych, głównie z powodu deserializacji ogromnej ilości klatek i samochodów do obiektów stworzonych w serwisie. Rozwiązaliśmy ten problem poprzez zaprzestanie deserializacji obiektów klatek, które składały się głównie z samochodów i informacji o ich położeniu. Zamiast przerabiać je na obiekty pozostawiliśmy je jako stringi.
Inną ważną kwestią jest bezpieczeństwo danych. Aplikacja backendowa została zabezpieczona przed nieautoryzowanym dostępem poprzez wprowadzenie mechanizmu uwierzytelniania użytkowników. Dzięki temu tylko zweryfikowane i uprawnione osoby mogą mieć dostęp do danych w aplikacji.
Aby móc skorzystać z API backendowego, konieczne jest dostarczenie specjalnego headera z kluczem uwierzetelniającym.
## Backend obliczeniowy (Jakub Brzozowski)
\label{BackendObliczeniowyPodrodział}
### Dobór technologii
Backend obliczeniowy został zrealizowany w oparciu o Python w wersji 3.9 oraz *framework Flask*. Wybraliśmy ten język ze względu na doświadczenie zespołu z nim oraz szeroki wachlarz bibliotek i *frameworków*, z których można skorzystać, aby szybko i efektywnie budować aplikacje internetowe. Spośród dostępnych *frameworków* wybraliśmy Flask ze względu na lekkość oraz łatwą instalację, wdrażanie i użytkowanie. Dodatkowo najlepiej sprawdza się do budowy małych i prostych aplikacji, jaką jest backend obliczeniowy.
Dzięki zaimplementowanemu procesowi *CI/CD* byliśmy w stanie w bardzo krótkim czasie dokonywać wszelkich zmian, dodawać nowe funkcjonalności, zmieniać założony schemat danych i na bieżąco dostosowywać aplikację do naszych potrzeb.
### Przetwarzanie danych symulacji
Aplikacja przetwarza dane o symulacjach do formatu umożliwiającego ich prezentację w aplikacji dashboardowej bez dodatkowych, kosztownych obliczeniowo, przekształceń.
Aplikacja tworzy następujące przekształcenia danych na potrzeby następujących prezentacji danych:
- mapa cieplna pozycji,
- mapa cieplna prędkości,
- mapa cieplna przyśpieszeń.
Rezultatem przetwarzania jest zapisanie przekształconych danych w bazie danych.
### API
API backendu obliczeniowego jest dostępne pod adresem https://ctscompms.bieda.it i wystawia następujące *endpointy*:
- /api/process, umożliwiający przetworzenie danych o symulacji do postaci konsumowalnej przez aplikację dashboardową,
- /health, umożliwiający sprawdzenie stanu zdrowia backendu obliczeniowego.
### Działanie /api/process
*Endpoint* /api/process obsługuje żądania POST zawierające parametry identyfikujące uruchomienie symulacji. Aplikacja sprawdza poprawność otrzymanych parametrów, następnie wysyła żądanie GET do backendu, aby załadować dane o symulacji. Po deserializacji danych do obiektu aplikacji, tworzy statystyki na temat symulacji i przekształca je do formatu JSON, a następnie zapisuje w bazie danych poprzez wysłanie żądania POST do aplikacji backendowej.
### Argumenty żądania
Obsłużenie żądania wymaga przekazania następujących argumentów:
- settings_hash - identyfikuje ustawienia symulacji,
- map_hash - identyfikuje mapę,
- run_id - opisuje numer kolejnych uruchomień symulacji na tej samej mapie, o tych samych ustawieniach.
Ten zestaw parametrów umożliwia jednoznaczną identyfikację danych o symulacji.
### Walidacja wejściowych danych o symulacji
Po otrzymaniu danych o symulacji od backendu aplikacja sprawdza ich zgodność z oczekiwanym formatem danych. W tym celu konwertuje otrzymane dane z formatu JSON do słownika, a następnie inicjuje nim instancję klasy symulacji. Podczas inicjalizacji każde pole jest pozyskiwane ze słownika przekazanego jako argument do metody inicjalizującej. W przypadku, gdy odpowiedni klucz nie zostanie znaleziony, do pola klasy jest wpisywana wartość *None*, a następnie podnoszony jest wyjątek.
Powyższe rozwiązanie, w miarę postępu prac nad projektem, okazało się problematyczne z powodu częstych zmian schematu danych. W konsekwencji wymagane były zmiany w kodzie aplikacji napisanej w konkretnym, niekoniecznie znanym każdemu programiście, języku programowania, zamiast w plikach konfiguracyjnych, które są zapisane w szeroko znanych, czytelnych dla człowieka formatach np. JSON lub YAML.
### Dane o symulacji i mapowanie do obiektu symulacji
Symulacja wykorzystuje całość oryginalnych danych, bez przekształceń. Jedyną transformacją jest zmiana nazewnictwa. Dane wejściowe w formacie JSON używają konwencji *camelCase*. Podczas ładowania danych do obiektu, w celu zachowania zgodności z zaleceniami *PEP 8*, muszą one jedynie zostać przekształcone do *snake_case*.
Figura \autoref{diagramKlasSymulacjiBackendObliczeniowy} obrazuje reprezentację symulacji w pamięci aplikacji.

Symulacja, poza danymi identyfikującymi, jest przedstawiona jako kolekcja ramek symulacji, zawierających stan wszystkich pojazdów biorących udział w symulacji w momencie przechwycenia stanu. Częstotliwość zapisu ramek jest ustalana przez aplikację symulującą. Podczas tworzenia obiektu symulacji tworzone są 2 nowe atrybuty: velocity_value i acceleration_value, które są wartościami skalarnymi odpowiednio wektorów prędkości i przyśpieszenia.
### Przekształcenia danych
Na potrzeby aplikacji dashboardowej dane mogą być przekształcane do postaci zagregowanej na poziomie:
- ramki symulacji - występuje mapowanie 1:1 między danymi z oryginalnej ramki i ramki wyjściowej,
- ramek symulacji kumulacyjnie - kolejne ramki wyjściowe są obliczane na podstawie danych z poprzednich ramek i obecnej,
- całej symulacji - dane wyjściowe nie są podzielone na ramki, powstają na podstawie wszystkich danych o symulacji.
Aplikacja wykonuje następujące przekształcenia:
- mapa cieplna pozycji - występuje w postaci zagregowanej na poziomie ramki symulacji i ramek symulacji kumulacyjnie. W pierwszym przypadku zwraca zbiór pozycji samochodów w każdej ramce, a w drugim przypadku zbiór pozycji wraz z ilością wystąpień w dotychczasowych ramkach.
- mapa cieplna prędkości - występuje w postaci zagregowanej na poziomie ramki symulacji. Zwraca zbiór pozycji samochodów i ich prędkości przekształconych do postaci skalarnej.
- mapa cieplna przyśpieszeń - występuje w postaci zagregowanej na poziomie ramki symulacji. Zwraca zbiór pozycji samochodów i ich przyśpieszeń przekształconych do postaci skalarnej.
- czas podróży pojazdów - przekształcenie agregujące na poziomie całej symulacji. Akumuluje wystąpienia poszczególnych id pojazdów w każdej ramce i zwraca ilość ramek dla każdego id pojazdu.
Każde z przekształceń zwraca instancję klasy *Dataframe* z biblioteki *pandas*, która jest tabelą, której rekordy zawierają numer ramki symulacji, id samochodu, jego pozycję na osi x, pozycję na osi y i wartość atrybutu, na który zorientowane jest przekształcenie. Dodatkowymi metadanymi, które są agregowane podczas wykonywania przekształceń, są:
- minimalne i maksymalne wartości pozycji pojazdów na osiach x i y
- ilość ramek symulacji
- dostępne znaczniki pojazdów
### Działanie /heath
Endpoint /health obsługuje żądania GET i zwraca stan zdrowia backendu obliczeniowego w formacie JSON. Główną zaletą jego zaimplementowania jest możliwość integracji z narzędziami do monitorowania i zarządzania infrastrukturą.
### Komunikacja z pozostałymi komponentami systemu
Podczas swojego działania aplikacja komunikuje się z aplikacją backendową za pomocą protokołu HTTPS w sposób przedstawiony na diagramie \autoref{diagramKomunikacjiBackenduObliczeniowego}.

Aplikacja backendowa po zakończeniu zapisywania danych o symulacji wysyła żądanie POST do backendu obliczeniowego na adres /api/process z parametrami identyfikującymi symulację. Podczas obsługi żądania aplikacja wysyła żądanie GET do backendu na adres /api/simulation w celu uzyskania surowych danych o symulacji. Po wykonaniu każdego przekształcenia danych wysyła żądanie POST na adres /api/simulation w celu zapisania przygotowanych danych.
### Konfiguracja
Klucze do API, z którymi serwis się komunikuje są przechowywane jako *Github Actions secrets*. Pozostałe parametry aplikacji oraz ustawienia endpointów są konfigurowalne przez plik YAML. W pliku konfiguracyjnym można zdefiniować dopuszczalne argumenty dla każdego z endpointów aplikacji. Plik konfiguracyjny także zawiera adresy endpointów, z którymi aplikacja ma się komunikować.
### Ograniczenia wynikające z doboru technologii
*Framework Flask* nie oferuje wbudowanego mechanizmu skalowania, co oznacza, że aplikacje napisane w tym frameworku mogą mieć trudności z obsługą dużych obciążeń. W przypadku naszego systemu, wraz z rosnącą ilością użytkowników, aplikacja natrafiłaby na problem z obsługą wymaganej liczby zapytań.
### Rozwiązania
Aplikacja zaimplementowana z użyciem *frameworka Flask* może być skalowana na wiele sposobów:
- uruchomienie wielu instancji aplikacji za reverse proxy np. Nginx,
- wdrożenie aplikacji na platformie chmurowej z wykorzystaniem usług takich jak *AWS Elastic Beanstalk* lub *Heroku*, które odpowiadają za monitorowanie i automatyczne skalowanie,
- wykorzystanie platformy serverless np. *AWS Lambda* do wdrożenia aplikacji.
Wykorzystanie *reverse proxy* połączone ze zwiększeniem ilości instancji poprawiłoby wydajność w momentach wysokiego zapotrzebowania, ale wprowadziłoby dodatkową złożoność do aplikacji i zwiększyło nakłady na utrzymanie aplikacji. Dodatkowo tworzyłoby zagrożenie, że zasoby *VPS Mikr.us* byłyby niewystarczające. Rozwiązania chmurowe zapewniłyby prostszą implementację i wdrażanie, jednocześnie wymagając większych nakładów finansowych.
Innym rozwiązaniem mogłoby być zbudowanie tego komponentu w oparciu o technologie chmurowe GCP (ang. *Google Cloud Platform*) lub AWS (ang. *Amazon Web Services*) przeznaczone do przetwarzania danych, które z natury charakteryzują się znakomitą skalowalnością. Przykładowa architektura takiego rozwiązania, na podstawie GCP, mogłaby wyglądać jak na diagramie \autoref{alternatywaArchitekturaBackenduObliczeniowego}.

Przepływ byłby inicjowany przesłaniem pliku z danymi o symulacji do *Bucketu Storage - Raw*, który następnie inicjowałby publikację wiadomości na *Pub/Sub PubSub - Raw* wykorzystując powiadomienia *Google Storage*. Następnie wiadomości byłyby konsumowane przez komponent dokonujący przekształcenia danych. Spośród dostępnych na GCP technologii *serverless* do przetwarzania danych najwłaściwszymi byłyby:
- *Cloud Functions* - w przypadku gdyby aplikacja w dalszym ciągu wykonywałaby nieskomplikowane operacje na małych zestawach danych, a głównymi priorytetami pozostałyby łatwość rozwoju i utrzymania,
- *Dataflow (Streaming)* - gdyby aplikacja miała wykonywać również bardziej skomplikowane operacje na dużej ilości danych oraz obsługiwać ciągłe przetwarzanie danych w kontraście do przetwarzania wywołanego zdarzeniami,
- *Dataflow (Batch)* - sprawdziłaby się dla bardzo dużych zestawów danych. Jej wadą jest stosunkowo długi czas wywołania.
Zaproponowane rozwiązanie mogłoby korzystać z jednego lub wielu z powyższych rozwiązań w zależności od skomplikowania przekształcenia i rozmiaru pliku wejściowego. Komponent przetwarzający zapisywałby przetworzone dane do *Bucketu Storage - Processed*, a następnie przesyłał je poprzez *Cloud Function* lub *Streaming Dataflow Job*, po wywołaniu przez *PubSub*, na *endpoint* /api/processed backendu. Zaproponowane rozwiązanie zachowuje schemat komunikacji zastosowany w obecnym systemie. W przypadku projektowania architektury dla całego systemu, opartej na GCP lub AWS, propozycja zmian byłaby zdecydowanie inna, rozbijająca funkcjonalności backendu pomiędzy odpowiednie technologie chmurowe, wykorzystująca *Google Storage* jako miejsce przechowywania danych o symulacjach, a baza danych np. *GCP CloudSQL*, przechowywała by dane jedynie na potrzeby audytowe.
Wdrażanie odbywałoby się, jak w przypadku zaimplementowanego rozwiązania, poprzez *Github Actions*. Do zarządzania architekturą wykorzystany by był *Terraform*.
Pomimo dobrej skalowalności i większej dojrzałości rozwiązania, zdecydowaliśmy się na pozostanie przy aplikacji *Flask* wdrożonej na *VPS Mikr.us* ze względu na koszty.
## Aplikacja dashboardowa (Maciej Adryan)
\label{AplikacjaDashboardowaPodrozdzial}
### Dobór technologii
Część projektu nazywana mianem "dashboardu", czy też "aplikacji dashboardowej" to w praktyce monolityczna aplikacja, utworzona za pomocą frameworka Dash by Plotly. W praktyce, funkcjonalnie framework ten podobny jest do frameworka Flask, notabene jest on o niego oparty, jednak dodatkowo wyposażono go w funkcje mające ułatwić zarówno manipulację jak i graficzną prezentację danych.
W praktyce prawie cały kod potrzebny do utworzenia aplikacji, napisany został w języku programowania Python, dokładnie w wersji 3.8. Wyboru tego dokonaliśmy, ponieważ nie wymaga on bardzo zróżnicowanego stacku programistycznego, tj. zarówno umiejętności tworzenia frontendu, backendu i manipulowania danymi. Framework zapewnia gotowe komponenty oraz wysoki poziom abstrakcji przy tworzeniu interfejsu użytkownika, pozwalając skupić się na transformacji i wizualizacji danych optymalizacji optymalizacji obliczeń. Podobnie jak większość narzędzi posiada on wiele zalet, ale nie jest pozbawiony wad, co niejednokrotnie przejawiało się w trakcie prac nad projektem. Temat ten został opisany w późniejszych podrozdziałach.
### Interfejs użytkownika
Po przejściu pod adres pod którym udostępniona jest aplikacja frontendowa, użytkownik trafia na stronę główną, wyposażoną w panel nawigacji widoczny na \autoref{PanelNawigacji}.
Panel nawigacji widoczny na \autoref{PanelNawigacji} jest wyposażony w hiperłącza do:
* profili twórców w platformie GitHub,
* dokumentacji backendu,
* dokumentacji wykorzystanych komponentów zewnętrznych,
* repozytoriów poszczególnych komponentów projektu,
* strony głównej Politechniki Gdańskiej w języku angielskim.
{ height=300px }
Na potrzeby czytelności pracy widoczny na \autoref{PanelNawigacji} pozbawiony jest sekcji z odnośnikami do dokumentacji komponentów zewnętrznych. Sekcja ta znajduje się w ostatecznej wersji aplikacji.
Oprócz nawigacji, na stronie znajdują się listy rozwijane, pozwalające na wybranie użytkownikowi interesującego go przypadku symulacji.
Interfejs wyposażony jest w listy rozwijane, pozwalające łatwe znalezienie konkretnego przypadku symulacji, nawet w przypadku gdyby liczba symulacji jest wysoka.

Każda z list rozwijanych widocznych na \autoref{listyRozwijane} aktualizuje się asynchronicznie, w zależności od wyboru użytkownika, użytkownik nie jest zatem w stanie wysłać do backendu zapytania o dane z nieprzeprowadzonej instancji symulacji.
Dodatkowo, przycisk wysyłający asynchroniczne zapytanie do backendu widoczny na \autoref{zablokowanyAnalyze} jest domyślnie zablokowany, odblokowuje się jedynie w momencie, gdy użytkownik wybrał z list rozwijanych wszystkie parametry potrzebne aby jednoznacznie rozróżnić instancję symulacji która go interesuje.

W przypadku prawidłowego wprowadzenia wszystkich wymaganych wartości przycisk ten zostaje odblokowany, tak jak widać na \autoref{odblokowanyAnalyze} i staje się klikalny, zatem możliwe jest wysłanie zapytania do backendu.

W przypadku wysłania do backendu zapytania o dane symulacji, zostaje ono wysłane asynchronicznie. Dodatkowo, podczas oczekiwania na wyniki, użytkownikowi wyświetlana jest animacja, tak jak jest to widoczne na \autoref{animacjaCzekania}.

Podczas oczekiwania na zwrot danych z backendu, dodatkowo, jak zobaczyć można na \autoref{cancelOdblokowany} odblokowany zostaje przycisk pozwalający na zatrzymanie asynchronicznego zapytania do serwera. Jest to możliwe dzięki mechanizmowi długiego zapytania zwrotnego, o którym więcej informacji znajduje się w podrozdziale \autoref{RozdziałCallbacki}. Po wciśnięciu przycisku anulującego callback aplikacja powraca do stanu bezpośrednio sprzed jego wywołania.

Przykładowy wykres może prezentować się tak jak na \autoref{przykladowaHeatmapa}. Fragmenty dróg na których częściej znajdują się samochody reprezentowane są kolorami znajdujacymi się bliżej górnej granicy widoczego po prawej stronie spektrum kolorystycznego, zaś rzadziej odwiedzane elementy reperezentowane są przez kolory znajdujące się bliżej dolnej granicy spektrum. Metryką określającą częstość odwiedzania fragmentu drogi jest ilość klatek symulacji podczas których znajdowały się na nich pojazdy.

### Powstawanie interfejsu użytkownika
Dzięki zastosowaniu frameworka Dash, definicja interfejsu użytkownika sprowadza się do zdefiniowania parametrów docelowego obiektu DOM który zostanie stworzony na podstawie kodu napisanego w języku Python, przykładowy kod definiujący prosty widok posiadający jeden guzik o id='example-button' i panel nawigacyjny o id='example-navbar' wyglądałby (w reprezentacji pseudokodem) jak na \autoref{definiowanieNavbaru}:
\begin{figure}[h!]
\begin{lstlisting}[language=Python]
layout = html.Div([
html.Navbar(id='example-navbar')
html.Button(id='example-button')
])
app.layout = layout
\end{lstlisting}
\caption{definiowanie interfejsu użytkownika \label{definiowanieNavbaru}}
\end{figure}
### Dash Bootstrap Components
Dzięki zastosowaniu zewnętrznej biblioteki Dash Bootstrap Components, możemy pominąć czasochłonne operacje manipulowania arkuszami stylu i używać predefiniowanych komponentów oraz arkuszy styli.
Na potrzeby projektu wykorzystane zostały między innymi komponenty:
* Navbar (NavbarSimple, NavItem, Navlink),
* Dropdown,
* Button,
* Label,
* Container,
* Col oraz Row,
* Themes (Darkly).
Powyższe elementy są wyposażone w wiele definiowalnych parametrów, dzięki czemu można je dopasować w zależności od preferencji czy wymagań twórców i użytkowników. Dzięki faktowi, iż komponenty te są dostępne w sposób analogiczny do komponentów natywnych frameworka Dash w łatwy sposób można je integrować między sobą oraz z Dashowymi callbackami o których więcej w podrozdziale \ref{RozdziałCallbacki}. Rozwiązanie to nie jest całkowicie pozbawione wad, o części z nich przeczytać można w podrozdziale \ref{DCC_DBC_niekompatybinosc}.
### Oczekiwanie na wyniki
W trakcie oczekiwania na wyniki, tj. odpowiedź ze strony dowolnego z odpytywanych backendów, użytkownikowi przedstawiona jest animacja, mająca na celu poinformowanie go, iż jego żądanie nadal jest przetwarzane i nie zostało przerwane. Przykład takiej animacji zobaczyć można na \autoref{animacjaCzekania}.
### Komunikacja
Zgodnie z przedstawionym wcześniej diagramem przepływu danych, monolit komunikuje się z endpointami udostępnianymi przez komponent **backend** za pomocą żądań HTTPS. Pobiera z niego potrzebne dane, takie jak na przykład lista przeprowadzonych symulacji, lista symulacji które poddane zostały transformacji przez komponent **backend obliczeniowy** czy dostępne dla symulacji metadane. Więcej o metadanych przeczytać można w podrozdziale \ref{MetadaneRozdzial}
### SSR
W zależności od zdefiniowanego przez użytkownika typu informacji, jaki chce aby zostały mu przedstawione, monolit pobiera z **backendu** wcześniej przygotowane przez **backend obliczeniowy** dane spreparowane w celu wyświetlania wykresów, na podstawie których dokonuje kalkulacji wewnątrz swojej własnej warstwy backendowej.
### Metadane symulacji
\label{MetadaneRozdzial}
Wraz z danymi dotyczącymi samego przebiegu symulacji, do aplikacji frontendowej przekazywane są z symulacją metadane, takie jak:
* charakterystyczne wartości nominalne uzyskane w trakcie transformacji przez **backend obliczeniowy**,
* szczegółowe dane określające dodatkowe parametry związane z zakresem danych,
* informacje dotyczące wartości uzyskanych przez funkcję hashującą na ustawieniach symulacji oraz jej mapie,
* listę współrzędnych krzywych Beziera wymaganych na potrzebę odtwarzania symulacji po stronie frontendowej aplikacji dashboardowej.
### Zastosowanie mechanizmu memoizacji
W trakcie tworzenia aplikacji zdecydowaliśmy się na wykorzystanie mechanizmu memoizacji. Mechanizm ten polega na przechowywaniu wyników wywołania funkcji i ponownym wykorzystaniu zwróconych przez nia danych w przypadku ponownego wywołania funkcji z takimi samymi parametrami wejściowymi. Wyniki takiego wywołania zapisywane zostają w lokalnym systemie plików serwera.
W momencie wystąpienia wyżej opisanego przypadku, zamiast wykonania funkcji ponownie, jej wartość odczytywana jest z lokalnego systemu plików, zdeserializowana i wykorzystana ponownie. Od strony użytkownika jest to zauważalne tylko poprzez zmniejszenie się czasu wykonania takiego zapytania.
Mechanizm ten w przypadku opisywanej pracy inżynierskiej został zrealizowany w oparciu o lokalny system plików ze względu na prostotę implementacji oraz pracy nad nim. Możliwe jest również przechowywanie zapamiętanych danych wewnątrz lokalnej lub zdalnej instancji bazy danych, jednak ze względu na rozmiar projektu okazało się być rozwiązaniem nieoptymalnym, pochłaniającym zasoby oraz spowalniającym działanie aplikacji z powodu wolnego czasu odczytu i zapisu oraz w przypadku korzystania ze zdalnej instancji serwera- dodatkowego czasu potrzebnego na transfer danych przez warstwę sieciową.
### Caching po stronie użytkownika
W projekcie zdecydowaliśmy się na nieimplementowanie mechanizmu cache'owania po stronie użytkownika aplikacji, wynika to z narzuconych ograniczeń rozmiaru danych które mogą być przechowywane w pamięci przeglądarki.
### Filtry zakresu danych \label{Filtrowanie}
W zależności od specyfiki analizy, możliwe jest odfiltrowywanie danych za pomocą filtrów definiowanych przez użytkownika z poziomu interfejsu aplikacji.
Filtry pozwalają użytkownikowi określić podzbiór samochodów który zostanie wyświetlony w poszczególnych analizach. Mechanizm ten jest ograniczony przez znane limitacje wynikające ze specyfiki frameworka Dash opisane w podrozdziale \nameref{złeLegendyITagi}.
### Przetwarzanie danych przez monolit
W przypadku zastosowania powyższych funkcjonalności, część frontendowa aplikacji dashboardowej będzie odsyłała informacje dotyczące wybranych wartości ustawień do komponentu backendowego i w zależności od nich rekalkulowała dane i generowała adekwatne wykresy. Proces ten przebiega w oparciu o generowanie masek danych oraz nakładanie ich na wcześniej otrzymane z backendu obliczeniowego przetworzone dane.
### Zapisywanie wyników
Aplikacja wyposażona jest w narzędzie umożliwiające prosty eksport przeprowadzonej analizy symulacji w zależności od ustawionych przez użytkownika parametrów. Proces ten polega na zapisaniu wykresu do formatu .png na dysku lokalnym użytkownika aplikacji.
### Wykresy z osią czasu
W zależności od specyfiki analizy, wykresy wyposażone mogą być w oś czasu, użytkownik może decydować jaki zakres czasu będzie go interesował. Aplikacja będzie pozwalała zarówno na wybranie jednego zarejestrowanego "momentu", bądź wielu klatek symulacji. Aplikacja będzie automatycznie agregowała i kalkulowała dane przed wyświetleniem ich użytkownikowi.
### Odtwarzanie symulacji na frontendzie
Aplikacja frontendowa jest wyposażona w uproszczony system wyświetlania przebiegu całej symulacji. System ten nie jest oparty o technologię Unity. Poszczególne samochody reprezentowane są przez punkty na wykresie punktowym, z kolei istniejące trasy wyświetlane są dwuwymiarowe krzywe Beziera. Możliwe jest płynne wyświetlanie ruchu samochodów zrzutowane na przestrzeń 2-wymiarową tak jak jest to widoczne na \autoref{przykladowyReplay}. Dodatkowo, użytkownik może przeglądać animację klatka po klatce. Udostępniona jest również możliwość odfiltrowywania samochodów z interesującymi użytkownika tagami nadanymi samochodom, tak jak w pozostałych rodzajach symulacji. Mechanizm filtrowania opisany jest w podrozdziale \nameref{Filtrowanie}.

### Główna zasada działania
\label{RozdziałCallbacki}
Działanie frameworka Dash oparte jest na tak zwanych "zapytaniach zwrotnych" (ang. *callbacks*). Mechanizm ten pozwala zdefiniować triggery (najczęściej konkretne parametry elementów drzewa DOM) które w przypadku zmiany wartości wywołają zdefiniowane przez użytkownika funkcje. Funkcje te mogą mieć również zdefiniowane elementy (w zasadzie ich parametry), które będa modyfikowane w przypadku wywołania funkcji. W praktyce, zapyania zwrotne są jedynie wrapperami poszczególnych funkcji, wywoływanym automatycznie w razie wykrycia zmiany wartości zdefiniowanych w nich parametrów.
Przykładowa definicja zapytania zwrotnego widoczna na \autoref{przykladowaDefinicjaCallbacku}, który jako wejście pobiera wartość (właściwość *value* komponentu o *id*='my-input') i umieszcza obiekt o *id*='new-DOM-component' w drzewie **DOM** jako wartość parametru *children*.
\begin{figure}[h!]
\begin{lstlisting}[language=Python]
@app.callback(
Output(component_id='DOM-component-id', component_property='children'),
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
return html.Div(id='new-DOM-component')
)
\end{lstlisting}
\caption{Przykładowa definicja callbacku. \label{przykladowaDefinicjaCallbacku}}
\end{figure}
Framework pozwala na zdefiniowanie wielu parametrów wejściowych (triggerów) oraz wyjściowych aplikacji.
Wywołania zwrotne których czas wykonania zajmuje więcej niż 30 sekund są problematyczne ze swojej natury i mogą wykorzystywać długotrwale zasoby serwerowe blokując je od bycia wykorzystanym przez innych klientów aplikacji chcących skorzystać z aplikacji w tym samym czasie. Może to wpłynąć negatywnie na czas wykonania ich żądań lub ,w skrajnych przypadkach, je uniemożliwić. Problem ten można rozwiązać poprzez zastosowanie mechanizmu długich wywołań zwrotnych. Oferuje on skalowalne rozwiązanie dla używania długotrwałych wywołań zwrotnych.
Długie zapytania zwrotne dodaje się do aplikacji poprzez zdefiniowanie parametru background=True oraz zdefiniowaniu dodatkowego backendu DiskCache.
Backend DiskCache, uruchamia logikę wywołania zwrotnego w osobnym procesie i przechowuje wyniki na dysku za pomocą pythonowej biblioteki diskcache.
### Ograniczenie - Mechanizm zapytań zwrotnych
Od początku powstania frameworku Dash, nie jest możliwe utworzenie zapytań zwrotnych które współdzieliły by parametr "Output". Oznacza to w praktyce, iż w przypadku gdy programista tworzy bardziej zaawansowaną funkcjonalność, w której parametry są od siebie wzajemnie zależne, zmuszony jest umieścić cały kod w pojedynczej funkcji obsługującej zdarzenie. Wpływa to negatywnie na czytelność oraz strukturę kodu, zakładając, iż funkcja ma x parametrów wejściowych "Input", problemem staje się obsłużenie funkcji w odpowiedni sposób, ponieważ w zależności od parametru, odświeżenia i/lub zmiany wymagać będą inne parametry wyjściowe. Możliwym byłoby liniowe potraktowanie zdarzenia oraz naiwne zaktualizowanie wszystkich parametrów, podejście takie jest jednak mało wydajne obliczeniowo.
Rozwiązaniem tego problemu może być wprowadzony przez framework mechanizm kontekstowy (dash context), pozwala on jednoznacznie określić która wartość parametru wejściowego uległa zmianie i wywołała zapytanie zwrotne obsługujące zdarzenie. Na tej podstawie programista może określić dokładną ścieżkę obsłużenia tego zdarzenia. Wpływa to niestety negatywnie na czytelność kodu, jednocześnie poprawiając jego wydajność. Ograniczenie to wynika ze sposobu w jaki framework został zaprojektowany i a mechanizm kontekstowy stanowi rozwiązanie tego problemu.
### Problem - Nieprawidłowe legendy na wykresach animowanych \label{złeLegendyITagi}
Ograniczeniem które odkryte zostało podczas implementacji aplikacji okazało się być generowanie legend wykresów przy użyciu biblioteki Plotly, którą wykorzystuje framework Dash, w przypadku stosowania funkcjonalności pozwalającej na animowanie wykresów. Problem polega na niewyświetlaniu wszystkich występujących w danych wejściowych kategorii. Problem ten jest niedeterministyczny, tj. nie da się z góry określić jaki podzbiór danych zostanie wyświetlony, jednak jest to w praktyce zawsze zbiór niepełny.
Problem ten występuje zarówno w wersji frameworka działającego z językiem R oraz Python oraz w wersji javascriptowej biblioteki Plotly.
### Problem - Niekompatybilne ze sobą komponenty dcc (Dash Core Components) oraz dbc (Dash Bootstrap Components) \label{DCC_DBC_niekompatybinosc}
W trakcie tworzenia części frontendowej aplikacji dashboardowej doszło do sytuacji w której zastosowanie predefiniowanego stylu nie zadziałało na elemencie biblioteki **dbc**, fakt ten spowodował, iż konieczne było utworzenie własnych arkuszy styli css oraz nadpisanie domyślnych wartości kolorystycznych elementów docelowo mających się znaleźć w drzewie DOM. Na \autoref{przedCSS} porównać można wygląd listy rozwijanej sprzed oraz po zastosowaniu własnych arkuszy stylu.

### Problem - Maksymalna liczba znaków dla dokumentu \label{podzialDokumentuNa2}
Maksymalna liczba znaków wspierana przez praltformę HackmD to 100000 dla pojedynczego dokumentu, limit ten został przez autorów pracy przekroczony, zatem celem umożliwienia dalszego tworzenia dokumentu problem ten musiał zostać rozwiązany. Usługa HackMD umożliwia samodzielne hostowanie dokumentu, w ramach którego możliwe jest zwiększenie maksymalnej liczby znaków dla pojedynczego dokumentu, rozwiązanie to jednak wprowadziłoby sporo dodatkowej komplikacji oraz generowałoby niezerowe koszta, autorzy zdecydowali zatem, iż ograniczą się do podzielenia dotychczasowego dokumentu na dwa oddzielne oraz wprowadzeniu modyfikacji umożliwiającej pobieranie dwóch plików wejściowych dla generatora plików PDF.
### Problem - Omijanie potwierdzenia EULA Microsoftu \label{fontsEULA}
Narzucone odnośnie dokumentu wymagania, zmusiły autorów do wykorzystania czcionki Arial w procesie generacji niniejszego dokumentu. Czcionki te nie są preinstalowane wewnątrz linuxowego subsystemu dla systemu Windows, oferowane są jednak przez firmę Microsoft w postaci instalowalnej za pomocą polecenia "apt-get install" paczki.
W procesie lokalnego instalowania (na prywatnej maszynie) proces ten przebiegł bezproblemowo. Jednakże, przy próbie zdockeryzowania narzędzia, narzędzie pandoc zwracało błąd o nieistniejącej czcionce Arial, pomimo replikacji wszystkich kroków wymaganych do ich instalacji. Po czasochłonnej analizie problemu, winowajcą okazała się potrzeba jawnego kliknięcia akceptacji licencji przy instalacji pakietu czcionek.
Czcionki Microsoft TrueType są objęte licencją użytkownika końcowego (EULA), która określa zasady korzystania z nich. W skrócie, licencja ta pozwala na używanie czcionek w celach osobistych i komercyjnych, ale nie pozwala na ich sprzedaż czy modyfikację bez zgody Microsoftu.
W przypadku instalacji czcionek za pomocą narzędzia ttf-mscorefonts-installer, użytkownik musi zaakceptować warunki licencji EULA przed instalacją. Po zainstalowaniu czcionek, ma on prawo do ich używania zgodnie z zasadami określonymi w licencji.
Pomimo zdefiniowania dyrektywy **ARG DEBIAN_FRONTEND=noninteractive** potrzebnej, aby instalator omijał akceptowanie umowy EULA, skrypt instalował jedynie czcionki niewymagające jej jawnej akceptacji, nie informując jednocześnie użytkownika o pominięciu instalacji czcionek wymagających akceptacji umowy licencyjnej. Początkowo problem ten wydawał się nierozwiązywalny, dyrektywa "noninteractive" była wymagana, a zatem interakcja z oknem akceptacji utrudniona. Jednakże, ostatecznie udało się znaleźć rozwiązanie, które wydawać może się niecodzienne, a polega na użycia polecenia "echo" celem ustawienia odpowiedzi na pytanie o akceptację licencji na wartość "prawda". Całość kodu zobaczyć można na \autoref{instalowanieCzcionek}.
\begin{figure}[h!]
\begin{lstlisting}[language=Dockerfile]
#setup ttf-mscore installer
RUN echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections
RUN apt-get install -y ttf-mscorefonts-installer
\end{lstlisting}
\caption{Kod pozwalający zainstalować pełny zestaw czcionek od firmy Microsoft w trybie bez interakcji z użytkownikiem. \label{instalowanieCzcionek}
}
\end{figure}
## Proces i narzędzia wykorzystane do utworzenia niniejszego dokumentu (Maciej Adryan)
\label{ProcesTworzeniaDokumentuPodrozdzial}
### Zamysł, Początkowy proces powstawania i decyzja o utworzeniu narzędzia
Autorzy niniejszej pracy inżynierskiej rozpoczęli jej tworzenie w internetowym edytorze "HackMD", wykorzystując do edycji język znaczników Markdown (MD). Początkowo, zamysłem było napisanie większości pracy bez potrzeby wykorzystywania środowiska \LaTeX a następnie ręczne przeniesienie napisanych rozdziałów do środowiska oferowanego przez platformę "Overleaf" służącą do asynchronicznego edytowania dokumentów. Przy procesie przenoszenia dokumentu, dojść miało również do licznych poprawek dokumentu, spowodowało to jednak zbytnie skomplikowanie tego procesu, co zmusiło autorów do opracowania systemu który pozwoli na dokończenie pisania pracy inżynierskiej w środowisku "HackMD" oraz automatycznie tłumaczyło będzie język znaczników Markdown na format wykorzystywany przez \LaTeX oraz generowało gotowe do pobrania pliki PDF.
Początkowo, przy opracowywaniu systemu skrypt wywoływany był lokalnie przy pomocy podsystemu linuxowego (WSL- Windows Subsystem for Linux) oferowanego przez firmę Microsoft.
Następnie wygenerowany przez skrypt plik w formacie PDF był wysyłany ręcznie do pozostałych autorów pracy za pomocą internetowego komunikatora przy pomocy którego odbywała się cała komunikacja dotycząca pracy inżynierskiej. Następnie po omówieniu zawartości dokumentu dokonywane były wymagane poprawki.
Po kilku wyglądających w ten sposób iteracjach autorzy pracy postanowili ulepszyć system generacji dokumentów tak, aby odbywał się automatycznie i nie wymagał, aby przy każdej zmianie dokumentu przez dowolnego autora, celem sprawdzenia poprawności generacji, skrypt musiał być uruchamiany ręcznie przez jednego z twórców oraz wysyłany do pozostałych.
Dotychczasowe rozwiązanie było szczególnie problematyczne ze względu na asynchroniczny system pisania pracy inżynierskiej. Opracowany przez nas system rozwiązujący te problemy został opisany w podrozdziale \ref{pipelineHACKMD}.
### Zaleta - Prostota tworzenia dokumentu
Ze względu na swoją specyfikę, język Markdown, wykorzystywany w narzędziu HackMD, jest prostszy niż \LaTeX oraz umożliwia tworzenie dokumentów w szybszy i mniej "blokujący kreatywność sposób". Rozumieć przez to można, iż ze względu na prostotę definiowania poszczególnych znaczników, pozwala on poświęcić więcej uwagi na treści a mniej na formie w jakiej jest ona wyświetlana. Dodatkowo, twórcy niniejszej pracy są zgodni, iż usługa HackMD oferuje wygodniejsze w obsłudze i mniej "przytłaczające" środowisko.
### Zaleta - Niższa bariera wejścia
Wspomnieć można tutaj również o aspekcie psychologicznym, zwanym "barierą wejścia".
Termin ten oznacza przeszkodę, która utrudnia rozpoczęcie jakiejś czynności lub działania, np. ze względu na trudność.
Przyświecającym autorom tej pracy od początku pomysłem, było zmniejszenie "bariery wejścia" na tyle, aby proces tworzenia niniejszego dokumentu stał się w jak najmniejszym stopniu zadaniem "odstraszającym".
### Zaleta - Łatwe umieszczanie obrazów w tekście
Ze względu na specyfikę niniejszej pracy inżynierskiej oraz fakt umieszczania w niej stosunkowo dużej ilości zdjęć, stosowanie narzędzia HackMD okazało się być narzędziem prostszym i bardziej komfortowym w obsłudze. Porównajmy proces dodawania zdjęcia będącego zrzutem ekranu do dokumentu za pomocą narzędzia Overleaf do tego samego procesu wykonanego za pomocą narzędzia HackMD:
Kroki procesu w przypadku stosowania narzędzia Overleaf:
1. Wykonanie zrzutu ekranu.
2. Zapisanie zrzutu ekranu do pliku w lokalnym systemie plików.
3. Kliknięcie przycisku "New File".
4. Kliknięcie zakładki "Upload" w nowo otwartym oknie dialogowym.
5. Kliknięcie fragmentu tekstu "select from your computer".
6. Przenawigowanie do folderu zawierającego plik w przypadku gdy nie został on zapisany w domyślnie otwierającym się folderze w nowo otwartym oknie narzędzia Eksplorator Plików.
7. Wybranie interesującego użytkownika pliku.
8. Utworzenie odpowiedniego środowiska dla dodanego obrazka, np. za pomocą poleceń "\begin{figure}" lub "\begin{wrapfigure}".
9. Dodanie dodatkowych cech obrazka takich jak opis czy etykieta.
10. Zdefiniowanie wewnątrz ww. środowiska ścieżki do pliku.
Ten sam efekt można otrzymać w środowisku HackMD za pomocą niżej opisanych kroków:
1. Wykonanie zrzutu ekranu.
2. Wklejenie obrazka do dokumentu bezpośrednio ze schowka systemowego.
3. Dodanie dodatkowych cech obrazka takich jak opis czy etykieta.
Pliki dodane za pomocą procesu opisanego w przypadku korzystania z narzędzia HackMD udostępniane są za pomocą darmowego internetowego serwisu hostingowego **imgur.com**. Generator pliku PDF oparty o narzędzie pandoc automatycznie pobiera obrazki przy generowaniu docelowego pliku PDF.
### Narzędzia - HackMD
**HackMD** to narzędzie do tworzenia i współdzielenia notatek online, umożliwiające pracę z dokumentami w formacie Markdown. Wśród jego funkcjonalności znajduje się:
- edytor z podglądem na żywo,
- współdzielenie notatek z innymi użytkownikami,
- synchronizacja z chmurą oraz integracja z serwisami takimi jak GitHub czy Google Drive.
### Narzędzia - pandoc
**Pandoc** to uniwersalny konwerter tekstu, który umożliwia konwersję pomiędzy różnymi formatami dokumentów. Dzięki niemu możliwe jest m.in. przekształcanie plików Markdown na formaty takie jak PDF, HTML czy Word, a także odwrotnie. Pandoc obsługuje wiele różnych języków i formatów, w tym m.in. Markdown, LaTeX, HTML, DOCX, EPUB czy ODT.
### Narzędzia - ttf-mscorefonts-installer (czcionki)
**ttf-mscorefonts-installer** to narzędzie służące do instalacji czcionek Microsoft TrueType (TTF) na systemach Linuksa. Jest to paczka debiana, która zawiera pakiet czcionek Microsoftu, w tym m.in. Arial, Times New Roman, Verdana czy Impact. Po zainstalowaniu tego narzędzia, czcionki te stają się dostępne dla systemu i mogą być używane przez aplikacje, które je obsługują.
### Narzędzia - latex (xelatex)
**XeLaTeX** to implementacja TeX'a, systemu składu dokumentów, która umożliwia wykorzystywanie różnych rodzajów czcionek w dokumentach. XeLaTeX wspiera wiele różnych języków i pozwala na łatwe umieszczanie znaków Unicode w dokumentach. Jest szczególnie przydatny dla osób pracujących z dokumentami w wielu językach lub potrzebujących użycia **specjalnych znaków lub czcionek**, tak jak było w przypadku niniejszej pracy, ze względu na potrzebę wykorzystania czcionki **Arial**, więcej o tym przeczytać można w podrozdziale \ref{fontsEULA}.
### Generowanie pliku PDF
\label{generowaniePlikuPDFRozdzial}
Widoczny na \autoref{kodMakefile} kod automatyzuje proces tworzenia pliku PDF. Skrypt ten pobiera z dedykowanego do pobierania plików endpointu oferowanego przez usługę HackMD dwa pliki Markdown (wyjaśnienie w podrozdziale \ref{podzialDokumentuNa2}) scalając je w tymczasowy plik Markdown wykorzystywany do generacji pliku PDF. Następnie po nadaniu rozszerzonych uprawnień za pomocą narzędzia pandoc konwertuje plik z rozszerzeniem .md na gotowy plik .pdf o nazwie zdefiniowanej w zmiennej AU_OUTFILE. Opcje pandoc użyte w tym kroku umożliwiają m.in:
- dodanie spisu treści,
- określenie z jakiego silnika generowania dokumentów korzystać ma narzędzie,
- ustawienie stylów kolorów dla kodu źródłowego,
- dodanie numeracji sekcji
- ustawienie rozmiaru strony na A4,
- określenia daty,
- określenia rozmiaru marginesów.
Wywołanie skryptu, zakładając, iż plik Makefile znajduje się w bieżącym katalogu roboczym zobaczyć można na \autoref{wywowalnieBezParsow1} lub w formie skróconej na \autoref{wywowalnieBezParsow2}.
\begin{figure}[h!]
\begin{lstlisting}[language=bash]
make hackmd2pdf
\end{lstlisting}
\caption{Przykład wywołania skryptu Makefile}
\label{wywowalnieBezParsow1}
\end{figure}
\begin{figure}[h!]
\begin{lstlisting}[language=bash]
make
\end{lstlisting}
\caption{Przykład wywołania skryptu Makefile}
\label{wywowalnieBezParsow2}
\end{figure}
Parametry AU_HACKMD_CODE_1, AU_HACKMD_CODE_2, AU_OUTFILE oraz AU_FORMAT mogą być nadpisane poprzez wywołanie skryptu z odpowiednimi flagami, np. widoczny na \autoref{wywolanieZParamsami} kod, spowoduje wygenerowanie pliku pdf w formacie "article" oraz zapisanie wyniku do pliku o nazwie "tmp.pdf".
\begin{figure}[h!]
\begin{lstlisting}[language=bash]
make AU_OUTFILE=tmp.pdf AU_FORMAT=article
\end{lstlisting}
\caption{Przykład wywołania skryptu Makefile}
\label{wywolanieZParamsami}
\end{figure}
\begin{figure}[h!]
\begin{lstlisting}[language=make]
AU_HACKMD_CODE_1 = *****
AU_HACKMD_CODE_2 = *****
AU_OUTFILE = ./foobar.pdf
AU_FORMAT = report
hackmd2pdf:
@echo format doc
curl -s -L $(AU_HACKMD_CODE_1) -o /tmp/1.md
curl -s -L $(AU_HACKMD_CODE_2) -o /tmp/2.md
cat /tmp/1.md /tmp/2.md > ./tmp/tmp.md
chmod 777 ./tmp/tmp.md
pandoc ./tmp/tmp.md -o $(AU_OUTFILE) \
--listings \
--table-of-contents \
--pdf-engine=xelatex \
--highlight-style=breezedark \
--number-sections \
-V 'listings: true' \
-V 'papersize:A4' \
-V 'urlcolor:blue' \
-V 'date:\today{}' \
-V 'documentclass:$(AU_FORMAT)' \
-V 'geometry:inner=3.5cm,outer=2.5cm,top=2.5cm,bottom=2.5cm'
\end{lstlisting}
\caption{Zawartość skryptu Makefile \label{kodMakefile}}
\end{figure}
### Łączenie kodu w LaTeXu oraz Markdownie
\label{mieszanieLatexaIMD}
Niektórych wymaganych przez autorów funkcjonalności takich jak: listingi z etykietami oraz formatowaniem, etykiet dla obrazków, narzucone formatowanie dokumentu czy bibliografii nie udało się uzyskać za pomoca jedynie języka Markdown, w takich przypadkach możliwym jest jednak umieszczanie kodu LaTeXowego bezpośrednio obok tekstu w zapisanego Markdown. Przykładowy sposób w jaki zdefiniowane zostało odwołanie w niniejszym akapicie zobaczyć można na \ref{etykietyIlabelkiPrzyklad}.
\begin{figure}[h!]
\fbox{\parbox{\textwidth}{
Niektórych wymaganych przez autorów funkcjonalności takich jak: listingi z etykietami oraz formatowaniem, etykiet dla obrazków, narzucone formatowanie dokumentu czy bibliografii nie udało się uzyskać za pomoca jedynie języka Markdown, w takich przypadkach możliwym jest jednak umieszczanie kodu LaTeXowego bezpośrednio obok tekstu w zapisanego Markdown. Przykładowy sposób w jaki zdefiniowane zostały odwołania i etykiety wykorzystane w niniejszym akapicie zobaczyć można na \symbol{92}\symbol{123}\symbol{101}\symbol{116}\symbol{121}\symbol{107}\symbol{105}\symbol{101}\symbol{116}\symbol{97}\symbol{80}\symbol{114}\symbol{122}\symbol{121}\symbol{107}\symbol{108}\symbol{97}\symbol{100}\symbol{125}.
}
}
\caption{Przykładowa definicja odwołania do etykiety wewnątrz dokumentu w Markdown \label{etykietyIlabelkiPrzyklad}}
\end{figure}
### Ciągła integracja i wdrażanie - Docker
Pierwsza linia kodu widocznego na \autoref{dockerfileDoGeneracjiPDF} definiuje bazowy obraz systemu operacyjnego, na którym będzie budowany obraz Docker - w tym przypadku jest to Ubuntu 20.04 (nazwana "focal").
Pierwsze kilka kroków procesu kopiuje potrzebne pliki (Makefile, pliki źródłowe i pliki tymczasowe) do obrazu Docker. Następnie instaluje wymagane komponenty, takie jak: make, curl, pandoc i pakiety TexLive, używając polecenia "apt-get install". Następnie instaluje pakiet "ttf-mscorefonts-installer" i ustawia odpowiedź na pytanie o akceptację EULA na "true", proces ten został dokładniej opisany w podrozdziale \nameref{fontsEULA}.
Polecenie "fc-cache -f" jest używane do odświeżenia listy dostępnych czcionek w systemie.
Ostatnia linia używa polecenia "make" do wywołania skryptu "hackmd2pdf", który został opisany w podrozdziale \nameref{generowaniePlikuPDFRozdzial}. Po uruchomieniu obrazu Docker za pomocą tej definicji, skrypt "hackmd2pdf" zostanie uruchomiony i zostanie wygenerowany plik PDF z plików Markdown.
\begin{figure}[h!]
\begin{lstlisting}[language=Dockerfile]
FROM ubuntu:focal
ARG DEBIAN_FRONTEND=noninteractive
WORKDIR ~
# copy required files
COPY Makefile ./
COPY /sources ./sources
COPY /tmp/ ./tmp/
# install required components
RUN apt-get update
RUN apt-get install -y make curl pandoc texlive-xetex texlive-lang-polish
#setup ttf-mscore installer
RUN echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections
RUN apt-get install -y ttf-mscorefonts-installer
RUN fc-cache -f
# create pdf
CMD make ./hackmd2pdf
\end{lstlisting}
\caption{Plik Dockerfile generujący plik pdf \label{dockerfileDoGeneracjiPDF}
}
\end{figure}
### Ciągła integracja i wdrażanie - Potok wdrażania aplikacji
\label{pipelineHACKMD}
Widoczny na \autoref{tttttttttt} definiuje zadanie GitHub Actions, automatyzujące proces budowania i wysyłania obrazu Docker do platformy DockerHub oraz aktualizacji kontenera na serwerze VPS (Virtual Private Server).
Proces składa się z dwóch zadań: "build-and-push" oraz "update-VPS-container".
Część ("build-and-push") odpowiada za budowanie i wysyłanie obrazu Docker do serwera Docker Hub. Część ("update-VPS-container") odpowiada za aktualizację kontenera na serwerze VPS za pomocą polecenia "docker pull" i uruchomienie nowego kontenera wykorzystującego nowy obraz.
Zadanie jest uruchamiane po każdej aktualizacji do gałęzi "main". W takim przypadku następuje:
1. Sprawdzenie kodu z repozytorium za pomocą akcji "actions/checkout@v2"
2. Ustawienie narzędzia QEMU za pomocą akcji "docker/setup-qemu-action@v1"
3. Ustawienie narzędzia Docker Buildx, które umożliwia budowanie obrazów Docker na różnych architekturach za pomocą akcji "docker/setup-buildx-action@v1"
4. Zalogowanie się do serwera Docker Hub za pomocą akcji "docker/login-action@v1"
5. Budowanie i wysyłanie obrazu Docker do serwera Docker Hub za pomocą akcji "docker/build-push-action@v2"
6. Aktualizacja kontenera na serwerze VPS za pomocą akcji "appleboy/ssh-action@master".
Wartości zmiennych dostępnych przez użycie "secrets" pochodzą ze zdefiniowanych z poziomu platformy GitHub wartości, przykład można zobaczyć na \autoref{przykladSecrets}, wartości te są dostępne z poziomu całego repozytorium.
\begin{figure}[h]
\begin{lstlisting}[style=yaml]
name: Deploy
on:
push:
branches:
- 'main'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: hevgan/cts-dash-application:latest
update-VPS-container:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Update image inside VPS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
USERNAME: ${{ secrets.USERNAME }}
PORT: ${{ secrets.PORT }}
KEY: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker stop $(docker ps -q --filter ancestor=hevgan/cts-dash-application )
docker pull hevgan/cts-dash-application
docker run -d -p 3003:8095 -e BACKEND_API_KEY=${{secrets.BACKEND_API_KEY}} hevgan/cts-dash-application
\end{lstlisting}
\caption{Potok wdrażania aplikacji \label{tttttttttt}
}
\end{figure}
{height=250px}
### Ciągła integracja i wdrażanie - Pipeline generujący plik PDF
Potok zdefinowany za pomocą kodu widocznego na \autoref{tworzenie_i_uploadingPliku} wykonuje zadanie o nazwie "build-pdf", które jest uruchamiane przy wysyłaniu na gałąź main lub co 5 minut według zdefiniowanego harmonogramu cron.
Po pobraniu najnowszej wersji kodu z repozytorium, proces ten pobiera oraz uruchania obraz dockerowy. Następnie, z wewnątrz kontenera do systemu operacyjnego maszyny wykonującej potok skopiowany zostaje wygenerowany plik PDF, na koniec dzięki dostępnemu w Github Actions mechanizmowi publikowania artefaktów potokowych oraz zdefiniowanego kroku "s3-upload-action" plik zostaje udostępniony pod docelowym adresem. Przykładem takiego adresu jest może być **https://<AWS_BUCKET>.s3.eu-central-1.amazonaws.com/artifacts/output/inzynierka.pdf**.
\begin{figure}[ht!]
\begin{lstlisting}[style=yaml]
name: Build PDF
on:
push:
branches:
- 'main'
schedule:
- cron: '*/5 * * * *'
jobs:
build-pdf:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run docker container
run: |
docker run --name hackmd2pdf hevgan/cts-inzynierka-md-latex-parser
docker cp hackmd2pdf:~/inzynierka_in_container.pdf ./inzynierka.pdf
- name: Publish artifact
uses: actions/upload-artifact@v2
with:
name: inzynierka
path: ./inzynierka.pdf
- name: Upload file to S3
uses: hkusu/s3-upload-action@v2
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 'eu-central-1'
aws-bucket: ${{ secrets.AWS_BUCKET }}
file-path: './inzynierka.pdf'
destination-dir: 'output'
\end{lstlisting}
\caption{Plik build-pdf.yml odpowiedzialny za generowanie pliku pdf oraz udostępnienie go pod predefiniowanym wewnątrz usługi S3 adresem \label{tworzenie_i_uploadingPliku}
}
\end{figure}
### Ciągła integracja i wdrażanie - Publikowanie pliku PDF w przypadku powodzenia
W przypadku prawidłowego wygenerowania się dokumentu, powstały w trakcie wykonywania potoku przetwarzania plik PDF zostaje udostępniony pod stałym adresem, z którego możliwe jest pobranie zawsze najnowszej dostępnej wersji dokumentu. Akcja ta zdefiniowana jest w kodzie potoku przetwarzania za pomocą fragmentu kodu widocznego na \autoref{uploadingPliku}.
\begin{figure}[ht!]
\begin{lstlisting}[style=yaml]
- name: Publish artifact
uses: actions/upload-artifact@v2
with:
name: inzynierka
path: ./inzynierka.pdf
- name: Upload file to S3
uses: hkusu/s3-upload-action@v2
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 'eu-central-1'
aws-bucket: ${{ secrets.AWS_BUCKET }}
file-path: './inzynierka.pdf'
destination-dir: 'output'
\end{lstlisting}
\caption{Fragment pliku build-pdf.yml odpowiedzialny za udostępnienie pliku pod predefiniowanym adresem wewnątrz bucketa usługi S3 Amazonu \label{uploadingPliku}
}
\end{figure}
### Ciągła integracja i wdrażanie - Powiadomienia w przypadku niepowodzenia
W przypadku niepowodzenia, dzięki zdefiniowanemu mechanizmowi obsługi takich zdarzeń, do wcześniej zdefiniowanej grupy osób zostaną wysłane wiadomości e-mail, informujące o niepowodzeniu wykonania się konkretnego potoku.
## Uruchomienie projektu na lokalnej maszynie (Michał Matejuk)
\label{UruchomieniePodrozdzial}
### Opis
Możliwość uruchamiania projektów informatycznych lokalnie jest bardzo ważna, ponieważ umożliwia tworzenie i testowanie aplikacji bez konieczności udostępniania ich publicznie. Dzięki temu można pracować nad aplikacją w prywatnym środowisku i dopracować ją do perfekcji, zanim zostanie udostępniona szerszej publiczności.
Uruchamianie projektów lokalnie pozwala na bardziej efektywne testowanie aplikacji, ponieważ nie trzeba czekać na zewnętrzne połączenie z serwerem ani przesyłać danych przez sieć. Ponadto umożliwia sprawdzenie stworzonej przez nas aplikacji w bezpiecznym i kontrolowanym środowisku.
### Docker Compose
Docker Compose to narzędzie, które umożliwia zarządzanie aplikacjami składającymi się z wielu kontenerów dockerowych. Dzięki Docker Compose można tworzyć pliki konfiguracyjne w YAML, w których opisane są wszystkie kontenery, jakie tworzą aplikację, oraz ich wzajemne zależności.
Dzięki temu, że konfiguracja jest zapisywana w pliku, można ją łatwo wdrażać i zarządzać całą aplikacją jako całością. Docker Compose umożliwia też uruchamianie wielu kontenerów jednocześnie za pomocą jednej komendy, co znacznie ułatwia i przyspiesza pracę.
Docker Compose można wykorzystywać zarówno do szybkiego przeprowadzania testów End-To-End lokalnie, jak również do konfiguracji i wdrożenia gotowych systemów w chmurze lub dedykowanej maszynie.
### Uruchomienie projektu
W celu łatwego testowania całego systemu na lokalnej maszynie przygotowany został specjalny plik *docker-compose.yml*, który uruchamiany jest przez wpisanie w konsoli komendy *docker compose up*.
Jeżeli komenda uruchamiana jest w tym samym folderze, gdzie znajduje się plik niewymagane jest dodatkowe podawanie lokalizacji pliku, chociaż jest to możliwe przez podanie argumentu *-f* i ścieżki do pliku compose. Opcję *-f* wykorzystać można aby lokalnie uruchomić system w "produkcyjnym" środowisku. W tym celu powstał plik *docker-compose-prod.yml*, który uruchamiamy komendą *docker compose -f docker-compose-prod.yml up*. Aby wszystko zadziałało musimy jednak uzupełnić pole hasła w połączeniu do bazy danych - wystarczy podmienić fragment "PASSWORD". (Hasło pozostaje puste dla bezpieczeństwa, pliki trzymane są w publicznym repozytorium.)
Struktura pliku *docker-compose.yml* jest bardzo prosta i prezentuje się następująco: \autoref{dockerCompose},
\begin{figure}[h!]
\begin{lstlisting}[style=yaml]
version: '3.9'
services:
mongodb:
image: mongo:latest
container_name: mongodb
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- MONGO_INITDB_DATABASE=test
networks:
- ctsNetwork
cts-compute-service:
image: brzzw5k/city-traffic-simulator-computing-microservice:latest
container_name: cts-compute-service
restart: always
ports:
- 8082:5000
environment:
- MONGOCONNECTION__CONNECTIONSTRING=mongodb://root:root@mongodb:27017
- MONGOCONNECTION__DATABASENAME=test
networks:
- ctsNetwork
cts-backend:
image: matejuk/city-traffic-simulator-backend:latest
container_name: cts-backend
restart: always
ports:
- 8080:80
environment:
- MONGOCONNECTION__CONNECTIONSTRING=mongodb://root:root@mongodb:27017
- MONGOCONNECTION__DATABASENAME=test
- APIKEY=1234
- COMPUTE_SERVICE_URL=http://cts-compute-service
networks:
- ctsNetwork
cts-frontend:
image: hevgan/cts-dash-application:latest
container_name: cts-frontend
restart: always
ports:
- 8081:8095
environment:
- BACKEND_API_KEY=1234
- BACKEND_API_URL=http://cts-backend
networks:
- ctsNetwork
networks:
ctsNetwork:
\end{lstlisting}
\caption{Plik docker-compose.yml\label{dockerCompose}}
\end{figure}
Jak widać, w pliku zdefiniowano trzy usługi: mongodb, cts-backend, cts-frontend i cts-compute-service. Każda usługa ma następujące pola:
- image - obraz Dockera, który ma być użyty do uruchomienia kontenera dla danej usługi,
- container_name - nazwa kontenera dla danej usługi,
- restart - opcja określająca czy kontener ma być automatycznie uruchamiany ponownie po awarii,
- ports - mapowanie portów z kontenera na porty hosta,
- environment - zmienne środowiskowe, które będą dostępne dla procesów uruchomionych w kontenerze,
- networks - sieci, do których ma być podłączony kontener.
W pliku zdefiniowano również jedną sieć o nazwie ctsNetwork, do której są podłączone wszystkie cztery usługi. Pozwala to na wymianę danych między kontenerami za pośrednictwem sieci.
Po uruchomieniu systemu lokalnie możemy przetestować działanie całego systemu wraz z symulacją przekierowując requesty *POST* symulatora do lokalnego kontenera backendowego.
# Analiza przeprowadzonych symulacji
\label{AnalizaRozdzial}
## Analiza fragmentu miasta Gdańsk - Piecki Migowo (Aleksander Błaszkiewicz)
\label{pieckimigowo}
W badaniu tym pokażemy, w jaki sposób symulator może pomóc rozwiązać problemy istniejącej sieci drogowej. W przygotowanej scenie odwzorowany został kawałek obszaru dzielnicy Piecki-Migowo w mieście Gdańsk. Obejmuje ulice:
- Franciszka Rakoczego,
- Piecewska,
- Warneńska,
- Wileńska,
- Schuberta,
- Jaśkowa Dolina,
- Antoniego Dobrowolskiego.
W przeprowadzonych symulacjach będziemy się posługiwać funkcjami rozkładu czasu reakcji kierowców:
- szybki czas reakcji,
- średni czas reakcji,
- wolny czas reakcji,
- brak czasu reakcji.
{ height=250px }
{ height=250px }
{ height=250px }
### Iteracja 1
W pierwszej iteracji zapoznajemy się z istniejącym problemem. Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **pieckiMigowo** - **initial** - **1**.
Po odtworzeniu symulacji (przedstawionej na \autoref{pieckiMigowoInitial1}) i zapoznaniu się z mapami ciepła pozycji (przedstawionymi na \autoref{pieckiMigowoInitial2}) pojazdów widać, że problem występuje w górnym fragmencie mapy w miejscu wjazdu na skrzyżowanie.
Początkowe ustawienie świateł na tym skrzyżowaniu zakłada zmianę światła co 3 sekundy. Jako że o wiele większy ruch nadchodzi z lewej strony niż z góry skrzyżowania, możemy zmienić ten czas na:
- 10 sekund dla pojazdów nadjeżdżających z lewej i prawej strony,
- 3 sekundy dla pojazdów nadjeżdżających z dołu i góry.
{height=250px}
{height=250px}
### Iteracja 2
Przyjrzymy się teraz, jak zachowuje się symulacja po poprawieniu parametrów jak opisano w iteracji 1.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **pieckiMigowo** - **crossroadAdjustment** - **1**.
Pomimo dostosowania harmonogramu świateł, w tym samym miejscu nadal tworzy się korek. Przy odtwarzaniu symulacji można zaobserwować, że tym razem powodem powstania zatoru jest zjawisko phantom traffic. Kierowcy mają zbyt wysoki czas reakcji, co powoduje opóźnienia. To samo widoczne jest na mapie cieplnej (przedstawionej na \autoref{pieckiMigowoCrossroadAdjustment2})


Początkowe ustawienie zakładało kierowców o średnim czasie reakcji (około 500ms). Możemy zmienić profil na taki o niskim czasie reakcji (około 200ms).
### Iteracja 3
Poprzedni problem został rozwiązany. Jednak po ponownej analizie powtórki symulacji (przedstawionej na \autoref{pieckiMigowoReactionTimeAdjustment1}) oraz mapy cieplnej (przedstawionej na \autoref{pieckiMigowoReactionTimeAdjustment2}) widać kolejny problem. Ruch korkuje się na skrzyżowaniu, które przylega do tego poprawionego w iteracji 1.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **pieckiMigowo** - **reactionTimeAdjustment** - **1**.


Na tym skrzyżowaniu harmonogram świateł również zakłada zmianę światła co 3 sekundy. Jako że o wiele większy ruch nadchodzi z prawej strony niż z góry skrzyżowania, możemy zmienić ten czas na:
- 10 sekund dla pojazdów nadjeżdżających z lewej i prawej strony,
- 3 sekundy dla pojazdów nadjeżdżających z dołu i góry.
Dodatkowo wydaje się, że skrzyżowania, które są blisko siebie powinny być zsynchronizowane, aby ruch w tym samym kierunku był płynny. Zmiana harmonogramu zsynchronizuje obydwa skrzyżowania
### Iteracja 4
W ostatniej iteracji po przeanalizowaniu powtórki symulacji oraz map cieplnych widać, że problemy w tym kawałku sieci drogowej zostały rozwiązane.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **pieckiMigowo** - **secondCrossroadAdjustment** - **1**.
## Autonomiczne pojazdy (Aleksander Błaszkiewicz)
\label{autonomicznePojazdyPodrozdzial}
### Opis
W tym badaniu chcemy pokazać, jaki wpływ ma czas reakcji na zakorkowanie skrzyżowań. Chcemy pokazać, że wizja autonomicznych samochodów, których czas reakcji wynosi 0 niesie za sobą kluczowe zyski.
Scena, na której odbędzie się badanie to klasyczne skrzyżowanie ze światłami z 4 wjazdami i wyjazdami bez pasów do skrętów warunkowych i z pasami skrętów kolizyjnych (jak na \autoref{badanieAutonomiczne1}).
Pojazdy pojawiają się na każdym wjeździe z jednakową częstotliwością i mogą wybrać jeden z 3 losowych dróg.
{ height=400px }
### Wysoki czas reakcji
W pierwszym wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu przedstawionej na \autoref{czasReakcjiSzybki}, której maksimum to 1.5s.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **autonomous** - **slowReaction** - **1** - **replay simulation**.
Na przeprowadzonej symulacji widać, że skrzyżowanie momentalnie się zapycha. Tak wysoki czas reakcji to ekstremalny przypadek, ale udowadnia, że czynnik ten jest w stanie sparaliżować ruch (\autoref{zapchaneSkrzyzowanieWolne}).
{ height=300px }
### Średni czas reakcji
W drugim wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu przedstawionej na \autoref{czasReakcjiSredni}, której maksimum to 0.5s.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **autonomous** - **mediumReaction** - **1** - **replay simulation**.
Na przeprowadzonej symulacji widać, że wraz z upływem czasu na wjazd na skrzyżowanie czeka coraz więcej pojazdów. Ruch na skrzyżowaniu nigdy się nie rozładuje, a będzie tylko rósł. Skrzyżowanie blokuje się jednak wolniej niż w przypadku wysokiego czasu reakcji (\autoref{zapchaneSkrzyzowanieSrednie}).
{ height=300px }
### Niski czas reakcji
W trzecim wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu przedstawionej na \autoref{czasReakcjiNiski}, której maksimum to 0.1s.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **autonomous** - **fastReaction** - **1** - **replay simulation**.
Przeprowadzana symulacja jest przełomem w tym badaniu, ponieważ wraz z upływem czasu, na zielonym świetle jest w stanie przejechać tyle samochodów, że ogólny ruch zostaje rozładowany. Udowadnia to, że czas reakcji może być jednym z czynników likwidujących korki (\autoref{zapchaneSkrzyzowanieSzybkie}).
{ height=300px }
### Brak czasu reakcji
W czwartym wariancie badania czas reakcji tworzonych kierowców to 0 sekund. Ma to symulować autonomiczne auta. W momencie zapalenia się zielonego światła wszyscy kierowcy ruszają jednocześnie
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów
**autonomous** - **zeroReaction** - **1** - **replay simulation**
Przeprowadzona symulacja pokazuje, jak płynny może być ruch, jeżeli ruszanie na światłach pozbyte jest czynnika ludzkiego - czasu reakcji (\autoref{zapchaneSkrzyzowanieZero}).
{ height=300px }
### Wnioski
Symulacje przeprowadzone powyżej jednoznacznie potwierdzają, że czas reakcji może zaważyć na tym, czy wraz z biegiem czasu na skrzyżowaniu będzie coraz więcej aut, czy ruch będzie na bieżąco rozładowywany.
Autonomiczne auta z brakiem czasu reakcji to ekstremalny przypadek i przedsięwzięcie na gigantyczną skalę, ale jak widać na symulacji z zerowym czasem reakcji - ruch wtedy jest o wiele płynniejszy niż w przypadku polegania na czasie reakcji kierowców.
Należy również pamiętać, że opóźnienia na skrzyżowaniach spowodowane czasem reakcji kierowców wpływają na ogólny czas trwania podróży dla wszystkich samochodów za nimi. Jeżeli pierwszy kierowca na skrzyżowaniu zagapi się i nie zauważy zielonego światła, wydłuży to czas trwania podróży wszystkich samochodów za nim.
Jeżeli pierwsza osoba na światłach się zagapi i nie zauważy zielonego światła, wydłuży to czas trwania podróży wszystkich samochodów za nim.
## Phantom traffic (Michał Matejuk i Aleksander Błaszkiewicz)
\label{PhantomTrafficPodrozdzial}
### Opis
Phantom traffic to zjawisko, które pojawia się, gdy niewielkie zaburzenie w ruchu drogowym, takie jak hamowanie pojedynczego pojazdu, rozprzestrzenia się do tyłu przez rząd kolejnych pojazdów, co w konsekwencji prowadzi do korków.
W badaniu zostanie sprawdzone, czy czas reakcji może rozładować phantom traffic.
Na początku sprawdzimy, jakie są warunki utworzenia się zjawiska phantom traffic przy czasie reakcji kierowców wyznaczonym funkcją rozkładu jak na \autoref{czasReakcjiNiski}.
### Przypadek 6 pojazdów
W warunkach panujących w symulacji 6 pojazdów nie tworzy zjawiska phantom traffic. Po usunięciu przeszkody korek rozładowuje się i ruch na rondzie jest płynny (sytuacja przedstawiona na \autoref{traffic6Aut}).
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów
**phantomTraffic** - **6samochodow** - **1** - **replay simulation**
{ height=300px }
### Przypadek 7 pojazdów
W warunkach panujących w symulacji 7 pojazdów tworzy zjawisko phantom traffic. Warto zauważyć, że pomimo utworzenia tego zjawiska, jest ono w stanie samoistnie się rozładować jak na \autoref{traffic7AutLaczone}.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów
**phantomTraffic** - **7samochodow** - **1** - **replay simulation**
{ height=300px }
### Przypadek 8 pojazdów
W warunkach panujących w symulacji 8 pojazdów tworzy zjawisko phantom traffic, które nigdy się nie rozładuje jak na \autoref{trafic8AutLaczone}.
Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów
**phantomTraffic** - **8samochodow** - **1** - **replay simulation**
{ height=300px }
## Porównanie ronda i skrzyżowania (Aleksander Błaszkiewicz)
\label{rondoVSskrzyzowanie}
### Opis
Rondo i skrzyżowanie to dwa najważniejsze elementy układu drogowego, które odgrywają kluczową rolę w organizacji ruchu na drodze. Oba te elementy służą do łączenia dwóch lub więcej dróg w celu umożliwienia pojazdom przejazdu w różnych kierunkach.
Rondo to specjalne rodzaje skrzyżowania, w którym drogi prowadzące do skrzyżowania łączą się z jednym, centralnym punktem, zwanym rondem. Rondo jest zwykle uważane za bezpieczniejsze i bardziej przepustowe niż skrzyżowanie, ponieważ pojazdy nie muszą wchodzić w bezpośrednie kolizyjne położenie z innymi pojazdami, jak ma to miejsce na skrzyżowaniach.
Standardowe skrzyżowanie z kolei to miejsce, w którym najczęściej dochodzi do wypadków z uwagi na bezpośrednie interakcje, w jakie muszą wchodzić ze sobą pojazdy (szczególnie podczas skrętów kolizyjnych)
### Rondo
Przykładowa scena reprezentująca rondo z 4 wjazdami i 4 wyjazdami przedstawiona na \autoref{rondoVsSkrzyzowanie1}.
{ height=300px }
### Skrzyżowanie
Przykładowa scena reprezentująca skrzyżowanie z 4 wjazdami i 4 wyjazdami przedstawiona na \autoref{rondoVsSkrzyzowanie2}.
{ height=300px }
### Parametry symulacji
W każdej z przeprowadzonych symulacji kierowcy przyjmują profil kierowcy o szybkim czasie reakcji. Czas reakcji przyznawany jest według funkcji rozkładu na \autoref{czasReakcjiSzybki}.
Ponadto każdy spawner pojazdów ustawiony jest na tworzenie aut z interwałem pomiędzy 1 a 3 sekundy.
Sprawdzenie zakorkowania danej struktury następuje w 200 klatce symulacji.
### Najgorszy przypadek
W przypadku ronda będzie to przypadek, w którym wszyscy skręcają na najdalej oddalony zjazd od tego, którym wjeżdżali.
Wizualizacja ronda dostępna jest w aplikacji frontendowej po wybraniu parametrów **roundabout** - **worstCase** - **1** - **replay simulation**.
Najgorszy przypadek skrzyżowania to równomierne rozłożenie ruchu bez skrętu w prawo, czyli każdy kierowcą ma równą szansę wybrać skręt w lewo lub jazdę prosto.
Wizualizacja skrzyżowania dostępna jest w aplikacji frontendowej po wybraniu parametrów **crossroad** - **worstCase** - **1** - **replay simulation**.
Z przeprowadzonych symulacji wynika, że nawet w najgorszych wypadkach rondo i tak jest lepsze od standardowego skrzyżowania (przedstawione na \autoref{najgorszeSkrzyzowanieLaczone}. Widać, że po pewnym czasie na skrzyżowaniu ruch nie rozładowuje się na bieżąc)o i powstaje korek. W przypadku ronda problem ten nie występuje.
{ height=300px }
### Średni przypadek
W średnim przypadku założyliśmy, że każdy kierowca ma równą szansę wybrać losowy zjazd z każdego ze skrzyżowań
Wizualizacja ronda dostępna jest w aplikacji frontendowej po wybraniu parametrów **roundabout** - **mediumCase** - **1** - **replay simulation**.
Wizualizacja skrzyżowania jest w aplikacji frontendowej po wybraniu parametrów **crossroad** - **mediumCase** - **1** - **replay simulation**.
Z przeprowadzonych symulacji wynika, że w średnim wypadku znów wygrywa rondo. Mimo że w obydwu typach skrzyżowania nie dochodzi już do tworzenia korka, kierowcy na skrzyżowaniu jak na \autoref{srednieRondoLaczone} spędzają trochę czasu na czerwonym świetle. Na rondzie ruch odbywa się płynnie. Kierowcy prawie nigdy nie czekają, aby wjechać na rondo.
{ height=300px }
### Najlepszy przypadek
W przypadku ronda będzie to przypadek, w którym wszyscy kierowcy zjeżdżają zjazdem najmniej oddalonym od tego, którym wjeżdżali.
Wizualizacja ronda dostępna jest w aplikacji frontendowej po wybraniu parametrów **roundabout** - **bestCase** - **1** - **replay simulation**.
Z kolei najlepszy przypadek skrzyżowania to wszyscy kierowcy skręcający w prawo.
Wizualizacja skrzyżowania jest w aplikacji frontendowej po wybraniu parametrów **crossroad** - **bestCase** - **1** - **replay simulation**.
Z przeprowadzonych symulacji wynika, że w najlepszym przypadku rondo i skrzyżowanie jak na \autoref{najlepszeSkrzyzowanieLaczone} zachowują się po prostu tak samo. W obydwu przypadkach ruch przez te struktury sieci drogowej jest płynny.

## Zajmowanie odpowiednich pasów (Aleksander Błaszkiewicz)
\label{zajmowaniePasowPodrozdzial}
### Opis
Zajmowanie odpowiednich pasów ruchu przez kierowców może mieć wpływ na poziom zakorkowania w danym miejscu. Jeśli kierowcy poruszają się po odpowiednich pasach ruchu, to oznacza, że ich samochody są rozmieszczone w sposób optymalny i nie tworzą zatory na drodze. Natomiast jeśli kierowcy zajmują nieodpowiednie pasy ruchu lub zmieniają pasy zbyt często, to może to prowadzić do zakorkowania, ponieważ samochody będą się wtedy blokować i utrudniać sobie wzajemnie przejazd.
### Przykład
Szczególny przypadek zajmowania odpowiednich pasów ruchu to przypadek skrętu z jednego pasa, który rozwidla się na dwa lub nawet trzy pasy jak na \autoref{trzyPasyPrzyklad}.
{ height=300px }
Parametry symulacji ustawione są tak, aby pojazdy tworzyły się losowo co 1-2 sekundy. Profil kierowców ustawiony jest na szybki. Ich czas reakcji przyznawany jest według funkcji rozkładu przedstawionej na \autoref{czasReakcjiSzybki}.
### Przypadek zajmowania jednego pasa
Popularnym problemem w przypadku możliwości zajmowania wielu pasów jest to, że wszyscy kierowcy zajmują jeden pas.
Wizualizacja tego problemu dostępna jest w aplikacji frontendowej po wybraniu parametrów **correctLane** - **initial** - **1** - **replay simulation**.
Na odtworzeniu symulacji widać, że takie zachowanie bardzo szybko prowadzi do zakorkowania się elementu drogowego. Już w 81 klatce można zaobserwować całkowite zablokowanie skrętu jak na \autoref{trzyPasyPrzykladZablokowanieSkretu}.
{ height=300px }
### Przypadek zajmowania losowych pasów
O wiele korzystniejszym jest zajmowanie przez kierowców losowych pasów.
Wizualizacja tego problemu dostępna jest w aplikacji frontendowej po wybraniu parametrów **correctLane** - **random** - **1** - **replay simulation**.
Przy takich samych parametrach symulacji i zmianie zachowania kierowców na zajmowanie losowych pasów widać poprawę. Symulacja trwa 430 klatek i nie dochodzi do zablokowania. Pojazdy na bieżąco odblokowują skręt jak na \autoref{trzyPasyPrzykladOdblokowanySkret}.

# Podsumowanie
\label{PodsumowanieRozdzial}
## Osiągnięte cele i wnioski
Projekt inżynierski został przez nas zrealizowany zgodnie z początkowymi założeniami. W trakcie projektu okazało się, że wybór architektury mikroserwisów był słuszny. Umożliwiło to równoległą pracę członków zespołu nad komponentami bez obaw o problemy w synchronizacji. Projekt został wykonany w oparciu o najlepsze praktyki kultury *DevOps*, co pozwoliło na szybkie i automatyczne wdrażanie kolejnych wersji. Wszystkie komponenty projektu pozbawione są błędów, a system jako całość działa stabilnie. Infrastruktura została zaprojektowana tak, aby nawet w wypadku całkowitej utraty serwera możliwe było przywrócenie stanu mikroserwisów w kilka minut. Wewnętrzna baza danych odporna jest na błędy w aplikacji backendowej systemu, przez co po ewentualnej awarii całego systemu dane o symulacjach nigdy nie zostaną stracone. Dzięki zastosowaniu architektury mikroserwisów cała aplikacja stała się łatwo skalowalna, co umożliwia równoległe przeprowadzanie symulacji na wielu aplikacjach symulatora jednocześnie, bez ryzyka utraty danych.
Dzięki przeprowadzneniu przez nas wielu symulacji ruchu, udało się zbadać między innymi wpływ zastosowania w pełni autonomicznych pojazdów na przepustowość dróg, porównać przepustowości skrzyżowania i ronda, a także sprawdzić zjawisko *phantom traffic*.
## Możliwości dalszego rozwoju
Dalszy rozwój samego symulatora może polegać na poprawieniu *UX* budowy dróg. Mogłaby powstać do tego nawet dedykowana aplikacja. Symulator można by także rozszerzyć o obsługę sytuacji jak zmiana pasów, pojazdy uprzywilejowane, czy losowe sytuacje np. piesi przebiegający przez ulicę.
Usprawnienie, które można by wprowadzić do całej infrastruktury to przygotowanie mikroserwisów do wprowadzenia na klaster *kubernetes*. Należałoby w tym celu skorzystać z narzędzia do automatyzacji wprowadzania kontenerów na klaster jak np. *helm*.
Dodatkowo, warto rozważyć wykorzystanie narzędzi do zarządzania mikroserwisami takich jak np. Istio lub Linkerd. Pozwoli to na łatwiejsze monitorowanie i zarządzanie poszczególnymi elementami infrastruktury, a także umożliwi wprowadzenie mechanizmów takich jak service mesh, które umożliwią lepszą obsługę ruchu między poszczególnymi mikroserwisami oraz pozwolą na szybsze rozwiązywanie ewentualnych problemów z komunikacją.
Rozbudowa aplikacji backendowej na początku polegałaby na usprawnieniu komunikacji z aplikacją symulatora. Najważniejszym wyzwaniem byłoby zwiększenie przepustowości przesyłania danych o symulacjach do bazy danych, na przykład poprzez użycie kolejki *Amazon SQS* lub zoptymalizowanie struktury bazy danych. Kolejnym krokiem mogłoby być dodanie obsługi protokołu WebSocket do aplikacji backendowej, co umożliwiłoby bardziej płynną komunikację z aplikacją symulatora, zwłaszcza w przypadku dużych ilości danych lub wysokiej częstotliwości aktualizacji. Warto też rozważyć wdrożenie mechanizmów kompresji danych, takich jak gzip, aby zmniejszyć rozmiar przesyłanych danych i zwiększyć wydajność komunikacji.
Kolejnymi krokami w rozwoju aplikacji do obsługi i przetwarzania danych mogłoby być przebudowanie aplikacji backendu i backendu obliczeniowego na architekturę wykorzystującą technologie chmurowe *Microsoft Azure*, *AWS* (Amazon Web Services) lub *GCP* (Google Cloud Platform). Te platformy oferują szerokie spektrum narzędzi i usług, które mogą pomóc w usprawnieniu działania aplikacji. Na przykład, można skorzystać z usług obliczeniowych typu serverless, takich jak *AWS Lambda* lub *Azure Functions*, aby przetwarzać dane w sposób elastyczny i skalowalny. Można też użyć rozwiązań do zarządzania bazami danych, takich jak *AWS RDS* lub *GCP Cloud SQL*, aby zoptymalizować działanie bazy danych i zwiększyć jej wydajność.
Innymi opcjami są narzędzia do cache'owania danych, takie jak *AWS ElastiCache* lub *GCP Memorystore*, które pozwolą na szybszy dostęp do często używanych informacji oraz zmniejszenie obciążenia bazy danych. Można też rozważyć skorzystanie z narzędzi do monitorowania i zarządzania aplikacjami, takich jak *AWS CloudWatch* lub *GCP Stackdriver*, aby łatwiej wykrywać ewentualne problemy i szybciej je rozwiązywać.
Rozbudowa backendu obliczeniowego mogłaby polegać na dodaniu nowych typów wykresów tj. czas postoju wymuszony zakorkowaniem, czas postoju wymuszony sygnalizacją świetlną lub zasadami organizacji ruchu. Za tym szedłby również rozwój aplikacji dashboardowej.
Dobrym z perspektywy architektonicznym rozwiązaniem byłoby usunięcie dedykowanej warstwy backendowej aplikacji dashboardowej oraz w miarę możliwości przeniesienie części jej dotychczasowych zadań polegających na odfiltrowywaniu wyników symulacji na komponent **backend obliczeniowy**.
W przypadku dalszego rozwijania aplikacji dashboardowej należałoby zastanowić się nad przeportowaniem dotychczasowego rozwiązania na inne technologie, przede wszystkim z powodu ograniczeń jakie wprowadza framework *Dash*.
Niezależnie od decyzji dotyczącej portowania dotychczasowego rozwiązania do innej technologii, do narzędzia można by wprowadzić liczne urozmaicenia w postaci nowych rodzajów wykresów oraz sekcji wyświetlającej dodatkowe metadane dotyczące danej instancji symulacji.
Dalszy proces rozwoju narzędzia przekładającego treść napisaną za pomocą Markdown w dokument PDF wygenerowany z pośredniego kodu \LaTeX mógłby polegać na utworzeniu oraz samodzielnym hostowaniu forka *HackMD*. System ten mógłby zostać wyposażony w predefiniowane znaczniki dla elementów które opisane zostały w podrozdziale \ref{mieszanieLatexaIMD}.
Tak skonstruowane narzędzie mogłoby stać się ciekawą alternatywą dla pisania dokumentacji technicznych oraz prac dyplomowych, w szczególności dla studentów Politechniki Gdańskiej.
\titleformat{\chapter}[hang]{\huge\bfseries}{\thechapter.}{1em}{}
\titlespacing*{\chapter}{0pt}{-3em}{1.1\parskip}
\titlelabel{\thetitle\quad}
\linespread{1.0}
\begin{thebibliography}{9}
\bibitem{plotly}
Plotly, Inc., Plotly. \url{https://plotly.com/}. [Online; accessed 16-October-2022]
\bibitem{plotly_dash}
Plotly, Inc., Dash by Plotly. \url{https://plotly.com/dash/}. [Online; accessed 16-October-2022]
\bibitem{dash_bootstrap}
Plotly, Inc., Dash Bootstrap Components. \url{https://dash-bootstrap-components.opensource.faculty.ai/}. [Online; accessed 16-October-2022]
\bibitem{bootstrap}
Bootstrap (2021). Bootstrap. \url{https://getbootstrap.com/}. [Online; accessed 17-October-2022]
\bibitem{pandas}
Pandas Developers, pandas documentation \url{https://pandas.pydata.org/docs/}. [Online; accessed 2-January-2022]
\bibitem{numpy}
Numpy Developers, NumPy. \url{https://numpy.org/}. [Online; accessed 10-April-2022]
\bibitem{pandoc}
John MacFarlane, pandoc. \url{https://pandoc.org/}. [Online; accessed 2-January-2023]
\bibitem{docker}
Docker, Inc., Docker. \url{https://www.docker.com/}. [Online; accessed 3-March-2022]
\bibitem{apt_get}
Debian Project, apt-get. \url{https://wiki.debian.org/apt-get}. [Online; accessed 5-February-2022]
\bibitem{linux}
Linux Foundation, Linux. \url{https://www.linux.org/}. [Online; accessed 2-January-2023]
\bibitem{echo}
Free Software Foundation, Inc., Echo. \url{https://linuxcommand.org/lc3_man_pages/echoh.html}. [Online; accessed 22-January-2022]
\bibitem{github_actions}
GitHub, Inc., GitHub Actions. \url{https://github.com/features/actions}. [Online; accessed 2-January-2022]
\bibitem{makefile}
Free Software Foundation, Makefile. \url{https://www.gnu.org/software/make/manual/make.html}. [Online; accessed 8-April-2022]
\bibitem{hackmd}
HackMD, Inc., HackMD. \url{https://hackmd.io/}. [Online; accessed 17-January-2022]
\bibitem{overleaf}
Overleaf, Overleaf. \url{https://www.overleaf.com/}. [Online; accessed 3-January-2022]
\bibitem{xelatex}
Overleaf, XeLaTeX. \url{https://www.overleaf.com/learn/latex/XeLaTeX}. [Online; accessed 2-January-2023]
\bibitem{opentrafficsim}
OpenTrafficSim, OpenTrafficSim. \url{https://opentrafficsim.org/}. [Online; accessed 11-September-2022]
\bibitem{sumo}
SUMO - Simulation of Urban Mobility, SUMO - Simulation of Urban Mobility. \url{https://www.dlr.de/ts/en/desktopdefault.aspx/tabid-9883/16931_read-41000/}. [Online; accessed 12-October-2022]
\bibitem{simwalk}
SIMWALK, SIMWALK Road Traffic Simulator 2.7. \url{https://www.simwalk.com/}. [Online; accessed 16-May-2022]
\bibitem{road_traffic_simulation}
Road Traffic Simulation Software, Road Traffic Simulation Software 2.5. \url{https://www.roadtraffic-simulation.com/}. [Online; accessed 11-April-2022]
\bibitem{simteract}
Simteract, Simteract Trafic AI 2.8. \url{https://simteract.com/}. [Online; accessed 11-April-2022]
\bibitem{traffic3d}
Traffic3d, Traffic3d 2.9. \url{https://www.traffic3d.com/}. [Online; accessed 11-April-2022]
\bibitem{unity}
Unity Technologies, Unity. \url{https://unity.com/}. [Online; accessed 2-April-2022]
\bibitem{swagger}
Swagger, Swagger. \url{https://swagger.io/}. [Online; accessed 2-January-2022]
\bibitem{bezier}
Sebastian Lague. Bezier path creator. \url{https://assetstore.unity.com/packages/tools/utilities/b-zier-path-creator-136082}. [Online; accessed 12-November-2022]
\bibitem{phantom}
SotonTRG. Phantom Traffic Jams. \url{https://www.youtube.com/watch?v=Rryu85BtALM&ab_channel=SotonTRG}. [Online; accessed 11-November-2022]
\bibitem{citiesSkylines}
Antti Lehto, Damien Morello, Karoliina Korppoo. Game Design Deep Dive: Traffic Systems in Cities: Skylines (2015). \url{https://www.gamedeveloper.com/design/game-design-deep-dive-traffic-systems-in-i-cities-skylines-i-}. [Online; accessed 13-October-2022]
\bibitem{micromacromeso}
Burghout, W., Koutsopoulos, H. N., \& Andréasson, I. Hybrid Mesoscopic–Microscopic Traffic Simulation. Transportation Research Record, 1934(1), 218–225. \url{https://doi.org/10.1177/0361198105193400123}. [Online; accessed 11-August-2022]
\bibitem{csharp}
Microsoft Corporation. C\# Programming Guide. \url{https://docs.microsoftcom/en-us/dotnet/csharp/programming-guide/}. [Online; accessed 11-January-2022]
\bibitem{twi}
TWI - What is simulation? \url{https://www.twi-global.com/technical-knowledge/faqs/faq-what-is-simulation/}. [Online; accessed 06-January-2022]
\bibitem{wikipediaSimulation}
Wikipedia - Simluation \url{https://en.wikipedia.org/wiki/Simulation/}. [Online; accessed 05-January-2022]
\end{thebibliography}
\listoffigures