qHexWalker 0.0.1
Hexagonal Grid Pathfinding & Maze Visualization on Interactive Maps
Loading...
Searching...
No Matches
h3TargetsModel.cpp
Go to the documentation of this file.
1#include "h3TargetsModel.h"
2#include "h3Target.h"
3
4#include <helper.h>
5
6H3TargetsModel::H3TargetsModel(QObject *parent) : QAbstractListModel(parent) {
7 auto zoomToResolution = [&](const double zoom) {
8 return std::max(std::min(zoom / 1.5, static_cast<double>(maxZoom_c)), 0.0);
9 };
10 for (auto zoom = minZoom_c; zoom < maxZoom_c; zoom++) {
11 zoomToRes_.emplace(zoom, std::floor(zoomToResolution(zoom)));
12 }
13}
14
16 qDeleteAll(cells_);
17 cells_.clear();
18}
19
20int H3TargetsModel::rowCount(const QModelIndex &parent) const {
21 Q_UNUSED(parent)
22 return static_cast<int>(cells_.size());
23}
24
25QVariant H3TargetsModel::data(const QModelIndex &index, const int role) const {
26 if (!index.isValid() || index.row() >= cells_.size()) {
27 return {};
28 }
29
30 const auto data = cells_.at(index.row());
31 switch (role) {
32 case ResRole:
33 return QVariant::fromValue(data->res());
34 case ZoomRole:
35 return QVariant::fromValue(data->zoom());
36 case OrderRole:
37 return QVariant::fromValue(data->order());
38 case IndexRole:
39 return QVariant::fromValue(data->index());
40 case CellColor:
41 return QVariant::fromValue(data->color());
42 case PathRole:
43 return QVariant::fromValue(data->path());
44 case CoordinatesRole:
45 return QVariant::fromValue(data->coordinate());
46 default:;
47 }
48 return {};
49}
50
51QHash<int, QByteArray> H3TargetsModel::roleNames() const {
52 // clang-format off
53 return { {ResRole, "res"},
54 {ZoomRole, "zoom"},
55 {OrderRole, "order"},
56 {IndexRole, "h3Index"},
57 {CellColor, "color"},
58 {PathRole, "path"},
59 {CoordinatesRole, "coordinate"}};
60 // clang-format on
61}
62
64 std::vector<H3Index> indexes;
65 indexes.reserve(cells_.size());
66 for (const auto &cell : cells_) {
67 indexes.emplace_back(cell->index());
68 }
69 emit onCompute(indexes);
70}
71void H3TargetsModel::move(const int from, const int to) {
72 if (from < 0 || from >= cells_.size() || to < 0 || to >= cells_.size() || from == to)
73 return;
74
75 // Правильно рассчитываем destinationRow для beginMoveRows
76 const int destinationRow = to > from ? to + 1 : to;
77
78 isClearing_ = true;
79 emit clearingStarted();
80
81 beginMoveRows(QModelIndex(), from, from, QModelIndex(), destinationRow);
82
83 // Ручное перемещение — 100% надёжно
84 H3Target *item = cells_.takeAt(from);
85 cells_.insert(to, item);
86
87 endMoveRows();
88
89 // Обновляем order
90 const int start = std::min(from, to);
91 const int end = std::max(from, to);
92 for (int i = start; i <= end; ++i) {
93 cells_[i]->setOrder(static_cast<quint16>(i + 1));
94 }
95
96 emit dataChanged(index(start), index(end), {OrderRole});
97
98 isClearing_ = false;
99 emit clearingFinished();
100 compute();
101}
102
103qsizetype H3TargetsModel::remove(const int row) {
104 if (row < 0 || row >= cells_.size())
105 return {};
106
107 isClearing_ = true;
108 emit clearingStarted();
109
110 // Правильно: удаляем одну строку
111 beginRemoveRows(QModelIndex(), row, row);
112 const H3Target *cell = cells_.takeAt(row);
113 delete cell;
114 endRemoveRows();
115
116 // Обновляем order у оставшихся элементов (начиная с row и до конца)
117 for (int i = row; i < cells_.size(); ++i) {
118 cells_[i]->setOrder(static_cast<quint16>(i + 1));
119 }
120
121 // Уведомляем QML, что order изменился у оставшихся элементов
122 if (row < cells_.size()) {
123 emit dataChanged(index(row), index(cells_.size() - 1), {OrderRole});
124 } else if (cells_.isEmpty()) {
125 // Если список стал пустым — уведомляем хотя бы одну (фиктивную) строку
126 emit dataChanged(index(0), index(0), {OrderRole});
127 }
128
129 isClearing_ = false;
130 emit clearingFinished();
131
132 std::vector<H3Index> indexes;
133 indexes.reserve(cells_.size());
134 for (const auto &c : cells_) {
135 indexes.emplace_back(c->index());
136 }
137 emit onRemoveCell(indexes);
138
139 compute();
140 return cells_.size();
141}
142
143void H3TargetsModel::requestCell(const quint8 mapZoom, const QGeoCoordinate &coordinate) {
144 if (!isCoordinateTargetValid(mapZoom, coordinate)) {
145 return;
146 }
147 if (isClearing_) {
148 return;
149 }
150 uint8_t res = 0;
151 try {
152 res = zoomToRes_.at(mapZoom);
153 if (res <= 3) {
154 res = 3;
155 }
156 } catch (const std::out_of_range &err) {
157 spdlog::error("Выбран недопустимый зум под разрешение {}", err.what());
158 return;
159 }
160 // Проверка: не находится ли точка за пределами допустимой области
161 if (mazeRadius_ > 0.0 && mazeCenter_.isValid()) {
162 if (const double distance = mazeCenter_.distanceTo(coordinate); distance > mazeRadius_) {
163 const auto message = QString("Cannot add target: point is outside the allowed area!");
164 emit showNotification(message, "warning");
165 return;
166 }
167 }
168
169 H3Index h3Index = H3_NULL;
170 const LatLng ll{.lat = degsToRads(coordinate.latitude()), .lng = degsToRads(coordinate.longitude())};
171 if (const auto errIdx = latLngToCell(&ll, res, &h3Index); errIdx != E_SUCCESS || h3Index == H3_NULL) {
172 spdlog::warn("Impossible to convert this lat:{} lng:{} coordinate to H3Index {}", coordinate.latitude(),
173 coordinate.longitude(), errIdx);
174 return;
175 }
176
177 // Проверка: не пытается ли пользователь добавить точку на стену
178 if (mazeWalls_.contains(h3Index)) {
179 const auto message = QString("Cannot add target: this cell is a wall!");
180 emit showNotification(message, "warning");
181 return;
182 }
183
184 auto comp = [h3Index](const H3Target *cell) { return cell->index() == h3Index; };
185 if (const auto isUnique = std::ranges::find_if(cells_, comp); isUnique != cells_.end()) {
186 return;
187 }
188
189 const auto polygon = H3_VIEWER::Helper::indexToPolygon(h3Index);
190 if (!polygon.has_value()) {
191 return;
192 }
193
194 auto cell = new H3Target(this);
195 cell->setRes(res);
196 cell->setZoom(mapZoom);
197 cell->setIndex(h3Index);
198 cell->setCoordinate(coordinate);
199 cell->setPath(polygon.value());
200 cell->setColor("red");
201
202 beginInsertRows(QModelIndex(), static_cast<int>(cells_.size()), static_cast<int>(cells_.size()));
203 cells_.emplace_back(cell);
204 endInsertRows();
205
206 cell->setOrder(static_cast<quint16>(cells_.size())); // после endInsertRows()
207}
208
210 // Проверяем, есть ли что очищать
211 if (cells_.isEmpty()) {
212 return;
213 }
214
215 // Предотвращаем повторный вызов во время очистки
216 if (isClearing_) {
217 spdlog::info("Already clearing, skipping...");
218 return;
219 }
220
221 spdlog::info("Starting clearAllCells, count: {}", cells_.size());
222
223 isClearing_ = true;
224 emit clearingStarted();
225
226 beginResetModel();
227 qDeleteAll(cells_);
228 cells_.clear();
229 endResetModel();
230
231 isClearing_ = false;
232 emit clearingFinished();
233}
234
235bool H3TargetsModel::isCoordinateTargetValid(const quint8 zoom, const QGeoCoordinate &coordinate) const {
236 if (!coordinate.isValid()) {
237 return false;
238 }
239 if (zoom >= maxZoom_c) {
240 return false;
241 }
242
243 return true;
244}
245
246void H3TargetsModel::setMazeWalls(const std::unordered_set<H3Index> &walls) {
247 mazeWalls_ = walls;
248 spdlog::info("H3TargetsModel: Maze walls updated, {} wall cells", mazeWalls_.size());
249}
250
251void H3TargetsModel::setMazeBounds(const QGeoCoordinate &center, const double radiusMeters) {
252 mazeCenter_ = center;
253 mazeRadius_ = radiusMeters;
254 spdlog::info("H3TargetsModel: Maze bounds set - center ({}, {}), radius {} meters", center.latitude(),
255 center.longitude(), radiusMeters);
256}
void onCompute(const std::vector< H3Index > &indexes)
void clearingFinished()
void setMazeWalls(const std::unordered_set< H3Index > &walls)
QList< H3Target * > cells_
void setMazeBounds(const QGeoCoordinate &center, double radiusMeters)
qsizetype remove(int row)
const uint8_t maxZoom_c
void clearingStarted()
std::unordered_map< uint8_t, uint8_t > zoomToRes_
std::unordered_set< H3Index > mazeWalls_
H3TargetsModel(QObject *parent=nullptr)
void showNotification(const QString &message, const QString &type)
QVariant data(const QModelIndex &index, int role) const override
bool isCoordinateTargetValid(quint8 zoom, const QGeoCoordinate &coordinate) const
QHash< int, QByteArray > roleNames() const override
std::atomic_bool isClearing_
public::void onRemoveCell(const std::vector< H3Index > &indexes)
QGeoCoordinate mazeCenter_
void move(int from, int to)
int rowCount(const QModelIndex &parent) const override
const uint8_t minZoom_c
~H3TargetsModel() override
void requestCell(quint8 mapZoom, const QGeoCoordinate &coordinate)
static std::optional< QVariantList > indexToPolygon(const H3Index index)
Definition helper.h:6