From a7fbe32ae01c5d787d1b5baad0d7fc7b7d63fc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sat, 5 Jan 2019 17:59:50 +0100 Subject: [PATCH] Initial code drop --- .clang-format | 1 + .clang-tidy | 5 + .editorconfig | 10 + .gitattributes | 5 + .gitignore | 3 + README.md | 10 +- meson.build | 24 ++ pi2-demo/data/sample-maps/sample-map_8.txt | 16 ++ pi2-demo/data/sample-maps/sample-map_9.txt | 27 ++ pi2-demo/include/Bicycle.hpp | 15 ++ pi2-demo/include/Car.hpp | 34 +++ pi2-demo/include/DeferralActionWrapper.hpp | 44 ++++ pi2-demo/include/Junction.hpp | 55 ++++ pi2-demo/include/Lane.hpp | 45 ++++ pi2-demo/include/Map.hpp | 42 +++ pi2-demo/include/MapAppABIWrapper.hpp | 24 ++ pi2-demo/include/Named.hpp | 20 ++ pi2-demo/include/Vehicle.hpp | 50 ++++ pi2-demo/meson.build | 19 ++ pi2-demo/src/Bicycle.cpp | 22 ++ pi2-demo/src/Car.cpp | 36 +++ pi2-demo/src/Junction.cpp | 87 ++++++ pi2-demo/src/Lane.cpp | 65 +++++ pi2-demo/src/Map.cpp | 131 ++++++++++ pi2-demo/src/MapAppABIWrapper.cpp | 35 +++ pi2-demo/src/Named.cpp | 12 + pi2-demo/src/Vehicle.cpp | 35 +++ pi2-demo/src/main.cpp | 87 ++++++ pi2-view/data/pi2-view.glade | 291 +++++++++++++++++++++ pi2-view/data/pi2-view.gresource.xml | 6 + pi2-view/include/Drawable.hpp | 68 +++++ pi2-view/include/DrawableJunction.hpp | 20 ++ pi2-view/include/DrawableRoad.hpp | 32 +++ pi2-view/include/DrawableVehicle.hpp | 35 +++ pi2-view/include/MapApp.hpp | 57 ++++ pi2-view/include/MapAppABI.h | 25 ++ pi2-view/include/MapArea.hpp | 35 +++ pi2-view/meson.build | 28 ++ pi2-view/src/Drawable.cpp | 31 +++ pi2-view/src/DrawableJunction.cpp | 40 +++ pi2-view/src/DrawableRoad.cpp | 112 ++++++++ pi2-view/src/DrawableVehicle.cpp | 44 ++++ pi2-view/src/MapApp.cpp | 244 +++++++++++++++++ pi2-view/src/MapAppABI.cpp | 43 +++ pi2-view/src/MapArea.cpp | 66 +++++ scripts/iwyu.sh | 4 + scripts/tidy.sh | 4 + subprojects/microsoft-gsl.wrap | 10 + 48 files changed, 2153 insertions(+), 1 deletion(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 meson.build create mode 100644 pi2-demo/data/sample-maps/sample-map_8.txt create mode 100644 pi2-demo/data/sample-maps/sample-map_9.txt create mode 100644 pi2-demo/include/Bicycle.hpp create mode 100644 pi2-demo/include/Car.hpp create mode 100644 pi2-demo/include/DeferralActionWrapper.hpp create mode 100644 pi2-demo/include/Junction.hpp create mode 100644 pi2-demo/include/Lane.hpp create mode 100644 pi2-demo/include/Map.hpp create mode 100644 pi2-demo/include/MapAppABIWrapper.hpp create mode 100644 pi2-demo/include/Named.hpp create mode 100644 pi2-demo/include/Vehicle.hpp create mode 100644 pi2-demo/meson.build create mode 100644 pi2-demo/src/Bicycle.cpp create mode 100644 pi2-demo/src/Car.cpp create mode 100644 pi2-demo/src/Junction.cpp create mode 100644 pi2-demo/src/Lane.cpp create mode 100644 pi2-demo/src/Map.cpp create mode 100644 pi2-demo/src/MapAppABIWrapper.cpp create mode 100644 pi2-demo/src/Named.cpp create mode 100644 pi2-demo/src/Vehicle.cpp create mode 100644 pi2-demo/src/main.cpp create mode 100644 pi2-view/data/pi2-view.glade create mode 100644 pi2-view/data/pi2-view.gresource.xml create mode 100644 pi2-view/include/Drawable.hpp create mode 100644 pi2-view/include/DrawableJunction.hpp create mode 100644 pi2-view/include/DrawableRoad.hpp create mode 100644 pi2-view/include/DrawableVehicle.hpp create mode 100644 pi2-view/include/MapApp.hpp create mode 100644 pi2-view/include/MapAppABI.h create mode 100644 pi2-view/include/MapArea.hpp create mode 100644 pi2-view/meson.build create mode 100644 pi2-view/src/Drawable.cpp create mode 100644 pi2-view/src/DrawableJunction.cpp create mode 100644 pi2-view/src/DrawableRoad.cpp create mode 100644 pi2-view/src/DrawableVehicle.cpp create mode 100644 pi2-view/src/MapApp.cpp create mode 100644 pi2-view/src/MapAppABI.cpp create mode 100644 pi2-view/src/MapArea.cpp create mode 100644 scripts/iwyu.sh create mode 100644 scripts/tidy.sh create mode 100644 subprojects/microsoft-gsl.wrap diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9b3aa8b --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..17a60cb --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,5 @@ +--- +Checks: 'bugprone-*,cppcoreguidelines-*,clang-analyzer-*,clang-diagnostic-*,llvm-*,modernize-*,performance-*,portability-*,readability-*' +WarningsAsErrors: 'bugprone-*,cppcoreguidelines-*,clang-analyzer-*,clang-diagnostic-*,llvm-*,modernize-*,performance-*,portability-*,readability-*' +FormatStyle: file +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2335c42 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5bb817f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Autodetect text files +* text=auto + +# Force the following filetypes to have unix eols, so Windows does not break them +*.* text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbacdc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build* +/subprojects/GSL-2.0.0 +/subprojects/packagecache diff --git a/README.md b/README.md index 8bddb8f..a1bc102 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ # PI2 View -The GUI library for showing the Map of the Praktikum Informatik 2 \ No newline at end of file +The GUI library for showing the Map of the Praktikum Informatik 2 + +## Dependencies + +* [Meson](https://mesonbuild.com/Getting-meson.html) + +* [gtkmm](https://www.gtkmm.org/en/download.html) + +* [Boost](https://www.boost.org/) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..4c451bd --- /dev/null +++ b/meson.build @@ -0,0 +1,24 @@ +project('pi2-view', ['cpp', 'c'], + license: 'GPL3', + meson_version: '>=0.47.0', + version: '1.0-SNAPSHOT', + default_options : ['warning_level=3', 'werror=true', 'cpp_std=c++17', 'c_std=c11'] +) + +microsoft_gsl_proj = subproject('microsoft-gsl') +microsoft_gsl_dep = microsoft_gsl_proj.get_variable('microsoft_gsl_dep') + +subdir('pi2-view') +subdir('pi2-demo') + +clangtidy = find_program('clang-tidy', required: false) +if clangtidy.found() + run_target('tidy', + command : 'scripts/tidy.sh') +endif + +iwyu_tool = find_program('iwyu_tool', required: false) +if iwyu_tool.found() + run_target('iwyu', + command : 'scripts/iwyu.sh') +endif diff --git a/pi2-demo/data/sample-maps/sample-map_8.txt b/pi2-demo/data/sample-maps/sample-map_8.txt new file mode 100644 index 0000000..4080f77 --- /dev/null +++ b/pi2-demo/data/sample-maps/sample-map_8.txt @@ -0,0 +1,16 @@ +KREUZUNG Kr1 0 680 40 +KREUZUNG Kr2 1000 680 300 +KREUZUNG Kr3 0 680 570 +KREUZUNG Kr4 0 320 300 +STRASSE Kr1 Kr2 W12 W21 40 1 1 2 680 40 680 300 +STRASSE Kr2 Kr3 W23a W32a 115 3 0 6 680 300 850 300 970 390 970 500 850 570 680 570 +STRASSE Kr2 Kr3 W23b W32b 40 1 1 2 680 300 680 570 +STRASSE Kr2 Kr4 W24 W42 55 1 1 2 680 300 320 300 +STRASSE Kr3 Kr4 W34 W43 85 3 0 5 680 570 500 570 350 510 320 420 320 300 +STRASSE Kr4 Kr4 W44a W44b 130 2 0 7 320 300 320 150 200 60 80 90 70 250 170 300 320 300 +PKW Trabant 100 6 50 Kr1 2.0 +PKW Mercedes 180 12 60 Kr1 2.5 +FAHRRAD Peugeot 40 Kr1 4 +PKW Porsche 240 14.5 70 Kr1 3.3 +FAHRRAD BMX 24 Kr1 1.5 +PKW Ferrari 250 15 100 Kr1 4.25 diff --git a/pi2-demo/data/sample-maps/sample-map_9.txt b/pi2-demo/data/sample-maps/sample-map_9.txt new file mode 100644 index 0000000..91b8916 --- /dev/null +++ b/pi2-demo/data/sample-maps/sample-map_9.txt @@ -0,0 +1,27 @@ +KREUZUNG Kr1 500 50 500 +KREUZUNG Kr2 0 300 500 +KREUZUNG Kr3 0 300 700 +KREUZUNG Kr4 0 600 150 +KREUZUNG Kr5 0 600 500 +KREUZUNG Kr6 0 850 50 +KREUZUNG Kr7 200 850 500 +KREUZUNG Kr8 1000 850 850 +STRASSE Kr1 Kr6 Weg16 Weg61 220 1 0 3 50 500 50 50 850 50 +STRASSE Kr1 Kr2 Weg12 Weg21 40 3 1 2 50 500 300 500 +STRASSE Kr1 Kr8 Weg18 Weg81 180 3 0 3 50 500 50 850 850 850 +STRASSE Kr2 Kr2 Weg22a Weg22b 80 1 1 6 300 500 200 350 250 250 350 250 400 350 300 500 +STRASSE Kr2 Kr5 Weg25 Weg52 40 3 1 2 300 500 600 500 +STRASSE Kr2 Kr3 Weg23 Weg32 30 3 1 2 300 500 300 700 +STRASSE Kr4 Kr5 Weg45b Weg54b 60 3 1 5 600 150 550 150 500 200 500 300 600 500 +STRASSE Kr5 Kr4 Weg54a Weg45a 60 3 1 5 600 500 700 300 700 200 650 150 600 150 +STRASSE Kr5 Kr8 Weg58 Weg85 70 3 1 2 600 500 850 850 +STRASSE Kr6 Kr7 Weg67 Weg76 50 3 1 2 850 50 850 500 +STRASSE Kr7 Kr8 Weg78 Weg87 50 3 1 2 850 500 850 850 +STRASSE Kr8 Kr6 Weg86 Weg68 120 2 1 4 850 850 950 850 950 50 850 50 +STRASSE Kr5 Kr7 Weg57 Weg75 30 3 1 2 600 500 850 500 +PKW Trabant 100 6 50 Kr3 2.0 +PKW Mercedes 180 12 60 Kr3 2.5 +FAHRRAD Peugeot 40 Kr7 4 +PKW Porsche 240 14.5 70 Kr7 3.3 +FAHRRAD BMX 24 Kr1 1.5 +PKW Ferrari 250 15 100 Kr6 4.25 diff --git a/pi2-demo/include/Bicycle.hpp b/pi2-demo/include/Bicycle.hpp new file mode 100644 index 0000000..29f005c --- /dev/null +++ b/pi2-demo/include/Bicycle.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "Vehicle.hpp" +#include +class MapAppABIWrapper; + +class Bicycle : public Vehicle { +public: + Bicycle(std::string name, double topSpeed, double timeOfStart, double time); + + void draw(MapAppABIWrapper &mapApp, const std::string &laneName, + double laneLength, double speedLimit) const override; + + double getSpeed(double speedLimit) const override; +}; diff --git a/pi2-demo/include/Car.hpp b/pi2-demo/include/Car.hpp new file mode 100644 index 0000000..044be76 --- /dev/null +++ b/pi2-demo/include/Car.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "Vehicle.hpp" +#include +class MapAppABIWrapper; + +class Car : public Vehicle { +public: + /// The fuel tank is half full at construction + Car(std::string name, double topSpeed, double timeOfStart, double fuelConsumption, + double fuelCapacity, double time); + + void draw(MapAppABIWrapper &mapApp, const std::string &laneName, + double laneLength, double speedLimit) const override; + + double getSpeed(double speedLimit) const override; + + void process(double time, double maximumDistanceOnLane, + double speedLimit) override; + + /// Refuels the vehicle provided the \p availableFuel in l + /// \returns The amount of fuel in l that has been taken + double refuel(double availableFuel); + +private: + /// Fuel consumption in l/100km + double fuelConsumption; + + /// Fuel capacity in l + double fuelCapacity; + + /// Remaining fuel in l + double fuel; +}; diff --git a/pi2-demo/include/DeferralActionWrapper.hpp b/pi2-demo/include/DeferralActionWrapper.hpp new file mode 100644 index 0000000..ed47932 --- /dev/null +++ b/pi2-demo/include/DeferralActionWrapper.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +/// Wraps an object and defers modifying actions +/// until explicitly applying them +/// +/// \param T The type of the wrapped object +template class DeferralActionWrapper { +public: + using ValueType = T; + using Function = std::function; + + /// Constructs a wrapper around a default constructed object + DeferralActionWrapper() = default; + + /// Constructs a wrapper around a given \p object + explicit DeferralActionWrapper(ValueType object) + : wrappedObject(std::move(object)) {} + + /// Returns a the wrapped object + ValueType &get() { return wrappedObject; } + const ValueType &get() const { return wrappedObject; } + + /// Enqueues an \p action + /// + /// Caution: Be sure to work on a reference to modify the wrapped object! + void enqueue(Function action) { + deferredActions.push_back(std::move(action)); + } + + /// Applies pending modifying actions + void apply() { + for (auto &&action : deferredActions) { + action(wrappedObject); + } + deferredActions.clear(); + } + +private: + ValueType wrappedObject; + std::vector deferredActions; +}; diff --git a/pi2-demo/include/Junction.hpp b/pi2-demo/include/Junction.hpp new file mode 100644 index 0000000..82b77ab --- /dev/null +++ b/pi2-demo/include/Junction.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "Lane.hpp" +#include "Named.hpp" +#include +#include +#include +#include +class Vehicle; +class MapAppABIWrapper; + +class Junction : public Named { +public: + /// Creates two lanes between \p junctionA and \p junctionB + static void connect(const std::weak_ptr &junctionA, + const std::weak_ptr &junctionB, + const std::string &laneAToBName, + const std::string &laneBToAName, double length, + double speedLimit, bool noPassing); + + Junction(Junction &&other) noexcept; + Junction &operator=(Junction &&other) noexcept; + + Junction(std::string name, double fuelAmount); + + void processVehicles(double time); + + void drawVehicles(MapAppABIWrapper &mapApp) const; + + void createCar(std::string name, double topSpeed, double fuelConsumption, + double fuelCapacity, double timeOfStart, double time); + + void createBicycle(std::string name, double topSpeed, double timeOfStart, + double time); + + void acceptVehicle(std::unique_ptr vehicle, + const std::string &prohibitedLaneName); + +private: + /// Mersenne Twister random number generator + /// for selection in randomOutboundLaneExcept + static std::mt19937 gen; + + std::vector outboundLanes; + + /// The amount of fuel in l in the filling station + double fuelAmount; + + /// Refuels the given vehicle using the filling station + void fillUp(Vehicle &fahrzeug); + + Lane &randomOutboundLane(); + + Lane &randomOutboundLaneExcept(const std::string &laneName); +}; diff --git a/pi2-demo/include/Lane.hpp b/pi2-demo/include/Lane.hpp new file mode 100644 index 0000000..c480902 --- /dev/null +++ b/pi2-demo/include/Lane.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Named.hpp" +#include "Vehicle.hpp" +#include +#include +#include +class MapAppABIWrapper; +class Junction; + +class Lane : public Named { +public: + Lane(Lane &&other) noexcept; + + Lane(std::string name, std::string oppositeLaneName, double length, + double speedLimit, bool noPassing, + std::weak_ptr destinationJunction); + + void drawVehicles(MapAppABIWrapper &mapApp) const; + + void processVehicles(double time); + + void acceptVehicle(std::unique_ptr vehicle); + + void createCar(std::string name, double maximumVelocity, + double fuelConsumption, double fuelCapacity, + double timeOfStart, double time); + + void createBicycle(std::string name, double maximumVelocity, + double timeOfStart, double time); + +private: + /// Length property in km + double length; + + std::weak_ptr destinationJunction; + + std::string oppositeLaneName; + + double speedLimit; + + bool noPassing; + + std::vector> vehicles; +}; diff --git a/pi2-demo/include/Map.hpp b/pi2-demo/include/Map.hpp new file mode 100644 index 0000000..dbe8c57 --- /dev/null +++ b/pi2-demo/include/Map.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "MapAppABIWrapper.hpp" +#include +#include +#include +#include +#include +class Junction; + +class Map { +public: + explicit Map(std::istream &is); + + void simulate(std::chrono::seconds duration, double speedFactor, + double frequency); + +private: + double time; + std::vector> junctions; + MapAppABIWrapper mapApp; + + std::weak_ptr getJunction(const std::string &name); + + /// Required format: + /// `name` `fuelAmount` `x` `y` + void extractJunctionAndDraw(std::istream &is, MapAppABIWrapper &mapApp); + + /// Required format: + /// `nameJunctionA` `nameJunctionB` `nameLaneAToB` `nameLaneBToA` `length in km` + /// `speedLimit in {1,2,3}` `noPassing` `coordinateCount` `coordinates...` + void extractRoadAndDraw(std::istream &is, MapAppABIWrapper &mapApp); + + /// Required format: + /// `name` `topSpeed` `nameStartJunction` `timeOfStart` + void extractBicycle(std::istream &is); + + /// Required format: + /// `name` `topSpeed` `fuelConsumption` `fuelCapacity` `nameStartJunction` + /// `timeOfStart` + void extractCar(std::istream &is); +}; diff --git a/pi2-demo/include/MapAppABIWrapper.hpp b/pi2-demo/include/MapAppABIWrapper.hpp new file mode 100644 index 0000000..1b56ca2 --- /dev/null +++ b/pi2-demo/include/MapAppABIWrapper.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +struct MapApp; + +class MapAppABIWrapper { +public: + MapAppABIWrapper(); + virtual ~MapAppABIWrapper(); + void addJunction(double x, double y); + void addRoad(const std::string &laneThereName, + const std::string &laneBackName, + const std::vector &coordinates); + void addOrReplaceVehicle(const std::string &vehicleName, + const std::string &laneName, double positionOnLane, + double speed, double remainingFuel, double red, + double green, double blue); + void setDurationLabel(std::chrono::minutes minutes); + +private: + MapApp *mapApp; +}; diff --git a/pi2-demo/include/Named.hpp b/pi2-demo/include/Named.hpp new file mode 100644 index 0000000..9976de6 --- /dev/null +++ b/pi2-demo/include/Named.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +class Named { +public: + Named(const Named &other); + Named(Named &&other) noexcept; + Named &operator=(const Named &other); + Named &operator=(Named &&other) noexcept; + virtual ~Named() = 0; + + std::string getName() const; + +protected: + explicit Named(std::string name); + +private: + std::string name; +}; diff --git a/pi2-demo/include/Vehicle.hpp b/pi2-demo/include/Vehicle.hpp new file mode 100644 index 0000000..0545ec5 --- /dev/null +++ b/pi2-demo/include/Vehicle.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "Named.hpp" +#include +class MapAppABIWrapper; + +class Vehicle : public Named { +public: + // Draws the vehicle on the provided \p mapApp on the specified \p laneName + // which has a \p laneLength and a \p speedLimit + virtual void draw(MapAppABIWrapper &mapApp, const std::string &laneName, + double laneLength, double speedLimit) const = 0; + + /// \returns The current speed in km/h + virtual double getSpeed(double speedLimit) const = 0; + + /// Updates the total traveled distance, the total traveled time and + /// the time of this tick + virtual void process(double time, double maximumDistanceOnLane, + double speedLimit); + + double getDistanceOnLane() const; + + void resetDistanceOnLane(); + +protected: + Vehicle(std::string name, double topSpeed, double timeOfStart, double time); + + double getTopSpeed() const; + + double getGesamtStrecke() const; + + bool isParking() const; + +private: + /// Maximum speed in km/h + double topSpeed; + + /// Total travel distance in km + double odometer; + + /// Current distance on lane in km + double distanceOnLane; + + bool parking; + + double timeOfStart; + + double timeOfLastProcessing; +}; diff --git a/pi2-demo/meson.build b/pi2-demo/meson.build new file mode 100644 index 0000000..156f0de --- /dev/null +++ b/pi2-demo/meson.build @@ -0,0 +1,19 @@ +pi2_demo_inc = include_directories('include') +pi2_demo_src = files( + 'src/Named.cpp', + 'src/Bicycle.cpp', + 'src/Vehicle.cpp', + 'src/Junction.cpp', + 'src/Car.cpp', + 'src/Lane.cpp', + 'src/Map.cpp', + 'src/main.cpp', + 'src/MapAppABIWrapper.cpp' +) +boost_po_dep = dependency('boost', modules : ['program_options']) +executable('pi2-demo', pi2_demo_src, + include_directories: pi2_demo_inc, + dependencies: [pi2_view_dep, boost_po_dep], + install: true +) +install_headers('include/MapAppABIWrapper.hpp', 'src/MapAppABIWrapper.cpp', subdir : 'pi2-view') diff --git a/pi2-demo/src/Bicycle.cpp b/pi2-demo/src/Bicycle.cpp new file mode 100644 index 0000000..a4a08f8 --- /dev/null +++ b/pi2-demo/src/Bicycle.cpp @@ -0,0 +1,22 @@ +#include "Bicycle.hpp" +#include "MapAppABIWrapper.hpp" +#include +#include +#include + +Bicycle::Bicycle(std::string name, const double topSpeed, + const double timeOfStart, const double time) + : Vehicle(std::move(name), topSpeed, timeOfStart, time) {} + +void Bicycle::draw(MapAppABIWrapper &mapApp, const std::string &laneName, + const double laneLength, const double speedLimit) const { + const auto positionOnLane = getDistanceOnLane() / laneLength; + mapApp.addOrReplaceVehicle(getName(), laneName, positionOnLane, + getSpeed(speedLimit), 0, 0, 1, 0); +} + +double Bicycle::getSpeed(const double speedLimit) const { + auto const slowingSpeed = + getTopSpeed() * std::pow(0.9, std::floor(getGesamtStrecke() / 20)); + return isParking() ? 0 : std::min(std::max(slowingSpeed, 12.0), speedLimit); +} diff --git a/pi2-demo/src/Car.cpp b/pi2-demo/src/Car.cpp new file mode 100644 index 0000000..069210e --- /dev/null +++ b/pi2-demo/src/Car.cpp @@ -0,0 +1,36 @@ +#include "Car.hpp" +#include "MapAppABIWrapper.hpp" +#include +#include + +Car::Car(std::string name, const double topSpeed, const double timeOfStart, + const double fuelConsumption, const double fuelCapacity, + const double time) + : Vehicle(std::move(name), topSpeed, timeOfStart, time), + fuelConsumption(fuelConsumption), fuelCapacity(fuelCapacity), + fuel(fuelCapacity / 2) {} + +void Car::draw(MapAppABIWrapper &mapApp, const std::string &laneName, + const double laneLength, const double speedLimit) const { + const auto positionOnLane = getDistanceOnLane() / laneLength; + mapApp.addOrReplaceVehicle(getName(), laneName, positionOnLane, + getSpeed(speedLimit), fuel, 1, 0, 0); +} + +double Car::getSpeed(const double speedLimit) const { + const auto potentialSpeed = std::min(speedLimit, getTopSpeed()); + return isParking() || fuel <= 0 ? 0 : potentialSpeed; +} + +void Car::process(const double time, const double maximumDistanceOnLane, + const double speedLimit) { + const auto oldDistance = getGesamtStrecke(); + Vehicle::process(time, maximumDistanceOnLane, speedLimit); + fuel -= fuelConsumption * (getGesamtStrecke() - oldDistance) / 100; +} + +double Car::refuel(const double availableFuel) { + const auto refueledAmount = std::min(availableFuel, fuelCapacity - fuel); + fuel += refueledAmount; + return refueledAmount; +} diff --git a/pi2-demo/src/Junction.cpp b/pi2-demo/src/Junction.cpp new file mode 100644 index 0000000..92e7f2c --- /dev/null +++ b/pi2-demo/src/Junction.cpp @@ -0,0 +1,87 @@ +#include "Junction.hpp" +#include "Car.hpp" +#include "Vehicle.hpp" +#include +#include +#include +#include +class MapAppABIWrapper; + +void Junction::connect(const std::weak_ptr &junctionA, + const std::weak_ptr &junctionB, + const std::string &laneAToBName, + const std::string &laneBToAName, const double length, + const double speedLimit, const bool noPassing) { + Lane laneAToB(laneAToBName, laneBToAName, length, speedLimit, noPassing, + junctionB); + Lane laneBToA(laneBToAName, laneAToBName, length, speedLimit, noPassing, + junctionA); + junctionA.lock()->outboundLanes.push_back(std::move(laneAToB)); + junctionB.lock()->outboundLanes.push_back(std::move(laneBToA)); +} + +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +Junction::Junction(Junction &&other) noexcept = default; +Junction &Junction::operator=(Junction &&other) noexcept = default; + +Junction::Junction(std::string name, const double fuelAmount) + : Named(std::move(name)), fuelAmount(fuelAmount) {} + +void Junction::processVehicles(const double time) { + for (auto &&lane : outboundLanes) { + lane.processVehicles(time); + } +} + +void Junction::drawVehicles(MapAppABIWrapper &mapApp) const { + for (auto &&weg : outboundLanes) { + weg.drawVehicles(mapApp); + } +} + +void Junction::createCar(std::string name, const double topSpeed, + const double fuelConsumption, + const double fuelCapacity, const double timeOfStart, + const double time) { + randomOutboundLane().createCar(std::move(name), topSpeed, fuelConsumption, + fuelCapacity, timeOfStart, time); +} + +void Junction::createBicycle(std::string name, const double topSpeed, + const double timeOfStart, const double time) { + randomOutboundLane().createBicycle(std::move(name), topSpeed, timeOfStart, + time); +} + +void Junction::acceptVehicle(std::unique_ptr vehicle, + const std::string &prohibitedLaneName) { + fillUp(*vehicle); + vehicle->resetDistanceOnLane(); + randomOutboundLaneExcept(prohibitedLaneName) + .acceptVehicle(std::move(vehicle)); +} + +std::mt19937 Junction::gen([]() { + std::random_device rd; + return rd(); +}()); + +void Junction::fillUp(Vehicle &fahrzeug) { + try { + auto &car = dynamic_cast(fahrzeug); + fuelAmount -= car.refuel(fuelAmount); + } catch (const std::bad_cast &e) { + } +} + +Lane &Junction::randomOutboundLane() { + std::uniform_int_distribution<> dis(0, outboundLanes.size() - 1); + return outboundLanes.at(dis(gen)); +} + +Lane &Junction::randomOutboundLaneExcept(const std::string &laneName) { + auto &randomLane = randomOutboundLane(); + return randomLane.getName() != laneName || outboundLanes.size() <= 1 + ? randomLane + : randomOutboundLaneExcept(laneName); +} diff --git a/pi2-demo/src/Lane.cpp b/pi2-demo/src/Lane.cpp new file mode 100644 index 0000000..6600ac6 --- /dev/null +++ b/pi2-demo/src/Lane.cpp @@ -0,0 +1,65 @@ +#include "Lane.hpp" +#include "Bicycle.hpp" +#include "Car.hpp" +#include "Junction.hpp" +#include "Vehicle.hpp" +#include +#include +#include +class MapAppABIWrapper; + +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +Lane::Lane(Lane &&other) noexcept = default; + +Lane::Lane(std::string name, std::string oppositeLaneName, const double length, + const double speedLimit, const bool noPassing, + std::weak_ptr destinationJunction) + : Named(std::move(name)), length(length), + destinationJunction(std::move(destinationJunction)), + oppositeLaneName(std::move(oppositeLaneName)), speedLimit(speedLimit), + noPassing(noPassing) {} + +void Lane::drawVehicles(MapAppABIWrapper &mapApp) const { + for (auto &&fahrzeug : vehicles) { + fahrzeug->draw(mapApp, getName(), length, speedLimit); + } +} + +void Lane::processVehicles(const double time) { + const auto furthestFirstComparator = [](const auto &vehicleA, + const auto &vehicleB) { + return vehicleA->getDistanceOnLane() > vehicleB->getDistanceOnLane(); + }; + std::sort(vehicles.begin(), vehicles.end(), furthestFirstComparator); + double maximumPosition = length; + for (auto &&fahrzeug : vehicles) { + fahrzeug->process(time, maximumPosition, speedLimit); + if (noPassing && fahrzeug->getSpeed(speedLimit) != 0) { + maximumPosition = fahrzeug->getDistanceOnLane(); + } + } + std::sort(vehicles.begin(), vehicles.end(), furthestFirstComparator); + while (!vehicles.empty() && length <= vehicles.at(0)->getDistanceOnLane()) { + destinationJunction.lock()->acceptVehicle(std::move(vehicles.front()), + oppositeLaneName); + vehicles.erase(vehicles.begin()); + } +} + +void Lane::acceptVehicle(std::unique_ptr vehicle) { + vehicles.push_back(std::move(vehicle)); +} + +void Lane::createCar(std::string name, const double maximumVelocity, + const double fuelConsumption, const double fuelCapacity, + const double timeOfStart, const double time) { + vehicles.push_back(std::make_unique(std::move(name), maximumVelocity, + timeOfStart, fuelConsumption, + fuelCapacity, time)); +} + +void Lane::createBicycle(std::string name, const double maximumVelocity, + const double timeOfStart, const double time) { + vehicles.push_back(std::make_unique(std::move(name), maximumVelocity, + timeOfStart, time)); +} diff --git a/pi2-demo/src/Map.cpp b/pi2-demo/src/Map.cpp new file mode 100644 index 0000000..b917203 --- /dev/null +++ b/pi2-demo/src/Map.cpp @@ -0,0 +1,131 @@ +#include "Map.hpp" +#include "Junction.hpp" +#include "MapAppABIWrapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Map::Map(std::istream &is) : time(0) { + std::string type; + while (is >> type) { + if (type == "KREUZUNG") { + extractJunctionAndDraw(is, mapApp); + } else if (type == "STRASSE") { + extractRoadAndDraw(is, mapApp); + } else if (type == "PKW") { + extractCar(is); + } else if (type == "FAHRRAD") { + extractBicycle(is); + } + } +} + +void Map::simulate(const std::chrono::seconds duration, + const double speedFactor, const double frequency) { + const auto count = + static_cast(duration.count() * frequency / speedFactor); + for (size_t i = 0; i < count; i++) { + time += static_cast(duration.count()) / 60 / 60 / count; + for (auto &&junction : junctions) { + junction->processVehicles(time); + junction->drawVehicles(mapApp); + } + mapApp.setDurationLabel(std::chrono::minutes(static_cast(time * 60))); + std::this_thread::sleep_for(std::chrono::nanoseconds( + static_cast(std::nano::den / frequency))); + } +} + +std::weak_ptr Map::getJunction(const std::string &name) { + return *std::find_if( + junctions.begin(), junctions.end(), + [&name](const auto &junction) { return junction->getName() == name; }); +} + +void Map::extractJunctionAndDraw(std::istream &is, MapAppABIWrapper &mapApp) { + std::string name; + double fuelAmount; + is >> name >> fuelAmount; + auto junction = std::make_shared(name, fuelAmount); + junctions.push_back(std::move(junction)); + int x; + int y; + is >> x >> y; + mapApp.addJunction(x, y); +} + +void Map::extractRoadAndDraw(std::istream &is, MapAppABIWrapper &mapApp) { + std::string nameJunctionA; + std::string nameJunctionB; + std::string nameLaneAToB; + std::string nameLaneBToA; + double lengthRoad; + int speedLimitLevel; + bool noPassing; + is >> nameJunctionA >> nameJunctionB >> nameLaneAToB >> nameLaneBToA >> + lengthRoad >> speedLimitLevel >> noPassing; + + double speedLimit; + switch (speedLimitLevel) { + case 1: + speedLimit = 50; + break; + case 2: + speedLimit = 100; + break; + case 3: + speedLimit = std::numeric_limits::infinity(); + break; + default: + throw std::invalid_argument('\'' + std::to_string(speedLimitLevel) + + "' does not encode a speed limit."); + } + + auto junctionA = getJunction(nameJunctionA); + auto junctionB = getJunction(nameJunctionB); + Junction::connect(junctionA, junctionB, nameLaneAToB, nameLaneBToA, + lengthRoad, speedLimit, noPassing); + int coordinatePairCount; + is >> coordinatePairCount; + std::vector coordinates(coordinatePairCount * 2); + std::generate(coordinates.begin(), coordinates.end(), [&is]() { + double x; + is >> x; + return x; + }); + mapApp.addRoad(nameLaneAToB, nameLaneBToA, coordinates); +} + +void Map::extractBicycle(std::istream &is) { + std::string name; + double maximumVelocity; + is >> name >> maximumVelocity; + std::string nameStartJunction; + double timeOfStart; + is >> nameStartJunction >> timeOfStart; + auto &junction = *getJunction(nameStartJunction).lock(); + junction.createBicycle(name, maximumVelocity, timeOfStart, time); +} + +void Map::extractCar(std::istream &is) { + std::string name; + double maximumVelocity; + double fuelConsumption; + double fuelCapacity; + is >> name >> maximumVelocity >> fuelConsumption >> fuelCapacity; + std::string nameStartJunction; + double timeOfStart; + is >> nameStartJunction >> timeOfStart; + auto &junction = *getJunction(nameStartJunction).lock(); + junction.createCar(name, maximumVelocity, fuelConsumption, fuelCapacity, + timeOfStart, time); +} diff --git a/pi2-demo/src/MapAppABIWrapper.cpp b/pi2-demo/src/MapAppABIWrapper.cpp new file mode 100644 index 0000000..a74fd1d --- /dev/null +++ b/pi2-demo/src/MapAppABIWrapper.cpp @@ -0,0 +1,35 @@ +#include "MapAppABIWrapper.hpp" +#include "MapAppABI.h" +#include +#include + +MapAppABIWrapper::MapAppABIWrapper() : mapApp(mapAppCreate()) {} + +MapAppABIWrapper::~MapAppABIWrapper() { + mapAppDestroy(mapApp); + mapApp = nullptr; +} + +void MapAppABIWrapper::addJunction(const double x, const double y) { + mapAppAddJunction(mapApp, x, y); +} + +void MapAppABIWrapper::addRoad(const std::string &laneThereName, + const std::string &laneBackName, + const std::vector &coordinates) { + mapAppAddRoad(mapApp, laneThereName.c_str(), laneBackName.c_str(), + coordinates.data(), coordinates.size()); +} + +void MapAppABIWrapper::addOrReplaceVehicle( + const std::string &vehicleName, const std::string &laneName, + const double positionOnLane, const double speed, const double remainingFuel, + const double red, const double green, const double blue) { + mapAppaddOrReplaceVehicle(mapApp, vehicleName.c_str(), laneName.c_str(), + positionOnLane, speed, remainingFuel, red, green, + blue); +} + +void MapAppABIWrapper::setDurationLabel(const std::chrono::minutes minutes) { + mapAppSetDurationLabel(mapApp, minutes.count()); +} diff --git a/pi2-demo/src/Named.cpp b/pi2-demo/src/Named.cpp new file mode 100644 index 0000000..0fb4491 --- /dev/null +++ b/pi2-demo/src/Named.cpp @@ -0,0 +1,12 @@ +#include "Named.hpp" +#include + +Named::Named(const Named &other) = default; +Named::Named(Named &&other) noexcept = default; +Named &Named::operator=(const Named &other) = default; +Named &Named::operator=(Named &&other) noexcept = default; +Named::~Named() = default; + +std::string Named::getName() const { return name; } + +Named::Named(std::string name) : name(std::move(name)) {} diff --git a/pi2-demo/src/Vehicle.cpp b/pi2-demo/src/Vehicle.cpp new file mode 100644 index 0000000..1fa0dd3 --- /dev/null +++ b/pi2-demo/src/Vehicle.cpp @@ -0,0 +1,35 @@ +#include "Vehicle.hpp" +#include +#include + +void Vehicle::process(const double time, const double maximumDistanceOnLane, + const double speedLimit) { + if (parking && time >= timeOfStart) { + parking = false; + } + + if (!parking) { + const auto possibleDistance = maximumDistanceOnLane - getDistanceOnLane(); + const auto potentialDistance = (time - timeOfLastProcessing) * getSpeed(speedLimit); + const auto deltaDistance = std::min(possibleDistance, potentialDistance); + distanceOnLane += deltaDistance; + odometer += deltaDistance; + } + + timeOfLastProcessing = time; +} + +double Vehicle::getDistanceOnLane() const { return distanceOnLane; } + +void Vehicle::resetDistanceOnLane() { distanceOnLane = 0; } + +Vehicle::Vehicle(std::string name, const double topSpeed, + const double timeOfStart, const double time) + : Named(std::move(name)), topSpeed(topSpeed), odometer(0), distanceOnLane(0), parking(true), + timeOfStart(timeOfStart), timeOfLastProcessing(time) {} + +double Vehicle::getTopSpeed() const { return topSpeed; } + +double Vehicle::getGesamtStrecke() const { return odometer; } + +bool Vehicle::isParking() const { return parking; } diff --git a/pi2-demo/src/main.cpp b/pi2-demo/src/main.cpp new file mode 100644 index 0000000..fb482ad --- /dev/null +++ b/pi2-demo/src/main.cpp @@ -0,0 +1,87 @@ +#include "Map.hpp" +#include +#include +#include +#include +#include +#include +#include +namespace po = boost::program_options; + +int main(int argc, char *argv[]) { + try { + std::string durationString; + double timescale; + double refreshRate; + std::string filename; + + po::options_description visibleOptions("Allowed options"); + visibleOptions.add_options()( + "duration,t", + po::value(&durationString)->default_value("20:00:00"), + "simulate this amount of time")( + "timescale,s", po::value(×cale)->default_value(30 * 60), + "scale the simulation time with this factor")( + "rate,r", po::value(&refreshRate)->default_value(60), + "refresh the simulation with this frequency")( + "help", "display this help and exit"); + + po::options_description hiddenOptions("Hidden options"); + hiddenOptions.add_options()("input-file", po::value(&filename), + "input file"); + + po::options_description allOptions; + allOptions.add(visibleOptions).add(hiddenOptions); + + po::positional_options_description positionalOptions; + positionalOptions.add("input-file", -1); + + po::variables_map variablesMap; + po::store(po::command_line_parser(argc, argv) + .options(allOptions) + .positional(positionalOptions) + .run(), + variablesMap); + po::notify(variablesMap); + + if (variablesMap.count("help") > 0) { + std::cout + << "Usage: ./pi2-demo/pi2-demo [OPTION]... [FILE]\n" + "Parses and simulates FILE according to the PI-2 of the RWTH.\n\n" + << visibleOptions << std::endl; + return 1; + } + + if (variablesMap.count("input-file") > 0) { + std::ifstream istrm(filename); + if (istrm.is_open()) { + Map map(istrm); + + std::istringstream buffer(durationString); + int hours; + int minutes = 0; + int seconds = 0; + buffer >> hours; + buffer.ignore(); + buffer >> minutes; + buffer.ignore(); + buffer >> seconds; + + std::chrono::seconds duration = std::chrono::hours(hours) + + std::chrono::minutes(minutes) + + std::chrono::seconds(seconds); + + map.simulate(duration, timescale, refreshRate); + std::cout << "Simulation done!" << std::endl; + } else { + std::cout << "Failed to open " << filename << std::endl; + return 1; + } + } else { + std::cout << "Input file was not set.\n"; + } + } catch (std::exception &e) { + std::cout << e.what() << std::endl; + return 1; + } +} diff --git a/pi2-view/data/pi2-view.glade b/pi2-view/data/pi2-view.glade new file mode 100644 index 0000000..57912bd --- /dev/null +++ b/pi2-view/data/pi2-view.glade @@ -0,0 +1,291 @@ + + + + + + False + + + + + + True + False + + + True + True + in + True + True + + + True + False + + + + + + + + True + True + 0 + + + + + True + False + vertical + + + True + False + 5 + 5 + 5 + 5 + + + True + False + end + Duration: + + + 0 + 1 + + + + + True + False + 00:00 + + + + + + 1 + 1 + 2 + + + + + True + False + end + Remaining fuel: + + + 0 + 6 + + + + + True + False + end + 0.00 + + + + + + 1 + 6 + + + + + True + False + start + l + + + 2 + 6 + + + + + True + False + end + Speed: + + + 0 + 5 + + + + + True + False + end + 0.00 + + + + + + 1 + 5 + + + + + True + False + start + km/h + + + 2 + 5 + + + + + True + False + start + % + + + 2 + 4 + + + + + True + False + end + 00 + + + + + + 1 + 4 + + + + + True + False + end + Position on Lane: + + + 0 + 4 + + + + + True + False + end + Drives on: + + + 0 + 3 + + + + + True + False + lane + + + 1 + 3 + 2 + + + + + True + False + 5 + 5 + 5 + 5 + True + + + True + 5 + + + + + 1 + 0 + 2 + + + + + True + False + end + Scale: + + + 0 + 0 + + + + + True + False + + + 0 + 2 + 3 + + + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 1 + + + + + + diff --git a/pi2-view/data/pi2-view.gresource.xml b/pi2-view/data/pi2-view.gresource.xml new file mode 100644 index 0000000..a2e0af6 --- /dev/null +++ b/pi2-view/data/pi2-view.gresource.xml @@ -0,0 +1,6 @@ + + + + pi2-view.glade + + diff --git a/pi2-view/include/Drawable.hpp b/pi2-view/include/Drawable.hpp new file mode 100644 index 0000000..1561467 --- /dev/null +++ b/pi2-view/include/Drawable.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +namespace Cairo { +class Context; +template class RefPtr; +} // namespace Cairo + +enum class DrawableTrait { + X, + Y, + XPlusWidth, + YPlusHeight, +}; + +class Drawable { +public: + static std::function + comparator(DrawableTrait drawableTrait); + + template + static void getRegion(ForwardIt first, ForwardIt last, int &x, int &y, + int &width, int &height) { + using Reference = typename std::iterator_traits::reference; + static_assert(std::is_convertible::value, + "We need to work on Drawables"); + + int xMinX, yMinX, widthMinX, heightMinX; + std::min_element(first, last, comparator(DrawableTrait::X)) + ->getRegion(xMinX, yMinX, widthMinX, heightMinX); + + int xMinY, yMinY, widthMinY, heightMinY; + std::min_element(first, last, comparator(DrawableTrait::Y)) + ->getRegion(xMinY, yMinY, widthMinY, heightMinY); + + int xMaxXPlusWidth, yMaxXPlusWidth, widthMaxXPlusWidth, heightMaxXPlusWidth; + std::max_element(first, last, comparator(DrawableTrait::XPlusWidth)) + ->getRegion(xMaxXPlusWidth, yMaxXPlusWidth, widthMaxXPlusWidth, + heightMaxXPlusWidth); + + int xMaxYPlusHeight, yMaxYPlusHeight, widthMaxYPlusHeight, + heightMaxYPlusHeight; + std::max_element(first, last, comparator(DrawableTrait::YPlusHeight)) + ->getRegion(xMaxYPlusHeight, yMaxYPlusHeight, widthMaxYPlusHeight, + heightMaxYPlusHeight); + + x = xMinX; + y = yMinY; + width = xMaxXPlusWidth + widthMaxXPlusWidth - x; + height = yMaxYPlusHeight + heightMaxYPlusHeight - y; + } + + Drawable(const Drawable &other); + Drawable(Drawable &&other) noexcept; + Drawable &operator=(const Drawable &other); + Drawable &operator=(Drawable &&other) noexcept; + virtual ~Drawable() = 0; + + virtual void draw(const Cairo::RefPtr &cr) const = 0; + virtual void getRegion(int &x, int &y, int &width, int &height) const = 0; + +protected: + Drawable(); +}; diff --git a/pi2-view/include/DrawableJunction.hpp b/pi2-view/include/DrawableJunction.hpp new file mode 100644 index 0000000..db6a899 --- /dev/null +++ b/pi2-view/include/DrawableJunction.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "Drawable.hpp" +namespace Cairo { +class Context; +template class RefPtr; +} // namespace Cairo + +class DrawableJunction : public Drawable { +public: + DrawableJunction(double xc, double yc); + + void draw(const Cairo::RefPtr &cr) const override; + void getRegion(int &x, int &y, int &width, int &height) const override; + +private: + const static double length; + + double x, y; +}; diff --git a/pi2-view/include/DrawableRoad.hpp b/pi2-view/include/DrawableRoad.hpp new file mode 100644 index 0000000..8961c6c --- /dev/null +++ b/pi2-view/include/DrawableRoad.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +namespace Cairo { +class Context; +template class RefPtr; +} // namespace Cairo + +class DrawableRoad : public Drawable { +public: + DrawableRoad(std::string laneThereName, std::string laneBackName, + std::vector> coordinates); + + void draw(const Cairo::RefPtr &cr) const override; + void getRegion(int &x, int &y, int &width, int &height) const override; + + void getCoordinatesOf(const std::string &laneName, double positionOnLane, + double &x, double &y) const; + + bool contains(const std::string &laneName) const; + +private: + const static double width; + + std::string laneThereName; + std::string laneBackName; + double pixelLength; + std::vector> coordinates; +}; diff --git a/pi2-view/include/DrawableVehicle.hpp b/pi2-view/include/DrawableVehicle.hpp new file mode 100644 index 0000000..7c5135f --- /dev/null +++ b/pi2-view/include/DrawableVehicle.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Drawable.hpp" +#include +class DrawableRoad; +namespace Cairo { +class Context; +template class RefPtr; +} // namespace Cairo + +class DrawableVehicle : public Drawable { +public: + DrawableVehicle(std::string carName, std::string laneName, + const DrawableRoad &road, double positionOnLane, double speed, + double remainingFuel, double red, double green, double blue); + + void draw(const Cairo::RefPtr &cr) const override; + void getRegion(int &x, int &y, int &width, int &height) const override; + + std::string getLaneName() const; + double getPositionOnLane() const; + double getSpeed() const; + double getRemainingFuel() const; + +private: + const static double radius; + + double xc, yc; + std::string carName; + std::string laneName; + double positionOnLane; + double speed; + double remainingFuel; + double red, green, blue; +}; diff --git a/pi2-view/include/MapApp.hpp b/pi2-view/include/MapApp.hpp new file mode 100644 index 0000000..468bb05 --- /dev/null +++ b/pi2-view/include/MapApp.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "DrawableJunction.hpp" +#include "DrawableRoad.hpp" +#include "DrawableVehicle.hpp" +#include "MapArea.hpp" +#include "glibmm/refptr.h" +#include +#include +#include +#include +#include +#include +#include +namespace Gtk { +class Application; +class ComboBoxText; +class Label; +class ListBox; +class ListBoxRow; +} // namespace Gtk + +class MapApp { +public: + MapApp(); + virtual ~MapApp(); + void addJunction(double x, double y); + void addRoad(std::string laneThereName, std::string laneBackName, + std::vector> coordinates); + void addOrReplaceVehicle(const std::string &vehicleName, + const std::string &laneName, double positionOnLane, + double speed, double remainingFuel, double red, + double green, double blue); + void setDurationLabel(std::chrono::minutes minutes); + +private: + static const std::vector zoomLevels; + void onZoomEntryActivate(); + void onZoomSelectionChanged(); + void onVehicleRowSelected(Gtk::ListBoxRow *row); + void refreshVehicleLabels(const DrawableVehicle &vehicle); + void refreshArea(int x, int y, int width, int height); + Glib::RefPtr app; + std::thread thread; + MapArea mapArea; + Gtk::Window win; + Gtk::ListBox *listBox; + Gtk::Label *durationLabel; + Gtk::Label *laneLabel; + Gtk::Label *lanePositionPercentageLabel; + Gtk::Label *speedLabel; + Gtk::Label *remainingFuelLabel; + Gtk::ComboBoxText *zoomComboBoxText; + std::vector junctions; + std::vector roads; + std::map nameVehicleMap; +}; diff --git a/pi2-view/include/MapAppABI.h b/pi2-view/include/MapAppABI.h new file mode 100644 index 0000000..b281bd3 --- /dev/null +++ b/pi2-view/include/MapAppABI.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct MapApp; +MapApp *mapAppCreate(); +void mapAppDestroy(MapApp *mapApp); +void mapAppAddJunction(MapApp *mapApp, double x, double y); +void mapAppAddRoad(MapApp *mapApp, const char *laneThereName, + const char *laneBackName, const double *coordinates, + size_t coordinateCount); +void mapAppaddOrReplaceVehicle(MapApp *mapApp, const char *vehicleName, + const char *roadName, double positionOnLane, + double speed, double remainingFuel, double red, + double green, double blue); +void mapAppSetDurationLabel(MapApp *mapApp, int64_t minutes); + +#ifdef __cplusplus +} +#endif diff --git a/pi2-view/include/MapArea.hpp b/pi2-view/include/MapArea.hpp new file mode 100644 index 0000000..84317d4 --- /dev/null +++ b/pi2-view/include/MapArea.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +class DrawableJunction; +class DrawableRoad; +class DrawableVehicle; +namespace Cairo { +class Context; +template class RefPtr; +} // namespace Cairo + +class MapArea : public Gtk::DrawingArea { +public: + MapArea(std::vector &junctions, + std::vector &roads, + std::map &nameVehicleMap); + + void adaptSizeRequest(); + void queue_draw_area(int x, int y, int width, int height); + void setScale(double scale); + +protected: + bool on_draw(const Cairo::RefPtr &cr) override; + +private: + const static int padding; + + double scale; + std::vector &junctions; + std::vector &roads; + std::map &nameVehicleMap; +}; diff --git a/pi2-view/meson.build b/pi2-view/meson.build new file mode 100644 index 0000000..6d25666 --- /dev/null +++ b/pi2-view/meson.build @@ -0,0 +1,28 @@ +pi2_view_inc = include_directories('include') +pi2_view_src = files( + 'src/Drawable.cpp', + 'src/DrawableJunction.cpp', + 'src/MapArea.cpp', + 'src/MapApp.cpp', + 'src/DrawableRoad.cpp', + 'src/DrawableVehicle.cpp', + 'src/MapAppABI.cpp' +) +gnome = import('gnome') +ui_resources = gnome.compile_resources( + 'pi2-view-resources', 'data/pi2-view.gresource.xml', + source_dir: 'data' +) +gtkmm_dep = dependency('gtkmm-3.0', version : '>=3.24.0') +thread_dep = dependency('threads') +pi2_view_lib = shared_library('pi2-view', [pi2_view_src, ui_resources], + dependencies : [gtkmm_dep, thread_dep, microsoft_gsl_dep], + include_directories: pi2_view_inc, + install: true +) +install_headers('include/MapAppABI.h', subdir : 'pi2-view') +pi2_view_dep = declare_dependency( + link_with : pi2_view_lib, + dependencies : [gtkmm_dep, thread_dep, microsoft_gsl_dep], + include_directories: pi2_view_inc +) diff --git a/pi2-view/src/Drawable.cpp b/pi2-view/src/Drawable.cpp new file mode 100644 index 0000000..478d450 --- /dev/null +++ b/pi2-view/src/Drawable.cpp @@ -0,0 +1,31 @@ +#include "Drawable.hpp" +#include + +std::function +Drawable::comparator(DrawableTrait drawableTrait) { + return [drawableTrait](const Drawable &drawableA, const Drawable &drawableB) { + int xA, yA, widthA, heightA; + drawableA.getRegion(xA, yA, widthA, heightA); + int xB, yB, widthB, heightB; + drawableB.getRegion(xB, yB, widthB, heightB); + switch (drawableTrait) { + case DrawableTrait::X: + return xA < xB; + case DrawableTrait::Y: + return yA < yB; + case DrawableTrait::XPlusWidth: + return xA + widthA < xB + widthB; + case DrawableTrait::YPlusHeight: + return yA + heightA < yB + heightB; + default: + throw std::invalid_argument("DrawableTrait unknown"); + } + }; +} + +Drawable::Drawable(const Drawable &other) = default; +Drawable::Drawable(Drawable &&other) noexcept = default; +Drawable &Drawable::operator=(const Drawable &other) = default; +Drawable &Drawable::operator=(Drawable &&other) noexcept = default; +Drawable::~Drawable() = default; +Drawable::Drawable() = default; diff --git a/pi2-view/src/DrawableJunction.cpp b/pi2-view/src/DrawableJunction.cpp new file mode 100644 index 0000000..27fbc51 --- /dev/null +++ b/pi2-view/src/DrawableJunction.cpp @@ -0,0 +1,40 @@ +#include "DrawableJunction.hpp" +#include "cairomm/context.h" +#include "cairomm/enums.h" +#include "cairomm/refptr.h" +#include +#include + +const double DrawableJunction::length = 30; + +DrawableJunction::DrawableJunction(double xc, double yc) + : x(xc - length / 2), y(yc - length / 2) {} + +void DrawableJunction::draw(const Cairo::RefPtr &cr) const { + cr->save(); + + cr->rectangle(x, y, length, length); + cr->fill(); + + cr->restore(); + + cr->save(); + + const auto lineWidth = 2; + cr->set_line_width(lineWidth); + cr->rectangle(x + lineWidth, y + lineWidth, length - 2 * lineWidth, + length - 2 * lineWidth); + cr->set_source_rgb(1, 1, 1); + cr->set_dash(std::vector({2, 9, 4, 9, 2, 0}), 0); + cr->set_line_cap(Cairo::LINE_CAP_SQUARE); + cr->stroke(); + + cr->restore(); +} + +void DrawableJunction::getRegion(int &x, int &y, int &width, + int &height) const { + x = std::floor(this->x); + y = std::floor(this->y); + height = width = std::ceil(length); +} diff --git a/pi2-view/src/DrawableRoad.cpp b/pi2-view/src/DrawableRoad.cpp new file mode 100644 index 0000000..03c654b --- /dev/null +++ b/pi2-view/src/DrawableRoad.cpp @@ -0,0 +1,112 @@ +#include "DrawableRoad.hpp" +#include "cairomm/context.h" +#include "cairomm/enums.h" +#include "cairomm/refptr.h" +#include +#include +#include +#include +#include +#include + +const double DrawableRoad::width = 30; + +DrawableRoad::DrawableRoad(std::string laneThereName, std::string laneBackName, + std::vector> coordinates) + : laneThereName(std::move(laneThereName)), + laneBackName(std::move(laneBackName)), pixelLength(0), + coordinates(coordinates) { + auto &lastCoordinatePair = coordinates.front(); + for (auto &coordinatePair : coordinates) { + auto nextStep = coordinatePair - lastCoordinatePair; + pixelLength += std::hypot(nextStep[0], nextStep[1]); + lastCoordinatePair = coordinatePair; + } +} + +void DrawableRoad::draw(const Cairo::RefPtr &cr) const { + cr->save(); + + cr->move_to(coordinates.front()[0], coordinates.front()[1]); + std::for_each(++coordinates.begin(), coordinates.end(), + [&cr](const std::valarray &coordinatePair) { + cr->line_to(coordinatePair[0], coordinatePair[1]); + }); + + cr->set_line_join(Cairo::LINE_JOIN_ROUND); + cr->set_line_width(30); + cr->stroke_preserve(); + cr->set_line_width(2.56); + cr->set_source_rgb(1, 1, 1); + cr->stroke(); + + cr->restore(); +} + +void DrawableRoad::getRegion(int &x, int &y, int &width, int &height) const { + x = y = std::numeric_limits::max(); + width = height = 0; + for (auto &&coordinatePair : coordinates) { + const auto currentX = static_cast( + std::floor(coordinatePair[0] - DrawableRoad::width / 2)); + const auto currentY = static_cast( + std::floor(coordinatePair[1] - DrawableRoad::width / 2)); + x = std::min(currentX, x); + y = std::min(currentY, y); + const auto currentWidth = static_cast( + std::ceil(coordinatePair[0] + DrawableRoad::width / 2 - x)); + const auto currentHeight = static_cast( + std::ceil(coordinatePair[1] + DrawableRoad::width / 2 - y)); + width = std::max(currentWidth, width); + height = std::max(currentHeight, height); + } +} + +void DrawableRoad::getCoordinatesOf(const std::string &laneName, + const double positionOnLane, double &x, + double &y) const { + if (positionOnLane < 0 || positionOnLane > 1) { + throw std::invalid_argument("positionOnLane is invalid: " + + std::to_string(positionOnLane)); + } + + double currentPixelDistance = 0; + auto currentCoordinatePair = + laneName == laneThereName ? coordinates.front() : coordinates.back(); + auto nextCoordinatePair = currentCoordinatePair; + const auto advanceTraversalAndFindNextStep = + [this, ¤tPixelDistance, ¤tCoordinatePair, + positionOnLane](std::valarray coordinatePair) { + auto nextStep = coordinatePair - currentCoordinatePair; + auto nextStepLength = std::hypot(nextStep[0], nextStep[1]); + auto nextStepIsTooFar = currentPixelDistance + nextStepLength > + positionOnLane * pixelLength; + if (!nextStepIsTooFar) { + currentPixelDistance += nextStepLength; + currentCoordinatePair = coordinatePair; + } + return nextStepIsTooFar; + }; + if (laneName == laneThereName) { + nextCoordinatePair = *std::find_if(++coordinates.begin(), coordinates.end(), + advanceTraversalAndFindNextStep); + } else { + nextCoordinatePair = + *std::find_if(++coordinates.rbegin(), coordinates.rend(), + advanceTraversalAndFindNextStep); + } + const auto nextStep = nextCoordinatePair - currentCoordinatePair; + const auto nextStepLength = std::hypot(nextStep[0], nextStep[1]); + auto offset = std::valarray({-nextStep[1], nextStep[0]}); + offset = offset / nextStepLength * 7.5; + currentCoordinatePair += + (positionOnLane * pixelLength - currentPixelDistance) / nextStepLength * + nextStep; + currentCoordinatePair += offset; + x = currentCoordinatePair[0]; + y = currentCoordinatePair[1]; +} + +bool DrawableRoad::contains(const std::string &laneName) const { + return laneThereName == laneName || laneBackName == laneName; +} diff --git a/pi2-view/src/DrawableVehicle.cpp b/pi2-view/src/DrawableVehicle.cpp new file mode 100644 index 0000000..540a092 --- /dev/null +++ b/pi2-view/src/DrawableVehicle.cpp @@ -0,0 +1,44 @@ +#include "DrawableVehicle.hpp" +#include "DrawableRoad.hpp" +#include "cairomm/context.h" +#include "cairomm/refptr.h" +#include +#include + +const double DrawableVehicle::radius = 5; + +DrawableVehicle::DrawableVehicle(std::string carName, std::string laneName, + const DrawableRoad &road, + const double positionOnLane, + const double speed, const double remainingFuel, + const double red, const double green, + const double blue) + : xc(0), yc(0), carName(std::move(carName)), laneName(std::move(laneName)), + positionOnLane(positionOnLane), speed(speed), + remainingFuel(remainingFuel), red(red), green(green), blue(blue) { + road.getCoordinatesOf(this->laneName, positionOnLane, xc, yc); +} + +void DrawableVehicle::draw(const Cairo::RefPtr &cr) const { + cr->save(); + + cr->set_source_rgb(red, green, blue); + cr->arc(xc, yc, radius, 0, 2 * std::acos(-1)); + cr->fill(); + + cr->restore(); +} + +void DrawableVehicle::getRegion(int &x, int &y, int &width, int &height) const { + x = static_cast(std::floor(xc - radius)) - 1; + y = static_cast(std::floor(yc - radius)) - 1; + width = height = static_cast(std::ceil(2 * radius)) + 2; +} + +std::string DrawableVehicle::getLaneName() const { return laneName; } + +double DrawableVehicle::getPositionOnLane() const { return positionOnLane; } + +double DrawableVehicle::getSpeed() const { return speed; } + +double DrawableVehicle::getRemainingFuel() const { return remainingFuel; } diff --git a/pi2-view/src/MapApp.cpp b/pi2-view/src/MapApp.cpp new file mode 100644 index 0000000..f898eba --- /dev/null +++ b/pi2-view/src/MapApp.cpp @@ -0,0 +1,244 @@ +#include "MapApp.hpp" +#include "glibmm/main.h" +#include "glibmm/priorities.h" +#include "glibmm/signalproxy.h" +#include "glibmm/ustring.h" +#include "gtkmm/application.h" +#include "gtkmm/listboxrow.h" +#include "gtkmm/object.h" +#include "gtkmm/widget.h" +#include "gtkmm/window.h" +#include "sigc++/functors/mem_fun.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Use Glib::PRIORITY_HIGH_IDLE + 10 for resizing operations +// and Glib::PRIORITY_HIGH_IDLE + 20 for redrawing operations +// https://developer.gnome.org/glibmm/2.55/namespaceGlib.html#a0c450f82b9e34689e2dda2038ba7834faf2d20696a8afab425c00268d981e9897 + +const std::vector MapApp::zoomLevels = {"60%", "80%", "100%"}; + +MapApp::MapApp() + : app(Gtk::Application::create()), + mapArea(junctions, roads, nameVehicleMap), listBox(nullptr), + durationLabel(nullptr), laneLabel(nullptr), + lanePositionPercentageLabel(nullptr), speedLabel(nullptr), + remainingFuelLabel(nullptr), zoomComboBoxText(nullptr) { + win.set_title("pi2-demo"); + + auto builder = + Gtk::Builder::create_from_resource("/pi2-view/pi2-view.glade", "box"); + Gtk::Box *box; + builder->get_widget("box", box); + + Gtk::Viewport *viewport; + builder->get_widget("viewport", viewport); + + builder->get_widget("listBox", listBox); + + builder->get_widget("durationLabel", durationLabel); + builder->get_widget("laneLabel", laneLabel); + builder->get_widget("lanePositionPercentageLabel", + lanePositionPercentageLabel); + builder->get_widget("speedLabel", speedLabel); + builder->get_widget("remainingFuelLabel", remainingFuelLabel); + + builder->get_widget("zoomComboBoxText", zoomComboBoxText); + for (auto &&zoomLevel : zoomLevels) { + zoomComboBoxText->append(zoomLevel); + } + zoomComboBoxText->set_active_text("100%"); + zoomComboBoxText->signal_changed().connect( + sigc::mem_fun(*this, &MapApp::onZoomSelectionChanged)); + zoomComboBoxText->get_entry()->signal_activate().connect( + sigc::mem_fun(*this, &MapApp::onZoomEntryActivate)); + + listBox->signal_row_selected().connect( + sigc::mem_fun(*this, &MapApp::onVehicleRowSelected)); + + win.add(*box); + viewport->add(mapArea); + mapArea.show(); + + thread = std::thread([this]() { + auto app = Gtk::Application::create(); + app->run(win); + app->remove_window(win); + }); +} + +void MapApp::onZoomEntryActivate() { + std::string input = zoomComboBoxText->get_active_text(); + double zoom; + std::istringstream zoomBuffer(input); + zoomBuffer >> zoom; + mapArea.setScale(zoom / 100); + // The window is only resizing, when called from the main thread like this: + mapArea.adaptSizeRequest(); + + std::ostringstream formatBuffer; + formatBuffer << zoom << '%'; + zoomComboBoxText->get_entry()->set_text(formatBuffer.str()); +} + +void MapApp::onZoomSelectionChanged() { + std::string selection = zoomComboBoxText->get_active_text(); + auto levelResult = std::find(zoomLevels.begin(), zoomLevels.end(), selection); + if (levelResult != zoomLevels.end()) { + zoomComboBoxText->set_active_text(selection); + double zoom; + std::istringstream zoomBuffer(selection); + zoomBuffer >> zoom; + mapArea.setScale(zoom / 100); + // The window is only resizing, when called from the main thread like this: + mapArea.adaptSizeRequest(); + } +} + +void MapApp::refreshArea(int x, int y, int width, int height) { + mapArea.queue_draw_area(x, y, width, height); +} + +MapApp::~MapApp() { + win.close(); + if (thread.joinable()) { + thread.join(); + } +} + +void MapApp::onVehicleRowSelected(Gtk::ListBoxRow *row) { + auto &label = dynamic_cast(*row->get_child()); + auto &vehicle = nameVehicleMap.at(label.get_text()); + refreshVehicleLabels(vehicle); +} + +void MapApp::refreshVehicleLabels(const DrawableVehicle &vehicle) { + laneLabel->set_text(vehicle.getLaneName()); + + std::ostringstream lanePisitionPercentageBuffer; + lanePisitionPercentageBuffer << std::setfill('0') << std::setw(2) + << std::round(vehicle.getPositionOnLane() * 100); + lanePositionPercentageLabel->set_text(lanePisitionPercentageBuffer.str()); + + std::ostringstream speedBuffer; + speedBuffer << std::setprecision(3) << vehicle.getSpeed(); + speedLabel->set_text(speedBuffer.str()); + + std::ostringstream remainingFuelBuffer; + remainingFuelBuffer << std::showpoint << std::setprecision(3) + << vehicle.getRemainingFuel(); + remainingFuelLabel->set_text(remainingFuelBuffer.str()); +} + +void MapApp::setDurationLabel(const std::chrono::minutes minutes) { + std::ostringstream durationBuffer; + durationBuffer + << std::setfill('0') << std::setw(2) + << std::chrono::duration_cast(minutes).count() << ':' + << std::setw(2) << minutes.count() % 60; + Glib::signal_idle().connect_once( + [this, durationString = durationBuffer.str()]() mutable { + durationLabel->set_text(std::move(durationString)); + }, + Glib::PRIORITY_HIGH_IDLE + 20); +} + +void MapApp::addJunction(const double x, const double y) { + auto &junction = junctions.emplace_back(x, y); + int xRegion, yRegion, widthRegion, heightRegion; + junction.getRegion(xRegion, yRegion, widthRegion, heightRegion); + Glib::signal_idle().connect_once( + [this, xRegion, yRegion, widthRegion, heightRegion]() { + mapArea.queue_draw_area(xRegion, yRegion, widthRegion, heightRegion); + }, + Glib::PRIORITY_HIGH_IDLE + 20); +} + +void MapApp::addRoad(std::string laneThereName, std::string laneBackName, + std::vector> coordinates) { + auto &road = roads.emplace_back(laneThereName, laneBackName, coordinates); + // The window is only resizing, when called from the main thread like this: + mapArea.adaptSizeRequest(); + int xRegion, yRegion, widthRegion, heightRegion; + road.getRegion(xRegion, yRegion, widthRegion, heightRegion); + Glib::signal_idle().connect_once( + [this, xRegion, yRegion, widthRegion, heightRegion]() { + mapArea.queue_draw_area(xRegion, yRegion, widthRegion, heightRegion); + }, + Glib::PRIORITY_HIGH_IDLE + 20); +} + +void MapApp::addOrReplaceVehicle(const std::string &vehicleName, + const std::string &laneName, + double positionOnLane, double speed, + double remainingFuel, double red, double green, + double blue) { + auto &road = *std::find_if(roads.begin(), roads.end(), + [&laneName](const DrawableRoad &road) { + return road.contains(laneName); + }); + auto selectedRow = listBox->get_selected_row(); + if (selectedRow != nullptr) { + auto selectedVehicleName = + dynamic_cast(*selectedRow->get_child()).get_text(); + if (selectedVehicleName == vehicleName) { + red = green = blue = 1; + } + } + + auto search = nameVehicleMap.find(vehicleName); + int oldX = 0; + int oldY = 0; + int oldWidth = 0; + int oldHeight = 0; + if (search != nameVehicleMap.end()) { + auto &foundVehicle = (*search).second; + foundVehicle.getRegion(oldX, oldY, oldWidth, oldHeight); + } + + auto insertion = nameVehicleMap.insert_or_assign( + vehicleName, DrawableVehicle(vehicleName, laneName, road, positionOnLane, + speed, remainingFuel, red, green, blue)); + + auto &insertedVehicle = insertion.first->second; + int newX, newY, newWidth, newHeight; + insertedVehicle.getRegion(newX, newY, newWidth, newHeight); + + Glib::signal_idle().connect_once( + [this, newX, newY, newWidth, newHeight, oldX, oldY, oldWidth, + oldHeight]() { + mapArea.queue_draw_area(newX, newY, newWidth, newHeight); + mapArea.queue_draw_area(oldX, oldY, oldWidth, oldHeight); + }, + Glib::PRIORITY_HIGH_IDLE + 20); + + if (selectedRow != nullptr) { + auto selectedVehicleName = + dynamic_cast(*selectedRow->get_child()).get_text(); + if (selectedVehicleName == vehicleName) { + Glib::signal_idle().connect_once( + [this, &insertedVehicle]() { refreshVehicleLabels(insertedVehicle); }, + Glib::PRIORITY_HIGH_IDLE + 20); + } + } + + if (insertion.second) { + auto *label = Gtk::make_managed(); + label->set_text(vehicleName); + auto *listBoxRow = Gtk::make_managed(); + listBoxRow->add(*label); + listBox->append(*listBoxRow); + listBox->show_all_children(); + listBox->select_row(*listBoxRow); + } +} diff --git a/pi2-view/src/MapAppABI.cpp b/pi2-view/src/MapAppABI.cpp new file mode 100644 index 0000000..4ee2ab9 --- /dev/null +++ b/pi2-view/src/MapAppABI.cpp @@ -0,0 +1,43 @@ +#include "MapAppABI.h" +#include "MapApp.hpp" +#include +#include +#include +#include +#include +#include +#include + +gsl::owner mapAppCreate() { return new MapApp(); } + +void mapAppDestroy(gsl::owner mapApp) { delete mapApp; } + +void mapAppAddJunction(MapApp *mapApp, const double x, const double y) { + mapApp->addJunction(x, y); +} + +void mapAppAddRoad(MapApp *mapApp, gsl::czstring<> laneThereName, + gsl::czstring<> laneBackName, const double coordinates[], + const size_t coordinateCount) { + const gsl::span coordinatesSpan(coordinates, coordinateCount); + std::vector> coordinateVector(coordinateCount / 2); + std::generate(coordinateVector.begin(), coordinateVector.end(), + [&coordinatesSpan, i = 0]() mutable { + return std::valarray( + {coordinatesSpan.at(i++), coordinatesSpan(i++)}); + }); + mapApp->addRoad(laneThereName, laneBackName, coordinateVector); +} + +void mapAppaddOrReplaceVehicle(MapApp *mapApp, gsl::czstring<> vehicleName, + gsl::czstring<> roadName, + const double positionOnLane, const double speed, + const double remainingFuel, const double red, + const double green, const double blue) { + mapApp->addOrReplaceVehicle(vehicleName, roadName, positionOnLane, speed, + remainingFuel, red, green, blue); +} + +void mapAppSetDurationLabel(MapApp *mapApp, int64_t minutes) { + mapApp->setDurationLabel(std::chrono::minutes(minutes)); +} diff --git a/pi2-view/src/MapArea.cpp b/pi2-view/src/MapArea.cpp new file mode 100644 index 0000000..f5789a2 --- /dev/null +++ b/pi2-view/src/MapArea.cpp @@ -0,0 +1,66 @@ +#include "MapArea.hpp" +#include "Drawable.hpp" +#include "DrawableJunction.hpp" +#include "DrawableRoad.hpp" +#include "DrawableVehicle.hpp" +#include "cairomm/refptr.h" +#include "gtkmm/widget.h" +#include +#include +#include + +const int MapArea::padding = 30; + +MapArea::MapArea(std::vector &junctions, + std::vector &roads, + std::map &nameVehicleMap) + : scale(1), junctions(junctions), roads(roads), + nameVehicleMap(nameVehicleMap) {} + +void MapArea::adaptSizeRequest() { + int xJunctions, yJunctions, widthJunctions, heightJunctions; + Drawable::getRegion(junctions.begin(), junctions.end(), xJunctions, + yJunctions, widthJunctions, heightJunctions); + + int xRoads, yRoads, widthRoads, heightRoads; + Drawable::getRegion(roads.begin(), roads.end(), xRoads, yRoads, widthRoads, + heightRoads); + + int width = std::ceil( + scale * + (std::max(xJunctions + widthJunctions, xRoads + widthRoads) + padding)); + int height = std::ceil( + scale * + (std::max(yJunctions + heightJunctions, yRoads + heightRoads) + padding)); + set_size_request(width, height); +} + +void MapArea::setScale(const double scale) { + this->scale = scale; + queue_draw(); +} + +void MapArea::queue_draw_area(const int x, const int y, const int width, + const int height) { + Gtk::Widget::queue_draw_area(std::floor(scale * x), std::floor(scale * y), + std::ceil(scale * width), + std::ceil(scale * height)); +} + +bool MapArea::on_draw(const Cairo::RefPtr &cr) { + cr->scale(scale, scale); + + for (auto &&road : roads) { + road.draw(cr); + } + + for (auto &&junction : junctions) { + junction.draw(cr); + } + + for (auto &&[name, vehicle] : nameVehicleMap) { + vehicle.draw(cr); + } + + return true; +} diff --git a/scripts/iwyu.sh b/scripts/iwyu.sh new file mode 100644 index 0000000..e00cd00 --- /dev/null +++ b/scripts/iwyu.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd "${MESON_SOURCE_ROOT}" +iwyu_tool -p "${MESON_BUILD_ROOT}" -- --no_comments $@ > "${MESON_BUILD_ROOT}/iwyu-result.txt" diff --git a/scripts/tidy.sh b/scripts/tidy.sh new file mode 100644 index 0000000..fe46737 --- /dev/null +++ b/scripts/tidy.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd "${MESON_SOURCE_ROOT}" +clang-tidy -p "${MESON_BUILD_ROOT}" $@ */src/* > "${MESON_BUILD_ROOT}/clang-tidy-result.txt" diff --git a/subprojects/microsoft-gsl.wrap b/subprojects/microsoft-gsl.wrap new file mode 100644 index 0000000..021932c --- /dev/null +++ b/subprojects/microsoft-gsl.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = GSL-2.0.0 + +source_url = https://github.com/Microsoft/GSL/archive/v2.0.0.zip +source_filename = GSL-2.0.0.zip +source_hash = 837f35a9f00b2543bada8ae680e5c56af84709d36d48cdc3ed8db69df043f5b2 + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/microsoft-gsl/2.0.0/1/get_zip +patch_filename = microsoft-gsl-2.0.0-1-wrap.zip +patch_hash = 4073b1cac61b0e14b3ce99340aba93442dc0925f72333ea85ab530c8c89509b1 -- GitLab