From e9b162bc9acd4d612b0f730023e46f4a6e9b79f0 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Sat, 10 May 2025 21:41:54 +0200 Subject: [PATCH 01/27] Add files via upload --- main.cpp | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 main.cpp diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4ba9d38 --- /dev/null +++ b/main.cpp @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace sf; + +const int WIDTH = 800; +const int HEIGHT = 600; +const int NUM_HOTPOINTS = 5; +const int NUM_ROUTES = 10; +const float MAX_DISTANCE_RATIO = 1.10f; + +struct Point { + float x, y; + float cost = 1.0f; // standaard tijd op locatie in minuten +}; + +bool operator==(const Point& a, const Point& b) { + return a.x == b.x && a.y == b.y; +} + +struct Edge { + int u, v; + float weight; +}; + +int find(int x, vector& parent) { + if (parent[x] == x) return x; + return parent[x] = find(parent[x], parent); +} + +float distance(const Point& a, const Point& b) { + return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2)); +} + +float totalRouteCost(const vector& route, const vector& points) { + float total = 0.0f; + for (size_t i = 0; i < route.size() - 1; ++i) { + float dist = distance(points[route[i]], points[route[i + 1]]); + float walkTime = dist / 84.0f; + total += walkTime + points[route[i]].cost; + } + total += points[route.back()].cost; + return total; +} + +vector computeMST(vector& points) { + int n = points.size(); + vector edges, mst; + vector parent(n); + for (int i = 0; i < n; i++) parent[i] = i; + + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + edges.push_back({ i, j, distance(points[i], points[j]) }); + } + } + + sort(edges.begin(), edges.end(), [](Edge a, Edge b) { + return a.weight < b.weight; + }); + + for (const auto& edge : edges) { + int setU = find(edge.u, parent); + int setV = find(edge.v, parent); + if (setU != setV) { + mst.push_back(edge); + parent[setU] = setV; + if (mst.size() == n - 1) break; + } + } + + return mst; +} + +void optimizeRoute(vector& route, vector& points) { + bool improved = true; + while (improved) { + improved = false; + for (size_t i = 1; i < route.size() - 2; ++i) { + for (size_t j = i + 1; j < route.size() - 1; ++j) { + float d1 = distance(points[route[i - 1]], points[route[i]]) + + distance(points[route[j]], points[route[j + 1]]); + float d2 = distance(points[route[i - 1]], points[route[j]]) + + distance(points[route[i]], points[route[j + 1]]); + if (d2 < d1) { + reverse(route.begin() + i, route.begin() + j + 1); + improved = true; + } + } + } + } +} + +vector christofides(vector& points) { + vector mst = computeMST(points); + vector degree(points.size(), 0); + + for (auto& edge : mst) { + degree[edge.u]++; + degree[edge.v]++; + } + + vector oddNodes; + for (int i = 0; i < points.size(); i++) { + if (degree[i] % 2 == 1) oddNodes.push_back(i); + } + + vector matching; + while (!oddNodes.empty()) { + int u = oddNodes.back(); oddNodes.pop_back(); + int v = oddNodes.back(); oddNodes.pop_back(); + matching.push_back({ u, v, distance(points[u], points[v]) }); + } + + vector eulerianGraph = mst; + eulerianGraph.insert(eulerianGraph.end(), matching.begin(), matching.end()); + + vector route; + vector visited(points.size(), false); + + route.push_back(0); + visited[0] = true; + + for (auto& edge : eulerianGraph) { + if (!visited[edge.v]) { + route.push_back(edge.v); + visited[edge.v] = true; + } + } + + for (int i = 0; i < NUM_HOTPOINTS; i++) { + if (find(route.begin(), route.end(), i) == route.end()) { + int bestInsertPos = 1; + float bestIncrease = FLT_MAX; + for (size_t j = 1; j < route.size(); j++) { + float increase = distance(points[route[j - 1]], points[i]) + + distance(points[i], points[route[j]]) - + distance(points[route[j - 1]], points[route[j]]); + if (increase < bestIncrease) { + bestIncrease = increase; + bestInsertPos = j; + } + } + route.insert(route.begin() + bestInsertPos, i); + } + } + + if (route.front() != 0) route.insert(route.begin(), 0); + if (route.back() != 0) route.push_back(0); + + optimizeRoute(route, points); + + return route; +} + +void saveRoutesToFile(const vector>& routes, const vector>& selectedPoints, const vector& allPoints, const string& filename) { + ofstream file(filename); + if (!file.is_open()) { + cerr << "Kon bestand niet openen: " << filename << endl; + return; + } + + for (int i = 0; i < routes.size(); ++i) { + file << "Route " << i + 1 << ": "; + for (int index : routes[i]) { + auto it = find(allPoints.begin(), allPoints.end(), selectedPoints[i][index]); + if (it != allPoints.end()) { + int locationID = distance(allPoints.begin(), it); + file << locationID << " "; + } + else { + file << "X "; + } + } + file << endl; + } + + file.close(); + cout << "Routes opgeslagen in '" << filename << "'." << endl; +} + +void drawGraph(RenderWindow& window, vector& allPoints, vector>& routes, vector>& selectedPoints) { + window.clear(Color::Black); + + for (auto& p : allPoints) { + CircleShape shape(5); + shape.setPosition(p.x - 3, p.y - 3); + shape.setFillColor(Color::White); + window.draw(shape); + } + + vector routeColors = { + Color::Red, Color::Green, Color::Blue, Color::Cyan, + Color::Magenta, Color::Yellow, Color(255, 165, 0), + Color(128, 0, 128), Color(0, 255, 127), Color(255, 105, 180) + }; + + for (int i = 0; i < NUM_ROUTES; ++i) { + auto& route = routes[i]; + auto& points = selectedPoints[i]; + Color color = routeColors[i % routeColors.size()]; + + for (size_t j = 0; j < points.size(); ++j) { + CircleShape shape(6); + shape.setPosition(points[j].x - 3, points[j].y - 3); + shape.setFillColor(j < NUM_HOTPOINTS ? Color::Blue : color); + window.draw(shape); + } + + for (size_t j = 0; j < route.size() - 1; ++j) { + Vertex line[] = { + Vertex(Vector2f(points[route[j]].x, points[route[j]].y), color), + Vertex(Vector2f(points[route[j + 1]].x, points[route[j + 1]].y), color) + }; + window.draw(line, 2, Lines); + } + } + + window.display(); +} + +int main() { + RenderWindow window(VideoMode(WIDTH, HEIGHT), "Christofides Algorithm Visualization"); + + vector allPoints = { + {100, 100, 2.0}, {300, 200, 1.5}, {500, 400, 3.5}, {200, 500, 2.5}, {600, 100, 4.0}, + {150, 300, 1.0}, {400, 350, 1.0}, {250, 50, 3.0}, {550, 250, 2.0}, {700, 150, 2.0}, + {120, 500, 1.5}, {320, 270, 1.0}, {530, 420, 2.5}, {260, 430, 2.0}, {580, 130, 3.0}, + {110, 220, 1.0}, {350, 180, 1.5}, {480, 380, 2.5}, {230, 510, 1.0}, {630, 170, 3.0}, + {50, 450, 1.5}, {450, 100, 2.0}, {370, 320, 1.0}, {670, 220, 2.5}, {280, 370, 1.0}, + {90, 290, 1.0}, {490, 470, 3.5}, {310, 150, 1.0}, {590, 290, 1.5}, {710, 410, 3.0}, + {400, 500, 2.5}, {540, 350, 2.0}, {620, 200, 1.0}, {260, 260, 1.0}, {150, 400, 1.0}, + {340, 530, 2.0}, {680, 300, 2.5}, {130, 140, 1.5}, {470, 240, 1.0}, {700, 500, 3.0}, + {330, 440, 1.0}, {210, 360, 1.0}, {570, 450, 2.0}, {90, 190, 1.0}, {610, 110, 3.0}, + {250, 480, 1.5}, {370, 210, 1.0}, {560, 310, 2.5}, {720, 180, 1.0}, {430, 280, 1.0} + }; + + vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); + vector waypoints(allPoints.begin() + NUM_HOTPOINTS, allPoints.end()); + + random_device rd; + mt19937 g(rd()); + + vector> selectedPoints; + vector> routes; + vector routeTimes; + bool valid = false; + + while (!valid) { + selectedPoints = vector>(NUM_ROUTES, vector(hotpoints)); + routes.clear(); + routeTimes.clear(); + + shuffle(waypoints.begin(), waypoints.end(), g); + + for (size_t i = 0; i < waypoints.size(); ++i) { + int routeIndex = i % NUM_ROUTES; + selectedPoints[routeIndex].push_back(waypoints[i]); + for (int r = 0; r < NUM_ROUTES; ++r) { + if (rand() % 10 < 3) { + selectedPoints[r].push_back(waypoints[i]); + } + } + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + // Zorg dat schoolpunt (100, 100) in elke lijst zit + if (find(selectedPoints[i].begin(), selectedPoints[i].end(), allPoints[0]) == selectedPoints[i].end()) { + selectedPoints[i].insert(selectedPoints[i].begin(), allPoints[0]); + } + + sort(selectedPoints[i].begin(), selectedPoints[i].end(), [](const Point& a, const Point& b) { + return tie(a.x, a.y) < tie(b.x, b.y); + }); + selectedPoints[i].erase(unique(selectedPoints[i].begin(), selectedPoints[i].end(), [](const Point& a, const Point& b) { + return a == b && !(a.x == 100 && a.y == 100); + }), selectedPoints[i].end()); + + routes.push_back(christofides(selectedPoints[i])); + routeTimes.push_back(totalRouteCost(routes.back(), selectedPoints[i])); + } + + float minTime = *min_element(routeTimes.begin(), routeTimes.end()); + float maxTime = *max_element(routeTimes.begin(), routeTimes.end()); + valid = (maxTime / minTime) <= MAX_DISTANCE_RATIO; + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + cout << "Route " << i + 1 << " tijd: " << routeTimes[i] << " min" << endl; + } + + saveRoutesToFile(routes, selectedPoints, allPoints, "routes.txt"); + + while (window.isOpen()) { + Event event; + while (window.pollEvent(event)) { + if (event.type == Event::Closed) + window.close(); + } + drawGraph(window, allPoints, routes, selectedPoints); + } + + return 0; +} From fcf6cb720282a3179e6dc7d95ddeb2033195469e Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 27 May 2025 15:32:07 +0200 Subject: [PATCH 02/27] Add files via upload --- RoutesGenereren/RoutesGenereren.pro | 27 +++ RoutesGenereren/RoutesGenereren.pro.user | 264 +++++++++++++++++++++++ RoutesGenereren/main.cpp | 11 + RoutesGenereren/mainwindow.cpp | 38 ++++ RoutesGenereren/mainwindow.h | 24 +++ RoutesGenereren/mainwindow.ui | 58 +++++ RoutesGenereren/routegenerator.cpp | 224 +++++++++++++++++++ RoutesGenereren/routegenerator.h | 6 + 8 files changed, 652 insertions(+) create mode 100644 RoutesGenereren/RoutesGenereren.pro create mode 100644 RoutesGenereren/RoutesGenereren.pro.user create mode 100644 RoutesGenereren/main.cpp create mode 100644 RoutesGenereren/mainwindow.cpp create mode 100644 RoutesGenereren/mainwindow.h create mode 100644 RoutesGenereren/mainwindow.ui create mode 100644 RoutesGenereren/routegenerator.cpp create mode 100644 RoutesGenereren/routegenerator.h diff --git a/RoutesGenereren/RoutesGenereren.pro b/RoutesGenereren/RoutesGenereren.pro new file mode 100644 index 0000000..6556e0c --- /dev/null +++ b/RoutesGenereren/RoutesGenereren.pro @@ -0,0 +1,27 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 +QT += concurrent + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + routegenerator.cpp + +HEADERS += \ + mainwindow.h \ + routegenerator.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/RoutesGenereren/RoutesGenereren.pro.user b/RoutesGenereren/RoutesGenereren.pro.user new file mode 100644 index 0000000..d1a4189 --- /dev/null +++ b/RoutesGenereren/RoutesGenereren.pro.user @@ -0,0 +1,264 @@ + + + + + + EnvironmentId + {6737c63b-8d94-4d04-854a-df0b2ddb36db} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + -fno-delayed-template-parsing + + true + Builtin.BuildSystem + + true + true + Builtin.DefaultTidyAndClazy + 4 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.1.3 MSVC2019 64bit + Desktop Qt 6.1.3 MSVC2019 64bit + qt.qt6.613.win64_msvc2019_64_kit + 0 + 0 + 0 + + 0 + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro + false + true + true + false + true + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/RoutesGenereren/main.cpp b/RoutesGenereren/main.cpp new file mode 100644 index 0000000..aff48df --- /dev/null +++ b/RoutesGenereren/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp new file mode 100644 index 0000000..f5e154c --- /dev/null +++ b/RoutesGenereren/mainwindow.cpp @@ -0,0 +1,38 @@ +#include +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "routegenerator.h" // of hoe je bestand heet +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + +void MainWindow::on_pushButton_clicked() +{ + ui->statusLabel->setText("Bezig met genereren..."); + QApplication::processEvents(); // direct UI updaten + + // Draai generate_routes() in aparte thread + QFuture future = QtConcurrent::run([this]() { + generate_routes(); + + // UI mag niet rechtstreeks uit andere thread: gebruik Qt signal + QMetaObject::invokeMethod(this, [this]() { + ui->statusLabel->setText("Routes succesvol gegenereerd!"); + }, Qt::QueuedConnection); + }); +} + + + diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h new file mode 100644 index 0000000..e02a946 --- /dev/null +++ b/RoutesGenereren/mainwindow.h @@ -0,0 +1,24 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_pushButton_clicked(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/RoutesGenereren/mainwindow.ui b/RoutesGenereren/mainwindow.ui new file mode 100644 index 0000000..959b06b --- /dev/null +++ b/RoutesGenereren/mainwindow.ui @@ -0,0 +1,58 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 300 + 210 + 141 + 101 + + + + Genereer Routes + + + + + + 310 + 350 + 161 + 20 + + + + + + + + + + + 0 + 0 + 800 + 26 + + + + + + + + diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp new file mode 100644 index 0000000..52e527e --- /dev/null +++ b/RoutesGenereren/routegenerator.cpp @@ -0,0 +1,224 @@ +#include "routegenerator.h" + +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; + +const int NUM_HOTPOINTS = 5; +const int NUM_ROUTES = 10; +const float MAX_DISTANCE_RATIO = 1.15f; +const float WALKING_SPEED_PX_PER_MIN = 84.0f; // 1.4 m/s → 84 px/min + +struct Point { + float x, y; + float cost = 1.0f; // tijd op locatie in minuten +}; + +bool operator==(const Point& a, const Point& b) { + return a.x == b.x && a.y == b.y; +} + +struct Edge { + int u, v; + float weight; +}; + +int find(int x, vector& parent) { + if (parent[x] == x) return x; + return parent[x] = find(parent[x], parent); +} + +float distance(const Point& a, const Point& b) { + return static_cast(sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2))); +} + +float totalRouteCost(const vector& route, const vector& points) { + float total = 0.0f; + for (size_t i = 0; i < route.size() - 1; ++i) { + float walk = distance(points[route[i]], points[route[i + 1]]) / WALKING_SPEED_PX_PER_MIN; + total += walk + points[route[i]].cost; + } + total += points[route.back()].cost; + return total; +} + +vector computeMST(vector& points) { + size_t n = points.size(); + vector edges, mst; + vector parent(n); + for (int i = 0; i < n; i++) parent[i] = i; + + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + edges.push_back({ i, j, distance(points[i], points[j]) }); + } + } + + sort(edges.begin(), edges.end(), [](Edge a, Edge b) { + return a.weight < b.weight; + }); + + for (const auto& edge : edges) { + int setU = find(edge.u, parent); + int setV = find(edge.v, parent); + if (setU != setV) { + mst.push_back(edge); + parent[setU] = setV; + if (mst.size() == n - 1) break; + } + } + + return mst; +} + +void optimizeRoute(vector& route, vector& points) { + bool improved = true; + while (improved) { + improved = false; + for (size_t i = 1; i < route.size() - 2; ++i) { + for (size_t j = i + 1; j < route.size() - 1; ++j) { + float d1 = distance(points[route[i - 1]], points[route[i]]) + + distance(points[route[j]], points[route[j + 1]]); + float d2 = distance(points[route[i - 1]], points[route[j]]) + + distance(points[route[i]], points[route[j + 1]]); + if (d2 < d1) { + reverse(route.begin() + i, route.begin() + j + 1); + improved = true; + } + } + } + } +} + +vector christofides(vector& points) { + vector mst = computeMST(points); + vector degree(points.size(), 0); + + for (auto& edge : mst) { + degree[edge.u]++; + degree[edge.v]++; + } + + vector oddNodes; + for (int i = 0; i < points.size(); i++) { + if (degree[i] % 2 == 1) oddNodes.push_back(i); + } + + vector matching; + while (!oddNodes.empty()) { + int u = oddNodes.back(); oddNodes.pop_back(); + int v = oddNodes.back(); oddNodes.pop_back(); + matching.push_back({ u, v, distance(points[u], points[v]) }); + } + + vector eulerianGraph = mst; + eulerianGraph.insert(eulerianGraph.end(), matching.begin(), matching.end()); + + vector route; + vector visited(points.size(), false); + route.push_back(0); + visited[0] = true; + + for (auto& edge : eulerianGraph) { + if (!visited[edge.v]) { + route.push_back(edge.v); + visited[edge.v] = true; + } + } + + route.push_back(route.front()); + optimizeRoute(route, points); + return route; +} + +void saveRoutesToFiles(const vector>& routes, const vector>& selectedPoints, const vector& allPoints) { + for (int i = 0; i < routes.size(); ++i) { + string filename = "Route_" + to_string(i + 1) + ".txt"; + ofstream file(filename); + if (!file.is_open()) { + cerr << "Kon bestand niet openen: " << filename << endl; + continue; + } + + file << "Route " << i + 1 << ": "; + for (int index : routes[i]) { + auto it = find(allPoints.begin(), allPoints.end(), selectedPoints[i][index]); + if (it != allPoints.end()) { + int locationID = static_cast(distance(allPoints.begin(), it)); + file << locationID << " "; + } else { + file << "X "; + } + } + + file << endl; + file.close(); + cout << "Opgeslagen in '" << filename << "'." << endl; + } +} + +void generate_routes() { + vector allPoints = { + {100, 100, 2.0}, {300, 200, 1.5}, {500, 400, 3.5}, {200, 500, 2.5}, {600, 100, 4.0}, + {150, 300, 1.0}, {400, 350, 1.0}, {250, 50, 3.0}, {550, 250, 2.0}, {700, 150, 2.0}, + {120, 500, 1.5}, {320, 270, 1.0}, {530, 420, 2.5}, {260, 430, 2.0}, {580, 130, 3.0}, + {110, 220, 1.0}, {350, 180, 1.5}, {480, 380, 2.5}, {230, 510, 1.0}, {630, 170, 3.0}, + {50, 450, 1.5}, {450, 100, 2.0}, {370, 320, 1.0}, {670, 220, 2.5}, {280, 370, 1.0}, + {90, 290, 1.0}, {490, 470, 3.5}, {310, 150, 1.0}, {590, 290, 1.5}, {710, 410, 3.0}, + {400, 500, 2.5}, {540, 350, 2.0}, {620, 200, 1.0}, {260, 260, 1.0}, {150, 400, 1.0}, + {340, 530, 2.0}, {680, 300, 2.5}, {130, 140, 1.5}, {470, 240, 1.0}, {700, 500, 3.0}, + {330, 440, 1.0}, {210, 360, 1.0}, {570, 450, 2.0}, {90, 190, 1.0}, {610, 110, 3.0}, + {250, 480, 1.5}, {370, 210, 1.0}, {560, 310, 2.5}, {720, 180, 1.0}, {430, 280, 1.0} + }; + + vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); + vector waypoints(allPoints.begin() + NUM_HOTPOINTS, allPoints.end()); + + random_device rd; + mt19937 g(rd()); + + vector> selectedPoints; + vector> routes; + vector routeTimes; + bool valid = false; + + while (!valid) { + selectedPoints = vector>(NUM_ROUTES, vector(hotpoints)); + routes.clear(); + routeTimes.clear(); + + shuffle(waypoints.begin(), waypoints.end(), g); + + for (size_t i = 0; i < waypoints.size(); ++i) { + int routeIndex = i % NUM_ROUTES; + selectedPoints[routeIndex].push_back(waypoints[i]); + for (int r = 0; r < NUM_ROUTES; ++r) { + if (rand() % 10 < 3) { + selectedPoints[r].push_back(waypoints[i]); + } + } + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + routes.push_back(christofides(selectedPoints[i])); + routeTimes.push_back(totalRouteCost(routes.back(), selectedPoints[i])); + } + + float minTime = *min_element(routeTimes.begin(), routeTimes.end()); + float maxTime = *max_element(routeTimes.begin(), routeTimes.end()); + valid = (maxTime / minTime) <= MAX_DISTANCE_RATIO; + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + cout << "Route " << i + 1 << " tijd: " << routeTimes[i] << " min" << endl; + } + + saveRoutesToFiles(routes, selectedPoints, allPoints); +} diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h new file mode 100644 index 0000000..b87fb7d --- /dev/null +++ b/RoutesGenereren/routegenerator.h @@ -0,0 +1,6 @@ +#ifndef ROUTEGENERATOR_H +#define ROUTEGENERATOR_H + +void generate_routes(); + +#endif // ROUTEGENERATOR_H From ee43fe39d26305a2ab6d2b2a927119813d60ffb5 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:45:27 +0200 Subject: [PATCH 03/27] Add files via upload --- RoutesGenereren/RoutesGenereren.pro.user | 7 +-- RoutesGenereren/mainwindow.cpp | 63 +++++++++++++++++++++--- RoutesGenereren/mainwindow.h | 4 ++ RoutesGenereren/mainwindow.ui | 22 ++++++--- RoutesGenereren/routegenerator.cpp | 31 ++++++------ RoutesGenereren/routegenerator.h | 15 ++++++ 6 files changed, 108 insertions(+), 34 deletions(-) diff --git a/RoutesGenereren/RoutesGenereren.pro.user b/RoutesGenereren/RoutesGenereren.pro.user index d1a4189..3f536be 100644 --- a/RoutesGenereren/RoutesGenereren.pro.user +++ b/RoutesGenereren/RoutesGenereren.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -237,8 +237,9 @@ 2 - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro + RoutesGenereren2 + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/RoutesVisua/RoutesGenereren.pro + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/RoutesVisua/RoutesGenereren.pro false true true diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index f5e154c..3c4bb23 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -1,15 +1,30 @@ -#include #include "mainwindow.h" #include "ui_mainwindow.h" -#include "routegenerator.h" // of hoe je bestand heet +#include "routegenerator.h" + +#include #include #include +#include +#include +#include +#include +#include + +// Externe data uit routegenerator.cpp +extern std::vector> routeIndices; +extern std::vector> selectedPoints; +extern std::vector allPoints; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); + + // Initialiseer de graphics scene + scene = new QGraphicsScene(this); + ui->graphicsView->setScene(scene); } MainWindow::~MainWindow() @@ -17,22 +32,54 @@ MainWindow::~MainWindow() delete ui; } +// Functie om routes visueel weer te geven +void MainWindow::drawRoutes() +{ + scene->clear(); // Leeg het canvas + + const int radius = 4; + const QColor colors[] = { + Qt::red, Qt::blue, Qt::green, Qt::darkYellow, + Qt::magenta, Qt::cyan, Qt::darkGreen, Qt::darkRed, + Qt::gray, Qt::black + }; + + for (int r = 0; r < routeIndices.size(); ++r) { + QPen pen(colors[r % 10]); + pen.setWidth(2); + const auto& route = routeIndices[r]; + const auto& points = selectedPoints[r]; + // Teken lijnen tussen punten + for (size_t i = 0; i < route.size() - 1; ++i) { + const Point& p1 = points[route[i]]; + const Point& p2 = points[route[i + 1]]; + scene->addLine(p1.x, p1.y, p2.x, p2.y, pen); + } + + // Teken de punten + for (const auto& pt : points) { + scene->addEllipse(pt.x - radius, pt.y - radius, + radius * 2, radius * 2, + QPen(Qt::black), QBrush(colors[r % 10])); + } + } + + scene->setSceneRect(scene->itemsBoundingRect()); +} + +// Bij klikken op de knop: generate en visualiseer void MainWindow::on_pushButton_clicked() { ui->statusLabel->setText("Bezig met genereren..."); QApplication::processEvents(); // direct UI updaten - // Draai generate_routes() in aparte thread QFuture future = QtConcurrent::run([this]() { - generate_routes(); + generate_routes(); // Genereer de routes - // UI mag niet rechtstreeks uit andere thread: gebruik Qt signal QMetaObject::invokeMethod(this, [this]() { ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); // Visualiseer routes }, Qt::QueuedConnection); }); } - - - diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h index e02a946..e658d8f 100644 --- a/RoutesGenereren/mainwindow.h +++ b/RoutesGenereren/mainwindow.h @@ -2,6 +2,7 @@ #define MAINWINDOW_H #include +#include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -20,5 +21,8 @@ private slots: private: Ui::MainWindow *ui; + QGraphicsScene *scene; + + void drawRoutes(); }; #endif // MAINWINDOW_H diff --git a/RoutesGenereren/mainwindow.ui b/RoutesGenereren/mainwindow.ui index 959b06b..8ec348a 100644 --- a/RoutesGenereren/mainwindow.ui +++ b/RoutesGenereren/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 600 + 1069 + 674 @@ -17,7 +17,7 @@ - 300 + 100 210 141 101 @@ -30,8 +30,8 @@ - 310 - 350 + 110 + 340 161 20 @@ -40,13 +40,23 @@ + + + + 320 + 60 + 701 + 531 + + + 0 0 - 800 + 1069 26 diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp index 52e527e..13b2684 100644 --- a/RoutesGenereren/routegenerator.cpp +++ b/RoutesGenereren/routegenerator.cpp @@ -1,3 +1,4 @@ + #include "routegenerator.h" #include @@ -8,7 +9,6 @@ #include #include - using namespace std; const int NUM_HOTPOINTS = 5; @@ -16,15 +16,6 @@ const int NUM_ROUTES = 10; const float MAX_DISTANCE_RATIO = 1.15f; const float WALKING_SPEED_PX_PER_MIN = 84.0f; // 1.4 m/s → 84 px/min -struct Point { - float x, y; - float cost = 1.0f; // tijd op locatie in minuten -}; - -bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; -} - struct Edge { int u, v; float weight; @@ -164,8 +155,13 @@ void saveRoutesToFiles(const vector>& routes, const vector> routeIndices; +vector> selectedPoints; +vector allPoints; + void generate_routes() { - vector allPoints = { + allPoints = { {100, 100, 2.0}, {300, 200, 1.5}, {500, 400, 3.5}, {200, 500, 2.5}, {600, 100, 4.0}, {150, 300, 1.0}, {400, 350, 1.0}, {250, 50, 3.0}, {550, 250, 2.0}, {700, 150, 2.0}, {120, 500, 1.5}, {320, 270, 1.0}, {530, 420, 2.5}, {260, 430, 2.0}, {580, 130, 3.0}, @@ -184,14 +180,14 @@ void generate_routes() { random_device rd; mt19937 g(rd()); - vector> selectedPoints; - vector> routes; + selectedPoints.clear(); + routeIndices.clear(); vector routeTimes; bool valid = false; while (!valid) { selectedPoints = vector>(NUM_ROUTES, vector(hotpoints)); - routes.clear(); + routeIndices.clear(); routeTimes.clear(); shuffle(waypoints.begin(), waypoints.end(), g); @@ -207,8 +203,9 @@ void generate_routes() { } for (int i = 0; i < NUM_ROUTES; ++i) { - routes.push_back(christofides(selectedPoints[i])); - routeTimes.push_back(totalRouteCost(routes.back(), selectedPoints[i])); + auto route = christofides(selectedPoints[i]); + routeIndices.push_back(route); + routeTimes.push_back(totalRouteCost(route, selectedPoints[i])); } float minTime = *min_element(routeTimes.begin(), routeTimes.end()); @@ -220,5 +217,5 @@ void generate_routes() { cout << "Route " << i + 1 << " tijd: " << routeTimes[i] << " min" << endl; } - saveRoutesToFiles(routes, selectedPoints, allPoints); + saveRoutesToFiles(routeIndices, selectedPoints, allPoints); } diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h index b87fb7d..5ea3f8a 100644 --- a/RoutesGenereren/routegenerator.h +++ b/RoutesGenereren/routegenerator.h @@ -1,6 +1,21 @@ #ifndef ROUTEGENERATOR_H #define ROUTEGENERATOR_H +#include + +// Verplaats de struct hierheen zodat andere bestanden hem kunnen gebruiken +struct Point { + float x, y; + float cost = 1.0f; + + bool operator==(const Point& other) const { + return x == other.x && y == other.y; + } +}; + void generate_routes(); +std::vector> getGeneratedRoutes(); +std::vector> getGeneratedIndices(); +std::vector getAllPoints(); #endif // ROUTEGENERATOR_H From 204fe03322daa266692a1a7fbf24741ad6d18727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Mon, 9 Jun 2025 10:21:25 +0200 Subject: [PATCH 04/27] Added shell again --- RoutesGenereren/RoutesGenereren.pro | 16 +- RoutesGenereren/RoutesGenereren.pro.user | 245 +++++++++++++--- .../RoutesGenereren.pro.user.6737c63 | 264 ++++++++++++++++++ RoutesGenereren/console.cpp | 62 ++++ RoutesGenereren/console.h | 33 +++ RoutesGenereren/images/application-exit.png | Bin 0 -> 11200 bytes RoutesGenereren/images/clear.png | Bin 0 -> 12543 bytes RoutesGenereren/images/connect.png | Bin 0 -> 15374 bytes RoutesGenereren/images/disconnect.png | Bin 0 -> 15092 bytes RoutesGenereren/images/settings.png | Bin 0 -> 16039 bytes RoutesGenereren/mainwindow.cpp | 173 ++++++++++-- RoutesGenereren/mainwindow.h | 50 +++- RoutesGenereren/mainwindow.ui | 255 ++++++++++++++--- RoutesGenereren/routegenerator.cpp | 15 +- RoutesGenereren/routegenerator.h | 6 +- RoutesGenereren/settingsdialog.cpp | 177 ++++++++++++ RoutesGenereren/settingsdialog.h | 63 +++++ RoutesGenereren/settingsdialog.ui | 177 ++++++++++++ RoutesGenereren/terminal.qrc | 9 + 19 files changed, 1425 insertions(+), 120 deletions(-) create mode 100644 RoutesGenereren/RoutesGenereren.pro.user.6737c63 create mode 100644 RoutesGenereren/console.cpp create mode 100644 RoutesGenereren/console.h create mode 100644 RoutesGenereren/images/application-exit.png create mode 100644 RoutesGenereren/images/clear.png create mode 100644 RoutesGenereren/images/connect.png create mode 100644 RoutesGenereren/images/disconnect.png create mode 100644 RoutesGenereren/images/settings.png create mode 100644 RoutesGenereren/settingsdialog.cpp create mode 100644 RoutesGenereren/settingsdialog.h create mode 100644 RoutesGenereren/settingsdialog.ui create mode 100644 RoutesGenereren/terminal.qrc diff --git a/RoutesGenereren/RoutesGenereren.pro b/RoutesGenereren/RoutesGenereren.pro index 6556e0c..209df6b 100644 --- a/RoutesGenereren/RoutesGenereren.pro +++ b/RoutesGenereren/RoutesGenereren.pro @@ -1,4 +1,4 @@ -QT += core gui +QT += core gui serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -10,18 +10,26 @@ QT += concurrent #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + console.cpp \ main.cpp \ mainwindow.cpp \ - routegenerator.cpp + routegenerator.cpp \ + settingsdialog.cpp HEADERS += \ + console.h \ mainwindow.h \ - routegenerator.h + routegenerator.h \ + settingsdialog.h FORMS += \ - mainwindow.ui + mainwindow.ui \ + settingsdialog.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + +RESOURCES += \ + terminal.qrc diff --git a/RoutesGenereren/RoutesGenereren.pro.user b/RoutesGenereren/RoutesGenereren.pro.user index 3f536be..7acdb67 100644 --- a/RoutesGenereren/RoutesGenereren.pro.user +++ b/RoutesGenereren/RoutesGenereren.pro.user @@ -1,14 +1,14 @@ - + EnvironmentId - {6737c63b-8d94-4d04-854a-df0b2ddb36db} + {2de608a0-7d4e-4466-820b-332fa0018787} ProjectExplorer.Project.ActiveTarget - 0 + 1 ProjectExplorer.Project.EditorSettings @@ -28,7 +28,7 @@ QmlJSGlobal - 2 + 2 UTF-8 false 4 @@ -37,6 +37,7 @@ true true 1 + 0 false true false @@ -54,6 +55,7 @@ *.md, *.MD, Makefile false true + true @@ -70,16 +72,12 @@ 0 true - - -fno-delayed-template-parsing - - true - Builtin.BuildSystem true true Builtin.DefaultTidyAndClazy - 4 + 6 + true @@ -91,16 +89,16 @@ ProjectExplorer.Project.Target.0 Desktop - Desktop Qt 6.1.3 MSVC2019 64bit - Desktop Qt 6.1.3 MSVC2019 64bit - qt.qt6.613.win64_msvc2019_64_kit - 0 - 0 - 0 + Qt 5.15.2 (mingw81_32) + Qt 5.15.2 (mingw81_32) + {db23b840-4fa8-4369-81d4-5c33831dd635} + 0 + 0 + 0 0 - C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug true @@ -112,7 +110,7 @@ true Qt4ProjectManager.MakeStep - 2 + 2 Build Build ProjectExplorer.BuildSteps.Build @@ -123,7 +121,7 @@ Qt4ProjectManager.MakeStep clean - 1 + 1 Clean Clean ProjectExplorer.BuildSteps.Clean @@ -131,14 +129,15 @@ 2 false + false Debug Qt4ProjectManager.Qt4BuildConfiguration 2 - C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release true @@ -150,7 +149,7 @@ true Qt4ProjectManager.MakeStep - 2 + 2 Build Build ProjectExplorer.BuildSteps.Build @@ -161,7 +160,7 @@ Qt4ProjectManager.MakeStep clean - 1 + 1 Clean Clean ProjectExplorer.BuildSteps.Clean @@ -169,6 +168,7 @@ 2 false + false Release Qt4ProjectManager.Qt4BuildConfiguration @@ -177,8 +177,8 @@ 0 - C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile true @@ -190,7 +190,7 @@ true Qt4ProjectManager.MakeStep - 2 + 2 Build Build ProjectExplorer.BuildSteps.Build @@ -201,7 +201,7 @@ Qt4ProjectManager.MakeStep clean - 1 + 1 Clean Clean ProjectExplorer.BuildSteps.Clean @@ -209,6 +209,7 @@ 2 false + false Profile Qt4ProjectManager.Qt4BuildConfiguration @@ -216,10 +217,10 @@ 0 0 - 3 + 3 - 0 + 0 Deploy Deploy ProjectExplorer.BuildSteps.Deploy @@ -229,30 +230,198 @@ false ProjectExplorer.DefaultDeployConfiguration - 1 + 1 true true + 0 true 2 + false RoutesGenereren2 - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/RoutesVisua/RoutesGenereren.pro - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/RoutesVisua/RoutesGenereren.pro - false + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro + true true true - false true - C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug - 1 + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop + Desktop Qt 5.15.2 MinGW 32-bit + Desktop Qt 5.15.2 MinGW 32-bit + qt.qt5.5152.win32_mingw81_kit + 0 + 0 + 0 + + 0 + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro + false + true + true + true + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug + + 1 ProjectExplorer.Project.TargetCount - 1 + 2 ProjectExplorer.Project.Updater.FileVersion diff --git a/RoutesGenereren/RoutesGenereren.pro.user.6737c63 b/RoutesGenereren/RoutesGenereren.pro.user.6737c63 new file mode 100644 index 0000000..98a8268 --- /dev/null +++ b/RoutesGenereren/RoutesGenereren.pro.user.6737c63 @@ -0,0 +1,264 @@ + + + + + + EnvironmentId + {6737c63b-8d94-4d04-854a-df0b2ddb36db} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + -fno-delayed-template-parsing + + true + Builtin.BuildSystem + + true + true + Builtin.DefaultTidyAndClazy + 4 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.1.3 MSVC2019 64bit + Desktop Qt 6.1.3 MSVC2019 64bit + qt.qt6.613.win64_msvc2019_64_kit + 0 + 0 + 0 + + 0 + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\lars\OneDrive\Documenten\Avans Elektrotechniek\TechnischeInformatica2025\ProjectBlok11-12\build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/RoutesGenereren/RoutesGenereren.pro + false + true + true + false + true + C:/Users/lars/OneDrive/Documenten/Avans Elektrotechniek/TechnischeInformatica2025/ProjectBlok11-12/build-RoutesGenereren-Desktop_Qt_6_1_3_MSVC2019_64bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/RoutesGenereren/console.cpp b/RoutesGenereren/console.cpp new file mode 100644 index 0000000..ce87f1a --- /dev/null +++ b/RoutesGenereren/console.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "console.h" + +#include + +Console::Console(QWidget *parent) : + QPlainTextEdit(parent) +{ + document()->setMaximumBlockCount(100); + QPalette p = palette(); + p.setColor(QPalette::Base, Qt::black); + p.setColor(QPalette::Text, Qt::green); + setPalette(p); +} + +void Console::putData(const QByteArray &data) +{ + insertPlainText(data); + + QScrollBar *bar = verticalScrollBar(); + bar->setValue(bar->maximum()); +} + +void Console::setLocalEchoEnabled(bool set) +{ + m_localEchoEnabled = set; +} + +void Console::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + break; + default: + if (m_localEchoEnabled) + QPlainTextEdit::keyPressEvent(e); + emit getData(e->text().toLocal8Bit()); + } +} + +void Console::mousePressEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + setFocus(); +} + +void Console::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_UNUSED(e); +} + +void Console::contextMenuEvent(QContextMenuEvent *e) +{ + Q_UNUSED(e); +} diff --git a/RoutesGenereren/console.h b/RoutesGenereren/console.h new file mode 100644 index 0000000..8cbc7d6 --- /dev/null +++ b/RoutesGenereren/console.h @@ -0,0 +1,33 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include + +class Console : public QPlainTextEdit +{ + Q_OBJECT + +signals: + void getData(const QByteArray &data); + +public: + explicit Console(QWidget *parent = nullptr); + + void putData(const QByteArray &data); + void setLocalEchoEnabled(bool set); + +protected: + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + +private: + bool m_localEchoEnabled = false; +}; + +#endif // CONSOLE_H diff --git a/RoutesGenereren/images/application-exit.png b/RoutesGenereren/images/application-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..32be6b3f1606ed0ed83fab77687d23b34643b380 GIT binary patch literal 11200 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 literal 0 HcmV?d00001 diff --git a/RoutesGenereren/images/disconnect.png b/RoutesGenereren/images/disconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..fd58f7a480228b072708e3ea3626f27b38fb9294 GIT binary patch literal 15092 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 +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + #include "mainwindow.h" #include "ui_mainwindow.h" +#include "console.h" #include "routegenerator.h" +#include "settingsdialog.h" -#include +#include +#include +#include #include #include #include @@ -11,31 +19,52 @@ #include #include -// Externe data uit routegenerator.cpp -extern std::vector> routeIndices; -extern std::vector> selectedPoints; -extern std::vector allPoints; +#include -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) -{ - ui->setupUi(this); +static constexpr std::chrono::seconds kWriteTimeout = std::chrono::seconds{5}; - // Initialiseer de graphics scene +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + m_ui(new Ui::MainWindow), + m_status(new QLabel), + m_console(new Console), + m_settings(new SettingsDialog(this)), + m_serial(new QSerialPort(this)) +{ + m_ui->setupUi(this); + m_console->setEnabled(false); + //setCentralWidget(m_console); scene = new QGraphicsScene(this); - ui->graphicsView->setScene(scene); + m_ui->graphicsView->setScene(scene); + + QWidget* tab1 = m_console; + m_ui->tabWidget->addTab(tab1, "Developer mode"); + + m_ui->actionConnect->setEnabled(true); + m_ui->actionDisconnect->setEnabled(false); + m_ui->actionQuit->setEnabled(true); + m_ui->actionConfigure->setEnabled(true); + + m_ui->statusBar->addWidget(m_status); + + initActionsConnections(); + + connect(m_serial, &QSerialPort::errorOccurred, this, &MainWindow::handleError); + connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); + connect(m_console, &Console::getData, this, &MainWindow::writeData); + + QObject::connect(m_ui->pushButton_2, SIGNAL(pressed()), this, SLOT(pushButton_2_clicked())); } MainWindow::~MainWindow() { - delete ui; + delete m_settings; + delete m_ui; } -// Functie om routes visueel weer te geven void MainWindow::drawRoutes() { - scene->clear(); // Leeg het canvas + scene->clear(); const int radius = 4; const QColor colors[] = { @@ -68,18 +97,122 @@ void MainWindow::drawRoutes() scene->setSceneRect(scene->itemsBoundingRect()); } -// Bij klikken op de knop: generate en visualiseer void MainWindow::on_pushButton_clicked() { - ui->statusLabel->setText("Bezig met genereren..."); + m_ui->statusLabel->setText("Bezig met genereren..."); QApplication::processEvents(); // direct UI updaten QFuture future = QtConcurrent::run([this]() { generate_routes(); // Genereer de routes QMetaObject::invokeMethod(this, [this]() { - ui->statusLabel->setText("Routes succesvol gegenereerd!"); - drawRoutes(); // Visualiseer routes - }, Qt::QueuedConnection); + m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); // Visualiseer routes + }, Qt::QueuedConnection); }); } + +void MainWindow::pushButton_2_clicked() +{ + QByteArray dataToSend("help \r\n"); + writeData(dataToSend); +} + +void MainWindow::openSerialPort() +{ + const SettingsDialog::Settings p = m_settings->settings(); + m_serial->setPortName(p.name); + m_serial->setBaudRate(p.baudRate); + m_serial->setDataBits(p.dataBits); + m_serial->setParity(p.parity); + m_serial->setStopBits(p.stopBits); + m_serial->setFlowControl(p.flowControl); + if (m_serial->open(QIODevice::ReadWrite)) { + m_console->setEnabled(true); + m_console->setLocalEchoEnabled(p.localEchoEnabled); + m_ui->actionConnect->setEnabled(false); + m_ui->actionDisconnect->setEnabled(true); + m_ui->actionConfigure->setEnabled(false); + showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6") + .arg(p.name, p.stringBaudRate, p.stringDataBits, + p.stringParity, p.stringStopBits, p.stringFlowControl)); + } else { + QMessageBox::critical(this, tr("Error"), m_serial->errorString()); + + showStatusMessage(tr("Open error")); + } +} + +void MainWindow::closeSerialPort() +{ + if (m_serial->isOpen()) + m_serial->close(); + m_console->setEnabled(false); + m_ui->actionConnect->setEnabled(true); + m_ui->actionDisconnect->setEnabled(false); + m_ui->actionConfigure->setEnabled(true); + showStatusMessage(tr("Disconnected")); +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("About Serial Terminal"), + tr("The Serial Terminal example demonstrates how to " + "use the Qt Serial Port module in modern GUI applications " + "using Qt, with a menu bar, toolbars, and a status bar.")); +} + +void MainWindow::writeData(const QByteArray &data) +{ + m_serial->write(data); +} + +void MainWindow::readData() +{ + const QByteArray data = m_serial->readAll(); + m_console->putData(data); +} + +void MainWindow::handleError(QSerialPort::SerialPortError error) +{ + if (error == QSerialPort::ResourceError) { + QMessageBox::critical(this, tr("Critical Error"), m_serial->errorString()); + closeSerialPort(); + } +} + +void MainWindow::handleBytesWritten(qint64 bytes) +{ + m_bytesToWrite -= bytes; + if (m_bytesToWrite == 0) + m_timer->stop(); +} + +void MainWindow::handleWriteTimeout() +{ + const QString error = tr("Write operation timed out for port %1.\n" + "Error: %2").arg(m_serial->portName(), + m_serial->errorString()); + showWriteError(error); +} + +void MainWindow::initActionsConnections() +{ + connect(m_ui->actionConnect, &QAction::triggered, this, &MainWindow::openSerialPort); + connect(m_ui->actionDisconnect, &QAction::triggered, this, &MainWindow::closeSerialPort); + connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(m_ui->actionConfigure, &QAction::triggered, m_settings, &SettingsDialog::show); + connect(m_ui->actionClear, &QAction::triggered, m_console, &Console::clear); + connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::about); + connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); +} + +void MainWindow::showStatusMessage(const QString &message) +{ + m_status->setText(message); +} + +void MainWindow::showWriteError(const QString &message) +{ + QMessageBox::warning(this, tr("Warning"), message); +} diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h index e658d8f..fb474ea 100644 --- a/RoutesGenereren/mainwindow.h +++ b/RoutesGenereren/mainwindow.h @@ -1,28 +1,68 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include #include +#include +#include QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } + +class QLabel; +class QTimer; + +namespace Ui { +class MainWindow; +} + QT_END_NAMESPACE +class Console; +class SettingsDialog; + class MainWindow : public QMainWindow { Q_OBJECT public: - MainWindow(QWidget *parent = nullptr); + explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_pushButton_clicked(); + void pushButton_2_clicked(); + + void openSerialPort(); + void closeSerialPort(); + void about(); + void writeData(const QByteArray &data); + void readData(); + + void handleError(QSerialPort::SerialPortError error); + void handleBytesWritten(qint64 bytes); + void handleWriteTimeout(); private: - Ui::MainWindow *ui; - QGraphicsScene *scene; + void initActionsConnections(); +private: + void showStatusMessage(const QString &message); + void showWriteError(const QString &message); void drawRoutes(); + + Ui::MainWindow *m_ui = nullptr; + QLabel *m_status = nullptr; + Console *m_console = nullptr; + SettingsDialog *m_settings = nullptr; + qint64 m_bytesToWrite = 0; + QTimer *m_timer = nullptr; + QSerialPort *m_serial = nullptr; + QString dataRead; + QString selectedNode; + QGraphicsScene *scene= nullptr; }; + #endif // MAINWINDOW_H diff --git a/RoutesGenereren/mainwindow.ui b/RoutesGenereren/mainwindow.ui index 8ec348a..61f006b 100644 --- a/RoutesGenereren/mainwindow.ui +++ b/RoutesGenereren/mainwindow.ui @@ -6,63 +6,230 @@ 0 0 - 1069 - 674 + 886 + 539 - MainWindow + Serial Terminal - - - - - 100 - 210 - 141 - 101 - - - - Genereer Routes + + + + + + 0 + + + + Tab 1 + + + + + 0 + 370 + 191 + 29 + + + + Genereer Routes + + + + + + 210 + 370 + 191 + 31 + + + + status + + + + + + 740 + 370 + 93 + 29 + + + + testMessage + + + + + + 10 + 10 + 821 + 351 + + + + + + + + + + + + 0 + 0 + 886 + 22 + + + + + Calls + + + + - - - - 110 - 340 - 161 - 20 - - - - + + + Tools + + - - - - 320 - 60 - 701 - 531 - + + + Help + + + + + - - - - 0 - 0 - 1069 - 26 - + + + TopToolBarArea + + + false + + + + + + + + + + toolBar + + TopToolBarArea + + + false + - + + + &About + + + About program + + + Alt+A + + + + + About Qt + + + + + + :/images/connect.png:/images/connect.png + + + C&onnect + + + Connect to serial port + + + Ctrl+O + + + + + + :/images/disconnect.png:/images/disconnect.png + + + &Disconnect + + + Disconnect from serial port + + + Ctrl+D + + + + + + :/images/settings.png:/images/settings.png + + + &Configure + + + Configure serial port + + + Alt+C + + + + + + :/images/clear.png:/images/clear.png + + + C&lear + + + Clear data + + + Alt+L + + + + + + :/images/application-exit.png:/images/application-exit.png + + + &Quit + + + Ctrl+Q + + - + + + + diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp index 13b2684..e68a570 100644 --- a/RoutesGenereren/routegenerator.cpp +++ b/RoutesGenereren/routegenerator.cpp @@ -16,6 +16,10 @@ const int NUM_ROUTES = 10; const float MAX_DISTANCE_RATIO = 1.15f; const float WALKING_SPEED_PX_PER_MIN = 84.0f; // 1.4 m/s → 84 px/min +std::vector> routeIndices; +std::vector> selectedPoints; +std::vector allPoints; + struct Edge { int u, v; float weight; @@ -76,9 +80,9 @@ void optimizeRoute(vector& route, vector& points) { for (size_t i = 1; i < route.size() - 2; ++i) { for (size_t j = i + 1; j < route.size() - 1; ++j) { float d1 = distance(points[route[i - 1]], points[route[i]]) + - distance(points[route[j]], points[route[j + 1]]); + distance(points[route[j]], points[route[j + 1]]); float d2 = distance(points[route[i - 1]], points[route[j]]) + - distance(points[route[i]], points[route[j + 1]]); + distance(points[route[i]], points[route[j + 1]]); if (d2 < d1) { reverse(route.begin() + i, route.begin() + j + 1); improved = true; @@ -155,13 +159,8 @@ void saveRoutesToFiles(const vector>& routes, const vector> routeIndices; -vector> selectedPoints; -vector allPoints; - void generate_routes() { - allPoints = { + vector allPoints = { {100, 100, 2.0}, {300, 200, 1.5}, {500, 400, 3.5}, {200, 500, 2.5}, {600, 100, 4.0}, {150, 300, 1.0}, {400, 350, 1.0}, {250, 50, 3.0}, {550, 250, 2.0}, {700, 150, 2.0}, {120, 500, 1.5}, {320, 270, 1.0}, {530, 420, 2.5}, {260, 430, 2.0}, {580, 130, 3.0}, diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h index 5ea3f8a..a8eb575 100644 --- a/RoutesGenereren/routegenerator.h +++ b/RoutesGenereren/routegenerator.h @@ -3,11 +3,12 @@ #include -// Verplaats de struct hierheen zodat andere bestanden hem kunnen gebruiken struct Point { float x, y; float cost = 1.0f; + Point(float x, float y, float cost = 1.0f) : x(x), y(y), cost(cost) {} + bool operator==(const Point& other) const { return x == other.x && y == other.y; } @@ -18,4 +19,7 @@ std::vector> getGeneratedRoutes(); std::vector> getGeneratedIndices(); std::vector getAllPoints(); +extern std::vector> routeIndices; +extern std::vector> selectedPoints; +extern std::vector allPoints; #endif // ROUTEGENERATOR_H diff --git a/RoutesGenereren/settingsdialog.cpp b/RoutesGenereren/settingsdialog.cpp new file mode 100644 index 0000000..4935c18 --- /dev/null +++ b/RoutesGenereren/settingsdialog.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +#include +#include +#include + +static const char blankString[] = QT_TRANSLATE_NOOP("SettingsDialog", "N/A"); + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + m_ui(new Ui::SettingsDialog), + m_intValidator(new QIntValidator(0, 4000000, this)) +{ + m_ui->setupUi(this); + + m_ui->baudRateBox->setInsertPolicy(QComboBox::NoInsert); + + connect(m_ui->applyButton, &QPushButton::clicked, + this, &SettingsDialog::apply); + connect(m_ui->serialPortInfoListBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &SettingsDialog::showPortInfo); + connect(m_ui->baudRateBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &SettingsDialog::checkCustomBaudRatePolicy); + connect(m_ui->serialPortInfoListBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &SettingsDialog::checkCustomDevicePathPolicy); + + fillPortsParameters(); + fillPortsInfo(); + + updateSettings(); +} + + +SettingsDialog::~SettingsDialog() +{ + delete m_ui; +} + +SettingsDialog::Settings SettingsDialog::settings() const +{ + return m_currentSettings; +} + +void SettingsDialog::showPortInfo(int idx) +{ + if (idx == -1) + return; + + const QString blankString = tr(::blankString); + + const QStringList list = m_ui->serialPortInfoListBox->itemData(idx).toStringList(); + m_ui->descriptionLabel->setText(tr("Description: %1").arg(list.value(1, blankString))); + m_ui->manufacturerLabel->setText(tr("Manufacturer: %1").arg(list.value(2, blankString))); + m_ui->serialNumberLabel->setText(tr("Serial number: %1").arg(list.value(3, blankString))); + m_ui->locationLabel->setText(tr("Location: %1").arg(list.value(4, blankString))); + m_ui->vidLabel->setText(tr("Vendor Identifier: %1").arg(list.value(5, blankString))); + m_ui->pidLabel->setText(tr("Product Identifier: %1").arg(list.value(6, blankString))); +} + +void SettingsDialog::apply() +{ + updateSettings(); + hide(); +} + +void SettingsDialog::checkCustomBaudRatePolicy(int idx) +{ + const bool isCustomBaudRate = !m_ui->baudRateBox->itemData(idx).isValid(); + m_ui->baudRateBox->setEditable(isCustomBaudRate); + if (isCustomBaudRate) { + m_ui->baudRateBox->clearEditText(); + QLineEdit *edit = m_ui->baudRateBox->lineEdit(); + edit->setValidator(m_intValidator); + } +} + +void SettingsDialog::checkCustomDevicePathPolicy(int idx) +{ + const bool isCustomPath = !m_ui->serialPortInfoListBox->itemData(idx).isValid(); + m_ui->serialPortInfoListBox->setEditable(isCustomPath); + if (isCustomPath) + m_ui->serialPortInfoListBox->clearEditText(); +} + +void SettingsDialog::fillPortsParameters() +{ + m_ui->baudRateBox->addItem(QStringLiteral("9600"), QSerialPort::Baud9600); + m_ui->baudRateBox->addItem(QStringLiteral("19200"), QSerialPort::Baud19200); + m_ui->baudRateBox->addItem(QStringLiteral("38400"), QSerialPort::Baud38400); + m_ui->baudRateBox->addItem(QStringLiteral("115200"), QSerialPort::Baud115200); + m_ui->baudRateBox->addItem(tr("Custom")); + + m_ui->dataBitsBox->addItem(QStringLiteral("5"), QSerialPort::Data5); + m_ui->dataBitsBox->addItem(QStringLiteral("6"), QSerialPort::Data6); + m_ui->dataBitsBox->addItem(QStringLiteral("7"), QSerialPort::Data7); + m_ui->dataBitsBox->addItem(QStringLiteral("8"), QSerialPort::Data8); + m_ui->dataBitsBox->setCurrentIndex(3); + + m_ui->parityBox->addItem(tr("None"), QSerialPort::NoParity); + m_ui->parityBox->addItem(tr("Even"), QSerialPort::EvenParity); + m_ui->parityBox->addItem(tr("Odd"), QSerialPort::OddParity); + m_ui->parityBox->addItem(tr("Mark"), QSerialPort::MarkParity); + m_ui->parityBox->addItem(tr("Space"), QSerialPort::SpaceParity); + + m_ui->stopBitsBox->addItem(QStringLiteral("1"), QSerialPort::OneStop); +#ifdef Q_OS_WIN + m_ui->stopBitsBox->addItem(tr("1.5"), QSerialPort::OneAndHalfStop); +#endif + m_ui->stopBitsBox->addItem(QStringLiteral("2"), QSerialPort::TwoStop); + + m_ui->flowControlBox->addItem(tr("None"), QSerialPort::NoFlowControl); + m_ui->flowControlBox->addItem(tr("RTS/CTS"), QSerialPort::HardwareControl); + m_ui->flowControlBox->addItem(tr("XON/XOFF"), QSerialPort::SoftwareControl); +} + +void SettingsDialog::fillPortsInfo() +{ + m_ui->serialPortInfoListBox->clear(); + const QString blankString = tr(::blankString); + const auto infos = QSerialPortInfo::availablePorts(); + + for (const QSerialPortInfo &info : infos) { + QStringList list; + const QString description = info.description(); + const QString manufacturer = info.manufacturer(); + const QString serialNumber = info.serialNumber(); + const auto vendorId = info.vendorIdentifier(); + const auto productId = info.productIdentifier(); + list << info.portName() + << (!description.isEmpty() ? description : blankString) + << (!manufacturer.isEmpty() ? manufacturer : blankString) + << (!serialNumber.isEmpty() ? serialNumber : blankString) + << info.systemLocation() + << (vendorId ? QString::number(vendorId, 16) : blankString) + << (productId ? QString::number(productId, 16) : blankString); + + m_ui->serialPortInfoListBox->addItem(list.constFirst(), list); + } + + m_ui->serialPortInfoListBox->addItem(tr("Custom")); +} + +void SettingsDialog::updateSettings() +{ + m_currentSettings.name = m_ui->serialPortInfoListBox->currentText(); + + if (m_ui->baudRateBox->currentIndex() == 4) { + m_currentSettings.baudRate = m_ui->baudRateBox->currentText().toInt(); + } else { + const auto baudRateData = m_ui->baudRateBox->currentData(); + m_currentSettings.baudRate = baudRateData.value(); + } + m_currentSettings.stringBaudRate = QString::number(m_currentSettings.baudRate); + + const auto dataBitsData = m_ui->dataBitsBox->currentData(); + m_currentSettings.dataBits = dataBitsData.value(); + m_currentSettings.stringDataBits = m_ui->dataBitsBox->currentText(); + + const auto parityData = m_ui->parityBox->currentData(); + m_currentSettings.parity = parityData.value(); + m_currentSettings.stringParity = m_ui->parityBox->currentText(); + + const auto stopBitsData = m_ui->stopBitsBox->currentData(); + m_currentSettings.stopBits = stopBitsData.value(); + m_currentSettings.stringStopBits = m_ui->stopBitsBox->currentText(); + + const auto flowControlData = m_ui->flowControlBox->currentData(); + m_currentSettings.flowControl = flowControlData.value(); + m_currentSettings.stringFlowControl = m_ui->flowControlBox->currentText(); + + m_currentSettings.localEchoEnabled = m_ui->localEchoCheckBox->isChecked(); +} diff --git a/RoutesGenereren/settingsdialog.h b/RoutesGenereren/settingsdialog.h new file mode 100644 index 0000000..fac74c4 --- /dev/null +++ b/RoutesGenereren/settingsdialog.h @@ -0,0 +1,63 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { +class SettingsDialog; +} + +class QIntValidator; + +QT_END_NAMESPACE + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + struct Settings { + QString name; + qint32 baudRate; + QString stringBaudRate; + QSerialPort::DataBits dataBits; + QString stringDataBits; + QSerialPort::Parity parity; + QString stringParity; + QSerialPort::StopBits stopBits; + QString stringStopBits; + QSerialPort::FlowControl flowControl; + QString stringFlowControl; + bool localEchoEnabled; + }; + + explicit SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + + Settings settings() const; + +private slots: + void showPortInfo(int idx); + void apply(); + void checkCustomBaudRatePolicy(int idx); + void checkCustomDevicePathPolicy(int idx); + +private: + void fillPortsParameters(); + void fillPortsInfo(); + void updateSettings(); + +private: + Ui::SettingsDialog *m_ui = nullptr; + Settings m_currentSettings; + QIntValidator *m_intValidator = nullptr; +}; + +#endif // SETTINGSDIALOG_H diff --git a/RoutesGenereren/settingsdialog.ui b/RoutesGenereren/settingsdialog.ui new file mode 100644 index 0000000..bf7753a --- /dev/null +++ b/RoutesGenereren/settingsdialog.ui @@ -0,0 +1,177 @@ + + + SettingsDialog + + + + 0 + 0 + 308 + 322 + + + + Settings + + + + + + Select Parameters + + + + + + BaudRate: + + + + + + + + + + Data bits: + + + + + + + + + + Parity: + + + + + + + + + + Stop bits: + + + + + + + + + + Flow control: + + + + + + + + + + + + + Select Serial Port + + + + + + + + + Description: + + + + + + + Manufacturer: + + + + + + + Serial number: + + + + + + + Location: + + + + + + + Vendor ID: + + + + + + + Product ID: + + + + + + + + + + + + Qt::Horizontal + + + + 96 + 20 + + + + + + + + Apply + + + + + + + + + Additional options + + + + + + Local echo + + + true + + + + + + + + + + + diff --git a/RoutesGenereren/terminal.qrc b/RoutesGenereren/terminal.qrc new file mode 100644 index 0000000..0b49879 --- /dev/null +++ b/RoutesGenereren/terminal.qrc @@ -0,0 +1,9 @@ + + + images/connect.png + images/disconnect.png + images/application-exit.png + images/settings.png + images/clear.png + + From 1f92e2bf37910e9e9fc10e65b236dfa4e8ecf3bb Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:04:20 +0200 Subject: [PATCH 05/27] Add files via upload --- mainwindow.cpp | 257 +++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 72 ++++++++++++ mainwindow.ui | 235 ++++++++++++++++++++++++++++++++++++++++ routegenerator.cpp | 265 +++++++++++++++++++++++++++++++++++++++++++++ routegenerator.h | 31 ++++++ 5 files changed, 860 insertions(+) create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 routegenerator.cpp create mode 100644 routegenerator.h diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..3918f6c --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,257 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "console.h" +#include "routegenerator.h" +#include "settingsdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static constexpr std::chrono::seconds kWriteTimeout = std::chrono::seconds{5}; + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + m_ui(new Ui::MainWindow), + m_status(new QLabel), + m_console(new Console), + m_settings(new SettingsDialog(this)), + m_serial(new QSerialPort(this)) +{ + m_ui->setupUi(this); + + // Initialiseer QGraphicsScene + scene = new QGraphicsScene(this); + m_ui->graphicsView->setScene(scene); + m_ui->graphicsView->setRenderHint(QPainter::Antialiasing, true); + + // SerialPort + console instellingen + m_console->setEnabled(false); + QWidget* tab1 = m_console; + m_ui->tabWidget->addTab(tab1, "Developer mode"); + + m_ui->actionConnect->setEnabled(true); + m_ui->actionDisconnect->setEnabled(false); + m_ui->actionQuit->setEnabled(true); + m_ui->actionConfigure->setEnabled(true); + + m_ui->statusBar->addWidget(m_status); + + initActionsConnections(); + + connect(m_serial, &QSerialPort::errorOccurred, this, &MainWindow::handleError); + connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); + connect(m_console, &Console::getData, this, &MainWindow::writeData); + + QObject::connect(m_ui->pushButton_2, SIGNAL(pressed()), this, SLOT(pushButton_2_clicked())); +} + +MainWindow::~MainWindow() +{ + delete m_settings; + delete m_ui; +} + +void MainWindow::drawRoutes() +{ + scene->clear(); + + // 1) Bepaal min/max van alle getoonde punten + int64_t minX = std::numeric_limits::max(), maxX = std::numeric_limits::lowest(); + int64_t minY = std::numeric_limits::max(), maxY = std::numeric_limits::lowest(); + for (auto& pts : selectedPoints) + for (auto& p : pts) { + minX = std::min(minX, p.x); + maxX = std::max(maxX, p.x); + minY = std::min(minY, p.y); + maxY = std::max(maxY, p.y); + } + + // 2) Bereken schaal (+ marge) + QSizeF viewSz = m_ui->graphicsView->viewport()->size(); + constexpr qreal M = 20.0; // marge in pixels + qreal scaleX = (viewSz.width() - 2*M) / qreal(maxX - minX); + qreal scaleY = (viewSz.height() - 2*M) / qreal(maxY - minY); + qreal scale = std::min(scaleX, scaleY); + + auto mapPt = [&](const Point& p) { + qreal x = (p.x - minX)*scale + M; + qreal y = (p.y - minY)*scale + M; + return QPointF(x,y); + }; + + const int radius = 4; + const QColor colors[] = { + Qt::red, Qt::blue, Qt::green, Qt::darkYellow, + Qt::magenta, Qt::cyan, Qt::darkGreen, Qt::darkRed, + Qt::gray, Qt::black + }; + + // 3) Teken elke route in een andere kleur + for (int r = 0; r < routeIndices.size(); ++r) { + QPen pen(colors[r%10]); + pen.setCosmetic(true); + pen.setWidth(0); + const auto& route = routeIndices[r]; + const auto& pts = selectedPoints[r]; + + // lijnen + for (int i = 0; i + 1 < route.size(); ++i) { + QPointF p1 = mapPt(pts[ route[i] ]); + QPointF p2 = mapPt(pts[ route[i+1] ]); + scene->addLine(p1.x(), p1.y(), p2.x(), p2.y(), pen); + } + // punten + for (auto& p : pts) { + QPointF c = mapPt(p); + scene->addEllipse(c.x()-radius, c.y()-radius, + radius*2, radius*2, + QPen(Qt::black), + QBrush(colors[r%10])); + } + } + + // 4) Scene instellen en fitten + scene->setSceneRect(0, 0, viewSz.width(), viewSz.height()); + m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); +} + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + if (scene && !scene->items().isEmpty()) { + m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); + } +} + +void MainWindow::on_pushButton_clicked() +{ + m_ui->statusLabel->setText("Bezig met genereren..."); + QApplication::processEvents(); + + QFuture future = QtConcurrent::run([this]() { + generate_routes(); + QMetaObject::invokeMethod(this, [this]() { + m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); + }, Qt::QueuedConnection); + }); +} + +void MainWindow::pushButton_2_clicked() +{ + QByteArray dataToSend("help \r\n"); + writeData(dataToSend); +} + +void MainWindow::openSerialPort() +{ + const SettingsDialog::Settings p = m_settings->settings(); + m_serial->setPortName(p.name); + m_serial->setBaudRate(p.baudRate); + m_serial->setDataBits(p.dataBits); + m_serial->setParity(p.parity); + m_serial->setStopBits(p.stopBits); + m_serial->setFlowControl(p.flowControl); + if (m_serial->open(QIODevice::ReadWrite)) { + m_console->setEnabled(true); + m_console->setLocalEchoEnabled(p.localEchoEnabled); + m_ui->actionConnect->setEnabled(false); + m_ui->actionDisconnect->setEnabled(true); + m_ui->actionConfigure->setEnabled(false); + showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6") + .arg(p.name, p.stringBaudRate, p.stringDataBits, + p.stringParity, p.stringStopBits, p.stringFlowControl)); + } else { + QMessageBox::critical(this, tr("Error"), m_serial->errorString()); + showStatusMessage(tr("Open error")); + } +} + +void MainWindow::closeSerialPort() +{ + if (m_serial->isOpen()) + m_serial->close(); + m_console->setEnabled(false); + m_ui->actionConnect->setEnabled(true); + m_ui->actionDisconnect->setEnabled(false); + m_ui->actionConfigure->setEnabled(true); + showStatusMessage(tr("Disconnected")); +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("About Serial Terminal"), + tr("The Serial Terminal example demonstrates how to " + "use the Qt Serial Port module in modern GUI applications " + "using Qt, with a menu bar, toolbars, and a status bar.")); +} + +void MainWindow::writeData(const QByteArray &data) +{ + m_serial->write(data); +} + +void MainWindow::readData() +{ + const QByteArray data = m_serial->readAll(); + m_console->putData(data); +} + +void MainWindow::handleError(QSerialPort::SerialPortError error) +{ + if (error == QSerialPort::ResourceError) { + QMessageBox::critical(this, tr("Critical Error"), m_serial->errorString()); + closeSerialPort(); + } +} + +void MainWindow::handleBytesWritten(qint64 bytes) +{ + m_bytesToWrite -= bytes; + if (m_bytesToWrite == 0) + m_timer->stop(); +} + +void MainWindow::handleWriteTimeout() +{ + const QString error = tr("Write operation timed out for port %1.\n" + "Error: %2").arg(m_serial->portName(), + m_serial->errorString()); + showWriteError(error); +} + +void MainWindow::initActionsConnections() +{ + connect(m_ui->actionConnect, &QAction::triggered, this, &MainWindow::openSerialPort); + connect(m_ui->actionDisconnect, &QAction::triggered, this, &MainWindow::closeSerialPort); + connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(m_ui->actionConfigure, &QAction::triggered, m_settings, &SettingsDialog::show); + connect(m_ui->actionClear, &QAction::triggered, m_console, &Console::clear); + connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::about); + connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); +} + +void MainWindow::showStatusMessage(const QString &message) +{ + m_status->setText(message); +} + +void MainWindow::showWriteError(const QString &message) +{ + QMessageBox::warning(this, tr("Warning"), message); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..745913a --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,72 @@ +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QLabel; +class QTimer; + +namespace Ui { +class MainWindow; +} + +QT_END_NAMESPACE + +class Console; +class SettingsDialog; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_pushButton_clicked(); + void pushButton_2_clicked(); + + void openSerialPort(); + void closeSerialPort(); + void about(); + void writeData(const QByteArray &data); + void readData(); + + void handleError(QSerialPort::SerialPortError error); + void handleBytesWritten(qint64 bytes); + void handleWriteTimeout(); + +private: + void initActionsConnections(); + void showStatusMessage(const QString &message); + void showWriteError(const QString &message); + void drawRoutes(); + +protected: + // Elke keer bij resize opnieuw fitten + void resizeEvent(QResizeEvent* event) override; + +private: + Ui::MainWindow *m_ui = nullptr; + QLabel *m_status = nullptr; + Console *m_console = nullptr; + SettingsDialog *m_settings = nullptr; + qint64 m_bytesToWrite = 0; + QTimer *m_timer = nullptr; + QSerialPort *m_serial = nullptr; + QString dataRead; + QString selectedNode; + QGraphicsScene *scene = nullptr; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..61f006b --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,235 @@ + + + MainWindow + + + + 0 + 0 + 886 + 539 + + + + Serial Terminal + + + + + + + 0 + + + + Tab 1 + + + + + 0 + 370 + 191 + 29 + + + + Genereer Routes + + + + + + 210 + 370 + 191 + 31 + + + + status + + + + + + 740 + 370 + 93 + 29 + + + + testMessage + + + + + + 10 + 10 + 821 + 351 + + + + + + + + + + + + 0 + 0 + 886 + 22 + + + + + Calls + + + + + + + + + Tools + + + + + + + Help + + + + + + + + + + + TopToolBarArea + + + false + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + &About + + + About program + + + Alt+A + + + + + About Qt + + + + + + :/images/connect.png:/images/connect.png + + + C&onnect + + + Connect to serial port + + + Ctrl+O + + + + + + :/images/disconnect.png:/images/disconnect.png + + + &Disconnect + + + Disconnect from serial port + + + Ctrl+D + + + + + + :/images/settings.png:/images/settings.png + + + &Configure + + + Configure serial port + + + Alt+C + + + + + + :/images/clear.png:/images/clear.png + + + C&lear + + + Clear data + + + Alt+L + + + + + + :/images/application-exit.png:/images/application-exit.png + + + &Quit + + + Ctrl+Q + + + + + + + + + diff --git a/routegenerator.cpp b/routegenerator.cpp new file mode 100644 index 0000000..97e16d3 --- /dev/null +++ b/routegenerator.cpp @@ -0,0 +1,265 @@ +#include "routegenerator.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +const int NUM_HOTPOINTS = 5; +const int NUM_ROUTES = 10; +const float MIN_ROUTE_TIME_MINUTES = 1.0f * 60.0f; // 60 min +const float MAX_ROUTE_TIME_MINUTES = 2.0f * 60.0f; // 120 min + +constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld +constexpr double WALKING_SPEED_M_PER_S = 1.4; // meter per seconde + + +Point::Point(int64_t x, int64_t y, float cost) + : x(x), y(y), cost(cost) +{} + +bool Point::operator==(const Point& other) const { + return x == other.x + && y == other.y + && cost == other.cost; +} + +vector> routeIndices; +vector> selectedPoints; +vector allPoints; + +struct Edge { + int u, v; + float weight; +}; + +// Bereken afstand in meters tussen twee microdegree-punten + +double geoDistanceMeters(const Point& a, const Point& b) { + double dx_deg = (a.x - b.x) * 1e-6; + double dy_deg = (a.y - b.y) * 1e-6; + double dx_m = dx_deg * METERS_PER_DEGREE; + double dy_m = dy_deg * METERS_PER_DEGREE; + return sqrt(dx_m*dx_m + dy_m*dy_m); +} + +// Bereken tijd in minuten tussen twee punten + +double stepTimeMinutes(const Point& a, const Point& b) { + double dist_m = geoDistanceMeters(a, b); + return (dist_m / WALKING_SPEED_M_PER_S) / 60.0; +} + +int findSet(int x, vector& parent) { + return parent[x] == x ? x : parent[x] = findSet(parent[x], parent); +} + +float totalRouteCost(const vector& route, const vector& points) { + float total = 0.0f; + for (size_t i = 0; i + 1 < route.size(); ++i) { + total += static_cast(stepTimeMinutes(points[route[i]], points[route[i+1]])) + + points[route[i]].cost; + } + total += points[route.back()].cost; + return total; +} + +vector computeMST(vector& points) { + size_t n = points.size(); + vector edges, mst; + vector parent(n); + for (size_t i = 0; i < n; ++i) parent[i] = i; + + for (size_t i = 0; i < n; ++i) { + for (size_t j = i + 1; j < n; ++j) { + double d = geoDistanceMeters(points[i], points[j]); + edges.push_back({ static_cast(i), static_cast(j), static_cast(d) }); + } + } + sort(edges.begin(), edges.end(), [](auto& a, auto& b){ return a.weight < b.weight; }); + for (auto& e : edges) { + int u = findSet(e.u, parent); + int v = findSet(e.v, parent); + if (u != v) { + mst.push_back(e); + parent[u] = v; + if (mst.size() == n - 1) break; + } + } + return mst; +} + + +void optimizeRoute(vector& route, vector& points) { + bool improved = true; + while (improved) { + improved = false; + for (size_t i = 1; i + 2 < route.size(); ++i) { + for (size_t j = i + 1; j + 1 < route.size(); ++j) { + double d1 = geoDistanceMeters(points[route[i-1]], points[route[i]]) + + geoDistanceMeters(points[route[j]], points[route[j+1]]); + double d2 = geoDistanceMeters(points[route[i-1]], points[route[j]]) + + geoDistanceMeters(points[route[i]], points[route[j+1]]); + if (d2 < d1) { + reverse(route.begin()+i, route.begin()+j+1); + improved = true; + } + } + } + } +} + +vector christofides(vector& points) { + auto mst = computeMST(points); + vector degree(points.size(), 0); + for (auto& e : mst) { + degree[e.u]++; degree[e.v]++; + } + vector odd; + for (size_t i = 0; i < points.size(); ++i) + if (degree[i] % 2) odd.push_back(static_cast(i)); + + vector matching; + while (odd.size() >= 2) { + int u = odd.back(); odd.pop_back(); + int v = odd.back(); odd.pop_back(); + matching.push_back({u, v, static_cast(geoDistanceMeters(points[u], points[v]))}); + } + + vector eulerian = mst; + eulerian.insert(eulerian.end(), matching.begin(), matching.end()); + + vector route; + vector used(points.size(), false); + route.push_back(0); + used[0] = true; + for (auto& e : eulerian) { + if (!used[e.v]) { + route.push_back(e.v); + used[e.v] = true; + } + } + route.push_back(route.front()); + optimizeRoute(route, points); + return route; +} + +void saveRoutesToFiles(const vector>& routes, + const vector>& selPts, + const vector& allPts) +{ + for (size_t i = 0; i < routes.size(); ++i) { + string filename = "Route_" + to_string(i+1) + ".json"; + ofstream file(filename); + if (!file) { + cerr << "Kon bestand niet openen: " << filename << "\n"; + continue; + } + file << "[\n"; + for (size_t j = 0; j < routes[i].size(); ++j) { + const Point& p = selPts[i][routes[i][j]]; + auto it = find(allPts.begin(), allPts.end(), p); + int mg_id = it != allPts.end() ? static_cast(distance(allPts.begin(), it)) : -1; + file << " { \"x\": " << p.x + << ", \"y\": " << p.y + << ", \"mg_id\": " << mg_id << " }" + << (j+1 < routes[i].size() ? "," : "") << "\n"; + } + file << "]\n"; + cout << "Opgeslagen in '" << filename << "'.\n"; + } +} + +void generate_routes() { + // definieer alle punten met microdegrees en kosten + allPoints = { + {51688573, 5287210, 2.0f}, // Avans + {51690224, 5296625, 1.5f}, // Jan de Groot + {51688460, 5303150, 2.0f}, // Stadhuis + {51686200, 5304500, 1.5f}, // Sint Jans Kathedraal + {51685051, 5289156, 2.0f}, // Paleisbrug + {51684258, 5302611, 1.5f}, // Zuidwal + {51691299, 5303950, 2.0f}, // Arena + {51689428, 5310484, 1.5f}, // Nationaal Carnavalsmuseum + {51695984, 5299074, 2.0f}, // Tramkade + {51689124, 5303969, 1.5f}, // De Markt + {51689619, 5299065, 2.0f}, // Bolwerk + {51693002, 5301264, 1.5f}, // VUE Cinema + {51697021, 5299328, 2.0f}, // Bossche Brouwers + {51689724, 5300408, 1.5f}, // Café Bar le Duc + {51686471, 5304106, 2.0f}, // Museumkwartier + {51689471, 5303200, 1.5f}, // Moriaan + {51689302, 5303396, 2.0f}, // ’t Opkikkertje + {51695457, 5297448, 1.5f}, // Verkadefabriek + {51694463, 5302862, 2.0f}, // BHIC + {51690467, 5294925, 1.5f}, // Station + {51683776, 5317938, 2.0f}, // Zuiderpark + {51687561, 5305911, 1.5f}, // Korte Putstraat + {51698630, 5292803, 1.5f}, // Brabanthallen + {51688691, 5309001, 1.5f}, // Café de Palm + {51687344, 5305871, 1.5f}, // Bistro Tante Pietje + {51696264, 5307460, 1.5f}, // Taxandriaplein Park + {51696501, 5312884, 1.5f}, // IJzeren Vrouw Park + {51691911, 5286594, 1.5f}, // Simon Stevinweg + {51690402, 5291740, 1.5f}, // Hugo de Grootplein + {51685400, 5289354, 1.5f}, // Kinepolis + {51690484, 5296206, 1.5f}, // Gouden Draak + {51686618, 5308459, 1.5f}, // Theater aan de Parade + {51686697, 5303137, 1.5f} // Gemeentehuis + }; + + vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); + vector waypoints(allPoints.begin() + NUM_HOTPOINTS, allPoints.end()); + + random_device rd; + mt19937 g(rd()); + + selectedPoints.clear(); + routeIndices.clear(); + vector routeTimes; + bool valid = false; + + // Blijf genereren totdat alle routes binnen de tijdslimieten vallen + while (!valid) { + selectedPoints = vector>(NUM_ROUTES, hotpoints); + routeIndices.clear(); + routeTimes.clear(); + + shuffle(waypoints.begin(), waypoints.end(), g); + for (size_t i = 0; i < waypoints.size(); ++i) { + int rid = static_cast(i % NUM_ROUTES); + selectedPoints[rid].push_back(waypoints[i]); + for (int r = 0; r < NUM_ROUTES; ++r) { + if (rand() % 10 < 3) + selectedPoints[r].push_back(waypoints[i]); + } + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + routeIndices.push_back(christofides(selectedPoints[i])); + routeTimes.push_back(totalRouteCost(routeIndices[i], selectedPoints[i])); + } + + // Controleer tijdslimieten + valid = true; + for (auto t : routeTimes) { + if (t < MIN_ROUTE_TIME_MINUTES || t > MAX_ROUTE_TIME_MINUTES) { + valid = false; + break; + } + } + } + + for (int i = 0; i < NUM_ROUTES; ++i) { + cout << "Route " << i+1 << " tijd: " + << fixed << setprecision(1) << routeTimes[i] << " min\n"; + } + + saveRoutesToFiles(routeIndices, selectedPoints, allPoints); +} diff --git a/routegenerator.h b/routegenerator.h new file mode 100644 index 0000000..edb3200 --- /dev/null +++ b/routegenerator.h @@ -0,0 +1,31 @@ +// routegenerator.h + + +#ifndef ROUTEGENERATOR_H +#define ROUTEGENERATOR_H + +#include +#include + +struct Point { + int64_t x; + int64_t y; + + float cost; + + Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f); + + bool operator==(const Point& other) const; + +}; + + +extern std::vector> routeIndices; +extern std::vector> selectedPoints; +extern std::vector allPoints; + + +void generate_routes(); + + +#endif // ROUTEGENERATOR_H From 5c3070f0ab4a4adfd72d70761c95eee66deebc20 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:06:49 +0200 Subject: [PATCH 06/27] Delete mainwindow.cpp --- mainwindow.cpp | 257 ------------------------------------------------- 1 file changed, 257 deletions(-) delete mode 100644 mainwindow.cpp diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index 3918f6c..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (C) 2012 Denis Shienkov -// Copyright (C) 2012 Laszlo Papp -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "console.h" -#include "routegenerator.h" -#include "settingsdialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static constexpr std::chrono::seconds kWriteTimeout = std::chrono::seconds{5}; - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - m_ui(new Ui::MainWindow), - m_status(new QLabel), - m_console(new Console), - m_settings(new SettingsDialog(this)), - m_serial(new QSerialPort(this)) -{ - m_ui->setupUi(this); - - // Initialiseer QGraphicsScene - scene = new QGraphicsScene(this); - m_ui->graphicsView->setScene(scene); - m_ui->graphicsView->setRenderHint(QPainter::Antialiasing, true); - - // SerialPort + console instellingen - m_console->setEnabled(false); - QWidget* tab1 = m_console; - m_ui->tabWidget->addTab(tab1, "Developer mode"); - - m_ui->actionConnect->setEnabled(true); - m_ui->actionDisconnect->setEnabled(false); - m_ui->actionQuit->setEnabled(true); - m_ui->actionConfigure->setEnabled(true); - - m_ui->statusBar->addWidget(m_status); - - initActionsConnections(); - - connect(m_serial, &QSerialPort::errorOccurred, this, &MainWindow::handleError); - connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); - connect(m_console, &Console::getData, this, &MainWindow::writeData); - - QObject::connect(m_ui->pushButton_2, SIGNAL(pressed()), this, SLOT(pushButton_2_clicked())); -} - -MainWindow::~MainWindow() -{ - delete m_settings; - delete m_ui; -} - -void MainWindow::drawRoutes() -{ - scene->clear(); - - // 1) Bepaal min/max van alle getoonde punten - int64_t minX = std::numeric_limits::max(), maxX = std::numeric_limits::lowest(); - int64_t minY = std::numeric_limits::max(), maxY = std::numeric_limits::lowest(); - for (auto& pts : selectedPoints) - for (auto& p : pts) { - minX = std::min(minX, p.x); - maxX = std::max(maxX, p.x); - minY = std::min(minY, p.y); - maxY = std::max(maxY, p.y); - } - - // 2) Bereken schaal (+ marge) - QSizeF viewSz = m_ui->graphicsView->viewport()->size(); - constexpr qreal M = 20.0; // marge in pixels - qreal scaleX = (viewSz.width() - 2*M) / qreal(maxX - minX); - qreal scaleY = (viewSz.height() - 2*M) / qreal(maxY - minY); - qreal scale = std::min(scaleX, scaleY); - - auto mapPt = [&](const Point& p) { - qreal x = (p.x - minX)*scale + M; - qreal y = (p.y - minY)*scale + M; - return QPointF(x,y); - }; - - const int radius = 4; - const QColor colors[] = { - Qt::red, Qt::blue, Qt::green, Qt::darkYellow, - Qt::magenta, Qt::cyan, Qt::darkGreen, Qt::darkRed, - Qt::gray, Qt::black - }; - - // 3) Teken elke route in een andere kleur - for (int r = 0; r < routeIndices.size(); ++r) { - QPen pen(colors[r%10]); - pen.setCosmetic(true); - pen.setWidth(0); - const auto& route = routeIndices[r]; - const auto& pts = selectedPoints[r]; - - // lijnen - for (int i = 0; i + 1 < route.size(); ++i) { - QPointF p1 = mapPt(pts[ route[i] ]); - QPointF p2 = mapPt(pts[ route[i+1] ]); - scene->addLine(p1.x(), p1.y(), p2.x(), p2.y(), pen); - } - // punten - for (auto& p : pts) { - QPointF c = mapPt(p); - scene->addEllipse(c.x()-radius, c.y()-radius, - radius*2, radius*2, - QPen(Qt::black), - QBrush(colors[r%10])); - } - } - - // 4) Scene instellen en fitten - scene->setSceneRect(0, 0, viewSz.width(), viewSz.height()); - m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); -} - -void MainWindow::resizeEvent(QResizeEvent* event) -{ - QMainWindow::resizeEvent(event); - if (scene && !scene->items().isEmpty()) { - m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); - } -} - -void MainWindow::on_pushButton_clicked() -{ - m_ui->statusLabel->setText("Bezig met genereren..."); - QApplication::processEvents(); - - QFuture future = QtConcurrent::run([this]() { - generate_routes(); - QMetaObject::invokeMethod(this, [this]() { - m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); - drawRoutes(); - }, Qt::QueuedConnection); - }); -} - -void MainWindow::pushButton_2_clicked() -{ - QByteArray dataToSend("help \r\n"); - writeData(dataToSend); -} - -void MainWindow::openSerialPort() -{ - const SettingsDialog::Settings p = m_settings->settings(); - m_serial->setPortName(p.name); - m_serial->setBaudRate(p.baudRate); - m_serial->setDataBits(p.dataBits); - m_serial->setParity(p.parity); - m_serial->setStopBits(p.stopBits); - m_serial->setFlowControl(p.flowControl); - if (m_serial->open(QIODevice::ReadWrite)) { - m_console->setEnabled(true); - m_console->setLocalEchoEnabled(p.localEchoEnabled); - m_ui->actionConnect->setEnabled(false); - m_ui->actionDisconnect->setEnabled(true); - m_ui->actionConfigure->setEnabled(false); - showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6") - .arg(p.name, p.stringBaudRate, p.stringDataBits, - p.stringParity, p.stringStopBits, p.stringFlowControl)); - } else { - QMessageBox::critical(this, tr("Error"), m_serial->errorString()); - showStatusMessage(tr("Open error")); - } -} - -void MainWindow::closeSerialPort() -{ - if (m_serial->isOpen()) - m_serial->close(); - m_console->setEnabled(false); - m_ui->actionConnect->setEnabled(true); - m_ui->actionDisconnect->setEnabled(false); - m_ui->actionConfigure->setEnabled(true); - showStatusMessage(tr("Disconnected")); -} - -void MainWindow::about() -{ - QMessageBox::about(this, tr("About Serial Terminal"), - tr("The Serial Terminal example demonstrates how to " - "use the Qt Serial Port module in modern GUI applications " - "using Qt, with a menu bar, toolbars, and a status bar.")); -} - -void MainWindow::writeData(const QByteArray &data) -{ - m_serial->write(data); -} - -void MainWindow::readData() -{ - const QByteArray data = m_serial->readAll(); - m_console->putData(data); -} - -void MainWindow::handleError(QSerialPort::SerialPortError error) -{ - if (error == QSerialPort::ResourceError) { - QMessageBox::critical(this, tr("Critical Error"), m_serial->errorString()); - closeSerialPort(); - } -} - -void MainWindow::handleBytesWritten(qint64 bytes) -{ - m_bytesToWrite -= bytes; - if (m_bytesToWrite == 0) - m_timer->stop(); -} - -void MainWindow::handleWriteTimeout() -{ - const QString error = tr("Write operation timed out for port %1.\n" - "Error: %2").arg(m_serial->portName(), - m_serial->errorString()); - showWriteError(error); -} - -void MainWindow::initActionsConnections() -{ - connect(m_ui->actionConnect, &QAction::triggered, this, &MainWindow::openSerialPort); - connect(m_ui->actionDisconnect, &QAction::triggered, this, &MainWindow::closeSerialPort); - connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close); - connect(m_ui->actionConfigure, &QAction::triggered, m_settings, &SettingsDialog::show); - connect(m_ui->actionClear, &QAction::triggered, m_console, &Console::clear); - connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::about); - connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); -} - -void MainWindow::showStatusMessage(const QString &message) -{ - m_status->setText(message); -} - -void MainWindow::showWriteError(const QString &message) -{ - QMessageBox::warning(this, tr("Warning"), message); -} From ea73c34725fb235595b4ac13b479851f102cb391 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:06:57 +0200 Subject: [PATCH 07/27] Delete mainwindow.h --- mainwindow.h | 72 ---------------------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 mainwindow.h diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index 745913a..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2012 Denis Shienkov -// Copyright (C) 2012 Laszlo Papp -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QLabel; -class QTimer; - -namespace Ui { -class MainWindow; -} - -QT_END_NAMESPACE - -class Console; -class SettingsDialog; - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - -private slots: - void on_pushButton_clicked(); - void pushButton_2_clicked(); - - void openSerialPort(); - void closeSerialPort(); - void about(); - void writeData(const QByteArray &data); - void readData(); - - void handleError(QSerialPort::SerialPortError error); - void handleBytesWritten(qint64 bytes); - void handleWriteTimeout(); - -private: - void initActionsConnections(); - void showStatusMessage(const QString &message); - void showWriteError(const QString &message); - void drawRoutes(); - -protected: - // Elke keer bij resize opnieuw fitten - void resizeEvent(QResizeEvent* event) override; - -private: - Ui::MainWindow *m_ui = nullptr; - QLabel *m_status = nullptr; - Console *m_console = nullptr; - SettingsDialog *m_settings = nullptr; - qint64 m_bytesToWrite = 0; - QTimer *m_timer = nullptr; - QSerialPort *m_serial = nullptr; - QString dataRead; - QString selectedNode; - QGraphicsScene *scene = nullptr; -}; - -#endif // MAINWINDOW_H From 7f4864242952420d0e54b1a6720a6cd4631f8888 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:07:11 +0200 Subject: [PATCH 08/27] Delete mainwindow.ui --- mainwindow.ui | 235 -------------------------------------------------- 1 file changed, 235 deletions(-) delete mode 100644 mainwindow.ui diff --git a/mainwindow.ui b/mainwindow.ui deleted file mode 100644 index 61f006b..0000000 --- a/mainwindow.ui +++ /dev/null @@ -1,235 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 886 - 539 - - - - Serial Terminal - - - - - - - 0 - - - - Tab 1 - - - - - 0 - 370 - 191 - 29 - - - - Genereer Routes - - - - - - 210 - 370 - 191 - 31 - - - - status - - - - - - 740 - 370 - 93 - 29 - - - - testMessage - - - - - - 10 - 10 - 821 - 351 - - - - - - - - - - - - 0 - 0 - 886 - 22 - - - - - Calls - - - - - - - - - Tools - - - - - - - Help - - - - - - - - - - - TopToolBarArea - - - false - - - - - - - - - - toolBar - - - TopToolBarArea - - - false - - - - - &About - - - About program - - - Alt+A - - - - - About Qt - - - - - - :/images/connect.png:/images/connect.png - - - C&onnect - - - Connect to serial port - - - Ctrl+O - - - - - - :/images/disconnect.png:/images/disconnect.png - - - &Disconnect - - - Disconnect from serial port - - - Ctrl+D - - - - - - :/images/settings.png:/images/settings.png - - - &Configure - - - Configure serial port - - - Alt+C - - - - - - :/images/clear.png:/images/clear.png - - - C&lear - - - Clear data - - - Alt+L - - - - - - :/images/application-exit.png:/images/application-exit.png - - - &Quit - - - Ctrl+Q - - - - - - - - - From f805d6b28c44028b6decb87f2f1077ae1247c34c Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:07:21 +0200 Subject: [PATCH 09/27] Delete routegenerator.cpp --- routegenerator.cpp | 265 --------------------------------------------- 1 file changed, 265 deletions(-) delete mode 100644 routegenerator.cpp diff --git a/routegenerator.cpp b/routegenerator.cpp deleted file mode 100644 index 97e16d3..0000000 --- a/routegenerator.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include "routegenerator.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -const int NUM_HOTPOINTS = 5; -const int NUM_ROUTES = 10; -const float MIN_ROUTE_TIME_MINUTES = 1.0f * 60.0f; // 60 min -const float MAX_ROUTE_TIME_MINUTES = 2.0f * 60.0f; // 120 min - -constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld -constexpr double WALKING_SPEED_M_PER_S = 1.4; // meter per seconde - - -Point::Point(int64_t x, int64_t y, float cost) - : x(x), y(y), cost(cost) -{} - -bool Point::operator==(const Point& other) const { - return x == other.x - && y == other.y - && cost == other.cost; -} - -vector> routeIndices; -vector> selectedPoints; -vector allPoints; - -struct Edge { - int u, v; - float weight; -}; - -// Bereken afstand in meters tussen twee microdegree-punten - -double geoDistanceMeters(const Point& a, const Point& b) { - double dx_deg = (a.x - b.x) * 1e-6; - double dy_deg = (a.y - b.y) * 1e-6; - double dx_m = dx_deg * METERS_PER_DEGREE; - double dy_m = dy_deg * METERS_PER_DEGREE; - return sqrt(dx_m*dx_m + dy_m*dy_m); -} - -// Bereken tijd in minuten tussen twee punten - -double stepTimeMinutes(const Point& a, const Point& b) { - double dist_m = geoDistanceMeters(a, b); - return (dist_m / WALKING_SPEED_M_PER_S) / 60.0; -} - -int findSet(int x, vector& parent) { - return parent[x] == x ? x : parent[x] = findSet(parent[x], parent); -} - -float totalRouteCost(const vector& route, const vector& points) { - float total = 0.0f; - for (size_t i = 0; i + 1 < route.size(); ++i) { - total += static_cast(stepTimeMinutes(points[route[i]], points[route[i+1]])) - + points[route[i]].cost; - } - total += points[route.back()].cost; - return total; -} - -vector computeMST(vector& points) { - size_t n = points.size(); - vector edges, mst; - vector parent(n); - for (size_t i = 0; i < n; ++i) parent[i] = i; - - for (size_t i = 0; i < n; ++i) { - for (size_t j = i + 1; j < n; ++j) { - double d = geoDistanceMeters(points[i], points[j]); - edges.push_back({ static_cast(i), static_cast(j), static_cast(d) }); - } - } - sort(edges.begin(), edges.end(), [](auto& a, auto& b){ return a.weight < b.weight; }); - for (auto& e : edges) { - int u = findSet(e.u, parent); - int v = findSet(e.v, parent); - if (u != v) { - mst.push_back(e); - parent[u] = v; - if (mst.size() == n - 1) break; - } - } - return mst; -} - - -void optimizeRoute(vector& route, vector& points) { - bool improved = true; - while (improved) { - improved = false; - for (size_t i = 1; i + 2 < route.size(); ++i) { - for (size_t j = i + 1; j + 1 < route.size(); ++j) { - double d1 = geoDistanceMeters(points[route[i-1]], points[route[i]]) - + geoDistanceMeters(points[route[j]], points[route[j+1]]); - double d2 = geoDistanceMeters(points[route[i-1]], points[route[j]]) - + geoDistanceMeters(points[route[i]], points[route[j+1]]); - if (d2 < d1) { - reverse(route.begin()+i, route.begin()+j+1); - improved = true; - } - } - } - } -} - -vector christofides(vector& points) { - auto mst = computeMST(points); - vector degree(points.size(), 0); - for (auto& e : mst) { - degree[e.u]++; degree[e.v]++; - } - vector odd; - for (size_t i = 0; i < points.size(); ++i) - if (degree[i] % 2) odd.push_back(static_cast(i)); - - vector matching; - while (odd.size() >= 2) { - int u = odd.back(); odd.pop_back(); - int v = odd.back(); odd.pop_back(); - matching.push_back({u, v, static_cast(geoDistanceMeters(points[u], points[v]))}); - } - - vector eulerian = mst; - eulerian.insert(eulerian.end(), matching.begin(), matching.end()); - - vector route; - vector used(points.size(), false); - route.push_back(0); - used[0] = true; - for (auto& e : eulerian) { - if (!used[e.v]) { - route.push_back(e.v); - used[e.v] = true; - } - } - route.push_back(route.front()); - optimizeRoute(route, points); - return route; -} - -void saveRoutesToFiles(const vector>& routes, - const vector>& selPts, - const vector& allPts) -{ - for (size_t i = 0; i < routes.size(); ++i) { - string filename = "Route_" + to_string(i+1) + ".json"; - ofstream file(filename); - if (!file) { - cerr << "Kon bestand niet openen: " << filename << "\n"; - continue; - } - file << "[\n"; - for (size_t j = 0; j < routes[i].size(); ++j) { - const Point& p = selPts[i][routes[i][j]]; - auto it = find(allPts.begin(), allPts.end(), p); - int mg_id = it != allPts.end() ? static_cast(distance(allPts.begin(), it)) : -1; - file << " { \"x\": " << p.x - << ", \"y\": " << p.y - << ", \"mg_id\": " << mg_id << " }" - << (j+1 < routes[i].size() ? "," : "") << "\n"; - } - file << "]\n"; - cout << "Opgeslagen in '" << filename << "'.\n"; - } -} - -void generate_routes() { - // definieer alle punten met microdegrees en kosten - allPoints = { - {51688573, 5287210, 2.0f}, // Avans - {51690224, 5296625, 1.5f}, // Jan de Groot - {51688460, 5303150, 2.0f}, // Stadhuis - {51686200, 5304500, 1.5f}, // Sint Jans Kathedraal - {51685051, 5289156, 2.0f}, // Paleisbrug - {51684258, 5302611, 1.5f}, // Zuidwal - {51691299, 5303950, 2.0f}, // Arena - {51689428, 5310484, 1.5f}, // Nationaal Carnavalsmuseum - {51695984, 5299074, 2.0f}, // Tramkade - {51689124, 5303969, 1.5f}, // De Markt - {51689619, 5299065, 2.0f}, // Bolwerk - {51693002, 5301264, 1.5f}, // VUE Cinema - {51697021, 5299328, 2.0f}, // Bossche Brouwers - {51689724, 5300408, 1.5f}, // Café Bar le Duc - {51686471, 5304106, 2.0f}, // Museumkwartier - {51689471, 5303200, 1.5f}, // Moriaan - {51689302, 5303396, 2.0f}, // ’t Opkikkertje - {51695457, 5297448, 1.5f}, // Verkadefabriek - {51694463, 5302862, 2.0f}, // BHIC - {51690467, 5294925, 1.5f}, // Station - {51683776, 5317938, 2.0f}, // Zuiderpark - {51687561, 5305911, 1.5f}, // Korte Putstraat - {51698630, 5292803, 1.5f}, // Brabanthallen - {51688691, 5309001, 1.5f}, // Café de Palm - {51687344, 5305871, 1.5f}, // Bistro Tante Pietje - {51696264, 5307460, 1.5f}, // Taxandriaplein Park - {51696501, 5312884, 1.5f}, // IJzeren Vrouw Park - {51691911, 5286594, 1.5f}, // Simon Stevinweg - {51690402, 5291740, 1.5f}, // Hugo de Grootplein - {51685400, 5289354, 1.5f}, // Kinepolis - {51690484, 5296206, 1.5f}, // Gouden Draak - {51686618, 5308459, 1.5f}, // Theater aan de Parade - {51686697, 5303137, 1.5f} // Gemeentehuis - }; - - vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); - vector waypoints(allPoints.begin() + NUM_HOTPOINTS, allPoints.end()); - - random_device rd; - mt19937 g(rd()); - - selectedPoints.clear(); - routeIndices.clear(); - vector routeTimes; - bool valid = false; - - // Blijf genereren totdat alle routes binnen de tijdslimieten vallen - while (!valid) { - selectedPoints = vector>(NUM_ROUTES, hotpoints); - routeIndices.clear(); - routeTimes.clear(); - - shuffle(waypoints.begin(), waypoints.end(), g); - for (size_t i = 0; i < waypoints.size(); ++i) { - int rid = static_cast(i % NUM_ROUTES); - selectedPoints[rid].push_back(waypoints[i]); - for (int r = 0; r < NUM_ROUTES; ++r) { - if (rand() % 10 < 3) - selectedPoints[r].push_back(waypoints[i]); - } - } - - for (int i = 0; i < NUM_ROUTES; ++i) { - routeIndices.push_back(christofides(selectedPoints[i])); - routeTimes.push_back(totalRouteCost(routeIndices[i], selectedPoints[i])); - } - - // Controleer tijdslimieten - valid = true; - for (auto t : routeTimes) { - if (t < MIN_ROUTE_TIME_MINUTES || t > MAX_ROUTE_TIME_MINUTES) { - valid = false; - break; - } - } - } - - for (int i = 0; i < NUM_ROUTES; ++i) { - cout << "Route " << i+1 << " tijd: " - << fixed << setprecision(1) << routeTimes[i] << " min\n"; - } - - saveRoutesToFiles(routeIndices, selectedPoints, allPoints); -} From 08d83048ae58cba9b86338e4aa9c4967ffda3412 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:07:28 +0200 Subject: [PATCH 10/27] Delete routegenerator.h --- routegenerator.h | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 routegenerator.h diff --git a/routegenerator.h b/routegenerator.h deleted file mode 100644 index edb3200..0000000 --- a/routegenerator.h +++ /dev/null @@ -1,31 +0,0 @@ -// routegenerator.h - - -#ifndef ROUTEGENERATOR_H -#define ROUTEGENERATOR_H - -#include -#include - -struct Point { - int64_t x; - int64_t y; - - float cost; - - Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f); - - bool operator==(const Point& other) const; - -}; - - -extern std::vector> routeIndices; -extern std::vector> selectedPoints; -extern std::vector allPoints; - - -void generate_routes(); - - -#endif // ROUTEGENERATOR_H From bd1c1159ca49a6de7195b1e7cdabcfef6b933a27 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:12:27 +0200 Subject: [PATCH 11/27] Update routegenerator.h --- RoutesGenereren/routegenerator.h | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h index a8eb575..6a600fa 100644 --- a/RoutesGenereren/routegenerator.h +++ b/RoutesGenereren/routegenerator.h @@ -1,25 +1,22 @@ +// routegenerator.h + + #ifndef ROUTEGENERATOR_H #define ROUTEGENERATOR_H +#include #include struct Point { - float x, y; - float cost = 1.0f; - - Point(float x, float y, float cost = 1.0f) : x(x), y(y), cost(cost) {} - - bool operator==(const Point& other) const { - return x == other.x && y == other.y; - } + int64_t x; + int64_t y; + float cost; + Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f); + bool operator==(const Point& other) const; }; - +extern std::vector> routeIndices; +extern std::vector> selectedPoints; +extern std::vector allPoints; void generate_routes(); -std::vector> getGeneratedRoutes(); -std::vector> getGeneratedIndices(); -std::vector getAllPoints(); -extern std::vector> routeIndices; -extern std::vector> selectedPoints; -extern std::vector allPoints; #endif // ROUTEGENERATOR_H From 7bdeafc8dd9448190f5b1ebbb371b60176a676c8 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:12:53 +0200 Subject: [PATCH 12/27] Update routegenerator.cpp --- RoutesGenereren/routegenerator.cpp | 257 +++++++++++++++++------------ 1 file changed, 151 insertions(+), 106 deletions(-) diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp index e68a570..97e16d3 100644 --- a/RoutesGenereren/routegenerator.cpp +++ b/RoutesGenereren/routegenerator.cpp @@ -1,4 +1,3 @@ - #include "routegenerator.h" #include @@ -8,37 +7,64 @@ #include #include #include +#include using namespace std; -const int NUM_HOTPOINTS = 5; -const int NUM_ROUTES = 10; -const float MAX_DISTANCE_RATIO = 1.15f; -const float WALKING_SPEED_PX_PER_MIN = 84.0f; // 1.4 m/s → 84 px/min +const int NUM_HOTPOINTS = 5; +const int NUM_ROUTES = 10; +const float MIN_ROUTE_TIME_MINUTES = 1.0f * 60.0f; // 60 min +const float MAX_ROUTE_TIME_MINUTES = 2.0f * 60.0f; // 120 min + +constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld +constexpr double WALKING_SPEED_M_PER_S = 1.4; // meter per seconde -std::vector> routeIndices; -std::vector> selectedPoints; -std::vector allPoints; + +Point::Point(int64_t x, int64_t y, float cost) + : x(x), y(y), cost(cost) +{} + +bool Point::operator==(const Point& other) const { + return x == other.x + && y == other.y + && cost == other.cost; +} + +vector> routeIndices; +vector> selectedPoints; +vector allPoints; struct Edge { int u, v; float weight; }; -int find(int x, vector& parent) { - if (parent[x] == x) return x; - return parent[x] = find(parent[x], parent); +// Bereken afstand in meters tussen twee microdegree-punten + +double geoDistanceMeters(const Point& a, const Point& b) { + double dx_deg = (a.x - b.x) * 1e-6; + double dy_deg = (a.y - b.y) * 1e-6; + double dx_m = dx_deg * METERS_PER_DEGREE; + double dy_m = dy_deg * METERS_PER_DEGREE; + return sqrt(dx_m*dx_m + dy_m*dy_m); } -float distance(const Point& a, const Point& b) { - return static_cast(sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2))); +// Bereken tijd in minuten tussen twee punten + +double stepTimeMinutes(const Point& a, const Point& b) { + double dist_m = geoDistanceMeters(a, b); + return (dist_m / WALKING_SPEED_M_PER_S) / 60.0; +} + +int findSet(int x, vector& parent) { + return parent[x] == x ? x : parent[x] = findSet(parent[x], parent); } float totalRouteCost(const vector& route, const vector& points) { float total = 0.0f; - for (size_t i = 0; i < route.size() - 1; ++i) { - float walk = distance(points[route[i]], points[route[i + 1]]) / WALKING_SPEED_PX_PER_MIN; - total += walk + points[route[i]].cost; + for (size_t i = 0; i + 1 < route.size(); ++i) { + total += static_cast(stepTimeMinutes(points[route[i]], points[route[i+1]])) + + points[route[i]].cost; } total += points[route.back()].cost; return total; @@ -48,43 +74,40 @@ vector computeMST(vector& points) { size_t n = points.size(); vector edges, mst; vector parent(n); - for (int i = 0; i < n; i++) parent[i] = i; + for (size_t i = 0; i < n; ++i) parent[i] = i; - for (int i = 0; i < n; i++) { - for (int j = i + 1; j < n; j++) { - edges.push_back({ i, j, distance(points[i], points[j]) }); + for (size_t i = 0; i < n; ++i) { + for (size_t j = i + 1; j < n; ++j) { + double d = geoDistanceMeters(points[i], points[j]); + edges.push_back({ static_cast(i), static_cast(j), static_cast(d) }); } } - - sort(edges.begin(), edges.end(), [](Edge a, Edge b) { - return a.weight < b.weight; - }); - - for (const auto& edge : edges) { - int setU = find(edge.u, parent); - int setV = find(edge.v, parent); - if (setU != setV) { - mst.push_back(edge); - parent[setU] = setV; + sort(edges.begin(), edges.end(), [](auto& a, auto& b){ return a.weight < b.weight; }); + for (auto& e : edges) { + int u = findSet(e.u, parent); + int v = findSet(e.v, parent); + if (u != v) { + mst.push_back(e); + parent[u] = v; if (mst.size() == n - 1) break; } } - return mst; } + void optimizeRoute(vector& route, vector& points) { bool improved = true; while (improved) { improved = false; - for (size_t i = 1; i < route.size() - 2; ++i) { - for (size_t j = i + 1; j < route.size() - 1; ++j) { - float d1 = distance(points[route[i - 1]], points[route[i]]) + - distance(points[route[j]], points[route[j + 1]]); - float d2 = distance(points[route[i - 1]], points[route[j]]) + - distance(points[route[i]], points[route[j + 1]]); + for (size_t i = 1; i + 2 < route.size(); ++i) { + for (size_t j = i + 1; j + 1 < route.size(); ++j) { + double d1 = geoDistanceMeters(points[route[i-1]], points[route[i]]) + + geoDistanceMeters(points[route[j]], points[route[j+1]]); + double d2 = geoDistanceMeters(points[route[i-1]], points[route[j]]) + + geoDistanceMeters(points[route[i]], points[route[j+1]]); if (d2 < d1) { - reverse(route.begin() + i, route.begin() + j + 1); + reverse(route.begin()+i, route.begin()+j+1); improved = true; } } @@ -93,84 +116,102 @@ void optimizeRoute(vector& route, vector& points) { } vector christofides(vector& points) { - vector mst = computeMST(points); + auto mst = computeMST(points); vector degree(points.size(), 0); - - for (auto& edge : mst) { - degree[edge.u]++; - degree[edge.v]++; - } - - vector oddNodes; - for (int i = 0; i < points.size(); i++) { - if (degree[i] % 2 == 1) oddNodes.push_back(i); + for (auto& e : mst) { + degree[e.u]++; degree[e.v]++; } + vector odd; + for (size_t i = 0; i < points.size(); ++i) + if (degree[i] % 2) odd.push_back(static_cast(i)); vector matching; - while (!oddNodes.empty()) { - int u = oddNodes.back(); oddNodes.pop_back(); - int v = oddNodes.back(); oddNodes.pop_back(); - matching.push_back({ u, v, distance(points[u], points[v]) }); + while (odd.size() >= 2) { + int u = odd.back(); odd.pop_back(); + int v = odd.back(); odd.pop_back(); + matching.push_back({u, v, static_cast(geoDistanceMeters(points[u], points[v]))}); } - vector eulerianGraph = mst; - eulerianGraph.insert(eulerianGraph.end(), matching.begin(), matching.end()); + vector eulerian = mst; + eulerian.insert(eulerian.end(), matching.begin(), matching.end()); vector route; - vector visited(points.size(), false); + vector used(points.size(), false); route.push_back(0); - visited[0] = true; - - for (auto& edge : eulerianGraph) { - if (!visited[edge.v]) { - route.push_back(edge.v); - visited[edge.v] = true; + used[0] = true; + for (auto& e : eulerian) { + if (!used[e.v]) { + route.push_back(e.v); + used[e.v] = true; } } - route.push_back(route.front()); optimizeRoute(route, points); return route; } -void saveRoutesToFiles(const vector>& routes, const vector>& selectedPoints, const vector& allPoints) { - for (int i = 0; i < routes.size(); ++i) { - string filename = "Route_" + to_string(i + 1) + ".txt"; +void saveRoutesToFiles(const vector>& routes, + const vector>& selPts, + const vector& allPts) +{ + for (size_t i = 0; i < routes.size(); ++i) { + string filename = "Route_" + to_string(i+1) + ".json"; ofstream file(filename); - if (!file.is_open()) { - cerr << "Kon bestand niet openen: " << filename << endl; + if (!file) { + cerr << "Kon bestand niet openen: " << filename << "\n"; continue; } - - file << "Route " << i + 1 << ": "; - for (int index : routes[i]) { - auto it = find(allPoints.begin(), allPoints.end(), selectedPoints[i][index]); - if (it != allPoints.end()) { - int locationID = static_cast(distance(allPoints.begin(), it)); - file << locationID << " "; - } else { - file << "X "; - } + file << "[\n"; + for (size_t j = 0; j < routes[i].size(); ++j) { + const Point& p = selPts[i][routes[i][j]]; + auto it = find(allPts.begin(), allPts.end(), p); + int mg_id = it != allPts.end() ? static_cast(distance(allPts.begin(), it)) : -1; + file << " { \"x\": " << p.x + << ", \"y\": " << p.y + << ", \"mg_id\": " << mg_id << " }" + << (j+1 < routes[i].size() ? "," : "") << "\n"; } - - file << endl; - file.close(); - cout << "Opgeslagen in '" << filename << "'." << endl; + file << "]\n"; + cout << "Opgeslagen in '" << filename << "'.\n"; } } void generate_routes() { - vector allPoints = { - {100, 100, 2.0}, {300, 200, 1.5}, {500, 400, 3.5}, {200, 500, 2.5}, {600, 100, 4.0}, - {150, 300, 1.0}, {400, 350, 1.0}, {250, 50, 3.0}, {550, 250, 2.0}, {700, 150, 2.0}, - {120, 500, 1.5}, {320, 270, 1.0}, {530, 420, 2.5}, {260, 430, 2.0}, {580, 130, 3.0}, - {110, 220, 1.0}, {350, 180, 1.5}, {480, 380, 2.5}, {230, 510, 1.0}, {630, 170, 3.0}, - {50, 450, 1.5}, {450, 100, 2.0}, {370, 320, 1.0}, {670, 220, 2.5}, {280, 370, 1.0}, - {90, 290, 1.0}, {490, 470, 3.5}, {310, 150, 1.0}, {590, 290, 1.5}, {710, 410, 3.0}, - {400, 500, 2.5}, {540, 350, 2.0}, {620, 200, 1.0}, {260, 260, 1.0}, {150, 400, 1.0}, - {340, 530, 2.0}, {680, 300, 2.5}, {130, 140, 1.5}, {470, 240, 1.0}, {700, 500, 3.0}, - {330, 440, 1.0}, {210, 360, 1.0}, {570, 450, 2.0}, {90, 190, 1.0}, {610, 110, 3.0}, - {250, 480, 1.5}, {370, 210, 1.0}, {560, 310, 2.5}, {720, 180, 1.0}, {430, 280, 1.0} + // definieer alle punten met microdegrees en kosten + allPoints = { + {51688573, 5287210, 2.0f}, // Avans + {51690224, 5296625, 1.5f}, // Jan de Groot + {51688460, 5303150, 2.0f}, // Stadhuis + {51686200, 5304500, 1.5f}, // Sint Jans Kathedraal + {51685051, 5289156, 2.0f}, // Paleisbrug + {51684258, 5302611, 1.5f}, // Zuidwal + {51691299, 5303950, 2.0f}, // Arena + {51689428, 5310484, 1.5f}, // Nationaal Carnavalsmuseum + {51695984, 5299074, 2.0f}, // Tramkade + {51689124, 5303969, 1.5f}, // De Markt + {51689619, 5299065, 2.0f}, // Bolwerk + {51693002, 5301264, 1.5f}, // VUE Cinema + {51697021, 5299328, 2.0f}, // Bossche Brouwers + {51689724, 5300408, 1.5f}, // Café Bar le Duc + {51686471, 5304106, 2.0f}, // Museumkwartier + {51689471, 5303200, 1.5f}, // Moriaan + {51689302, 5303396, 2.0f}, // ’t Opkikkertje + {51695457, 5297448, 1.5f}, // Verkadefabriek + {51694463, 5302862, 2.0f}, // BHIC + {51690467, 5294925, 1.5f}, // Station + {51683776, 5317938, 2.0f}, // Zuiderpark + {51687561, 5305911, 1.5f}, // Korte Putstraat + {51698630, 5292803, 1.5f}, // Brabanthallen + {51688691, 5309001, 1.5f}, // Café de Palm + {51687344, 5305871, 1.5f}, // Bistro Tante Pietje + {51696264, 5307460, 1.5f}, // Taxandriaplein Park + {51696501, 5312884, 1.5f}, // IJzeren Vrouw Park + {51691911, 5286594, 1.5f}, // Simon Stevinweg + {51690402, 5291740, 1.5f}, // Hugo de Grootplein + {51685400, 5289354, 1.5f}, // Kinepolis + {51690484, 5296206, 1.5f}, // Gouden Draak + {51686618, 5308459, 1.5f}, // Theater aan de Parade + {51686697, 5303137, 1.5f} // Gemeentehuis }; vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); @@ -184,36 +225,40 @@ void generate_routes() { vector routeTimes; bool valid = false; + // Blijf genereren totdat alle routes binnen de tijdslimieten vallen while (!valid) { - selectedPoints = vector>(NUM_ROUTES, vector(hotpoints)); + selectedPoints = vector>(NUM_ROUTES, hotpoints); routeIndices.clear(); routeTimes.clear(); shuffle(waypoints.begin(), waypoints.end(), g); - for (size_t i = 0; i < waypoints.size(); ++i) { - int routeIndex = i % NUM_ROUTES; - selectedPoints[routeIndex].push_back(waypoints[i]); + int rid = static_cast(i % NUM_ROUTES); + selectedPoints[rid].push_back(waypoints[i]); for (int r = 0; r < NUM_ROUTES; ++r) { - if (rand() % 10 < 3) { + if (rand() % 10 < 3) selectedPoints[r].push_back(waypoints[i]); - } } } for (int i = 0; i < NUM_ROUTES; ++i) { - auto route = christofides(selectedPoints[i]); - routeIndices.push_back(route); - routeTimes.push_back(totalRouteCost(route, selectedPoints[i])); + routeIndices.push_back(christofides(selectedPoints[i])); + routeTimes.push_back(totalRouteCost(routeIndices[i], selectedPoints[i])); } - float minTime = *min_element(routeTimes.begin(), routeTimes.end()); - float maxTime = *max_element(routeTimes.begin(), routeTimes.end()); - valid = (maxTime / minTime) <= MAX_DISTANCE_RATIO; + // Controleer tijdslimieten + valid = true; + for (auto t : routeTimes) { + if (t < MIN_ROUTE_TIME_MINUTES || t > MAX_ROUTE_TIME_MINUTES) { + valid = false; + break; + } + } } for (int i = 0; i < NUM_ROUTES; ++i) { - cout << "Route " << i + 1 << " tijd: " << routeTimes[i] << " min" << endl; + cout << "Route " << i+1 << " tijd: " + << fixed << setprecision(1) << routeTimes[i] << " min\n"; } saveRoutesToFiles(routeIndices, selectedPoints, allPoints); From edbf4b0cd824b87cd2b74a5fd914d41225b9d194 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:13:14 +0200 Subject: [PATCH 13/27] Update mainwindow.cpp --- RoutesGenereren/mainwindow.cpp | 93 ++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 26c32d1..3918f6c 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -13,9 +13,10 @@ #include #include #include -#include #include #include +#include +#include #include #include @@ -32,11 +33,14 @@ MainWindow::MainWindow(QWidget *parent) : m_serial(new QSerialPort(this)) { m_ui->setupUi(this); - m_console->setEnabled(false); - //setCentralWidget(m_console); + + // Initialiseer QGraphicsScene scene = new QGraphicsScene(this); m_ui->graphicsView->setScene(scene); + m_ui->graphicsView->setRenderHint(QPainter::Antialiasing, true); + // SerialPort + console instellingen + m_console->setEnabled(false); QWidget* tab1 = m_console; m_ui->tabWidget->addTab(tab1, "Developer mode"); @@ -66,6 +70,30 @@ void MainWindow::drawRoutes() { scene->clear(); + // 1) Bepaal min/max van alle getoonde punten + int64_t minX = std::numeric_limits::max(), maxX = std::numeric_limits::lowest(); + int64_t minY = std::numeric_limits::max(), maxY = std::numeric_limits::lowest(); + for (auto& pts : selectedPoints) + for (auto& p : pts) { + minX = std::min(minX, p.x); + maxX = std::max(maxX, p.x); + minY = std::min(minY, p.y); + maxY = std::max(maxY, p.y); + } + + // 2) Bereken schaal (+ marge) + QSizeF viewSz = m_ui->graphicsView->viewport()->size(); + constexpr qreal M = 20.0; // marge in pixels + qreal scaleX = (viewSz.width() - 2*M) / qreal(maxX - minX); + qreal scaleY = (viewSz.height() - 2*M) / qreal(maxY - minY); + qreal scale = std::min(scaleX, scaleY); + + auto mapPt = [&](const Point& p) { + qreal x = (p.x - minX)*scale + M; + qreal y = (p.y - minY)*scale + M; + return QPointF(x,y); + }; + const int radius = 4; const QColor colors[] = { Qt::red, Qt::blue, Qt::green, Qt::darkYellow, @@ -73,42 +101,54 @@ void MainWindow::drawRoutes() Qt::gray, Qt::black }; + // 3) Teken elke route in een andere kleur for (int r = 0; r < routeIndices.size(); ++r) { - QPen pen(colors[r % 10]); - pen.setWidth(2); - const auto& route = routeIndices[r]; - const auto& points = selectedPoints[r]; - - // Teken lijnen tussen punten - for (size_t i = 0; i < route.size() - 1; ++i) { - const Point& p1 = points[route[i]]; - const Point& p2 = points[route[i + 1]]; - scene->addLine(p1.x, p1.y, p2.x, p2.y, pen); + QPen pen(colors[r%10]); + pen.setCosmetic(true); + pen.setWidth(0); + const auto& route = routeIndices[r]; + const auto& pts = selectedPoints[r]; + + // lijnen + for (int i = 0; i + 1 < route.size(); ++i) { + QPointF p1 = mapPt(pts[ route[i] ]); + QPointF p2 = mapPt(pts[ route[i+1] ]); + scene->addLine(p1.x(), p1.y(), p2.x(), p2.y(), pen); } - - // Teken de punten - for (const auto& pt : points) { - scene->addEllipse(pt.x - radius, pt.y - radius, - radius * 2, radius * 2, - QPen(Qt::black), QBrush(colors[r % 10])); + // punten + for (auto& p : pts) { + QPointF c = mapPt(p); + scene->addEllipse(c.x()-radius, c.y()-radius, + radius*2, radius*2, + QPen(Qt::black), + QBrush(colors[r%10])); } } - scene->setSceneRect(scene->itemsBoundingRect()); + // 4) Scene instellen en fitten + scene->setSceneRect(0, 0, viewSz.width(), viewSz.height()); + m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); +} + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + if (scene && !scene->items().isEmpty()) { + m_ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); + } } void MainWindow::on_pushButton_clicked() { m_ui->statusLabel->setText("Bezig met genereren..."); - QApplication::processEvents(); // direct UI updaten + QApplication::processEvents(); QFuture future = QtConcurrent::run([this]() { - generate_routes(); // Genereer de routes - + generate_routes(); QMetaObject::invokeMethod(this, [this]() { - m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); - drawRoutes(); // Visualiseer routes - }, Qt::QueuedConnection); + m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); + }, Qt::QueuedConnection); }); } @@ -138,7 +178,6 @@ void MainWindow::openSerialPort() p.stringParity, p.stringStopBits, p.stringFlowControl)); } else { QMessageBox::critical(this, tr("Error"), m_serial->errorString()); - showStatusMessage(tr("Open error")); } } From 8e65cdf23b509910de1f41741de4ca561216cf63 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:13:32 +0200 Subject: [PATCH 14/27] Update mainwindow.h --- RoutesGenereren/mainwindow.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h index fb474ea..745913a 100644 --- a/RoutesGenereren/mainwindow.h +++ b/RoutesGenereren/mainwindow.h @@ -5,8 +5,9 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include #include +#include +#include #include QT_BEGIN_NAMESPACE @@ -47,12 +48,15 @@ private slots: private: void initActionsConnections(); - -private: void showStatusMessage(const QString &message); void showWriteError(const QString &message); void drawRoutes(); +protected: + // Elke keer bij resize opnieuw fitten + void resizeEvent(QResizeEvent* event) override; + +private: Ui::MainWindow *m_ui = nullptr; QLabel *m_status = nullptr; Console *m_console = nullptr; @@ -62,7 +66,7 @@ private slots: QSerialPort *m_serial = nullptr; QString dataRead; QString selectedNode; - QGraphicsScene *scene= nullptr; + QGraphicsScene *scene = nullptr; }; #endif // MAINWINDOW_H From 3909e7182906a40c3cbc411872a4d972faf61980 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:14:22 +0200 Subject: [PATCH 15/27] Update mainwindow.ui From 7cc6f61fc393d93dd82686e0744341f3c46c7a21 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 18:07:25 +0200 Subject: [PATCH 16/27] Update RoutesGenereren.pro --- RoutesGenereren/RoutesGenereren.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoutesGenereren/RoutesGenereren.pro b/RoutesGenereren/RoutesGenereren.pro index 209df6b..203165a 100644 --- a/RoutesGenereren/RoutesGenereren.pro +++ b/RoutesGenereren/RoutesGenereren.pro @@ -2,7 +2,7 @@ QT += core gui serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -CONFIG += c++11 +CONFIG += c++14 QT += concurrent # You can make your code fail to compile if it uses deprecated APIs. From 991b3e584e27dfee3dda7818a745980e7499b419 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 21:20:22 +0200 Subject: [PATCH 17/27] Update routegenerator.cpp --- RoutesGenereren/routegenerator.cpp | 157 +++++++---------------------- 1 file changed, 39 insertions(+), 118 deletions(-) diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp index 97e16d3..ee10eec 100644 --- a/RoutesGenereren/routegenerator.cpp +++ b/RoutesGenereren/routegenerator.cpp @@ -20,57 +20,15 @@ constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld constexpr double WALKING_SPEED_M_PER_S = 1.4; // meter per seconde -Point::Point(int64_t x, int64_t y, float cost) - : x(x), y(y), cost(cost) +Point::Point(int64_t x, int64_t y, float cost, int mg_id) + : x(x), y(y), cost(cost), mg_id(mg_id) {} bool Point::operator==(const Point& other) const { return x == other.x && y == other.y - && cost == other.cost; -} - -vector> routeIndices; -vector> selectedPoints; -vector allPoints; - -struct Edge { - int u, v; - float weight; -}; - -// Bereken afstand in meters tussen twee microdegree-punten - -double geoDistanceMeters(const Point& a, const Point& b) { - double dx_deg = (a.x - b.x) * 1e-6; - double dy_deg = (a.y - b.y) * 1e-6; - double dx_m = dx_deg * METERS_PER_DEGREE; - double dy_m = dy_deg * METERS_PER_DEGREE; - return sqrt(dx_m*dx_m + dy_m*dy_m); -} - -// Bereken tijd in minuten tussen twee punten - -double stepTimeMinutes(const Point& a, const Point& b) { - double dist_m = geoDistanceMeters(a, b); - return (dist_m / WALKING_SPEED_M_PER_S) / 60.0; -} - -int findSet(int x, vector& parent) { - return parent[x] == x ? x : parent[x] = findSet(parent[x], parent); -} - -float totalRouteCost(const vector& route, const vector& points) { - float total = 0.0f; - for (size_t i = 0; i + 1 < route.size(); ++i) { - total += static_cast(stepTimeMinutes(points[route[i]], points[route[i+1]])) - + points[route[i]].cost; - } - total += points[route.back()].cost; - return total; -} - -vector computeMST(vector& points) { + && cost == other.cost + && mg_id == other.mg_id; size_t n = points.size(); vector edges, mst; vector parent(n); @@ -128,47 +86,10 @@ vector christofides(vector& points) { vector matching; while (odd.size() >= 2) { int u = odd.back(); odd.pop_back(); - int v = odd.back(); odd.pop_back(); - matching.push_back({u, v, static_cast(geoDistanceMeters(points[u], points[v]))}); - } - - vector eulerian = mst; - eulerian.insert(eulerian.end(), matching.begin(), matching.end()); - - vector route; - vector used(points.size(), false); - route.push_back(0); - used[0] = true; - for (auto& e : eulerian) { - if (!used[e.v]) { - route.push_back(e.v); - used[e.v] = true; - } - } - route.push_back(route.front()); - optimizeRoute(route, points); - return route; -} - -void saveRoutesToFiles(const vector>& routes, - const vector>& selPts, - const vector& allPts) -{ - for (size_t i = 0; i < routes.size(); ++i) { - string filename = "Route_" + to_string(i+1) + ".json"; - ofstream file(filename); - if (!file) { - cerr << "Kon bestand niet openen: " << filename << "\n"; - continue; - } - file << "[\n"; - for (size_t j = 0; j < routes[i].size(); ++j) { - const Point& p = selPts[i][routes[i][j]]; - auto it = find(allPts.begin(), allPts.end(), p); - int mg_id = it != allPts.end() ? static_cast(distance(allPts.begin(), it)) : -1; + file << " { \"x\": " << p.x << ", \"y\": " << p.y - << ", \"mg_id\": " << mg_id << " }" + << ", \"mg_id\": " << p.mg_id << " }" << (j+1 < routes[i].size() ? "," : "") << "\n"; } file << "]\n"; @@ -179,39 +100,39 @@ void saveRoutesToFiles(const vector>& routes, void generate_routes() { // definieer alle punten met microdegrees en kosten allPoints = { - {51688573, 5287210, 2.0f}, // Avans - {51690224, 5296625, 1.5f}, // Jan de Groot - {51688460, 5303150, 2.0f}, // Stadhuis - {51686200, 5304500, 1.5f}, // Sint Jans Kathedraal - {51685051, 5289156, 2.0f}, // Paleisbrug - {51684258, 5302611, 1.5f}, // Zuidwal - {51691299, 5303950, 2.0f}, // Arena - {51689428, 5310484, 1.5f}, // Nationaal Carnavalsmuseum - {51695984, 5299074, 2.0f}, // Tramkade - {51689124, 5303969, 1.5f}, // De Markt - {51689619, 5299065, 2.0f}, // Bolwerk - {51693002, 5301264, 1.5f}, // VUE Cinema - {51697021, 5299328, 2.0f}, // Bossche Brouwers - {51689724, 5300408, 1.5f}, // Café Bar le Duc - {51686471, 5304106, 2.0f}, // Museumkwartier - {51689471, 5303200, 1.5f}, // Moriaan - {51689302, 5303396, 2.0f}, // ’t Opkikkertje - {51695457, 5297448, 1.5f}, // Verkadefabriek - {51694463, 5302862, 2.0f}, // BHIC - {51690467, 5294925, 1.5f}, // Station - {51683776, 5317938, 2.0f}, // Zuiderpark - {51687561, 5305911, 1.5f}, // Korte Putstraat - {51698630, 5292803, 1.5f}, // Brabanthallen - {51688691, 5309001, 1.5f}, // Café de Palm - {51687344, 5305871, 1.5f}, // Bistro Tante Pietje - {51696264, 5307460, 1.5f}, // Taxandriaplein Park - {51696501, 5312884, 1.5f}, // IJzeren Vrouw Park - {51691911, 5286594, 1.5f}, // Simon Stevinweg - {51690402, 5291740, 1.5f}, // Hugo de Grootplein - {51685400, 5289354, 1.5f}, // Kinepolis - {51690484, 5296206, 1.5f}, // Gouden Draak - {51686618, 5308459, 1.5f}, // Theater aan de Parade - {51686697, 5303137, 1.5f} // Gemeentehuis + {51688573, 5287210, 2.0f, 0}, // Avans + {51690224, 5296625, 1.5f, 101}, // Jan de Groot + {51688460, 5303150, 2.0f, 112}, // Stadhuis + {51686200, 5304500, 1.5f, 1}, // Sint Jans Kathedraal + {51685051, 5289156, 2.0f, 9}, // Paleisbrug + {51684258, 5302611, 1.5f, 111}, // Zuidwal + {51691299, 5303950, 2.0f, 5}, // Arena + {51689428, 5310484, 1.5f, 100}, // Nationaal Carnavalsmuseum + {51695984, 5299074, 2.0f, 113}, // Tramkade + {51689124, 5303969, 1.5f, 108}, // De Markt + {51689619, 5299065, 2.0f, 114}, // Bolwerk + {51693002, 5301264, 1.5f, 115}, // VUE Cinema + {51697021, 5299328, 2.0f, 102}, // Bossche Brouwers + {51689724, 5300408, 1.5f, 116}, // Café Bar le Duc + {51686471, 5304106, 2.0f, 110}, // Museumkwartier + {51689471, 5303200, 1.5f, 109}, // Moriaan + {51689302, 5303396, 2.0f, 117}, // ’t Opkikkertje + {51695457, 5297448, 1.5f, 118}, // Verkadefabriek + {51694463, 5302862, 2.0f, 119}, // BHIC + {51690467, 5294925, 1.5f, 8}, // Station + {51683776, 5317938, 2.0f, 11}, // Zuiderpark + {51687561, 5305911, 1.5f, 120}, // Korte Putstraat + {51698630, 5292803, 1.5f, 121}, // Brabanthallen + {51688691, 5309001, 1.5f, 7}, // Café de Palm + {51687344, 5305871, 1.5f, 122}, // Bistro Tante Pietje + {51696264, 5307460, 1.5f, 10}, // Taxandriaplein Park + {51696501, 5312884, 1.5f, 123}, // IJzeren Vrouw Park + {51691911, 5286594, 1.5f, 2}, // Simon Stevinweg + {51690402, 5291740, 1.5f, 124}, // Hugo de Grootplein + {51685400, 5289354, 1.5f, 3}, // Kinepolis + {51690484, 5296206, 1.5f, 106}, // Gouden Draak + {51686618, 5308459, 1.5f, 125}, // Theater aan de Parade + {51686697, 5303137, 1.5f, 6} // Gemeentehuis }; vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); From bfb00b9c8a091c3ed9a323388ac634a461f27133 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 21:21:35 +0200 Subject: [PATCH 18/27] Update routegenerator.h --- RoutesGenereren/routegenerator.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h index 6a600fa..7bfef94 100644 --- a/RoutesGenereren/routegenerator.h +++ b/RoutesGenereren/routegenerator.h @@ -10,13 +10,17 @@ struct Point { int64_t x; int64_t y; + int mg_id; float cost; - Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f); + + Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f, int mg_id = -1); bool operator==(const Point& other) const; }; + extern std::vector> routeIndices; extern std::vector> selectedPoints; extern std::vector allPoints; + void generate_routes(); #endif // ROUTEGENERATOR_H From 93a328776910e187ccf42c7878a0dbb09fa12a2f Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:19:45 +0200 Subject: [PATCH 19/27] Update mainwindow.cpp added error handling --- RoutesGenereren/mainwindow.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 3918f6c..0d010c1 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -140,18 +140,35 @@ void MainWindow::resizeEvent(QResizeEvent* event) void MainWindow::on_pushButton_clicked() { - m_ui->statusLabel->setText("Bezig met genereren..."); + ui->statusLabel->setText("Bezig met genereren..."); QApplication::processEvents(); QFuture future = QtConcurrent::run([this]() { - generate_routes(); - QMetaObject::invokeMethod(this, [this]() { - m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); - drawRoutes(); - }, Qt::QueuedConnection); + try { + generate_routes(); + + QMetaObject::invokeMethod(this, [this]() { + ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); + }, Qt::QueuedConnection); + + } catch (const std::exception& ex) { + QString errorMsg = QString("Fout bij genereren van routes: %1").arg(ex.what()); + QMetaObject::invokeMethod(this, [this, errorMsg]() { + QMessageBox::critical(this, "Fout", errorMsg); + ui->statusLabel->setText("Fout bij genereren van routes!"); + }, Qt::QueuedConnection); + + } catch (...) { + QMetaObject::invokeMethod(this, [this]() { + QMessageBox::critical(this, "Fout", "Onbekende fout bij genereren van routes!"); + ui->statusLabel->setText("Fout bij genereren van routes!"); + }, Qt::QueuedConnection); + } }); } + void MainWindow::pushButton_2_clicked() { QByteArray dataToSend("help \r\n"); From eeeb4c74e9aba66f6512baaf90289c582565b728 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:01:13 +0200 Subject: [PATCH 20/27] Update mainwindow.cpp --- RoutesGenereren/mainwindow.cpp | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 0d010c1..1a88371 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -171,8 +171,9 @@ void MainWindow::on_pushButton_clicked() void MainWindow::pushButton_2_clicked() { - QByteArray dataToSend("help \r\n"); - writeData(dataToSend); + QString filePath = qApp->applicationDirPath() + "/Route_1.json"; + + sendJsonFileToDevice(filepath); } void MainWindow::openSerialPort() @@ -272,3 +273,35 @@ void MainWindow::showWriteError(const QString &message) { QMessageBox::warning(this, tr("Warning"), message); } + +void MainWindow::sendJsonFileToDevice(const QString& filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, tr("Error"), tr("Could not open file %1").arg(filePath)); + return; + } + + QByteArray fileData = file.readAll(); + file.close(); + + // Stuur start commando + QByteArray header = "SEND_JSON:" + QFileInfo(filePath).fileName().toUtf8() + "\n"; + m_serial->write(header); + m_serial->waitForBytesWritten(100); + + // Stuur de data in stukjes + const int chunkSize = 64; + for (int pos = 0; pos < fileData.size(); pos += chunkSize) { + QByteArray chunk = fileData.mid(pos, chunkSize); + m_serial->write(chunk); + m_serial->waitForBytesWritten(100); + } + + // Stuur einde + QByteArray footer = "\nEOF\n"; + m_serial->write(footer); + m_serial->waitForBytesWritten(100); + + QMessageBox::information(this, tr("Success"), tr("File %1 sent to device").arg(filePath)); +} From 59c1105d6204a98ba798acac1e98c15687f527e7 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:03:17 +0200 Subject: [PATCH 21/27] Update mainwindow.h --- RoutesGenereren/mainwindow.h | 1 + 1 file changed, 1 insertion(+) diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h index 745913a..0d3344c 100644 --- a/RoutesGenereren/mainwindow.h +++ b/RoutesGenereren/mainwindow.h @@ -51,6 +51,7 @@ private slots: void showStatusMessage(const QString &message); void showWriteError(const QString &message); void drawRoutes(); + void sendJsonFileToDevice(const QString& filePath); protected: // Elke keer bij resize opnieuw fitten From b45debe2eab7351230170677795f8a766090d1c4 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:14:48 +0200 Subject: [PATCH 22/27] Add files via upload --- RoutesGenereren/console.cpp | 124 ++++----- RoutesGenereren/console.h | 66 ++--- RoutesGenereren/locations.json | 200 ++++++++++++++ RoutesGenereren/mainwindow.cpp | 427 +++++++++++++++++++++++++---- RoutesGenereren/mainwindow.h | 29 +- RoutesGenereren/mainwindow.ui | 137 ++++++++- RoutesGenereren/routegenerator.cpp | 124 ++++++--- RoutesGenereren/routegenerator.h | 8 +- 8 files changed, 907 insertions(+), 208 deletions(-) create mode 100644 RoutesGenereren/locations.json diff --git a/RoutesGenereren/console.cpp b/RoutesGenereren/console.cpp index ce87f1a..b4c1ee1 100644 --- a/RoutesGenereren/console.cpp +++ b/RoutesGenereren/console.cpp @@ -1,62 +1,62 @@ -// Copyright (C) 2012 Denis Shienkov -// Copyright (C) 2012 Laszlo Papp -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "console.h" - -#include - -Console::Console(QWidget *parent) : - QPlainTextEdit(parent) -{ - document()->setMaximumBlockCount(100); - QPalette p = palette(); - p.setColor(QPalette::Base, Qt::black); - p.setColor(QPalette::Text, Qt::green); - setPalette(p); -} - -void Console::putData(const QByteArray &data) -{ - insertPlainText(data); - - QScrollBar *bar = verticalScrollBar(); - bar->setValue(bar->maximum()); -} - -void Console::setLocalEchoEnabled(bool set) -{ - m_localEchoEnabled = set; -} - -void Console::keyPressEvent(QKeyEvent *e) -{ - switch (e->key()) { - case Qt::Key_Backspace: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: - break; - default: - if (m_localEchoEnabled) - QPlainTextEdit::keyPressEvent(e); - emit getData(e->text().toLocal8Bit()); - } -} - -void Console::mousePressEvent(QMouseEvent *e) -{ - Q_UNUSED(e); - setFocus(); -} - -void Console::mouseDoubleClickEvent(QMouseEvent *e) -{ - Q_UNUSED(e); -} - -void Console::contextMenuEvent(QContextMenuEvent *e) -{ - Q_UNUSED(e); -} +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "console.h" + +#include + +Console::Console(QWidget *parent) : + QPlainTextEdit(parent) +{ + document()->setMaximumBlockCount(100); + QPalette p = palette(); + p.setColor(QPalette::Base, Qt::black); + p.setColor(QPalette::Text, Qt::green); + setPalette(p); +} + +void Console::putData(const QByteArray &data) +{ + insertPlainText(data); + + QScrollBar *bar = verticalScrollBar(); + bar->setValue(bar->maximum()); +} + +void Console::setLocalEchoEnabled(bool set) +{ + m_localEchoEnabled = set; +} + +void Console::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + break; + default: + if (m_localEchoEnabled) + QPlainTextEdit::keyPressEvent(e); + emit getData(e->text().toLocal8Bit()); + } +} + +void Console::mousePressEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + setFocus(); +} + +void Console::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_UNUSED(e); +} + +void Console::contextMenuEvent(QContextMenuEvent *e) +{ + Q_UNUSED(e); +} diff --git a/RoutesGenereren/console.h b/RoutesGenereren/console.h index 8cbc7d6..d5a39ef 100644 --- a/RoutesGenereren/console.h +++ b/RoutesGenereren/console.h @@ -1,33 +1,33 @@ -// Copyright (C) 2012 Denis Shienkov -// Copyright (C) 2012 Laszlo Papp -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef CONSOLE_H -#define CONSOLE_H - -#include - -class Console : public QPlainTextEdit -{ - Q_OBJECT - -signals: - void getData(const QByteArray &data); - -public: - explicit Console(QWidget *parent = nullptr); - - void putData(const QByteArray &data); - void setLocalEchoEnabled(bool set); - -protected: - void keyPressEvent(QKeyEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseDoubleClickEvent(QMouseEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - -private: - bool m_localEchoEnabled = false; -}; - -#endif // CONSOLE_H +// Copyright (C) 2012 Denis Shienkov +// Copyright (C) 2012 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include + +class Console : public QPlainTextEdit +{ + Q_OBJECT + +signals: + void getData(const QByteArray &data); + +public: + explicit Console(QWidget *parent = nullptr); + + void putData(const QByteArray &data); + void setLocalEchoEnabled(bool set); + +protected: + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + +private: + bool m_localEchoEnabled = false; +}; + +#endif // CONSOLE_H diff --git a/RoutesGenereren/locations.json b/RoutesGenereren/locations.json new file mode 100644 index 0000000..efc0cd5 --- /dev/null +++ b/RoutesGenereren/locations.json @@ -0,0 +1,200 @@ +[ + { + "cost": 2, + "mg_id": 0, + "x": 51688573, + "y": 5287210 + }, + { + "cost": 1.5, + "mg_id": 101, + "x": 51690224, + "y": 5296625 + }, + { + "cost": 2, + "mg_id": 112, + "x": 51688460, + "y": 5303150 + }, + { + "cost": 1.5, + "mg_id": 1, + "x": 51686200, + "y": 5304500 + }, + { + "cost": 2, + "mg_id": 9, + "x": 51685051, + "y": 5289156 + }, + { + "cost": 1.5, + "mg_id": 111, + "x": 51684258, + "y": 5302611 + }, + { + "cost": 2, + "mg_id": 5, + "x": 51691299, + "y": 5303950 + }, + { + "cost": 1.5, + "mg_id": 100, + "x": 51689428, + "y": 5310484 + }, + { + "cost": 2, + "mg_id": 113, + "x": 51695984, + "y": 5299074 + }, + { + "cost": 1.5, + "mg_id": 108, + "x": 51689124, + "y": 5303969 + }, + { + "cost": 2, + "mg_id": 114, + "x": 51689619, + "y": 5299065 + }, + { + "cost": 1.5, + "mg_id": 115, + "x": 51693002, + "y": 5301264 + }, + { + "cost": 2, + "mg_id": 102, + "x": 51697021, + "y": 5299328 + }, + { + "cost": 1.5, + "mg_id": 116, + "x": 51689724, + "y": 5300408 + }, + { + "cost": 2, + "mg_id": 110, + "x": 51686471, + "y": 5304106 + }, + { + "cost": 1.5, + "mg_id": 109, + "x": 51689471, + "y": 5303200 + }, + { + "cost": 2, + "mg_id": 117, + "x": 51689302, + "y": 5303396 + }, + { + "cost": 1.5, + "mg_id": 118, + "x": 51695457, + "y": 5297448 + }, + { + "cost": 2, + "mg_id": 119, + "x": 51694463, + "y": 5302862 + }, + { + "cost": 1.5, + "mg_id": 8, + "x": 51690467, + "y": 5294925 + }, + { + "cost": 2, + "mg_id": 11, + "x": 51683776, + "y": 5317938 + }, + { + "cost": 1.5, + "mg_id": 120, + "x": 51687561, + "y": 5305911 + }, + { + "cost": 1.5, + "mg_id": 121, + "x": 51698630, + "y": 5292803 + }, + { + "cost": 1.5, + "mg_id": 7, + "x": 51688691, + "y": 5309001 + }, + { + "cost": 1.5, + "mg_id": 122, + "x": 51687344, + "y": 5305871 + }, + { + "cost": 1.5, + "mg_id": 10, + "x": 51696264, + "y": 5307460 + }, + { + "cost": 1.5, + "mg_id": 123, + "x": 51696501, + "y": 5312884 + }, + { + "cost": 1.5, + "mg_id": 2, + "x": 51691911, + "y": 5286594 + }, + { + "cost": 1.5, + "mg_id": 124, + "x": 51690402, + "y": 5291740 + }, + { + "cost": 1.5, + "mg_id": 3, + "x": 51685400, + "y": 5289354 + }, + { + "cost": 1.5, + "mg_id": 106, + "x": 51690484, + "y": 5296206 + }, + { + "cost": 1.5, + "mg_id": 125, + "x": 51686618, + "y": 5308459 + }, + { + "cost": 1.5, + "mg_id": 6, + "x": 51686697, + "y": 5303137 + } +] diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 1a88371..a38ee56 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -34,15 +35,39 @@ MainWindow::MainWindow(QWidget *parent) : { m_ui->setupUi(this); - // Initialiseer QGraphicsScene + m_ui->tabWidget->setCurrentIndex(0); + + connect(m_ui->btnRefreshRoutes, &QPushButton::clicked, + this, &MainWindow::on_btnRefreshRoutes_clicked); + connect(m_ui->btnRefreshPorts, &QPushButton::clicked, + this, &MainWindow::on_btnRefreshPorts_clicked); + connect(m_ui->btnUploadSerial, &QPushButton::clicked, + this, &MainWindow::on_btnUploadSerial_clicked); + + connect(m_ui->btnAddLoc, &QPushButton::clicked, + this, &MainWindow::on_btnAddLoc_clicked); + connect(m_ui->btnEditLoc, &QPushButton::clicked, + this, &MainWindow::on_btnEditLoc_clicked); + connect(m_ui->btnRemoveLoc, &QPushButton::clicked, + this, &MainWindow::on_btnRemoveLoc_clicked); + + loadLocationsFromFile(); + // if there was nothing in the file, fall back to defaults + if (allPoints.empty()) + loadDefaultPoints(); + populateLocationsTable(); + + refreshRouteList(); + refreshSerialPorts(); + scene = new QGraphicsScene(this); m_ui->graphicsView->setScene(scene); m_ui->graphicsView->setRenderHint(QPainter::Antialiasing, true); // SerialPort + console instellingen - m_console->setEnabled(false); - QWidget* tab1 = m_console; - m_ui->tabWidget->addTab(tab1, "Developer mode"); + //m_console->setEnabled(false); + //QWidget* tab1 = m_console; + //m_ui->tabWidget->addTab(tab1, "Developer mode"); m_ui->actionConnect->setEnabled(true); m_ui->actionDisconnect->setEnabled(false); @@ -57,7 +82,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); connect(m_console, &Console::getData, this, &MainWindow::writeData); - QObject::connect(m_ui->pushButton_2, SIGNAL(pressed()), this, SLOT(pushButton_2_clicked())); + //QObject::connect(m_ui->pushButton_2, SIGNAL(pressed()), this, SLOT(pushButton_2_clicked())); } MainWindow::~MainWindow() @@ -70,7 +95,6 @@ void MainWindow::drawRoutes() { scene->clear(); - // 1) Bepaal min/max van alle getoonde punten int64_t minX = std::numeric_limits::max(), maxX = std::numeric_limits::lowest(); int64_t minY = std::numeric_limits::max(), maxY = std::numeric_limits::lowest(); for (auto& pts : selectedPoints) @@ -81,7 +105,6 @@ void MainWindow::drawRoutes() maxY = std::max(maxY, p.y); } - // 2) Bereken schaal (+ marge) QSizeF viewSz = m_ui->graphicsView->viewport()->size(); constexpr qreal M = 20.0; // marge in pixels qreal scaleX = (viewSz.width() - 2*M) / qreal(maxX - minX); @@ -101,7 +124,6 @@ void MainWindow::drawRoutes() Qt::gray, Qt::black }; - // 3) Teken elke route in een andere kleur for (int r = 0; r < routeIndices.size(); ++r) { QPen pen(colors[r%10]); pen.setCosmetic(true); @@ -140,40 +162,21 @@ void MainWindow::resizeEvent(QResizeEvent* event) void MainWindow::on_pushButton_clicked() { - ui->statusLabel->setText("Bezig met genereren..."); + m_ui->statusLabel->setText("Bezig met genereren..."); QApplication::processEvents(); QFuture future = QtConcurrent::run([this]() { - try { - generate_routes(); - - QMetaObject::invokeMethod(this, [this]() { - ui->statusLabel->setText("Routes succesvol gegenereerd!"); - drawRoutes(); - }, Qt::QueuedConnection); - - } catch (const std::exception& ex) { - QString errorMsg = QString("Fout bij genereren van routes: %1").arg(ex.what()); - QMetaObject::invokeMethod(this, [this, errorMsg]() { - QMessageBox::critical(this, "Fout", errorMsg); - ui->statusLabel->setText("Fout bij genereren van routes!"); - }, Qt::QueuedConnection); - - } catch (...) { - QMetaObject::invokeMethod(this, [this]() { - QMessageBox::critical(this, "Fout", "Onbekende fout bij genereren van routes!"); - ui->statusLabel->setText("Fout bij genereren van routes!"); - }, Qt::QueuedConnection); - } + generate_routes(allPoints); + QMetaObject::invokeMethod(this, [this]() { + m_ui->statusLabel->setText("Routes succesvol gegenereerd!"); + drawRoutes(); + }, Qt::QueuedConnection); }); } - void MainWindow::pushButton_2_clicked() { - QString filePath = qApp->applicationDirPath() + "/Route_1.json"; - - sendJsonFileToDevice(filepath); + } void MainWindow::openSerialPort() @@ -274,34 +277,348 @@ void MainWindow::showWriteError(const QString &message) QMessageBox::warning(this, tr("Warning"), message); } -void MainWindow::sendJsonFileToDevice(const QString& filePath) +void MainWindow::refreshRouteList() +{ + QDir buildDir(QCoreApplication::applicationDirPath()); + buildDir.setNameFilters(QStringList{"*.json"}); + buildDir.setFilter(QDir::Files | QDir::NoDotAndDotDot); + + auto files = buildDir.entryList(); + m_ui->comboRouteFiles->clear(); + if (files.isEmpty()) { + m_ui->comboRouteFiles->addItem(tr("[geen routes]")); + } else { + m_ui->comboRouteFiles->addItems(files); + } +} + +void MainWindow::refreshSerialPorts() +{ + auto ports = QSerialPortInfo::availablePorts(); + m_ui->comboPorts->clear(); + for (const auto &info : ports) + m_ui->comboPorts->addItem(info.portName()); + if (ports.isEmpty()) + m_ui->comboPorts->addItem(tr("[geen poorten]")); +} + +void MainWindow::on_btnRefreshRoutes_clicked() +{ + refreshRouteList(); +} + +void MainWindow::on_btnRefreshPorts_clicked() { - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Could not open file %1").arg(filePath)); + refreshSerialPorts(); +} + +void MainWindow::on_btnUploadSerial_clicked() +{ + // Get json file + QString fileName = m_ui->comboRouteFiles->currentText(); + if (fileName.startsWith("[")) { + QMessageBox::warning(this, tr("Geen route"), tr("Genereer eerst routes op tab 1.")); + return; + } + QDir buildDir(QCoreApplication::applicationDirPath()); + QString src = buildDir.filePath(fileName); + + // Get selected port + QString portName = m_ui->comboPorts->currentText(); + if (portName.startsWith("[")) { + QMessageBox::warning(this, tr("Geen poort"), tr("Sluit koffer aan en ververs poorten.")); + return; + } + + // Read json + QFile f(src); + if (!f.open(QIODevice::ReadOnly)) { + QMessageBox::critical(this, tr("Fout"), tr("Kan %1 niet openen").arg(src)); + return; + } + QByteArray data = f.readAll(); + f.close(); + + // Open and write to serial port + QSerialPort port(portName, this); + port.setBaudRate(QSerialPort::Baud9600); + port.setDataBits(QSerialPort::Data8); + port.setParity(QSerialPort::NoParity); + port.setStopBits(QSerialPort::OneStop); + if (!port.open(QIODevice::WriteOnly)) { + QMessageBox::critical(this, tr("Fout"), + tr("Kan %1 niet openen:\n%2").arg(portName, port.errorString())); + return; + } + qint64 written = port.write(data); + if (!port.waitForBytesWritten(5000)) { + QMessageBox::critical(this, tr("Fout"), + tr("Schrijven naar %1 timed-out").arg(portName)); + port.close(); + return; + } + port.close(); + + if (written == data.size()) { + QMessageBox::information(this, tr("Klaar"), + tr("Route %1 geüpload via %2").arg(fileName, portName)); + } else { + QMessageBox::critical(this, tr("Fout"), + tr("Slechts %1 van %2 bytes verzonden") + .arg(written).arg(data.size())); + } +} + +void MainWindow::on_btnAddLoc_clicked() +{ + bool ok = false; + // 1) Vraag X en Y (int64_t) + QString xs = QInputDialog::getText(this, tr("Nieuw punt"), tr("X (microdegrees):"), QLineEdit::Normal, + QString(), &ok); + if (!ok) return; + int64_t x = xs.toLongLong(&ok); + if (!ok) { + QMessageBox::warning(this, tr("Ongeldige invoer"), tr("Geef een geldig getal op.")); return; } - QByteArray fileData = file.readAll(); - file.close(); + QString ys = QInputDialog::getText(this, tr("Nieuw punt"), tr("Y (microdegrees):"), QLineEdit::Normal, + QString(), &ok); + if (!ok) return; + int64_t y = ys.toLongLong(&ok); + if (!ok) { + QMessageBox::warning(this, tr("Ongeldige invoer"), tr("Geef een geldig getal op.")); + return; + } - // Stuur start commando - QByteArray header = "SEND_JSON:" + QFileInfo(filePath).fileName().toUtf8() + "\n"; - m_serial->write(header); - m_serial->waitForBytesWritten(100); + // cost (float) + double cost = QInputDialog::getDouble(this, tr("Nieuw punt"), tr("Cost:"), + 1.0, 0.0, 1e6, 2, &ok); + if (!ok) return; + + // mg_id (int) + int mg = QInputDialog::getInt(this, tr("Nieuw punt"), tr("mg_id:"), + 0, 0, std::numeric_limits::max(), 1, &ok); + if (!ok) return; + + allPoints.emplace_back(x, y, static_cast(cost), mg); + + populateLocationsTable(); + saveLocationsToFile(); +} - // Stuur de data in stukjes - const int chunkSize = 64; - for (int pos = 0; pos < fileData.size(); pos += chunkSize) { - QByteArray chunk = fileData.mid(pos, chunkSize); - m_serial->write(chunk); - m_serial->waitForBytesWritten(100); +void MainWindow::on_btnEditLoc_clicked() +{ + auto *tbl = m_ui->tableLocations; + int row = tbl->currentRow(); + if (row < 0 || row >= int(allPoints.size())) { + QMessageBox::warning(this, tr("Bewerken"), tr("Selecteer eerst een rij.")); + return; + } + + Point &p = allPoints[row]; + bool ok = false; + int64_t newX = 0, newY = 0; + double newCost = 0.0; + int newMg = 0; + + do { + QString xs = QInputDialog::getText( + this, + tr("Bewerk punt"), + tr("X (microdegrees):"), + QLineEdit::Normal, + QString::number(p.x), + &ok + ); + if (!ok) return; + newX = xs.toLongLong(&ok); + if (!ok) { + QMessageBox::warning(this, + tr("Ongeldige invoer"), + tr("Voer een geldig geheel getal in voor X.")); + } + } while (!ok); + + do { + QString ys = QInputDialog::getText( + this, + tr("Bewerk punt"), + tr("Y (microdegrees):"), + QLineEdit::Normal, + QString::number(p.y), + &ok + ); + if (!ok) return; + newY = ys.toLongLong(&ok); + if (!ok) { + QMessageBox::warning(this, + tr("Ongeldige invoer"), + tr("Voer een geldig geheel getal in voor Y.")); + } + } while (!ok); + + do { + double c = QInputDialog::getDouble( + this, + tr("Bewerk punt"), + tr("Cost:"), + double(p.cost), + 0.0, 1e6, 2, + &ok + ); + if (!ok) return; + newCost = c; + } while (!ok); + + do { + int m = QInputDialog::getInt( + this, + tr("Bewerk punt"), + tr("mg_id:"), + p.mg_id, + 0, std::numeric_limits::max(), + 1, + &ok + ); + if (!ok) return; + newMg = m; + } while (!ok); + + p.x = newX; + p.y = newY; + p.cost = static_cast(newCost); + p.mg_id = newMg; + + populateLocationsTable(); + saveLocationsToFile(); +} + + +void MainWindow::on_btnRemoveLoc_clicked() +{ + auto *tbl = m_ui->tableLocations; + int row = tbl->currentRow(); + if (row < 0 || row >= int(allPoints.size())) { + QMessageBox::warning(this, tr("Verwijderen"), tr("Selecteer eerst een rij.")); + return; + } + + if (QMessageBox::question(this, tr("Verwijderen"), + tr("Weet je zeker dat je punt %1 wilt verwijderen?") + .arg(row+1)) + != QMessageBox::Yes) + { + return; + } + + allPoints.erase(allPoints.begin() + row); + populateLocationsTable(); + saveLocationsToFile(); +} + +void MainWindow::loadDefaultPoints() +{ + allPoints = { + {51688573, 5287210, 2.0f, 0}, // Avans + {51690224, 5296625, 1.5f, 101}, // Jan de Groot + {51688460, 5303150, 2.0f, 112}, // Stadhuis + {51686200, 5304500, 1.5f, 1}, // Sint Jans Kathedraal + {51685051, 5289156, 2.0f, 9}, // Paleisbrug + {51684258, 5302611, 1.5f, 111}, // Zuidwal + {51691299, 5303950, 2.0f, 5}, // Arena + {51689428, 5310484, 1.5f, 100}, // Nationaal Carnavalsmuseum + {51695984, 5299074, 2.0f, 113}, // Tramkade + {51689124, 5303969, 1.5f, 108}, // De Markt + {51689619, 5299065, 2.0f, 114}, // Bolwerk + {51693002, 5301264, 1.5f, 115}, // VUE Cinema + {51697021, 5299328, 2.0f, 102}, // Bossche Brouwers + {51689724, 5300408, 1.5f, 116}, // Café Bar le Duc + {51686471, 5304106, 2.0f, 110}, // Museumkwartier + {51689471, 5303200, 1.5f, 109}, // Moriaan + {51689302, 5303396, 2.0f, 117}, // ’t Opkikkertje + {51695457, 5297448, 1.5f, 118}, // Verkadefabriek + {51694463, 5302862, 2.0f, 119}, // BHIC + {51690467, 5294925, 1.5f, 8}, // Station + {51683776, 5317938, 2.0f, 11}, // Zuiderpark + {51687561, 5305911, 1.5f, 120}, // Korte Putstraat + {51698630, 5292803, 1.5f, 121}, // Brabanthallen + {51688691, 5309001, 1.5f, 7}, // Café de Palm + {51687344, 5305871, 1.5f, 122}, // Bistro Tante Pietje + {51696264, 5307460, 1.5f, 10}, // Taxandriaplein Park + {51696501, 5312884, 1.5f, 123}, // IJzeren Vrouw Park + {51691911, 5286594, 1.5f, 2}, // Simon Stevinweg + {51690402, 5291740, 1.5f, 124}, // Hugo de Grootplein + {51685400, 5289354, 1.5f, 3}, // Kinepolis + {51690484, 5296206, 1.5f, 106}, // Gouden Draak + {51686618, 5308459, 1.5f, 125}, // Theater aan de Parade + {51686697, 5303137, 1.5f, 6} // Gemeentehuis + }; +} + +void MainWindow::populateLocationsTable() +{ + auto *tbl = m_ui->tableLocations; + tbl->clearContents(); + tbl->setRowCount(int(allPoints.size())); + for (int i = 0; i < int(allPoints.size()); ++i) { + const Point &p = allPoints[i]; + tbl->setItem(i, 0, new QTableWidgetItem(QString::number(p.x))); + tbl->setItem(i, 1, new QTableWidgetItem(QString::number(p.y))); + tbl->setItem(i, 2, new QTableWidgetItem(QString::number(p.cost))); + tbl->setItem(i, 3, new QTableWidgetItem(QString::number(p.mg_id))); } +} + +void MainWindow::loadLocationsFromFile() +{ + QString path = QCoreApplication::applicationDirPath() + "/locations.json"; - // Stuur einde - QByteArray footer = "\nEOF\n"; - m_serial->write(footer); - m_serial->waitForBytesWritten(100); + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) + return; + auto raw = f.readAll(); + f.close(); + + QJsonDocument doc = QJsonDocument::fromJson(raw); + if (!doc.isArray()) + return; - QMessageBox::information(this, tr("Success"), tr("File %1 sent to device").arg(filePath)); + allPoints.clear(); + for (auto v : doc.array()) { + if (!v.isObject()) continue; + auto o = v.toObject(); + int64_t x = qint64(o.value("x").toDouble()); + int64_t y = qint64(o.value("y").toDouble()); + float cost = float(o.value("cost").toDouble()); + int mg = o.value("mg_id").toInt(); + allPoints.emplace_back(x,y,cost,mg); + } } + +void MainWindow::saveLocationsToFile() +{ + QString path = QCoreApplication::applicationDirPath() + "/locations.json"; + + + QJsonArray arr; + for (auto &p : allPoints) { + QJsonObject o; + o["x"] = double(p.x); + o["y"] = double(p.y); + o["cost"] = double(p.cost); + o["mg_id"]= p.mg_id; + arr.append(o); + } + QJsonDocument doc(arr); + + QFile f(path); + if (!f.open(QIODevice::WriteOnly|QIODevice::Truncate)) { + QMessageBox::warning(this, tr("Error"), + tr("Kan locaties niet opslaan in %1").arg(path)); + return; + } + f.write(doc.toJson()); + f.close(); +} + diff --git a/RoutesGenereren/mainwindow.h b/RoutesGenereren/mainwindow.h index 0d3344c..de28b76 100644 --- a/RoutesGenereren/mainwindow.h +++ b/RoutesGenereren/mainwindow.h @@ -9,6 +9,18 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "routegenerator.h" QT_BEGIN_NAMESPACE @@ -46,15 +58,27 @@ private slots: void handleBytesWritten(qint64 bytes); void handleWriteTimeout(); + void on_btnRefreshRoutes_clicked(); + void on_btnRefreshPorts_clicked(); + void on_btnUploadSerial_clicked(); + + void on_btnAddLoc_clicked(); + void on_btnEditLoc_clicked(); + void on_btnRemoveLoc_clicked(); + private: void initActionsConnections(); void showStatusMessage(const QString &message); void showWriteError(const QString &message); void drawRoutes(); - void sendJsonFileToDevice(const QString& filePath); + void refreshRouteList(); + void refreshSerialPorts(); + void loadDefaultPoints(); + void populateLocationsTable(); + void loadLocationsFromFile(); + void saveLocationsToFile(); protected: - // Elke keer bij resize opnieuw fitten void resizeEvent(QResizeEvent* event) override; private: @@ -68,6 +92,7 @@ private slots: QString dataRead; QString selectedNode; QGraphicsScene *scene = nullptr; + }; #endif // MAINWINDOW_H diff --git a/RoutesGenereren/mainwindow.ui b/RoutesGenereren/mainwindow.ui index 61f006b..f79d2c2 100644 --- a/RoutesGenereren/mainwindow.ui +++ b/RoutesGenereren/mainwindow.ui @@ -22,12 +22,12 @@ - Tab 1 + Genereer routes - 0 + 10 370 191 29 @@ -50,28 +50,139 @@ status - + - 740 - 370 - 93 - 29 + 10 + 10 + 821 + 351 + + + + + + + Overzetten naar koffer + + + + + 60 + 170 + 711 + 27 + + + + + + + Laad Routes in + + + + + + + + + + Refresh Poort + + + + + + + + + + Upload Route + + + + + + + + + Beheer locaties + + + + + 60 + 50 + 431 + 301 + + + + 4 + + + 99 + + + + X + + + + + Y + + + + + Cost + + + + + mg_id + + + + + + + 590 + 90 + 80 + 25 - testMessage + Toevoegen - + - 10 - 10 - 821 - 351 + 590 + 160 + 80 + 25 + + Bewerken + + + + + + 590 + 240 + 80 + 25 + + + + Verwijderen + diff --git a/RoutesGenereren/routegenerator.cpp b/RoutesGenereren/routegenerator.cpp index ee10eec..fc4e537 100644 --- a/RoutesGenereren/routegenerator.cpp +++ b/RoutesGenereren/routegenerator.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace std; @@ -16,7 +17,7 @@ const int NUM_ROUTES = 10; const float MIN_ROUTE_TIME_MINUTES = 1.0f * 60.0f; // 60 min const float MAX_ROUTE_TIME_MINUTES = 2.0f * 60.0f; // 120 min -constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld +constexpr double METERS_PER_DEGREE = 111320.0; // gemiddeld constexpr double WALKING_SPEED_M_PER_S = 1.4; // meter per seconde @@ -29,6 +30,47 @@ bool Point::operator==(const Point& other) const { && y == other.y && cost == other.cost && mg_id == other.mg_id; +} + +vector> routeIndices; +vector> selectedPoints; +vector allPoints; + +struct Edge { + int u, v; + float weight; +}; + +// Calculate distance in metres between two microdegree points +double geoDistanceMeters(const Point& a, const Point& b) { + double dx_deg = (a.x - b.x) * 1e-6; + double dy_deg = (a.y - b.y) * 1e-6; + double dx_m = dx_deg * METERS_PER_DEGREE; + double dy_m = dy_deg * METERS_PER_DEGREE; + return sqrt(dx_m*dx_m + dy_m*dy_m); +} + +// Calculate time in minutes between two points +double stepTimeMinutes(const Point& a, const Point& b) { + double dist_m = geoDistanceMeters(a, b); + return (dist_m / WALKING_SPEED_M_PER_S) / 60.0; +} + +int findSet(int x, vector& parent) { + return parent[x] == x ? x : parent[x] = findSet(parent[x], parent); +} + +float totalRouteCost(const vector& route, const vector& points) { + float total = 0.0f; + for (size_t i = 0; i + 1 < route.size(); ++i) { + total += static_cast(stepTimeMinutes(points[route[i]], points[route[i+1]])) + + points[route[i]].cost; + } + total += points[route.back()].cost; + return total; +} + +vector computeMST(vector& points) { size_t n = points.size(); vector edges, mst; vector parent(n); @@ -53,7 +95,6 @@ bool Point::operator==(const Point& other) const { return mst; } - void optimizeRoute(vector& route, vector& points) { bool improved = true; while (improved) { @@ -86,7 +127,42 @@ vector christofides(vector& points) { vector matching; while (odd.size() >= 2) { int u = odd.back(); odd.pop_back(); - + int v = odd.back(); odd.pop_back(); + matching.push_back({u, v, static_cast(geoDistanceMeters(points[u], points[v]))}); + } + + vector eulerian = mst; + eulerian.insert(eulerian.end(), matching.begin(), matching.end()); + + vector route; + vector used(points.size(), false); + route.push_back(0); + used[0] = true; + for (auto& e : eulerian) { + if (!used[e.v]) { + route.push_back(e.v); + used[e.v] = true; + } + } + route.push_back(route.front()); + optimizeRoute(route, points); + return route; +} + +void saveRoutesToFiles(const vector>& routes, + const vector>& selPts, + const vector& allPts) +{ + for (size_t i = 0; i < routes.size(); ++i) { + string filename = "Route_" + to_string(i+1) + ".json"; + ofstream file(filename); + if (!file) { + cerr << "Kon bestand niet openen: " << filename << "\n"; + continue; + } + file << "[\n"; + for (size_t j = 0; j < routes[i].size(); ++j) { + const Point& p = selPts[i][routes[i][j]]; file << " { \"x\": " << p.x << ", \"y\": " << p.y << ", \"mg_id\": " << p.mg_id << " }" @@ -97,43 +173,9 @@ vector christofides(vector& points) { } } -void generate_routes() { - // definieer alle punten met microdegrees en kosten - allPoints = { - {51688573, 5287210, 2.0f, 0}, // Avans - {51690224, 5296625, 1.5f, 101}, // Jan de Groot - {51688460, 5303150, 2.0f, 112}, // Stadhuis - {51686200, 5304500, 1.5f, 1}, // Sint Jans Kathedraal - {51685051, 5289156, 2.0f, 9}, // Paleisbrug - {51684258, 5302611, 1.5f, 111}, // Zuidwal - {51691299, 5303950, 2.0f, 5}, // Arena - {51689428, 5310484, 1.5f, 100}, // Nationaal Carnavalsmuseum - {51695984, 5299074, 2.0f, 113}, // Tramkade - {51689124, 5303969, 1.5f, 108}, // De Markt - {51689619, 5299065, 2.0f, 114}, // Bolwerk - {51693002, 5301264, 1.5f, 115}, // VUE Cinema - {51697021, 5299328, 2.0f, 102}, // Bossche Brouwers - {51689724, 5300408, 1.5f, 116}, // Café Bar le Duc - {51686471, 5304106, 2.0f, 110}, // Museumkwartier - {51689471, 5303200, 1.5f, 109}, // Moriaan - {51689302, 5303396, 2.0f, 117}, // ’t Opkikkertje - {51695457, 5297448, 1.5f, 118}, // Verkadefabriek - {51694463, 5302862, 2.0f, 119}, // BHIC - {51690467, 5294925, 1.5f, 8}, // Station - {51683776, 5317938, 2.0f, 11}, // Zuiderpark - {51687561, 5305911, 1.5f, 120}, // Korte Putstraat - {51698630, 5292803, 1.5f, 121}, // Brabanthallen - {51688691, 5309001, 1.5f, 7}, // Café de Palm - {51687344, 5305871, 1.5f, 122}, // Bistro Tante Pietje - {51696264, 5307460, 1.5f, 10}, // Taxandriaplein Park - {51696501, 5312884, 1.5f, 123}, // IJzeren Vrouw Park - {51691911, 5286594, 1.5f, 2}, // Simon Stevinweg - {51690402, 5291740, 1.5f, 124}, // Hugo de Grootplein - {51685400, 5289354, 1.5f, 3}, // Kinepolis - {51690484, 5296206, 1.5f, 106}, // Gouden Draak - {51686618, 5308459, 1.5f, 125}, // Theater aan de Parade - {51686697, 5303137, 1.5f, 6} // Gemeentehuis - }; +void generate_routes(const std::vector &inputPoints) { + + std::vector allPoints = inputPoints; vector hotpoints(allPoints.begin(), allPoints.begin() + NUM_HOTPOINTS); vector waypoints(allPoints.begin() + NUM_HOTPOINTS, allPoints.end()); @@ -146,7 +188,6 @@ void generate_routes() { vector routeTimes; bool valid = false; - // Blijf genereren totdat alle routes binnen de tijdslimieten vallen while (!valid) { selectedPoints = vector>(NUM_ROUTES, hotpoints); routeIndices.clear(); @@ -167,7 +208,6 @@ void generate_routes() { routeTimes.push_back(totalRouteCost(routeIndices[i], selectedPoints[i])); } - // Controleer tijdslimieten valid = true; for (auto t : routeTimes) { if (t < MIN_ROUTE_TIME_MINUTES || t > MAX_ROUTE_TIME_MINUTES) { diff --git a/RoutesGenereren/routegenerator.h b/RoutesGenereren/routegenerator.h index 7bfef94..d0f8dc7 100644 --- a/RoutesGenereren/routegenerator.h +++ b/RoutesGenereren/routegenerator.h @@ -11,16 +11,22 @@ struct Point { int64_t x; int64_t y; int mg_id; + float cost; Point(int64_t x = 0, int64_t y = 0, float cost = 1.0f, int mg_id = -1); + bool operator==(const Point& other) const; + }; + extern std::vector> routeIndices; extern std::vector> selectedPoints; extern std::vector allPoints; -void generate_routes(); + +void generate_routes(const std::vector& inputPoints); + #endif // ROUTEGENERATOR_H From b1fea38af489cf9b1cb859cda97c9540f99bfaeb Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:15:45 +0200 Subject: [PATCH 23/27] Delete RoutesGenereren/main.cpp --- RoutesGenereren/main.cpp | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 RoutesGenereren/main.cpp diff --git a/RoutesGenereren/main.cpp b/RoutesGenereren/main.cpp deleted file mode 100644 index aff48df..0000000 --- a/RoutesGenereren/main.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "mainwindow.h" - -#include - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - MainWindow w; - w.show(); - return a.exec(); -} From f2fec69f290289455d2125eb1b495ae7c5cdaad8 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:20:23 +0200 Subject: [PATCH 24/27] Added to set progress.txt to 0 --- RoutesGenereren/mainwindow.cpp | 117 +++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index a38ee56..79afe84 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -314,59 +314,78 @@ void MainWindow::on_btnRefreshPorts_clicked() void MainWindow::on_btnUploadSerial_clicked() { - // Get json file - QString fileName = m_ui->comboRouteFiles->currentText(); - if (fileName.startsWith("[")) { - QMessageBox::warning(this, tr("Geen route"), tr("Genereer eerst routes op tab 1.")); - return; - } - QDir buildDir(QCoreApplication::applicationDirPath()); - QString src = buildDir.filePath(fileName); + // Get JSON file + QString fileName = m_ui->comboRouteFiles->currentText(); + if (fileName.startsWith("[")) { + QMessageBox::warning(this, tr("Geen route"), + tr("Genereer eerst routes op tab 1.")); + return; + } + QDir buildDir(QCoreApplication::applicationDirPath()); + QString src = buildDir.filePath(fileName); - // Get selected port - QString portName = m_ui->comboPorts->currentText(); - if (portName.startsWith("[")) { - QMessageBox::warning(this, tr("Geen poort"), tr("Sluit koffer aan en ververs poorten.")); - return; - } + // Get selected port + QString portName = m_ui->comboPorts->currentText(); + if (portName.startsWith("[")) { + QMessageBox::warning(this, tr("Geen poort"), + tr("Sluit koffer aan en ververs poorten.")); + return; + } - // Read json - QFile f(src); - if (!f.open(QIODevice::ReadOnly)) { - QMessageBox::critical(this, tr("Fout"), tr("Kan %1 niet openen").arg(src)); - return; - } - QByteArray data = f.readAll(); - f.close(); - - // Open and write to serial port - QSerialPort port(portName, this); - port.setBaudRate(QSerialPort::Baud9600); - port.setDataBits(QSerialPort::Data8); - port.setParity(QSerialPort::NoParity); - port.setStopBits(QSerialPort::OneStop); - if (!port.open(QIODevice::WriteOnly)) { - QMessageBox::critical(this, tr("Fout"), - tr("Kan %1 niet openen:\n%2").arg(portName, port.errorString())); - return; - } - qint64 written = port.write(data); - if (!port.waitForBytesWritten(5000)) { - QMessageBox::critical(this, tr("Fout"), - tr("Schrijven naar %1 timed-out").arg(portName)); - port.close(); - return; - } + // Read JSON + QFile f(src); + if (!f.open(QIODevice::ReadOnly)) { + QMessageBox::critical(this, tr("Fout"), + tr("Kan %1 niet openen").arg(src)); + return; + } + QByteArray jsonData = f.readAll(); + f.close(); + + // Open serial port + QSerialPort port(portName, this); + port.setBaudRate(QSerialPort::Baud9600); + port.setDataBits(QSerialPort::Data8); + port.setParity(QSerialPort::NoParity); + port.setStopBits(QSerialPort::OneStop); + if (!port.open(QIODevice::WriteOnly)) { + QMessageBox::critical(this, tr("Fout"), + tr("Kan %1 niet openen:\n%2") + .arg(portName, port.errorString())); + return; + } + + // Send JSON-route + port.write(jsonData); + if (!port.waitForBytesWritten(5000)) { + QMessageBox::critical(this, tr("Fout"), + tr("Schrijven naar %1 timed-out").arg(portName)); port.close(); + return; + } - if (written == data.size()) { - QMessageBox::information(this, tr("Klaar"), - tr("Route %1 geüpload via %2").arg(fileName, portName)); - } else { - QMessageBox::critical(this, tr("Fout"), - tr("Slechts %1 van %2 bytes verzonden") - .arg(written).arg(data.size())); - } + // Reset progress.txt ("0") + { + QByteArray header = "SEND_FILE:progress.txt\n"; + port.write(header); + port.waitForBytesWritten(100); + + // De werkelijke inhoud + QByteArray prog = "0"; + port.write(prog); + port.waitForBytesWritten(100); + + // EOF-markering + QByteArray footer = "\nEOF\n"; + port.write(footer); + port.waitForBytesWritten(100); + } + + port.close(); + + QMessageBox::information(this, tr("Klaar"), + tr("Route %1 geüpload via %2\nprogress.txt gereset naar 0") + .arg(fileName, portName)); } void MainWindow::on_btnAddLoc_clicked() From 705361e3c1348c26779d874dd338f1434e6bb4ff Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:21:01 +0200 Subject: [PATCH 25/27] Update mainwindow.cpp --- RoutesGenereren/mainwindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 79afe84..2558f90 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -370,12 +370,10 @@ void MainWindow::on_btnUploadSerial_clicked() port.write(header); port.waitForBytesWritten(100); - // De werkelijke inhoud QByteArray prog = "0"; port.write(prog); port.waitForBytesWritten(100); - // EOF-markering QByteArray footer = "\nEOF\n"; port.write(footer); port.waitForBytesWritten(100); From 476bba82e21bd363ed5d237989f332529c0fc9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mari=C3=ABlle=20Korthout?= Date: Sun, 29 Jun 2025 13:35:47 +0200 Subject: [PATCH 26/27] Fixed missing main.cpp for the Qt program --- RoutesGenereren/RoutesGenereren.pro.user | 51 ++++++++++++------------ RoutesGenereren/main.cpp | 11 +++++ 2 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 RoutesGenereren/main.cpp diff --git a/RoutesGenereren/RoutesGenereren.pro.user b/RoutesGenereren/RoutesGenereren.pro.user index 7acdb67..edb4a0f 100644 --- a/RoutesGenereren/RoutesGenereren.pro.user +++ b/RoutesGenereren/RoutesGenereren.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -8,7 +8,7 @@ ProjectExplorer.Project.ActiveTarget - 1 + 0 ProjectExplorer.Project.EditorSettings @@ -89,16 +89,16 @@ ProjectExplorer.Project.Target.0 Desktop - Qt 5.15.2 (mingw81_32) - Qt 5.15.2 (mingw81_32) - {db23b840-4fa8-4369-81d4-5c33831dd635} + Desktop Qt 5.15.2 MinGW 32-bit + Desktop Qt 5.15.2 MinGW 32-bit + qt.qt5.5152.win32_mingw81_kit 0 0 0 0 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug true @@ -136,8 +136,8 @@ 2 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release true @@ -177,8 +177,8 @@ 0 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile true @@ -241,13 +241,13 @@ false RoutesGenereren2 - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/NPC/Documents/School/_ProjBlok11/QtApp2/RoutesGenereren/RoutesGenereren.pro + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp2/RoutesGenereren/RoutesGenereren.pro true true true true - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug 1 @@ -256,16 +256,16 @@ ProjectExplorer.Project.Target.1 Desktop - Desktop Qt 5.15.2 MinGW 32-bit - Desktop Qt 5.15.2 MinGW 32-bit - qt.qt5.5152.win32_mingw81_kit + Qt 5.15.2 (mingw81_32) + Qt 5.15.2 (mingw81_32) + {db23b840-4fa8-4369-81d4-5c33831dd635} 0 0 0 0 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Debug true @@ -303,8 +303,8 @@ 2 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Release + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Release true @@ -344,8 +344,8 @@ 0 - C:\Users\NPC\Documents\School\_ProjBlok11\QtApp - Copy\build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Profile + C:\Users\NPC\Documents\School\_ProjBlok11\QtApp\build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile + C:/Users/NPC/Documents/School/_ProjBlok11/QtApp/build-RoutesGenereren-Qt_5_15_2_mingw81_32-Profile true @@ -407,14 +407,13 @@ 2 false - + RoutesGenereren2 Qt4ProjectManager.Qt4RunConfiguration:C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/RoutesGenereren/RoutesGenereren.pro - false + true true true true - C:/Users/NPC/Documents/School/_ProjBlok11/QtApp - Copy/build-RoutesGenereren-Desktop_Qt_5_15_2_MinGW_32_bit-Debug 1 diff --git a/RoutesGenereren/main.cpp b/RoutesGenereren/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/RoutesGenereren/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} From 06578cedccb5c7718c0f7fa0eebfad193517c7c0 Mon Sep 17 00:00:00 2001 From: LarsFaase <54939303+LarsFaase@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:38:37 +0200 Subject: [PATCH 27/27] fix duplicate messages and send routes to sd --- RoutesGenereren/mainwindow.cpp | 149 ++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 51 deletions(-) diff --git a/RoutesGenereren/mainwindow.cpp b/RoutesGenereren/mainwindow.cpp index 2558f90..eec08d1 100644 --- a/RoutesGenereren/mainwindow.cpp +++ b/RoutesGenereren/mainwindow.cpp @@ -37,20 +37,6 @@ MainWindow::MainWindow(QWidget *parent) : m_ui->tabWidget->setCurrentIndex(0); - connect(m_ui->btnRefreshRoutes, &QPushButton::clicked, - this, &MainWindow::on_btnRefreshRoutes_clicked); - connect(m_ui->btnRefreshPorts, &QPushButton::clicked, - this, &MainWindow::on_btnRefreshPorts_clicked); - connect(m_ui->btnUploadSerial, &QPushButton::clicked, - this, &MainWindow::on_btnUploadSerial_clicked); - - connect(m_ui->btnAddLoc, &QPushButton::clicked, - this, &MainWindow::on_btnAddLoc_clicked); - connect(m_ui->btnEditLoc, &QPushButton::clicked, - this, &MainWindow::on_btnEditLoc_clicked); - connect(m_ui->btnRemoveLoc, &QPushButton::clicked, - this, &MainWindow::on_btnRemoveLoc_clicked); - loadLocationsFromFile(); // if there was nothing in the file, fall back to defaults if (allPoints.empty()) @@ -314,7 +300,7 @@ void MainWindow::on_btnRefreshPorts_clicked() void MainWindow::on_btnUploadSerial_clicked() { - // Get JSON file + // Read json file QString fileName = m_ui->comboRouteFiles->currentText(); if (fileName.startsWith("[")) { QMessageBox::warning(this, tr("Geen route"), @@ -323,67 +309,128 @@ void MainWindow::on_btnUploadSerial_clicked() } QDir buildDir(QCoreApplication::applicationDirPath()); QString src = buildDir.filePath(fileName); - - // Get selected port - QString portName = m_ui->comboPorts->currentText(); - if (portName.startsWith("[")) { - QMessageBox::warning(this, tr("Geen poort"), - tr("Sluit koffer aan en ververs poorten.")); - return; - } - - // Read JSON QFile f(src); if (!f.open(QIODevice::ReadOnly)) { QMessageBox::critical(this, tr("Fout"), tr("Kan %1 niet openen").arg(src)); return; } - QByteArray jsonData = f.readAll(); + const QByteArray jsonData = f.readAll(); f.close(); // Open serial port + QString portName = m_ui->comboPorts->currentText(); + if (portName.startsWith("[")) { + QMessageBox::warning(this, tr("Geen poort"), + tr("Sluit koffer aan en ververs poorten.")); + return; + } QSerialPort port(portName, this); - port.setBaudRate(QSerialPort::Baud9600); + port.setBaudRate(QSerialPort::Baud115200); port.setDataBits(QSerialPort::Data8); port.setParity(QSerialPort::NoParity); port.setStopBits(QSerialPort::OneStop); - if (!port.open(QIODevice::WriteOnly)) { + port.setFlowControl(QSerialPort::NoFlowControl); + if (!port.open(QIODevice::ReadWrite)) { QMessageBox::critical(this, tr("Fout"), - tr("Kan %1 niet openen:\n%2") - .arg(portName, port.errorString())); + tr("Kan poort %1 niet openen:\n%2") + .arg(portName, port.errorString())); return; } - // Send JSON-route - port.write(jsonData); - if (!port.waitForBytesWritten(5000)) { - QMessageBox::critical(this, tr("Fout"), - tr("Schrijven naar %1 timed-out").arg(portName)); - port.close(); - return; + + // Delete loc.txt, progress.txt and score.txt + QByteArray resetCmd = "fs rm /SD:/loc.txt\r\n"; + port.write(resetCmd); + port.waitForBytesWritten(1000); + + QThread::msleep(300); + + resetCmd = "fs rm /SD:/progress.txt\r\n"; + port.write(resetCmd); + port.waitForBytesWritten(1000); + + QThread::msleep(300); + + resetCmd = "fs rm /SD:/score.txt\r\n"; + port.write(resetCmd); + port.waitForBytesWritten(1000); + + QThread::msleep(300); + + // upload to /SD:/loc.txt + const qint64 CHUNK_SIZE = 16; + const qint64 totalLen = jsonData.size(); + qint64 offset = 0; + const QByteArray remotePath("/SD:/loc.txt"); + + while (offset < totalLen) { + qint64 chunkLen = qMin(CHUNK_SIZE, totalLen - offset); + QByteArray chunk = jsonData.mid(offset, chunkLen); + + QString offsetHex = QString("%1") + .arg(offset, 2, 16, QChar('0')); + + // build hex-strings + QStringList hexBytes; + hexBytes.reserve(chunkLen); + for (uchar b : chunk) { + hexBytes << QString("%1") + .arg(b, 2, 16, QChar('0')); + } + QByteArray spacedHex = hexBytes.join(' ').toUtf8(); + + // build fs command + QByteArray cmd = "fs write " + + remotePath + " " + + spacedHex + + "\r\n"; + + qDebug() << ">>" << cmd.trimmed(); + + qint64 written = port.write(cmd); + if (written != cmd.size() || + !port.waitForBytesWritten(5000)) { + QMessageBox::critical(this, tr("Fout"), + tr("Chunk offset %1 niet verzonden").arg(offsetHex)); + port.close(); + return; + } + + QThread::msleep(300); + offset += chunkLen; } - // Reset progress.txt ("0") + // Reset progress.txt (ASCII ‘0’ = 0x30) { - QByteArray header = "SEND_FILE:progress.txt\n"; - port.write(header); - port.waitForBytesWritten(100); + QByteArray resetCmd = "fs write /SD:/progress.txt 30\r\n"; + port.write(resetCmd); + port.waitForBytesWritten(500); + } - QByteArray prog = "0"; - port.write(prog); - port.waitForBytesWritten(100); + // Reset score.txt + { + QByteArray resetCmd = "fs write /SD:/score.txt 30\r\n"; + port.write(resetCmd); + port.waitForBytesWritten(500); + } - QByteArray footer = "\nEOF\n"; - port.write(footer); - port.waitForBytesWritten(100); + // (Optional feedback message) + if (port.waitForReadyRead(500)) { + QByteArray fb; + do { fb += port.readAll(); } + while (port.waitForReadyRead(100)); + qDebug() << "Feedback:" << fb.trimmed(); + QMessageBox::information(this, tr("Feedback"), + QString::fromUtf8(fb)); } - + port.close(); - QMessageBox::information(this, tr("Klaar"), - tr("Route %1 geüpload via %2\nprogress.txt gereset naar 0") - .arg(fileName, portName)); + tr("JSON (%1 bytes) in %2 chunks geüpload naar %3") + .arg(totalLen) + .arg((totalLen + CHUNK_SIZE - 1)/CHUNK_SIZE) + .arg(portName)); } void MainWindow::on_btnAddLoc_clicked()