qHexWalker 0.0.1
Hexagonal Grid Pathfinding & Maze Visualization on Interactive Maps
Loading...
Searching...
No Matches
h3Model.cpp
Go to the documentation of this file.
1#include "h3Model.h"
2#include "h3Cell.h"
3#include "h3MazeAdapter.h"
4#include "h3Worker.h"
5
6#include "helper.h"
7
8#include <QtConcurrent/qtconcurrentrun.h>
9#include <algorithm>
10
11H3Model::H3Model(QObject *parent) : QAbstractListModel(parent) {
12 auto zoomToResolution = [&](const double zoom) {
13 return std::max(std::min(zoom / 1.5, static_cast<double>(maxZoom_c)), 0.0);
14 };
15 for (auto zoom = minZoom_c; zoom < maxZoom_c; zoom++) {
16 zoomToRes_.emplace(zoom, std::floor(zoomToResolution(zoom)));
17 }
18}
19
21 // Останавливаем worker перед очисткой
22 if (worker_ && thread_) {
23 thread_->quit();
24 thread_->wait(250);
25 }
26
27 qDeleteAll(pathCells_);
28 pathCells_.clear();
29}
30
31int H3Model::rowCount(const QModelIndex &parent) const {
32 Q_UNUSED(parent)
33 return static_cast<int>(pathCells_.size());
34}
35
36QVariant H3Model::data(const QModelIndex &index, const int role) const {
37 if (!index.isValid() || index.row() >= pathCells_.size()) {
38 return {};
39 }
40
41 const auto data = pathCells_.at(index.row());
42 switch (role) {
43 case ResRole:
44 return QVariant::fromValue(data->res());
45 case IndexRole:
46 return QVariant::fromValue(data->index());
47 case CellColor:
48 return QVariant::fromValue(data->color());
49 case PathRole:
50 return QVariant::fromValue(data->path());
51 default:;
52 }
53 return {};
54}
55
56QHash<int, QByteArray> H3Model::roleNames() const {
57 // clang-format off
58 return {{ResRole, "res"}, {IndexRole, "h3Index"}, {CellColor, "color"}, {PathRole, "path"}};
59 // clang-format on
60}
61
63
65 // Создаем и настраиваем worker
67 thread_ = new QThread();
68 worker_->moveToThread(thread_);
69
70 connect(worker_, &H3_VIEWER::H3Worker::finished, worker_, &H3_VIEWER::H3Worker::deleteLater, Qt::QueuedConnection);
71 connect(thread_, &QThread::finished, thread_, &QThread::deleteLater, Qt::QueuedConnection);
72
73 // Запуск рабочего цикла в потоке
74 connect(thread_, &QThread::started, worker_, &H3_VIEWER::H3Worker::doWork, Qt::QueuedConnection);
75 // Получение результатов пересчета
76 connect(worker_, &H3_VIEWER::H3Worker::cellComputed, this, &H3Model::onCellComputed, Qt::QueuedConnection);
77 connect(worker_, &H3_VIEWER::H3Worker::cellsComputed, this, &H3Model::onCellsComputed, Qt::QueuedConnection);
79 Qt::QueuedConnection);
80 connect(worker_, &H3_VIEWER::H3Worker::searchStats, this, &H3Model::onSearchStats, Qt::QueuedConnection);
81 thread_->start();
82
83 // Создаем и настраиваем maze adapter
85
86 // Подключаем сигналы maze adapter
87 // Сигнал для визуализации полигонов стен
88 connect(mazeAdapter_, &H3MazeAdapter::mazePolygonsComputed, this, &H3Model::onMazePolygonsComputed,
89 Qt::QueuedConnection);
90
91 // Сигнал для передачи стен в worker для A* алгоритма
92 connect(
94 [this](const std::unordered_set<H3Index> &walls) { worker_->setWalls(walls); }, Qt::QueuedConnection);
95
96 // Пробрасываем сигнал mazeWallsGenerated наружу для targetsModel
97 connect(mazeAdapter_, &H3MazeAdapter::mazeWallsGenerated, this, &H3Model::mazeWallsGenerated, Qt::QueuedConnection);
98
99 // Получаем вычисленный радиус лабиринта и передаем дальше
100 connect(
102 [this](const QGeoCoordinate &center, double radiusMeters) {
103 mazeCenter_ = center;
104 mazeRadius_ = radiusMeters;
105
106 emit mazeCenterChanged();
107 emit mazeRadiusChanged();
109
110 spdlog::info("H3Model: Maze bounds received - center ({}, {}), radius {} meters", center.latitude(),
111 center.longitude(), radiusMeters);
112 },
113 Qt::QueuedConnection);
114
115 // Запускаем генерацию лабиринта асинхронно
116 try {
117 constexpr double mazeLat = 0.0;
118 constexpr double mazeLon = 0.0;
119 constexpr int kRingRadius = 45;
120
121 mazeAdapter_->generateMazeAsync(mazeLat, mazeLon, kRingRadius);
122 } catch (const std::exception &e) {
123 spdlog::critical("{}", e.what());
124 }
125}
126
127bool H3Model::isCoordinateTargetValid(const quint8 zoom, const QGeoCoordinate &coordinate) const {
128 if (!coordinate.isValid()) {
129 return false;
130 }
131 if (zoom >= maxZoom_c) {
132 return false;
133 }
134
135 return true;
136}
137
138std::optional<H3Cell *> H3Model::findCellByRes(const quint8 res) const {
139 const auto it = std::ranges::find_if(pathCells_, [res](const auto &cell) { return cell->res() == res; });
140 if (it == pathCells_.end()) {
141 return std::nullopt;
142 }
143 return *it;
144}
145
146std::optional<H3Cell *> H3Model::findCellByID(const quint64 id) const {
147 const auto it = std::ranges::find_if(pathCells_, [id](const auto &cell) { return cell->index() == id; });
148 if (it == pathCells_.end()) {
149 return std::nullopt;
150 }
151 return *it;
152}
153
154QString H3Model::getColorForResolution(const quint8 resolution) const {
155 // Цветовая схема: от крупных ячеек (теплые цвета) к мелким (холодные цвета)
156 return resolutionColors_c.value(resolution, "gray");
157}
158void H3Model::addCell(const quint8 res, const H3Index index, const QVariantList &polygon, const QColor &color) {
159 // Не добавляем новые ячейки во время очистки
160 if (isClearing_) {
161 return;
162 }
163
164 if (findCellByID(index).has_value()) {
165 return;
166 }
167
168 auto cell = new H3Cell(this);
169 cell->setRes(res);
170 cell->setIndex(index);
171 cell->setPath(polygon);
172 cell->setColor(color);
173
174 beginInsertRows(QModelIndex(), static_cast<int>(pathCells_.size()), static_cast<int>(pathCells_.size()));
175 pathCells_.emplace_back(cell);
176 endInsertRows();
177}
179 std::vector<H3Index> pentagons;
180 pentagons.resize(pentagonCount());
181 for (auto res = 2; res < 15; res++) {
182 if (const auto err = getPentagons(res, pentagons.data()); err != E_SUCCESS) {
183 spdlog::warn(describeH3Error(err));
184 }
185 for (const auto &pentagon : pentagons) {
186 auto pentagonPolygon = H3_VIEWER::Helper::indexToPolygon(pentagon);
187 if (!pentagonPolygon.has_value()) {
188 continue;
189 }
190 onCellComputed(getResolution(pentagon), pentagon, pentagonPolygon.value(), false);
191 }
192 }
193}
194
195void H3Model::onCellsComputed(const QVariantList &list) {
196 coordinates_ = list;
197 emit coordinatesChanged();
198}
199
200void H3Model::onCellComputed(const quint8 res, const H3Index index, const QVariantList &polygon,
201 const bool isSearching) {
202 // Не добавляем новые ячейки во время очистки
203 if (isClearing_) {
204 return;
205 }
206
207 // isSearching=false: A* исследует ячейку (светло-голубой)
208 // isSearching=true: финальный путь (цвет по разрешению)
209 const QColor cellColor =
210 isSearching ? QColor(getColorForResolution(res)) : QColor(100, 200, 255, 120); // Cyan для поиска
211
212 addCell(res, index, polygon, cellColor);
213}
214
215void H3Model::onPathCellsBatch(const std::vector<std::tuple<quint8, H3Index, QVariantList>> &cells) {
216 if (isClearing_ || cells.empty()) {
217 return;
218 }
219
220 // Filter out duplicates
221 std::vector<std::tuple<quint8, H3Index, QVariantList, QColor>> newCells;
222 newCells.reserve(cells.size());
223
224 for (const auto &[res, index, polygon] : cells) {
225 if (!findCellByID(index).has_value()) {
226 const auto cellColor = QColor(getColorForResolution(res));
227 newCells.emplace_back(res, index, polygon, cellColor);
228 }
229 }
230
231 if (newCells.empty()) {
232 return;
233 }
234
235 // Single batch insert - much more efficient than individual inserts
236 const int first = static_cast<int>(pathCells_.size());
237 const int last = first + static_cast<int>(newCells.size()) - 1;
238
239 beginInsertRows(QModelIndex(), first, last);
240
241 for (const auto &[res, index, polygon, color] : newCells) {
242 auto cell = new H3Cell(this);
243 cell->setRes(res);
244 cell->setIndex(index);
245 cell->setPath(polygon);
246 cell->setColor(color);
247 pathCells_.emplace_back(cell);
248 }
249
250 endInsertRows();
251
252 spdlog::debug("Batch inserted {} path cells", newCells.size());
253}
254
255void H3Model::requestCells(const std::vector<H3Index> &indexes) {
256 if (indexes.empty()) {
257 return;
258 }
259 // Если есть старые ячейки, очищаем их перед добавлением новой
260 if (!pathCells_.empty()) {
262
263 if (!isClearing_) {
264 worker_->requestCell(indexes);
265 }
266 } else {
267 // Если модель пустая, запрашиваем сразу
268 worker_->requestCell(indexes);
269 }
270}
272 // Предотвращаем повторный вызов во время очистки
273 if (isClearing_) {
274 spdlog::info("Already clearing, skipping...");
275 return;
276 }
277
278 // Проверяем, есть ли что очищать
279 if (pathCells_.isEmpty()) {
280 return;
281 }
282
283 isClearing_ = true;
284 emit clearingStarted();
285
286 beginResetModel();
287 qDeleteAll(pathCells_);
288 pathCells_.clear();
289 endResetModel();
290
291 isClearing_ = false;
292 emit clearingFinished();
293}
294
295void H3Model::onMazePolygonsComputed(const std::vector<QVariantList> &polygons) {
296 if (isClearing_) {
297 return;
298 }
299
300 mazePolygons_.clear();
301 mazePolygons_.reserve(static_cast<qsizetype>(polygons.size()));
302
303 for (const auto &polygon : polygons) {
304 mazePolygons_.append(polygon);
305 }
306
307 spdlog::info("Maze polygons updated: {} polygons", mazePolygons_.size());
308 emit mazePolygonsChanged();
309}
310
311void H3Model::onSearchStats(int exploredCells, double timeMs, int pathLength) {
312 searchStatsText_ = QString("Explored: %1 cells | Time: %2 ms | Path: %3 cells")
313 .arg(exploredCells)
314 .arg(timeMs, 0, 'f', 2)
315 .arg(pathLength);
316
317 spdlog::info("Search stats: {} explored, {:.2f} ms, {} path length", exploredCells, timeMs, pathLength);
318 emit searchStatsChanged();
319}
Definition h3Cell.h:6
void mazeWallsGenerated(const std::unordered_set< H3Index > &walls)
void generateMazeAsync(double lat, double lon, int kRingRadius)
void mazeRadiusComputed(const QGeoCoordinate &center, double radiusMeters)
~H3Model() override
Destructor.
Definition h3Model.cpp:20
QHash< int, QByteArray > roleNames() const override
Returns the role names for QML access.
Definition h3Model.cpp:56
void mazeRadiusChanged()
Emitted when maze radius changes.
QString searchStatsText_
Formatted search statistics.
Definition h3Model.h:287
void mazePolygonsChanged()
Emitted when maze polygons change.
void requestCells(const std::vector< H3Index > &indexes)
Requests visualization of specific cells.
Definition h3Model.cpp:255
void mazeWallsGenerated(const std::unordered_set< H3Index > &walls)
Emitted when maze walls are generated.
int rowCount(const QModelIndex &parent) const override
Returns the number of cells in the model.
Definition h3Model.cpp:31
bool isCoordinateTargetValid(quint8 zoom, const QGeoCoordinate &coordinate) const
Validates if a coordinate is a valid target.
Definition h3Model.cpp:127
void coordinatesChanged()
Emitted when coordinates property changes.
QGeoCoordinate mazeCenter_
Maze center coordinate.
Definition h3Model.h:288
void addCell(quint8 res, H3Index index, const QVariantList &polygon, const QColor &color)
Adds a new cell to the model.
Definition h3Model.cpp:158
void clearingFinished()
Emitted when cell clearing completes.
void mazeBoundsGenerated(const QGeoCoordinate &center, double radiusMeters)
Emitted with maze boundary information.
const uint8_t maxZoom_c
Maximum supported zoom level.
Definition h3Model.h:292
void onCellComputed(quint8 res, H3Index index, const QVariantList &polygon, bool isSearching)
Handles individual cell computation during search.
Definition h3Model.cpp:200
H3Model(QObject *parent=nullptr)
Constructs an H3Model.
Definition h3Model.cpp:11
std::optional< H3Cell * > findCellByRes(quint8 res) const
Finds a cell by its resolution.
Definition h3Model.cpp:138
void onSearchStats(int exploredCells, double timeMs, int pathLength)
Handles search statistics update.
Definition h3Model.cpp:311
void onMazePolygonsComputed(const std::vector< QVariantList > &polygons)
Handles maze polygon computation results.
Definition h3Model.cpp:295
void Init()
Initializes the model and starts worker thread.
Definition h3Model.cpp:64
QString getColorForResolution(quint8 resolution) const
Gets the display color for a resolution level.
Definition h3Model.cpp:154
void onCellsComputed(const QVariantList &list)
Handles batch cell computation results.
Definition h3Model.cpp:195
double mazeRadius_
Maze boundary radius in meters.
Definition h3Model.h:289
@ PathRole
Whether cell is part of path.
Definition h3Model.h:86
@ CellColor
Cell display color.
Definition h3Model.h:85
@ IndexRole
H3 cell index.
Definition h3Model.h:84
@ ResRole
Cell resolution (3-15).
Definition h3Model.h:83
QList< H3Cell * > pathCells_
Cells that are part of the current path.
Definition h3Model.h:284
const uint8_t minZoom_c
Minimum supported zoom level.
Definition h3Model.h:291
QVariantList coordinates_
Cell coordinates for display.
Definition h3Model.h:285
QThread * thread_
Worker thread.
Definition h3Model.h:281
QVariant data(const QModelIndex &index, int role) const override
Returns data for a cell at the given index.
Definition h3Model.cpp:36
H3_VIEWER::H3Worker * worker_
Worker for async operations.
Definition h3Model.h:280
std::optional< H3Cell * > findCellByID(quint64 id) const
Finds a cell by its H3 index.
Definition h3Model.cpp:146
std::atomic_bool isClearing_
Flag indicating clearing in progress.
Definition h3Model.h:301
void clearAllCells()
Clears all cells from the model.
Definition h3Model.cpp:271
std::unordered_map< uint8_t, uint8_t > zoomToRes_
Zoom to resolution mapping.
Definition h3Model.h:293
const QHash< int, QString > resolutionColors_c
Color palette for different resolutions.
Definition h3Model.h:296
void searchStatsChanged()
Emitted when search statistics update.
H3MazeAdapter * mazeAdapter_
Maze generation adapter.
Definition h3Model.h:282
void addPentagons()
Adds pentagon cells to the model.
Definition h3Model.cpp:178
void onPathCellsBatch(const std::vector< std::tuple< quint8, H3Index, QVariantList > > &cells)
Handles batch cell computation for path visualization (optimized).
Definition h3Model.cpp:215
QList< QVariantList > mazePolygons_
Maze wall polygons.
Definition h3Model.h:286
void mazeCenterChanged()
Emitted when maze center changes.
Worker thread for asynchronous H3 operations.
Definition h3Worker.h:54
void cellsComputed(const QVariantList &polygon)
Emitted when batch cell computation completes.
void mazePolygonsComputed(const std::vector< QVariantList > &polygons)
Emitted with merged maze wall polygons for rendering.
void requestCell(const std::vector< H3Index > &index)
Requests computation for a set of H3 cells.
Definition h3Worker.cpp:79
void searchStats(int exploredCells, double timeMs, int pathLength)
Emitted with pathfinding statistics after search completes.
void cellComputed(quint8 res, H3Index id, const QVariantList &polygon, bool isSearching)
Emitted when a single cell computation completes.
void setWalls(const std::unordered_set< H3Index > &mazeWalls)
Sets maze walls for pathfinding obstacle avoidance.
Definition h3Worker.cpp:93
void doWork()
Main work loop executed in the worker thread.
Definition h3Worker.cpp:17
Qt list model for managing H3 hexagonal cells and pathfinding visualization.
Worker thread for asynchronous H3 cell processing and pathfinding.
static std::optional< QVariantList > indexToPolygon(const H3Index index)
Definition helper.h:6