--- title: | | ![](sources/pg-logo.jpg) | 'City Trafic Simulator' author: | | Aleksander Błaszkiewicz | Jakub Brzozowski | Maciej Adryan | Michał Matejuk header-includes: | \usepackage{fontspec} \usepackage[english,polish]{babel} \selectlanguage{polish} \setmainfont{LiberationSans} \fontsize{10}{10} \linespread{1.3} \selectfont \usepackage{indentfirst} \setlength{\parindent}{1.25cm} \setlength{\parskip}{1pt plus2pt} \definecolor{mygreen}{rgb}{0,0.6,0} \definecolor{mygray}{rgb}{0.5,0.5,0.5} \definecolor{mymauve}{rgb}{0.58,0,0.82} \selectlanguage{polish} \lstset{ % backgroundcolor=\color{white}, % choose the background color basicstyle=\footnotesize, % size of fonts used for the code breaklines=true, % automatic line breaking only at whitespace captionpos=b, % sets the caption-position to bottom commentstyle=\color{mygreen}, % comment style escapeinside={\%*}{*)}, % if you want to add LaTeX within your code keywordstyle=\color{blue}, % keyword style stringstyle=\color{mymauve}, % string literal style } papersize: a4 ... \selectlanguage{polish} \begin{otherlanguage}{polish} \begin{abstract} Celem pracy było projekt i realizacja prototypowego symulatora ruchu miejskiego obejmującego aspekty, jak: - wpływ zachowań kierowców na zakorkowanie miasta, - optymalny dobór infrastruktury drogowej. Projekt składa się z 4 podsystemów: - symulator - aplikacja wykonana w technologii Unity odpowiedzialna za zbudowanie mapy i przeprowadzenie symulacji zgodnie z wprowadzonymi parametrami. Pozwala na pełną dowolność w budowaniu infrastruktury, zapewniając nie tylko różne gotowe elementy, ale także możliwość tworzenia własnym. Zawiera warstwę logiczną, która pozwala kierowcom respektować zasady ruchu drogowego jak dozwolona prędkość, światła drogowe, skrzyżowania ze światłami, skrzyżowania z pierwszeństwem, skrzyżowania z warunkowym skrętem oraz ronda, - backend - aplikacja wykonana w technologii .NET z wykorzystaniem bazy danych NoSQL (MongoDB). Odpowiedzialna za komunikację z pozostałymi podsystemami, zapisuje dane dotyczące przeprowadzanych symulacji, uruchamia backend obliczeniowy, dostarcza gotowych danych statystycznych do aplikacji frontendowej, - backend obliczeniowy - aplikacja wykonana w Pythonie z wykorzystaniem frameworka Flask. Służy do tworzenia statystyk dotyczących przebiegu symulacji oraz udostępniania API do inicjowania tych statystyk. Statystyki są tworzone dla każdego uruchomienia symulacji na podstawie danych otrzymanych z aplikacji backendowej, - frontend - aplikacja wykonana za pomocą pythonowego frameworka Dash by Plotly. Służy do wizualizacji otrzymanych wyników oraz, w razie potrzeby, przetwarzania tych danych. Celem tego podsystemu jest uproszczenie analizy wyników oraz umożliwienie użytkownikowi odtworzenia przebiegu symulacji. Wszystkie podsystemy są ze sobą ściśle powiązane i komunikują się ze sobą warstwą sieciową. Wysokopoziomowym zamysłem jest umożliwienie użytkownikowi frontendowego serwisu przeglądania wyników wygenerowanych przez symulator i obliczonych przez backend obliczeniowy, do których ma dostęp poprzez backend. Aleksander Błaszkiewicz był odpowiedzialny za wykonanie symulatora w technologii Unity. Jest autorem rozdziałów todo i współautorem rozdziałów todo \end{abstract} \end{otherlanguage} \begin{otherlanguage}{english} \begin{abstract} The aim of the work is to investigate the impact of driver behavior and road infrastructure on city congestion. The project consists of 4 subsystems: - simulator - an application made in Unity technology responsible for building the map and conducting simulations according to the introduced parameters. It allows full freedom in building infrastructure, providing not only different ready-made elements, but also the ability to create your own. It contains a logical layer that allows drivers to respect traffic rules such as allowed speed, traffic lights, intersections with traffic lights, intersections with priority, intersections with conditional turns, and roundabouts, - backend - an application made in .NET technology using NoSQL database (MongoDB). Responsible for communication with the remaining subsystems, saves data on conducted simulations, starts the computational backend, provides ready statistical data to the frontend application, - computational backend - an application made in Python using the Flask framework. It is used to create statistics on the course of simulations and to provide an API to initiate these statistics. Statistics are created for each simulation run based on data received from the backend application, - frontend - an application made using the Python Dash by Plotly framework. It is used to visualize the obtained results and, if necessary, process these data. The goal of this subsystem is to simplify the analysis of results and enable the user to reproduce the course of the simulation. All subsystems are closely related and communicate with each other through the network layer. The high-level idea is to enable the user of the frontend service to browse the results generated by the simulator and calculated by the computational backend, to which they have access through the backend. \end{abstract} \end{otherlanguage} \selectlanguage{polish} # Wstęp i cel pracy (Wszyscy) <!-- todo formalizacja jak sie widzi takie sformulowanie to sie odechciewa czytac podobno XD, to nie jest rozprawka w gimanzjum --> Większość ludzi w miastach porusza się pojazdami, które korzystają z infrastruktury drogowej. Jednym z największych problemów użytkowników sieci drogowych są zatory. W ogólności, im więcej pojazdów zostanie wpuszczonych do miejskiej sieci, tym dłużej zajmie pokonanie danej trasy. Aby zminimalizować skutki rosnącej liczby pojazdów na drogach, ważne jest odpowiednie dobranie elementów infrastruktury, ale także oraz poprawne zachowanie kierowców. Dużą rolę odgrywają miejskie zespoły planowania ruchu - specjalne jednostki odpowiedzialne za zarządzanie i optymalizację ruchu drogowego w miastach. Są one zaangażowane w monitorowanie ruchu drogowego, wprowadzanie rozwiązań mających na celu poprawę efektywności ruchu oraz współpracę z kierowcami i użytkownikami drogi w celu promowania zrównoważonych zachowań. Zespoły te korzystają z różnych metod optymalizacji ruchu drogowego, w tym analizy danych ruchu, modeli matematycznych i symulacji komputerowych. Dzięki temu mogą one przeprowadzać testy różnych scenariuszy i zmian w infrastrukturze drogowej, aby zidentyfikować te, które prowadzą do najlepszych wyników. Projekt ma na celu zbadać, w jakim stopniu wybrane czynniki wpływają na powstawanie zatorów reprezentując przepływ samochodów przez wybrane odcinki w postaci wykresów i obliczając wybrane statystyki. Kluczowymi zagadnieniami, które warto zbadać w ramach tej pracy inżynierskiej są: - rola autonomicznych pojazdów w usprawnieniu ruchu w sieci miejskiej (przypadek braku czasu reakcji), - zjawisko tworzenia się sztucznych zatorów na drogach przez kierowców o wysokim czasie reakcji, - czynniki determinujące wymianę tradycyjnego skrzyżowania na rondo, - wpływ harmonogramu świateł na przepustowość skrzyżowań. # Słownik pojęć ## Pojęcia używane w pracy (Michał Matejuk) W poniższej pracy inżynierskiej używanych jest wiele "skrótowych" pojęć, których dokładne definicje brzmią: - frontend - część systemu informatycznego odpowiedzialna za wyświetlanie i interakcję z użytkownikiem (UI), zazwyczaj za pomocą przeglądarki internetowej, - backend - część systemu informatycznego odpowiedzialna za logikę biznesową i przetwarzanie danych, zazwyczaj uruchamiana na serwerze, - CI/CD (Continuous Integration/Continuous Deployment) - metodologia tworzenia oprogramowania, polegająca na ciągłym integrowaniu i wdrażaniu zmian do aplikacji, - krzywa Beziera - matematyczny sposób reprezentowania krzywych w grafice komputerowej, służący do tworzenia wielokątów i splajnów, - Github - platforma internetowa, na której użytkownicy mogą przechowywać i współdzielić swój kod. GitHub umożliwia współpracę nad projektami oraz ich wersjonowanie, dzięki czemu możliwe jest śledzenie zmian w kodzie oraz powrót do poprzednich wersji, - Github Actions - narzędzie dostępne na platformie GitHub, umożliwiające automatyzację procesów związanych z tworzeniem, testowaniem i wydawaniem oprogramowania. Dzięki niemu można zdefiniować sekwencję działań, które zostaną wykonane w odpowiedzi na określone zdarzenia (np. push do repozytorium) i uruchamiać je w chmurze. GitHub Actions pozwala na stworzenie spójnego i zautomatyzowanego procesu tworzenia i wydawania oprogramowania. Inne znane narzędzia tego typu to np. Azure DevOps lub Jenkins, - metadane - informacje o innych danych. Są one zazwyczaj dodawane do plików lub dokumentów, aby opisać ich zawartość lub ułatwić ich wyszukiwanie. Metadane w projekcie opisują między innymi tagi/typy pojazdów lub numer uruchomienia symulacji, - caching - proces przechowywania danych w celu ich szybszego dostępu w przyszłości. W przypadku stron internetowych caching polega na zapisywaniu kopii stron lub poszczególnych elementów na lokalnym komputerze użytkownika, dzięki czemu przy kolejnym odwiedzeniu tej samej strony część danych jest pobierana z lokalnej pamięci, co znacznie przyspiesza jej wczytanie, - API - inaczej Application Programming Interface, to interfejs programistyczny, który umożliwia komunikację pomiędzy różnymi aplikacjami lub systemami. API definiuje sposób, w jaki aplikacje mogą wymieniać dane i wykonywać określone operacje. Dzięki API możliwe jest wykorzystywanie funkcjonalności innych aplikacji bez konieczności zgłębiania ich wewnętrznej struktury, - REST - czyli REpresentational State Transfer, to architektura sieci Web, która definiuje zasady tworzenia i wymiany danych w sieci. REST opiera się na wykorzystaniu protokołu HTTP do przekazywania danych w postaci dokumentów, takich jak XML lub JSON. REST jest często wykorzystywany do tworzenia API, ponieważ umożliwia proste i elastyczne wymianę danych pomiędzy różnymi aplikacjami, Zasady REST API to: 1. Jednolity interfejs - REST API korzysta z jednego, uniwersalnego interfejsu, który umożliwia komunikację między różnymi aplikacjami i systemami. 2. Rozdzielenie klient-serwer - w architekturze REST klient i serwer są od siebie niezależne i działają na zasadzie rozdzielenia odpowiedzialności. Klient odpowiada za wysyłanie żądań do serwera, a serwer odpowiada na te żądania przetwarzając dane i zwracając odpowiedzi. 3. Bezstanowość - serwer w architekturze REST nie przechowuje stanu sesji klienta. Oznacza to, że każde żądanie jest traktowane niezależnie, bez względu na poprzednie żądania wysłane przez danego klienta. 4. Buforowalność - REST API umożliwia buforowanie odpowiedzi, co oznacza, że serwer może zwracać te same odpowiedzi na powtarzające się żądania, co pozwala na zwiększenie wydajności i szybkości działania. 5. Warstwowa architektura systemu - REST API jest zbudowane na zasadzie warstw, co oznacza, że poszczególne elementy systemu są od siebie niezależne i mogą być rozwijane i zmieniane niezależnie od pozostałych części systemu. - Docker - narzędzie, które umożliwia tworzenie, uruchamianie i zarządzanie kontenerami w dowolnym systemie operacyjnym. Kontenery to lekkie i izolowane od siebie środowiska, które zawierają wszystko, co jest potrzebne do uruchomienia aplikacji, w tym kod, zależności i konfiguracje. Dzięki temu, że kontenery są lekkie i izolowane, można je łatwo przenosić i uruchamiać na różnych systemach operacyjnych bez konieczności modyfikowania kodu ani konfiguracji. Docker umożliwia też tworzenie i wdrażanie aplikacji w postaci kontenerów, co ułatwia zarządzanie nimi i wdrażanie na różnych środowiskach, - YAML - rekurencyjny akronim "YAML Ain't Markup Language" - język opisowy, który służy do zapisywania danych w formie tekstowej. Jest on bardzo prosty, dzięki czemu łatwo można go wykorzystać do konfiguracji różnych aplikacji i systemów. YAML opiera się na indeksowaniu danych za pomocą kluczy i wartości, a także na strukturze danych za pomocą odpowiednich znaków takich jak dwukropek, kreska i wcięcia. Dzięki temu dane zapisane w YAML są bardzo czytelne nawet dla osób bez doświadczenia w programowaniu. # Symulacja ruchu drogowego ## Definicja symulacji (Aleksander Błaszkiewicz) *Symulacja* to przybliżone odtwarzanie zjawisk czy zachowań pewnego obiektu i środowiska, w którym się ten obiekt znajduje za pomocą matematycznego modelu. Model to próba przybliżenia rzeczywistych zachowań obiektu przy jednoczesnym ograniczeniu jego złożoności. > "*All models are wrong but some are useful" - George Box* Symulatorem nazywamy program, który pozwala przeprowadzić symulację. Taki program charakteryzuje się tym, że jest przystosowany do danego typu symulacji. Przykładowo w przypadku symulatorów ruchu miejskiego będą to dedykowane widoki do edycji parametrów pojazdów, układu ulic czy harmonogramu świateł. Cele tworzenia symulatorów: - zbadanie, czy dany system działa z optymalną wydajnością, - trenowanie pracowników, - rozrywka (gry wideo), - przeprowadzenie symulacji w warunkach niemożliwych do odwzorowania w aktualnych warunkach na Ziemii (np. lądowanie na innej planecie), - przeprowadzenie symulacji w warunkach na tyle niebezpiecznych, że stwarzałyby zagrożenie dla ludzi. Problemy tworzenia symulacji: - są one zwykle bardzo złożonymi systemami, które wymagają dużej ilości danych wejściowych i wyjściowych, a także precyzyjnego modelowania różnych procesów i zjawisk. Dlatego tworzenie symulatorów może być czasochłonne i kosztowne, - dokładność symulacji (niekiedy odwzorowywane modele muszą być bardzo dokładne, aby zapewnić wiarygodne wyniki), - trudność w doborze właściwych parametrów, - ograniczenia wydajnościowe programu symulującego, - ograniczenia czasowe (symulacja długotrwałych procesów może być czasochłonna), - ryzyko błędów w implementacji. ## Typy sumulacji ### Próbkowanie (Aleksander Błaszkiewicz) Symulacje można podzielić na dwa główne typy: - *dyskretne* - zdarzenia dzieją się w określonych punktach czasowych, zmieniając stan symulowanego obiektu. Liczba "punktów" czasu w trakcie symulacji jest skończona, - *dynamiczne* - stan zmienia się w sposób ciągły - taka symulacja zamodelowana jest przy pomocy równań różniczkowych. Trzeba przy tym pamiętać, że komputery z powodu swojej cyfrowej natury nie mogą przeprowadzać rzeczywistych ciągłych symulacji. Bardzo często spotykanym zabiegiem jest ustalenie na tyle małej delty czasu, aby symulacja dawała wrażenie ciągłej (ang. near real-time). Zabieg dzielenia czasu na coraz mniejsze odcinki ma na celu zwiększenie realizmu symulacji. ### Rozmiar (Maciej Adryan) Ze względu na wymagania oraz możliwości przeprowadzenia symulacji, naturalnie wyodrębniły się trzy najpopularniejsze rodzaje symulacji ruchu drogowego: - mikrosymulacje (ang. micro simulations) - pozwalają uzyskać najwyższy poziom detali oraz szczegółowość, - symulują poszczególne agenty biorące udział w ruchu drogowym. - mesosymulacje (ang. meso simulations) - pozwalają uzyskać niższy poziom detali w porównaniu do mikrosymulacji, - są szybsze, mniej czasochłonne i zasobochłonne, - ze względu na mniejszą złożoność ułatwiają symulowanie większych i bardziej zaawansowanych scenariuszy w porównaniu do mikrosymulacji. - symulacje hybrydowa (ang. hybrid simulations) - pozwalają na dobór poziomu detali w taki sposób, aby wybrane komponenty były symulowane w sposób dokładny lub mniej dokładny w zależności od potrzeb użytkownika. ## Symulatory ruchu miejskiego w grach (Aleksander Błaszkiewicz) Popularnym zastosowaniem symulatorów ruchu miejskiego są gry. Logika symulacji pozwala na zintegrowanie elementów infrastruktury drogowej z resztą gry. Dzięki symulacji budowanie dróg nie jest tylko prostym zadaniem, a wymaga przemyślenia, w jaki sposób funkcjonuje miasto. Jednym z przykładów gier opierających się na symulowaniu ruchu drogowego jest gra **Cities: Skylines**. W grze tej gracz wciela się w projektanta miasta. Jego zadaniem jest zaprojektowanie sieci drogowej w taki sposób, żeby sztuczni agenci mogli sprawnie pokonywać miasto i żeby nie powstawały korki.\ref{CitiesSkylines} ![Gra "Cities: Skylines" \label{CitiesSkylines}](https://i.imgur.com/1MhjbpN.png) Kolejnym przykładem gry operającej się na podobnej mechanice jest gra **Mini Metro**. W grze tej gracz staje się planistą sieci metra, którego zadaniem jest optymalizacja tej sieci.\ref{MiniMetro} ![Gra"MiniMetro" \label{MiniMetro}](https://i.imgur.com/FRFGTNX.png) Innym przykładem z rodziny gier **Mini** jest **Mini Motorways** .Zasada działania jest taka sama jak w przypadku poprzedniej gry, jednak tutaj zamiast sieci metra gracz planuje sieć drogową aut.\ref{MiniMotorways} ![Gra "Mini Motorways" \label{MiniMotorways}](https://i.imgur.com/R64s5zi.png) Ostatni przykład to gra **Freeways**. Tutaj gracz ma się skupić bardziej na optymalizacji jednego skrzyżowania, niż na całej sieci drogowej.\ref{Freeways} ![Gra "Freeways" \label{Freeways}](https://cdn.cloudflare.steamstatic.com/steam/apps/780210/ss_6932c9f368ea31cf1d3baa33361b3266feb5f58a.1920x1080.jpg?t=1572994131) ## Zastosowanie symulatorów do planowania ruchu miejskiego (Maciej) Oprócz zastosowania w dziedzinie rozrywki, symulatory znajdują szerokie zastosowanie praktyce. W zależności od stopnia zaawansowania pozwalają one na mniej lub bardziej skomplikowane i szczegółowe symulowanie, a za czym idzie, badanie oraz dalszą analizę ruchu drogowego której głównym celem mogą być m.in: - planowanie sieci ulic, - planowanie rozmieszczenia skrzyżowań oraz dobrania prawidłowego typu, spośród skrzyżowań: - o ruchu okrężnym (ronda), - o ruchu kierowanym, - bezkolizyjne, - równorzędne, - nierównorzędne, - w kształcie litery "Y" (rozwidlenia), - w kształcie litery "T", - w kształcie litery "X". - planowanie rozmieszczenia sygnalizacji świetlnej, - planowanie rozmieszczenia znaków drogowych, - planowanie odpowiednich dla infrastruktury oraz architektury ograniczeń prędkości, - unikanie zastojów (ang. deadlocks), - planowanie architektury mającej zapewnić pieszym bezpieczeńtwo - rozplanowanie przejść dla pieszych. - badanie wpływu tymczasowego bądź stałego zamknięcia danej ulicy (np. celem jej modernizacji) i wynikających z niej potencjalnych problemów w ruchu miejskim, - skutki zwiększenia/zmniejszenia ilości pasów ruchu, - integracja oraz planowanie ruchu komunikacji miejskiej, np. - autobusów, - tramwaji, - trolejbusów. - integracja oraz planowanie architektury komunikacji miejskiej, np. - przystanki autobusowe, - przystanki tramwajowe, - przystanki trolejbusowe, - przejazdy kolejowe. - optymalizacja i skoordynowanie czasu trwania sygnalizacji świetlnej na wybranym odcinku drogi lub w danym sąsiedztwie, - skutki integracji nowych punktów zainteresowania na mapie miejskiej, takich jak: - galerie handlowe, - centra rozrywki, - węzły komunikacyjne (dworce autobusowe, dworce kolejowe, lotniska), - parkingi, - osiedla mieszkaniowe, - badanie spodziewanej emisji spalin towarzyszącej wyżej wymienionym zdarzeniom. ## Wybrane rozwiązania dostępne na rynku * [Road Traffic Simulation Software \ref{RTSS}](https://www.anylogic.com/road-traffic/) Zaawansowane narzędzie komercyjne, skupiające się na: - planowaniu sieci dróg i autostrad, - ocenianiu przepływu oraz tendencji do korkowania się ulic, - planowaniu harmonogramów sygnalizacji świetlnej, - integrowaniu budynków użyteczności publicznej z siecią drogową. ![Symulator "Road Traffic Simulation Software" \label{RTSS}](https://i.imgur.com/u7KjrbR.png) * [SUMO (Simulation of Urban MObility \ref{SUMO})](https://www.eclipse.org/sumo/) Otwartoźródłowe narzędzie na licencji Eclipse Public License Version 2 służące do przeprowadzania mikrosymulacji na dużą skalę. ![Symulator "SUMO" \label{SUMO}](https://i.imgur.com/O7NwlRJ.png) * [OpenTrafficSim](https://opentrafficsim.org/manual/) Otwartoźródłowe narzędzie na licencjach BSD, Apache oraz MIT służące do przeprowadzania mikro- oraz makrosymulacji będące owocem pracy grupy pracowników Uniwersytetu Technicznego w Delfcie. * [SIMWALK Road Traffic Simulator \ref{SIMWALK}](https://www.simwalk.com/modules/simwalk_roadtraffic.html) Narzędzie komercyjne dedykowane do badania interakcji ruchu pieszego i samochodowego o małym i średnim natężeniu np. badanie poszczególnych skrzyżowań, małych obszarów miejskich okolic węzłów komunikacyjnych. ![Symulator "SIMWALK Road Traffic Simulator" \label{SIMWALK}](https://i.imgur.com/4hftBuS.jpg) * [SIMTERACT TRAFFIC AI \ref{SIMTERACT}](https://simteract.com/projects/traffic-ai/) Biblioteka, dostępna komercyjnie, którą można zintegrować z symulatorem bądź grą wideo. Została wykorzystana do szkolenia inteligentnych algorytmów służących do kierowania prawdziwymi, autonomicznymi pojazdami. Biblioteka pozwala na: - wprowadzenie dynamicznych zachowań kierowców: - zmienianie pasów, - wyprzedzanie, - omijanie kolizji. - konfigurowanie liczności pojazdów, - występowania specjalnych stref wpływających na zachowania kierowców, - konfigurowanie scenariuszy zachowań, - tworzenie opartego o siatkę systemu dróg. ![Symulator "SIMTERACT TRAFFIC AI" \label{SIMTERACT}](https://i.imgur.com/Rcb8U6b.png) * [Traffic3D \ref{Traffic3D}](https://traffic3d.org/) Otwartoźródłowe narzędzie na licencji Mozilla Public License Version 2.0.. Narzędzie jest wyposażone w komponenty ułatwiające mimikowanie istniejących dróg za pomocą projektu OpenStreetMap. Narzędzie korzysta również z wyżej wspomnianego narzędzia [SUMO (Simulation of Urban MObility)](https://www.eclipse.org/sumo/). ![Symulator "Traffic3D" \label{Traffic3D}](https://i.imgur.com/dGtspoh.png) # Projekt systemu ## Cel i przeznaczenie systemu (Wszyscy) ### Założenie i cel tworzenia aplikacji Aplikacja nastawiona jest na optymalizację infrastruktury drogowej i zachowania kierowców. Chcemy, aby jak najwięcej parametrów było modyfikowalnych, a dodatkowo, żeby odbywało się to w przemyślany sposób (na przykład nie podawanie średniego czasu reakcji kierowców, tylko funkcji rozkładu tych czasów). Zależy nam także na bardzo szczegółowym tworzeniu map (wielopasowe skrzyżowania z warunkowymi pasami skrętu). Ważne jest również, aby aplikacja umożliwiała tworzenie bardzo szczegółowych map skrzyżowań. Końcowy użytkownik aplikacji nie musi mieć wiedzy technicznej, aby skorzystać z jej funkcjonalności - wyniki będą przedstawione w prosty i czytelny sposób w aplikacji frontendowej. ### Ogólna charakterystyka systemu Aplikacja przeprowadza symulację ruchu na mapie przygotowanej przez użytkownika. Nieprzerwanie komunikuje się z mikroserwisem backendowym, wysyłając informacje o dobranych parametrach oraz samych samochodach (pozycje, odległość od najbliższego samochodu, odległość do najbliższego skrzyżowania itp.). Po zakończeniu symulacji mikroserwis backendu obliczeniowego odbiera te dane, przetwarza je (wylicza interesujące wskaźniki, generuje odpowiednie heatmapy) i oddaje je aplikacji backendowej, która umieszcza te wyniki w bazie danych. Finalnie użytkownik może przejrzeć podsumowanie symulacji w aplikacji frontendowej. ### Ograniczenia dotyczące projektu aplikacji W symulacji nie ma losowych zdarzeń takich jak: - wypadki, - kierowcy zajmujący złe pasy ruchu, - piesi, - pojazdy uprzywilejowane. ### Środowisko pracy systemu Backend oraz backend obliczeniowy to skonteneryzowane aplikacje działające na serwerze linuxowym. Aplikacja symulatora to projekt Unity zbudowany pod system operacyjny Windows. Frontend natomiast może być włączony na każdym urządzeniu posiadającym nowoczesną przeglądarkę internetową. ## Specyfikacja wymagań funkcjonalnych Specyfikacja zawiera ogólne wymagania użytkowe dotyczące aplikacji symulatora ruchu drogowego, a także szczegółowe wymagania dotyczące symulatora, backendu, backendu obliczeniowego oraz frontendu. ### Ogólne wymagania użytkowe - użytkownik może przeprowadzić symulację ruchu drogowego na okrojonym fragmencie miasta, - dane dotyczące map i wyników symulacji zapisywane są w bazie danych, - użytkownik może zobaczyć wyniki symulacji. Może w szczególności przeglądać różne wyniki symulacji dla różnych zestawów parametrów dla tej samej mapy. ### Wymaganie względem symulatora - auta potrafią poruszać się po siatce ulic i skrzyżowań, - auta respektują zasady ruchu drogowego (światła, skrzyżowania z pierwszeństwem, ronda), - symulatorowi można podawać zestawy parametrów, które wpływają na wynik symulacji (zachowania kierowców, czasy świateł), - można tworzyć i zapisywać mapy. ### Wymagania względem backendu - serwis musi w czasie rzeczywistym zapisywać dane o przeprowadzanej przez aplikację Unity symulację, - serwis nie może pozwolić sobie na utratę informacji od aplikacji Unity, bo skutkować to będzie niedokładnymi późniejszymi wynikami aplikacji obliczeniowej, - serwis powinien pozwalać na jak najszybszy odczyt nowych danych z symulacji, w celu sprawnego przeprowadzania obliczeń, - serwis na żądanie aplikacji frontendowej powinien udostępniać dane o wybranej symulacji - statystyki, heatmapy, strukturę dróg. ### Wymagania względem backendu obliczeniowego - serwis musi udostępniać API dla backendu do inicjowania tworzenia statystyk, - serwis musi być odporny na złe dane, - serwis musi tworzyć statystyki i zapisywać je do bazy danych, - serwis musi tworzyć statystyki takie jak: - średnia prędkość wszystkich pojazdów, - heatmapa pozycji, - heatmapa prędkości, - heatmapa przyśpieszeń. ### Wymagania względem frontendu - serwis ma prezentować przekazane mu przez backend wyniki dotyczące przeprowadzonej symulacji w schludny oraz czytelny sposób, - rodzaj danych które będą w danym momencie wyświetlane będzie konfigurowalny przez użytkownika serwisu frontendowego, - interfejs aplikacji będzie responsywny i zmieniał wyświetlane dane w zależności od ww. konfiguracji, - serwis ma być odporny na złe dane, - nawigacja po serwisie odbywać się będzie w intuicyjny sposób. ## Specyfikacja wymagań użytkowych ### Wymagania jakościowe - interfejs serwisu frontendowego oraz symulacji jest schludny, przejrzysty i naturalny w obsłudze (nie wymaga szkolenia ani poradnika, żeby go obsłużyć), - użytkownik nieposiadający wiedzy z zakresu programowania jest w stanie stworzyć nowe mapy do symulacji. ### Wymagania wydajnościowe - serwis frontendowy działa poprawnie na każdym urządzeniu z nowoczesną przeglądarką, - symulacja działa w minimum 60 klatkach na sekundę w rozdzielczości 1080p na komputerze z systemem operacyjnym Windows z procesorem Ryzen 5 3600 lub lepszym i z 8Gb pamięci RAM lub więcej. # Charakterystyka i implementacja systemu ## Infrastruktura (Aleksander) ### Przepływ danych między podsystemami Poniżej przedstawiony jest uproszczony model przepływu danych pomiędzy podsystemami.\ref{ModelPrzeplywu} ![Model przepływu danych pomiędzy systemami \label{ModelPrzeplywu} ](https://i.imgur.com/bdUJIC6.png) **Użytkownik włącza symulację (prostokąt Symulator)** 1. Niebieski - w trakcie trwania symulacji Unity wysyła do backendu zserializowane informacje o mapie, samochodach oraz ustawieniach co stały interwał. 2. Zielony - po zakończeniu symulacji Unity wysyła informację do backendu obliczeniowego, że wszystkie dane symulacji zostały już załadowane i może zacząć obliczenia **Użytkownik włącza aplikację frontendową (prostokąt Frontend)** 1. Czerwone - użytkownik pobiera od backendu obliczone dane symulacji ### Dedykowany serwer #### Opis Jako platformę, na której uruchomione są wszystkie mikroserwisy wybraliśmy serwis z maszynami dedykowanymi mikr.us. Ograniczenia serwera to współdzielony procesor, 768Mb pamięci RAM oraz tylko jeden port ale to w zupełności wystarcza na potrzeby projektu. #### Domeny Mikr.us oferuje darmowe subdomeny z sufixem ".bieda.it" \ref{domeny}. Można je zdobyć z poziomu panela serwera. ![Pzykładowe darmowe domeny z sufixem ".bieda.it" \label{domeny}](https://i.imgur.com/TCCz2Wx.png) Wszystkie darmowe domeny domyślnie kierują na port wewnętrzny 80 serwera. #### Wystawienie usług na świat z jednym portem Nasza architektura zakłada istnienie 3 mikroserwisów. Chcieliśmy, aby do wszystkich z nich był dostęp z poziomu sieci internet, jednak ograniczeniem jest wcześniej wspomniany jeden port. Rozwiązaniem problemu jest usługa typu reverse-proxy zawierające mapę połączeń domena-port_wewnętrzny. Przy obsługiwaniu żądania przez serwer, reverse-proxy najpierw sprawdza oryginalną domenę i na podstawie tego parametru kieruje żądanie do odpowiedniego mikroserwisu.\ref{ReverseProxy} ![Diagram ilustrujący Reverse Proxy \label{ReverseProxy}](https://i.imgur.com/qL3IXTz.png) ### Konteneryzacja Wszystkie mikroserwisy oprócz symulatora są skonteneryzowane, a jako narzędzie konteneryzacji wybraliśmy Docker. Serwer mikr.us sam w sobie nie jest dedykowaną maszyną, tylko kontenerem Proxmox, dlatego wybór Dockera nie był przypadkowy - jest to jedyne narzędzie które działa na serwerze nie wymagając przy tym dodatkowej konfiguracji. ### Automatyczny deployment Aby nasz projekt był zgodny z kulturą DevOps, wszystkie mikroserwisy oprócz symulatora mają zaimplementowane akcje, które automatycznie konteneryzują aplikację, wysyłają ją do repozytorium Dockerhub, a następnie łączą się z naszym serwerem i włączają zaktualizowaną wersję.\ref{GHActions} Byliśmy to w stanie osiągnąć dzięki [Github Actions](https://github.com/features/actions). Dodatku do serwisu Github, który oferuje nieograniczony i darmowy dostęp do maszyn Ubuntu dla projektów open-source. ![Diagram potoku CD \label{GHActions}](https://i.imgur.com/0FOnFzs.png) ## Symulator (Aleksander) ### Pojazd #### Wstęp Naturalne jest, że w przypadku takiej natury projektu symulowany obiekt powinien być obiektem typu **Rigidbody**. Silnik fizyki Unity jest znacznie bardziej zaawansowany niż to, czego wymagamy w tym symulatorze, a co za tym idzie, wymaga znacznie więcej zasobów. Po dokładnym przyjrzeniu się wymaganiom projektu, doszliśmy do wniosku, że silnik fizyki w ogóle nie jest potrzebny. Ruch pojazdów można zasymulować równaniami matematycznymi. Poza tym każdy pojazd przestrzega zasad ruchu drogowego, dlatego nie ma potrzeby sprawdzania kolizji pomiędzy pojazdami. #### Model Pojazd składa się z podstawowych brył brył. Zdecydowaliśmy się na uproszczony model aby nie rozpraszać się rzeczami, które nie mają wpływu na zachowanie w trakcie symulacji, ale także ze względu na wydajność - prosty model zajmuje o wiele mniej czasu, żeby go wyrenderować.\ref{Pojazd} ![Pojazd \label{Pojazd}](https://i.imgur.com/FVcHm5P.png) #### Czujnik odległości Pojazd musi być świadomy tego, czy może się poruszać. W tym celu ma na sobie klasę **ProximitySensor**. Czujnik odległości odpowiada nie tylko za wykrywanie odległości do najbliższego obiektu, ale także za: - wykrywanie odległości do najbliższego obiektu oraz jego identyfikacja (czy to inny samochód, czy to **Collider** ograniczający poruszanie się na skrzyżowaniach -na przykład skręt kolizyjny, warunkowy lub światła drogowe), - przekazywanie maszynie stanów informacji na temat typu wykrytego obiektu, #### Silnik Jako że pojazd nie jest **Rigidbody** jak zostało opisane we wstępie, to musi posiadać jednostkę sterującą jego zachowaniem natury fizycznej. Posiada więc klasę **Engine**, która odpowiada za jego poruszanie się. Klasa **Engine** udostępnia metodę **SetCanMove()**, która informuje silnik, czy samochód ma przyspieszać czy zwalniać. Aby zachowanie samochodu jak najlepiej odwzorowywało rzeczywistość, jego siła hamowania wyrażona jest w funkcji dystansu do obiektu, przed którym ma zachamować. Pozwala na to zmienna Unity typu **Curve**.\ref{krzywaHamowania} ![Krzywa siły hamowania \label{krzywaHamowania}](https://i.imgur.com/WZYeiXw.png) Na podstawie zmiennej **canMove** silnik przykłada do samochodu przyspieszenie oraz odpowiedzialny jest za nałożenie prędkości na trasę samochodu, którą pobiera z klasy **PathPlanner** #### PathPlanner Aby pojazd wiedział, po jakich drogach ma się poruszać, ma na sobie klasę **PathPlanner**. Odpowiedzialna jest ona za pobranie z danych elementów infrastruktury drogowej konkretnych dróg, którymi będzie poruszał się pojazd. Zasada działania dróg wytłumaczona jest w dziale **Drogi**. #### Maszyna stanów Pojazd ma na sobie maszynę stanów, dzięki czemu łatwiej zorganizować kod odpowiedzialny za poruszanie się pojazdu, a także łatwo go zserializować. Maszyna stanów wspiera także propagację stanów, czyli na przykład w sytuacji w której pierwszy pojazd zatrzymał się, ponieważ ma czerwone światło, drugi pojazd stojący za nim również ustawi taki stan (mimo że bezpośrednim powodem zatrzymania się, nie jest czerwone świało, tylko pojazd, który zatrzymał się przed nim) #### Parametry ### Drogi #### Tworzenie dróg Drogi reprezentowane są za pomocą krzywych beziera. Każdy pas na jezdni to oddzielna krzywa beziera. Skorzystaliśmy z gotowego narzędzia Sebastiana Lague[^sebastian_krzywe] [^sebastian_krzywe]: Link [Sebastian lague - paczka do krzywych beziera](https://assetstore.unity.com/packages/tools/utilities/b-zier-path-creator-136082) Narzędzie oprócz oczywistego, czym jest modelowanie dróg za pomocą krzywych beziera, ma inne przydatne funkcje - znalezienie najbliższego punktu na krzywej na podstawie podanej pozycji na scenie - znalezienie punktu na krzywej na podstawie przebytego dystansu - wyznaczenie wektora normalnego w dowolnym punkcie krzywej (żeby samochód był zwrócony wzdłuż krzywej) - stworzenie modelu drogi ![Narzędzie do edytowania krzywych beziera](https://i.imgur.com/JJwuf4Z.png) Na potrzeby uproszczenia symulacji wszystkie drogi reprezentowane są na płaszczyźnie XZ (rzut z góry) #### Podstawowa droga Aby zapewnić generyczność systemu dróg, wyabstraktowaliśmy element, który nazwaliśmy **IRoadElement**. Instancją **IRoadElement** jest zbiór poddróg, które pozwalają przejechać przez dany element sieci dróg. Charakteryzuje się tym, że posiada: - wejścia - wyjścia - poddrogi łączące wejścia z wyjściami Szczególnym przypadkiem **IRoadElement** jest zwykły odcinek drogi, która ma jedno wejście, jedno wyjście i nie zawiera poddróg \ref{prostyIRoadElement}. ![Najprostszy IRoadElement \label{prostyIRoadElement}](https://i.imgur.com/e8NL2Kd.png) **IRoadElement** jest świadomy tego, w jaki sposób należy po nim przejechać, znając wejście i wyjście. Ta teoria zostanie wytłumaczona lepiej w podrozdziale **Skrzyżowanie** #### Skrzyżowanie Skrzyżowanie to typ **IRoadElement**, który zawiera w sobie poddrogi służące do przejechania po danym elemencie ![](https://i.imgur.com/uP79DqM.png) Zakładając, że trasa agenta składa się z drogi 1, skrzyżowania, a następnie drogi 2, to jeżeli taki agent poda skrzyżowaniu (**IRoadElement**) takie dane, to skrzyżowanie odpowie mu, że aby przez nie przejechać w takiej konfiguracji, należy to zrobić drogą numer 3 W ramach debugu i lepszego zrozumienia, w jaki sposób zachowuje się konkretne skrzyżowanie, inspektor Unity tej klasy udostępnia wygodne listy.\ref{ispektorListySkrzyzowanie} ![Listy udostępnione przez inspektor Unity dla klasy skrzyżowania \label{ispektorListySkrzyzowanie}](https://i.imgur.com/xHNaHzX.png) **PreviousRoadElements** to referencje na drogi, które dochodzą do skrzyżowania **NextRoadElements** to referencje na drogi, które wychodzą ze skrzyżowania **PathCreatorsMap** to obiekty, które dla danej pary dróg wchodzącej i wychodzącej zawiera referencję na poddrogę, która łączy te dwie drogi #### Budowanie zależności między drogami Opisany wyżej system pozwala samochodom pokonywać dowolnie skomplikowane skrzyżowania, ale najciekawsza część systemu to samo pokonywanie konkretnego skrzyżowania. Skrzyżowanie musi wiedzieć, w jaki sposób należy je pokonać. Jednym z potencjalnych rozwiązań jest udostępnienie listy ze środka klasy do edytora Unity i dać użytkownikowi wypełnić wszystkie reference ręcznie. Jest to rozwiązanie poprawne, jednak bardzo czasochłonne. W naszym symulatorze chcieliśmy to zautomatyzować. W tym celu istnieje seria klas, które dla każdej drogi jaka jest w symulacji biorą jej koniec i początek, a następnie przeszukują wszystkie inne dostępne drogi, żeby znaleźć te, które się z nimi łączą. Taka kalkulacja wykonywana jest za każdym razem, gdy startuje symulacja. Ta sama metoda wyszukiwania dróg używana jest do łączenia potencjalnych dróg oraz do łączenia poddróg z potencjalnymi drogami #### Budowanie zależności między drogami - przykład 1 Zacznę od podstaw, czyli od tego, czym jest element drogowy. Najprostszy element drogowy to element zawierający w sobie jedną drogę.\ref{elementDrogowyPrzyklad1} ![Przykład elementu drogowego \label{elementDrogowyPrzyklad1}](https://i.imgur.com/cPggjtV.png) Na początku symulacji taki element najpierw sprawdza, jakie znajdują się w nim drogi. W tym wypadku to **Droga 1**. Następnie na podstawie punktów startowych i końcowych dróg w środku wyznacza swoje punkty startowe i końcowe.\ref{elementDrogowyPrzyklad2} ![Element drogowy z wejściami i wyjściami \label{elementDrogowyPrzyklad2}](https://i.imgur.com/2m6enpD.png) Taki element gotowy jest na to, aby połączyć się z innymi elementami w celu zbudowania sieci drogowej. Najprostszy przykład takiej sieci to 2 elementy drogowe.\ref{elementDrogowyPrzyklad3} ![Prosta sieć z elementami drogowymi \label{elementDrogowyPrzyklad3}](https://i.imgur.com/Yelxj8Z.png) Na początku symulacji już po wyznaczeniu punktów końcowych oraz startowych elementów drogowych następuje część kojarzenia między sobą poszczególnych elementów. Pierwszy krok to zebranie listy punktów startowych i końcowych. W tym wypadku będzie to lista punktów **[1,2,3,4]**. Kolejny krok to znalezienie par punktów, między którymi odległość jest mniejsza niż ustalona. W tym wypadku taką parą są punkty **[2,3]**. Po znalezieniu takiej pary następuje kojarzenie.\ref{elementDrogowyPrzyklad4} ![Kojarzenie elementów \label{elementDrogowyPrzyklad4}](https://i.imgur.com/6I0BkPx.png) Element drogowy 1 zapisuje w sobie informację, że jednym z jego potencjalnych elementów wyjściowych jest Element drogowy 2. Tak zbudowana sieć pozwala teraz pojazdowi pokonać trasę składającą się z elementów drogowych 1 oraz 2. Pojazd, który chce przejechać taką trasę, "spyta się" elementu drogowego 1 - w jaki sposób mogę przez ciebie trafić do elementu drogowego 2? Element drogowy numer 1 odpowie wtedy, zwracając pojazdowi drogę znajdującą się w środku - **Droga 1**. Taki przykład jest jednak trywialny i nie pokazuje całego potencjału systemu, dlatego przeanalizuję przykład zawierający element drogowy, który ma w sobie więcej niż jedną drogę #### Budowanie zależności między drogami - przykład 2 Najprostszym przykładem z elementem drogowym zawierającym więcej niż jedna droga jest takie rozwidlenie.\ref{trudnaDrogaPrzyklad1} ![Przykład sieci drogowej zawierającej więcej niż jedna droga \label{trudnaDrogaPrzyklad1}](https://i.imgur.com/vBfoKa8.png) Krok pierwszy - kojarzenie punktów startowych z końcowymi.\ref{trudnaDrogaPrzyklad2} ![Skojarzenie punktów na elementach drogowych \label{trudnaDrogaPrzyklad2}](https://i.imgur.com/jjVqUfy.png) Należy zwrócić uwagę na to, że punkt **2** został skojarzony z dwoma punktami - **3** oraz **4**. Jest to jak najbardziej możliwa sytuacja. Oznacza to, że z elementu drogowego 1 można dostać się do kilku innych dróg. Zakładając, że autko planuje pokonać trasę elementów drogowych 1, 2 oraz 4, jego seria zapytań będzie wyglądała następująco: - zapytanie elementu drogowego, w jaki sposób go pokonać, aby dojechać do elementu drogowego 2. Element odpowie referencją na obiekt **Droga 1**, - zapytanie elementu drogowego, w jaki sposób go pokonać, aby dojechać do elementu drogowego 4. Element odpowie referencją na obiekt **Droga 3**, Na tym przykładzie widać, dlaczego same drogi ukryte są w elementach drogowych. Pojazd nie musi wiedzieć, w jaki sposób działają skrzyżowania. Logika ta ukryta jest w samym skrzyżowaniu i to ono odpowiada na pytania, w jaki sposób je pokonać w zależności od poprzedniego oraz następnego elementu drogowego. Po "przepytaniu" elementów drogowych pojazd zostanie z listą dróg [Droga 1, Droga 3, Droga 1], a to jest już coś, co pojazd jak najbardziej rozumie. Wystarczy teraz zmieniać pokonany dystans na każdej drodze, a po pokonaniu całej drogi zacząć pokonywać następną #### Sygnalizacja świetlna Sygnalizacja świetlna zrealizowana jest jako prostopadłościany z komponentem **Collider**, które wykrywa **ProximitySensor** i przekazuje tę informację pojazdowui.\ref{skrzyzowanieEdytorUnity} ![Prezentacja wyglądu skrzyżowania w edytorze Unity \label{skrzyzowanieEdytorUnity}](https://i.imgur.com/nANEBQM.png){ height=250px } Skrzyżowanie ma na sobie klasę **LightsTimer**. Klasa ta odpowiedzialna jest za ustalenie harmonogramu świateł. Wygodny edytor dostępny jest w inspektorze Unity. \label{klasaHarmonogramSwiatel} ![Wygląd wzbogaconego inspektora Unity dla klasy zajmującej się ustaleniem harmonogramu świateł \ref{klasaHarmonogramSwiatel}](https://i.imgur.com/rlhukEk.png){ height=250px } Klasa iterując po elementach listy odpowiednio: - wyłącza wszystkie światła powiązane ze skrzyżowaniem, - włącza światła w danym elemencie listy, - czeka **Duration** sekund i powtarza całą sytuację, Coś, co trudno było z początku przewidzieć, to zachowanie pojazdów, w którym niekiedy są blokowane przez światła, które ich nie dotyczą. Rozwiązaniem tego problemu jest przypisanie świateł do drogi, której mają dotyczyć. W uproszczeniu zmienia to zachowanie pojazdu z takiego.\ref{dzialanieSwiatel1} ![Początkowa zasada działania wykrywania świateł \label{dzialanieSwiatel1}](https://i.imgur.com/2SilCkG.png){ height=250px } Na takie.\ref{dzialanieSwiatel2} ![Ulepszona zasada działania wykrywania świateł \label{dzialanieSwiatel2}](https://i.imgur.com/S61sSfn.png){ height=250px } Dzięki temu jeżeli pojazd przypadkiem trafi promieniem w czerwone światło, które go nie dotyczy, nie spowoduje to zatrzymania się pojazdu #### Skręty kolizyjne - definicja Przykładowe skrzyżowanie, w którym pojazdy znajdujące się na dole mogą jechać prosto lub skręcić w lewo, a pojazdy znajdujące się na górze mogą jechać tylko prosto.\ref{skretyKolizyjne1} ![Przykładowe skrzyżowanie ze skrętami kolizyjnymi \label{skretyKolizyjne1}](https://i.imgur.com/7FQdVKJ.png){ height=250px } W momencie gdy pojazdy z przeciwległych pasów mogą się poruszać, istnieje szansa, że zderzą się w tym punkcie.\ref{skretyKolizyjne2} ![Moment zderzenia \label{skretyKolizyjne2}](https://i.imgur.com/yp7D3e1.png){ height=250px } Aby temu zapobiec, należy w jakiś sposób kontrolować ruch pojazdów, które przecinają inny pas. W tym wypadku będą to pojazdy z dołu. Na takich pasach stoi zatem bariera (czerwony kolor na rysunku).\ref{skretyKolizyjne3} ![Definicja bariery \label{skretyKolizyjne3}](https://i.imgur.com/NvgTBzn.png){ height=250px } Taka bariera nie może jednak cały czas blokować pojazdów. Powinna to robić jedynie w momencie, w którym istnieje ryzyko zderzenia. Zatem bariera powiązana jest z czujnikiem pojazdów i jest aktywna tylko jeżeli czujnik pojazdów coś wykrywa.\ref{skretyKolizyjne4} ![Bariera razem z czujnikiem \label{skretyKolizyjne4}](https://i.imgur.com/t5DqmkS.png){ height=250px } Poniżej przykład działania całego mechanizmu. Pojazd 2 chce dokonać skrętu kolizyjnego. Bariera wykryła pojazd 1 jadący z naprzeciwka, dlatego się uaktywniła. Pojazd 2 wykrywa barierę i się przed nią zatrzymuje.\ref{skretyKolizyjne5} ![Cały mechanizm \label{skretyKolizyjne5}](https://i.imgur.com/ilKiCah.png){ height=250px } Następnie pojazd 1 wyjeżdża już z zakresu czujnika aut, co powoduje dezaktywację z bariery, co z kolei pozwala pojazdowi 2 pokonać skrzyżowanie.\ref{skretyKolizyjne6} ![Zwolnienie bariery po przejechaniu pojazdu \label{skretyKolizyjne6}](https://i.imgur.com/MHSGlll.png){ height=250px } Rozwiązanie wydaje się być gotowe, jednak istnieje kilka przypadków szczególnych, które zostaną omówione w następnych podrozdziałach. #### Skręty kolizyjne - problem przynależności bariery Przykład skrzyżowania omówiony przy definicji skrętów warunkowych jest trywialny - w rzeczywistości w symulatorze istnieją bardziej skomplikowane skrzyżowania. Minimalnym przykładem skrzyżowania uwidaczniającego problem przynależności bariery jest ![ TODO ALEKSANDER add title ](https://i.imgur.com/FD26j3B.png){ height=250px } Zakładając, że jest to skrzyżowanie ze światłami i według harmonogramu świateł pojazd 1 oraz 2 nigdy nie mogą się zderzyć, pojawia się problem. Czujnik zapobiegający zderzeniom na skręcie warunkowym blokuje również przejazd auta numer 2. Dzieje się tak całkowicie przypadkiem - bariera stoi po prostu na dwóch pasach jednocześnie. Aby to rozwiązać, wprowadziłem przynależność bariery do danego pasa ruchu. Bariera posiada w sobie referencję na obiekt drogi, którego ma dotyczyć. Jako że nie da się tego ustalić automatycznie, w inspektorze Unity skrypt bariery ma w sobie pole widoczne w inspektorze, do którego wygodnie można dodawać daną drogę ![ TODO ALEKSANDER add title ](https://i.imgur.com/OtQlyx5.png){ height=250px } Oprócz samego dowiązania drogi do bariery wymagało to również modyfikacji zasady działania wykrywania przeszkód przez czujnik odległości. Z początkowego wykrywania w taki sposób.\ref{sposobWykrywania1} ![Sposób wykrywania przeszkód przed wprowadzeniem przynależności bariery do pasa \label{sposobWykrywania1}](https://i.imgur.com/aqPSfCi.png){ height=250px } Na taki.\ref{sposobWykrywania2} ![Sposób wykrywania przeszkód po wprowadzeniem przynależności bariery do pasa \label{sposobWykrywania2}](https://i.imgur.com/ivQnpn9.png){ height=250px } #### Skręty kolizyjne - problem przeciwległych pasów kolizyjnych Problem pojawia się w momencie, gdy obydwa pojazdy jadące z naprzeciwka dokonują jednocześnie skrętu kolizyjnego jak przedstawiono na rysunku \ref{przeciwlegleKolizje1} ![Problem przeciwległych pasów kolizyjnych \label{przeciwlegleKolizje1}](https://i.imgur.com/1wed54E.png){ height=250px } Czujniki w obydwu pojazdach wykryją pojazd jadący z naprzeciwka, przez co obydwa się zatrzymają. Doprowadziło to do zmiany zasady działania czujnika odległości. Wcześniejsza zasada, która strzelała promieniem RayCast szukając kolizji z pojazdem została wzbogacona o dodatkowy warunek. Teraz czujnik odległości szuka kolizji ze specjalnym obiektem, który wyznacza tył auta. \ref{nowaKolizja} ![Nowa zasada wyznaczania przeszkód przez czujnik odległości \label{nowaKolizja}](https://i.imgur.com/uQzeXlE.png){ height=250px } W przypadku gdy dwa pojazdy jadą na siebie z naprzeciwka, czujnik odległości nie zwraca wykrycia przeszkody - taka sytuacja zawsze oznacza tylko chwilowe ustawienie pojazdów w ten sposób i nie powinna być traktowana jako przeszkoda #### Skręty kolizyjne - problem zakleszczenia W ruchu drogowym, który odbywa się według wszystkich poprzednich zasad może dojść do wyjątkowej sytuacji zakleszczenia na skrzyżowaniu ![Problem zakleszczenia skrzyżowania przez skręty kolizyjne](https://i.imgur.com/BjLtLqC.png){ height=250px } Pojazdy oznaczone jako "A" chcą przez skrzyżowanie przejechać prosto, a pojazdy oznaczone jako "B" chcą wykonać skręt kolizyjny. Zgodnie z zasadą działania czujników na skrzyżowaniu, w momencie, który jest przedstawiony na rysunku auta oznaczone jako "A" spowodują ustawienie barier dla obydwu samochodów oznaczonych jako "B". Samochody oznaczone jako "B" nigdy nie przejadą przez skrzyżowanie, co zablokuje również samochody "A". Aby temu zapobiec, czujniki na skrzyżowaniach sprawdzają dodatkowy warunek. Oprócz wcześniejszego sprawdzania, czy w ich środku znajduje się pojazd, teraz sprawdzają również, czy znajdujący się w środku pojazd się porusza. Jeżeli nie, to bariera się odblokowuje do kolejnej zmiany swojego stanu (wjechanie kolejnego pojazdu). W tym wypadku po puszczeniu pojazdów B problem zakleszczenia znika.\ref{rozwiazanieZakleszczenia} ![Rozwiązanie sytuacji zakleszczenia \label{rozwiazanieZakleszczenia}](https://i.imgur.com/y3jj4ie.png){ height=250px } Istnieje jednak szansa, że puszczenie jednej serii samochodów w przypadku zakleszczenia nie rozwiąże go. Logika czujnika jest napisana tak, żeby kontynuowała puszczanie po jednym samochodzie w przypadku powtórzenia sytuacji zakleszczenia. Dzięki temu wszystkie sytuacje zakleszczenia są poprawnie obsłużone #### Skrzyżowanie o ruchu okrężnym - struktura Problem przedstawienie ronda używając istniejącego rozwiązania do generowania dróg można rozwiązać po zauważeniu, że rondo tak na prawdę jest po prostu serią skrzyżowań. Rondo jak na rysunku poniżej można przeciąć na okręgu.\ref{rondo1} ![Rondo przed rozłożeniem \label{rondo1}](https://i.imgur.com/N9FCKJ1.png){ height=250px } Co po "rozłożeniu" układa się w serię skrzyżowań.\ref{rondo2} ![Rondo po rozłożeniu \label{rondo2}](https://i.imgur.com/grbuMRI.png){ height=250px } Pojedyncze skrzyżowanie może być przedstawione jako struktura zawierająca kawałek drogi należącej do ronda, wjazd oraz wyjazd.\ref{rondo3} ![Struktura przedstawiająca kawałek ronda \label{rondo3}](https://i.imgur.com/T9pcLee.png) Rondo zatem jest serią skrzyżowań złożonych ze struktur tego typu. #### Skrzyżowanie o ruchu okrężnym - pierwszeństwo todo ### Serializacja #### Serializacja symulacji w czasie rzeczywistym Pojazdy implementują klasę **ISerializable**, co pozwala w łatwy sposób je serializować. Klasa **Car** posiada metodę **GenerateCarDTO**, która zwraca obiekt typu **CarDTO**. Obiekt typu **CarDTO** posiada pola: - id - losowo nadany identyfikator, - position - wektor 2d pozycji, - velocity - wektor 2d prędkości Co każdą klatkę symulacji obiekt **JsonSerializer** serializuje wszystkie auta i zapisuje wynik w liście. Dodatkowo wyznacza też hash mapy oraz hash ustawień. Tak przygotowane parametry składają się na obiekt typu **SimulationChunkDTO**, który zawiera: - frameNumber - liczba reprezentująca aktualną klatkę, - cars - lista zserializowanych pojazdów, - mapHash - hash, który stworzony jest z tego, jak zbudowa jest mapa. Pozwala jednoznacznie określić, do jakiej mapy należy to DTO, - settingsHash - hash, który stworzony jest z ustawień kierowców wprowadzonych przez użytkownika #### Serializacja mapy Wszystkie drogi zbudowane są z krzywych beziera, a te serializujemy według ogólnie przyjętej zasady podawania po kolei współczynników jako punkty [X, Y] w kolejności [Punkt A, Modyfikator punktu A , Modyfikator punktu B, Punkt B, ...]\ref{krzyweBeziera} ![Zasada serializacji krzywych beziera \label{krzyweBeziera}](https://i.imgur.com/kGQHPP0.png) Klasa **JSONSerializer** na początku symulacji znajduje wszystkie obiekty typu **PathCreator**, a następnie wyciąga z nich referencje na obiekt typu **BezierCurve** i z każdego obiektu tego typu tworzy listę punktów według wcześniej ustalonej zasady. Końcowy obiekt ma strukturę jest obiektem typu **MapDTO**, który zawiera listę obiektów **RoadDTO**, które to zawierają listę obiektów **Vector2** ### Szukanie ścieżki Za szukanie najkrótszej ścieżki pomiędzy elementami odpowiedzialna jest klasa **PathFinder**. Posiada ona metodę **FindPath(PathCreator start, PathCreator end)**, która zwraca listę obiektów **IRoadElement**, które należy pokonać, aby dostać się z elementu **start** do elementu **end** najkrótszą trasą. Algorytm rekurencyjnie zaczynając od elementu start wykonuje swój krok, czyli: - wyznacza wszystkie możliwe wariacje tras, które mogą powstać. W każdym następnym, - wykonuje sam siebie podając aktualnie już sprawdzone elementy **IRoadElement** jako dodatkowy argument Algorytm kończy się w momencie gdy dotrze do elementu **end**. Wtedy zwraca listę sprawdzonych elementów, które go do tego elementu doprowadziły. ### Spawnery pojazdów Klasa odpowiedzialna za tworzenie pojazdów to **CarSpawner**. Tworzy ona pojazdy co losowy czas, który ustala się za pomocą zmiennych udostępnionych do inspektora Unity: - minSpawnInterval, - maxSpawnInterval Spawner w momencie tworzenia samochodu może mu nadać jedną z 3 właściwości: - podać samochodowi zadaną trasę jako listę obiektów **IRoadElement**, - stworzyć samochód podając mu tylko element początkowy **IRoadElement** i polecić mu, aby przejechał trasę losowo (przy każdym przejeździe na nowe skrzyżowanie pojazd wybierze losową opcję), - stworzyć samochód podając mu listę możliwych tras - pojazd wybierze wtedy jedną z możliwych tras i zadziała analogicznie do pierwszego podpunktu ### Pomocnicze gizmo Aby ułatwić tworzenie aplikacji, edytor Unity udostępnia programiście możliwość rysowania dodatkowych gizmo ponad już istniejącymi. W symulatorze znalazło się na to kilka zastosowań. #### Kierunki dróg Każda droga, która znajduje się na mapie jest jednokierunkowa. Aby ułatwić wizualizację, w którą stronę odbywa się ruch na danej drodze, istnieje pomocnicza metoda na klasie reprezentującej drogę, która rysuje niebieskie strzałki symbolizujące kierunek poruszania.\ref{gizmoStrzalki} ![Strzałki wskazujące kierunek ruchu \label{gizmoStrzalki}](https://i.imgur.com/hA1G7vA.png) #### Wykrywacze aut Aby lepiej zwizualizować zachowanie klasy **ProximitySensor**, oprócz wykonywania samego RayCast, rysuje też ona wizualizację tego, co się stało\ref{gizmoCzujnik}: - niebieski promień - nic nie zostało wykryte w zadnej odległości, - czerwony promień - wykryto przeszkodę, która potencjalnie może zatrzymać pojazd ![Linie symbolizujące stan czujnika odległości\label{gizmoCzunik}](https://i.imgur.com/AlYGNgz.png) #### Wykrywacze aut Wykrywacze, które używane są przy skrętach kolizyjnych oraz w przypadku ustępywania pierwszeństwa symbolizowane są jako zielone prostopadłościany\ref{gizmoWykrywacz1}, które stają się czerwone, gdy powinny zgłosić, że w ich obrębie jest jakiś pojazd\ref{gizmoWykrywacz2} ![Wykrywacz w stanie spoczynku \label{gizmoWykrywacz1}](https://i.imgur.com/yjjTJkH.png) ![Wykrywacz w stanie wykrycia \label{gizmoWykrywacz2}](https://i.imgur.com/PEp5Q7y.png) ## Backend (Michał Matejuk) ### 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 dokonowywać wszelakich zmian, dodawać nowe funkcjonalności, zmieniać założony schemat danych, na bieżąco dostosowywać aplikację do naszych potrzeb. ### API Jako że aplikacja udostępnia wiele endpointów, użyliśmy Swaggera - narzędzia ułatwiającego 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ć. \ref{swagger} ![Strona wygenerowana dla aplikacji backendowej przez narzędzie Swagger. \label{swagger}](https://i.imgur.com/63rPnSR.png){ 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 tagach 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 drógmi i punktami na nich. #### Dane o tagach samochodów Dokument Tag posiada dwa pola: nazwę oraz opis. Pozwala to na definiowanie opisów do samochodów w postaci krótkich tagó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 tagów poprzez podanie ich nazwy oraz opisu. Można także usunąć istniejące tagi, pobrać wszystkie dostępne tagi, oraz wyszukać tag po jego nazwie. #### Dane o ustawieniach Dokumenty ustawień opisują wszystkie możliwe Tagi dla samochodów w danej symulacji. Podobnie jak mapy, ustawienia identyfikowane są jednoznacznie przez swój hash. Aplikacja umożliwia dodawnie nowych dokumentów ustawień przez podanie ich hasha oraz kolekcji tagó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łają 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. Obiekt ### 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 symulacje na części, i wysyłać je w tle w trakcie działania symulacji. Aplikacja backendowa miała spore problemu z odbieraniem danych, głównie z powodu deserializacji ogromnej ilości klatek i samochodów do obiektów stworzonych w serwisie. Rozwiązalimy 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 spejalnego headera z kluczem uwierzetelniającym. ## Backend obliczeniowy (Kuba) ### Dobór technologii Backend obliczeniowy jest zrealizowany w języku Python w wersji 3.9 wykorzystując framework Flask. Zdecydowaliśmy się na wybór tych technologii ze względu na doświadczenie członków zespołu z tymi technologiami i łatwość wystawienia API oraz implementacji przekształceń danych. ### 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 ### API API backendu obliczeniowego jest dostępne pod adresem https://ctscompms.bieda.it i wystawia następujące endpointy: /api/process, umożliwiający wywołanie symulacji i przetworzenie danych o jej przebiegu, /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 w formacie JSON, które następnie są zapisywane w bazie danych poprzez wysłanie żądania POST do aplikacji backendowej. ##### Argumenty żądania ##### Wejściowe dane symulacji ##### Obiekt symulacji #### Dane wyjściowe #### Działanie /heath Endpoint /health obsługuje żądania GET i zwraca stan zdrowia backendu obliczeniowego w formacie JSON. ### Konfiguracja Plik konfiguracyjny YAML umożliwia łatwe zarządzanie parametrami aplikacji oraz ustawieniami endpointów. 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ć. Dzięki temu można łatwo zmienić adres backendu, bez konieczności modyfikowania kodu aplikacji. Konfiguracja aplikacji w postaci pliku YAML jest bardzo przydatna w przypadku, gdy aplikacja jest uruchamiana w różnych środowiskach, np. wersja produkcyjna i testowa. Można zdefiniować osobne pliki konfiguracyjne dla każdego środowiska, co pozwala na łatwe dostosowanie aplikacji do konkretnego środowiska. ### Ograniczenia wynikające z doboru technologii //todo ## Aplikacja dashboardowa (Maciej) ### 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śći 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 rozdziale \nameref{Ograniczenia}. ### 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 \ref{PanelNawigacji}. ![Panel nawigacji widoczny po naciśnięciu przycisku 'Links', pozbawiony sekcji "External components". \label{PanelNawigacji}](https://i.imgur.com/TpI8eHl.png){ height=300px } Panel nawigacji widoczny na \ref{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łownej Politechniki Gdańskiej w języku angielskim. Na potrzeby czytelności pracy widoczny na \ref{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. ![Listy rozwijane pozwalające użytkownikowi zdefiniować interesującą go instancję symulacji. \label{listyRozwijane}](https://i.imgur.com/pHr0UFr.png) Każda z list rozwijanych widocznych na \ref{listyRozwijane} aktualizuje się asynchronicznie, w zależności od wyboru użytkownika, użytkownik nie jest zastem w stanie wysłać do backendu zapytania o dane z nieprzeprowadzonej instancji symulacji. Dodatkowo, przycisk wysyłający asynchroniczne zapytanie do backendu widoczny w figurze \ref{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. ![Niebieski przycisk wysyłający zapytanie do backendu w stanie zablokowanym. Jedna opcja pozostaje niewybrana. \label{zablokowanyAnalyze}](https://i.imgur.com/NDRRolu.png) W przpadku prawidłowego wprowadzenia wszystkich wymaganych wartości przycisk ten zostaje odblokowany, tak jak widać na figurze \ref{odblokowanyAnalyze} i staje się klikalny, zatem możliwe jest wysłanie zapytania do backendu. ![Niebieski przycisk wysyłający zapytanie do backendu w stanie odblokowanym. Wszystkie opcje zostały wybrane. \label{odblokowanyAnalyze} ](https://i.imgur.com/cVPxRZC.png) 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 figurze \ref{animacjaCzekania}. ![Pojedyncza klatka z animacji wyświetlanej użytkownikowi podczas oczekiwania na odpowiedź zwrotną z backendu. \label{animacjaCzekania}](https://i.imgur.com/qQg3tPl.png) Podczas oczekiwania na zwrot danych z backendu, dodatkowo odblokowany zostaje przycisk \ref{cancelOdblokowany} pozwalający na zatrzymanie asynchronicznego zapytania do serwera. Jest to możliwe dzięki mechanizmowi "long callback", o którym więcej informacji znajduje się w \refname{longCallbacks}. Po wciśnieciu przycisku anulującego callback aplikacja powraca do stanu bespośrednio sprzed jego wywołania. ![Czerwony przycisk pozwalający na przerwanie zapytania do serwera w stanie zablokowanym. \label{cancelZablokowany}](https://i.imgur.com/1KC80hb.png){ height=150px } ![Czerwony przycisk pozwalający na przerwanie zapytania do serwera w stanie odblokowanym. \label{cancelOdblokowany}](https://i.imgur.com/5TtJcXB.png){ height=150px } Przykładowy wykres może prezentować się tak jak na \ref{przykladowaHeatmapa}. ![Przykładowy wykres mapy gorąca pozycji samochodów dla wybranej symulacji \label{przykladowaHeatmapa}](https://i.imgur.com/azOR9Oq.png) Na \ref{przykladowaHeatmapa} widoczne są pozycje najczęściej zajmowane przez samochody w danej instancji symulacji. #### Powstawanie 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 nawigacyjmy o id='example-navbar' wyglądałby (w reprezentacji pseudokodem) jak na \ref{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 uzytkownika \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 \nameref{RozdziałCallbacki}. Rozwiązanie to nie jest całkowicie pozbawione wad, o części z nich przeczytać można w rozdziale \nameref{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 \ref{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 transfromacji przez komponent **backend obliczeniowy** czy dostępne dla symulacji metadane. Więcej o metadanych przeczytać można w \nameref{MetadaneRozdzial} ### SSR (Server Side Rendering) 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 funkcę hashującą na ustawieniach symulacji oraz jej mapie * listę współrzędnych krzywych Beziera wymaganych na potrzebę odtwarzania symulacji po stronie frontendowej aplikacji dashboardowej. ### Endpoint healts //TODO ### Caching danych po stronie serwera #### Memoizacja 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 funckji 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 wykoniania 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 sie czasu wykonania takiego zapytania. #### Konfiguracja przechowywania 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 machanizmu cache'owania po stronie użytkownika aplikacji, wynika to z narzuconych ograniczeń rozmiaru danych które mogą być przechowywane w pamięci przeglądarki. //todo jesli jednak sie uda ### 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 \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. ### Rodzaje wykresów #### 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. #### Histogramy //TODO ### Odtwarzanie symulacji na frontendzie Aplikacja frontendowa jest wyposażona w uporszczony 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świetlenie ruchu samochodów zrzutowane na przestrzeń 2-wymiarową tak jak jest to widoczne na \ref{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 \nameref{Filtrowanie}. ![Przykładowa klatka z animacji odtwarzającej przebieg symulacji. \label{przykladowyReplay}](https://i.imgur.com/1piIsle.png) ### Główna zasada działania (callbacks) \label{RozdziałCallbacki} #### Działanie callbacków Działanie frameworka Dash oparte jest na tak zwanych callbackach. 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, callbacki są jedynie wrapperami poszczególnych funkcji, wywoływanym automatycznie w razie wykrycia zmiany wartości zdefiniowanych w nich parametrów. Przykładowa definicja callbacku widoczna na \ref{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. #### Long callbacks \label{longCallbacks} Wywołania zwrotne których czas wykoniania 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, co może wpłynąć negatywnie na czas wykonania ich żądań lub ,w skrajnych przypadkach, je uniemożliwic. Długie wywołania zwrotne (callbacks) oferują 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. ### Ograniczenia i problemy wynikające z doboru technologii \label{Ograniczenia} #### Mechanizm callbackowy Od początku powstania frameworku Dash, nie jest możliwe utworzenie callbacków 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śćić 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 parametó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śc parametru wejściowego uległa zmianie i wywołała callback obłsugujący 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. #### Nieprawidłowe legendy na wykresach animowanych \label{złeLegendyITagi} Ograniczeniem które odkryte zostało podczas implementacji aplikacji okazł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 wystpę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. #### 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 \ref{przedCSS} oraz \ref{poCSS} porównać można wygląd aplikacji sprzed oraz po zastosowaniu własnych arkuszy stylu. ![ Przed zastosowaniem własnych arkuszy stylu \label{przedCSS}](https://i.imgur.com/XxQmQKk.png){ height=150px } ![ Po zastosowaniu własnych arkuszy stylu \label{poCSS}](https://i.imgur.com/c7wopnY.png){ height=150px } ## Uruchomienie projektu na lokalnej maszynie (Michał Matejuk) ### 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. Struktura pliku *docker-compose.yml* jest bardzo prosta i prezentuje się następująco: \ref{dockerCompose}, \begin{figure}[h!] \begin{lstlisting} version: '3.8' services: google.pubsub.emulator: container_name: google.pubsub.emulator image: bigtruedata/gcloud-pubsub-emulator command: [ "start", "--host-port", "0.0.0.0:500", "--log-http", "--verbosity=debug", "--user-output-enabled"] networks: - campaignschedules-services networks: campaignschedules-storage: campaignschedules-services: volumes: google-credentials: scripts: \end{lstlisting} \caption{Plik docker-compose.yml\label{dockerCompose}} \end{figure} # Analiza przeprowadzonych symulacji ## Analiza fragmentu miasta Gdańsk - Piecki Migowo ### Fragment miasta Gdańsk - Piecki Migowo #### Opis 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. #### 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\ref{pieckiMigowoInitial1} i zapoznaniu się z mapami ciepła pozycji\ref{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 zmienia się 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 ![Klatka z odtwarzacza symulacji z zaznaczonym miejscem, w którym tworzy się korek \label{pieckiMigowoInitial1}](https://i.imgur.com/q3M3CQh.png){height=250px} ![Mapa cieplna pojazdów z zaznaczonym miejscem, w którym tworzy się korek \label{pieckiMigowoInitial2}](https://i.imgur.com/MxnXqNB.png){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 w tym samym miejscu powstaje teraz phantom traffic. Kierowcy mają zby ![Klatka z odtwarzacza symulacji z zaznaczonym miejscem, w którym tworzy się phantom traffic \label{pieckiMigowoCrossroadAdjustment1}](https://i.imgur.com/2icVjvf.png) ## Autonomiczne pojazdy (Aleksander Błaszkiewicz) ### 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.\ref{badanieAutonomiczne1} Pojazdy pojawiają się na każdym wjeździe z jednakową częstotliwością i mogą wybrać jeden z 3 losowych dróg. ![Wizualizacja skrzyżowania w aplikacji frontendowej\label{badanieAutonomiczne1}](https://i.imgur.com/ve7Wq4g.png){ height=400px } ### Wysoki czas reakcji W pierwszym wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu, której maksimum to 1.5s.\ref{czasReakcjiWysoki} ![Funkcja rozkładu czasu reakcji kierowców (przypadek wysokiego czasu reakcji)\label{czasReakcjiWysoki}](https://i.imgur.com/daSwvCZ.png){ height=250px } 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 ### Średni czas reakcji W drugim wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu, której maksimum to 0.5s.\ref{czasReakcjiSredni} ![Funkcja rozkładu czasu reakcji kierowców (przypadek średniego czasu reakcji)\label{czasReakcjiSredni}](https://i.imgur.com/QGPNQS0.png){ height=250px } 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. ### Niski czas reakcji W trzecim wariancie badania czas reakcji tworzonych kierowców przyznawany jest według funkcji rozkładu, której maksimum to 0.1s.\ref{czasReakcjiNiski} ![Funkcja rozkładu czasu reakcji kierowców (przypadek niskiego czasu reakcji)\label{czasReakcjiNiski}](https://i.imgur.com/Cgi7oU6.png){ height=250px } 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 ### Brak czas reakcji W czwarym 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. ### Wnioski Symulacje przeprowadzone powyżej jednoznacznie potwierdzają, że czas reakcji może zaważyć na tym, czy wraz z biegem 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) ### 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. ![Funkcja rozkładu czasu reakcji kierowców (przypadek średniego czasu reakcji)\label{sredniCzas}](https://i.imgur.com/QGPNQS0.png) #### 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.\ref{traffic6Aut} Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **phantomTraffic** - **6samochodow** - **1** - **replay simulation** ![Płynny ruch na rondzie w przypadku 6 samochodów\label{traffic6Aut}](https://i.imgur.com/oSLJwgh.png){ 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\ref{traffic7Aut1}, jest ono w stanie samoistnie się rozładować\ref{traffic7Aut2}. Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **phantomTraffic** - **7samochodow** - **1** - **replay simulation** ![Utworzony phantom traffic na rondzie w przypadku 7 samochodów w 45 klatce\label{traffic7Aut1}](https://i.imgur.com/h9tqhug.png){ height=300px } ![Rozładowany phantom traffic na rondzie w przypadku 7 samochodów w 100 klatce\label{traffic7Aut2}](https://i.imgur.com/pExResU.png){ height=300px } #### Przypadek 8 pojazdów W warunkach panujących w symulacji 8 pojazdów tworzy zjawisko phantom traffic\ref{traffic8Aut1}, które nigdy się nie rozładuje\ref{traffic8Aut2}. Wizualizacja dostępna jest w aplikacji frontendowej po wybraniu parametrów **phantomTraffic** - **8samochodow** - **1** - **replay simulation** ![Utworzony phantom traffic na rondzie w przypadku 8 samochodów w 71 klatce\label{traffic8Aut1}](https://i.imgur.com/r98GLTP.png){ height=300px } ![Nierozładowany phantom traffic na rondzie w przypadku 8 samochodów w 126 klatce\label{traffic8Aut2}](https://i.imgur.com/jDaTAVF.png){ height=300px } ## Porównanie ronda i skrzyżowania (Aleksander Błaszkiewicz) ### 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ć zez sobą pojazdy (szczególnie podczas skrętów kolizyjnych) ### Rondo Przykładowa scena reprezentująca rondo z 4 wjazdami i 4 wyjazdami.\ref{rondoVsSkrzyzowanie1} ![Scena z rondem\label{rondoVsSkrzyzowanie1}](https://i.imgur.com/zJ2gAV2.png){ height=300px } ### Skrzyżowanie Przykładowa scena reprezentująca skrzyżowanie z 4 wjazdami i 4 wyjazdami.\ref{rondoVsSkrzyzowanie2} ![Scena ze skrzyżowaniem\label{rondoVsSkrzyzowanie2}](https://i.imgur.com/WxgnSGf.png){ height=300px } ### Parametry symulacji W każdej z przeprowadzonych symulacji kierowcy przyjmują profil kierowcy o szybkim czasie reakcji. Czas reakcji przyznawany jest według następującej funkcji rozkładu.\ref{rozkladCzasReakcjiSzybki} ![Funkcja rozkładu szybkiego czasu reakcji kieroców \label{rozkladCzasReakcjiSzybki}](https://i.imgur.com/VqRJodN.png) 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\ref{najgorszeRondo} i tak jest lepsze od standardowego skrzyżowania\ref{najgorszeSkrzyzowanie}. Widać, że po pewnym czasie na skrzyżowaniu ruch nie rozładowuje się na bieżąco i powstaje korek. W przypadku ronda problem ten nie występuje. ![Zakorkowanie najgorszego przypadku ronda w 200 klatce\label{najgorszeRondo}](https://i.imgur.com/Hgij3OC.png){ height=300px } ![Zakorkowanie najgorszego przypadku skrzyżowania w 200 klatce\label{najgorszeSkrzyzowanie}](https://i.imgur.com/oqTDNV7.png){ 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\ref{srednieRondo}. Mimo że w obydwu typach skrzyżowania nie dochodzi już do tworzenia korka, kierowcy na skrzyżowaniu\ref{srednieSkrzyzowanie} spędzają trochę czasu na czerwonym świetle. Na rondzie ruch odbywa się płynnie. Kierowcy prawie nigdy nie czekają, aby wjechać na rondo. ![Zakorkowanie średniego przypadku ronda w 200 klatce\label{srednieRondo}](https://i.imgur.com/lxvgYNo.png){ height=300px } ![Zakorkowanie średniego przypadku skrzyżowanie w 200 klatce\label{srednieSkrzyzowanie}](https://i.imgur.com/VOdBk29.png){ 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\ref{najlepszeRondo} i skrzyżowanie\ref{najlepszeSkrzyzowanie} zachowują się po prostu tak samo. W obydwu przypadkach ruch przez te struktury sieci drogowej jest płynny. ![Zakorkowanie najlepszego przypadku rondaw 200 klatce\label{najlepszeRondo}](https://i.imgur.com/eCFW6xh.png){ height=300px } ![Zakorkowanie najlepszego przypadku skrzyżowaniaw 200 klatce\label{najlepszeSkrzyzowanie}](https://i.imgur.com/00jEy0K.png){ height=300px } ## Zajmowanie odpowiednich pasów ### 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. Szczególny przypadek zajmownia odpowiednich pasów ruchu to przypadek skrętu z jednego pasu, który rozwidla się na dwa lub nawet trzy pasy. \begin{thebibliography}{9} \bibitem{lamport94} Example Author, \textit{\LaTeX: a document preparation system}, Example Author, Gdańsk, 2nd edition, 2019. \end{thebibliography}