Marble

RoutingModel.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
4//
5
6#include "RoutingModel.h"
7
8#include "GeoDataAccuracy.h"
9#include "MarbleGlobal.h"
10#include "Planet.h"
11#include "PlanetFactory.h"
12#include "PositionTracking.h"
13#include "Route.h"
14#include "RouteRequest.h"
15
16#include <QIODevice>
17#include <QPixmap>
18
19namespace Marble
20{
21
22class RoutingModelPrivate
23{
24public:
25 enum RouteDeviation {
26 Unknown,
27 OnRoute,
28 OffRoute
29 };
30
31 explicit RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request);
32
33 Route m_route;
34
35 PositionTracking *const m_positionTracking;
36 RouteRequest *const m_request;
37 QHash<int, QByteArray> m_roleNames;
38 RouteDeviation m_deviation;
39
40 void updateViaPoints(const GeoDataCoordinates &position);
41};
42
43RoutingModelPrivate::RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request)
44 : m_positionTracking(positionTracking)
45 , m_request(request)
46 , m_deviation(Unknown)
47{
48 // nothing to do
49}
50
51void RoutingModelPrivate::updateViaPoints(const GeoDataCoordinates &position)
52{
53 // Mark via points visited after approaching them in a range of 500m or less
54 qreal const threshold = 500 / EARTH_RADIUS;
55 for (int i = 0; i < m_request->size(); ++i) {
56 if (!m_request->visited(i)) {
57 if (position.sphericalDistanceTo(m_request->at(i)) < threshold) {
58 m_request->setVisited(i, true);
59 }
60 }
61 }
62}
63
64RoutingModel::RoutingModel(RouteRequest *request, PositionTracking *positionTracking, QObject *parent)
65 : QAbstractListModel(parent)
66 , d(new RoutingModelPrivate(positionTracking, request))
67{
68 connect(d->m_positionTracking, SIGNAL(gpsLocation(GeoDataCoordinates, qreal)), this, SLOT(updatePosition(GeoDataCoordinates, qreal)));
69
71 roles.insert(Qt::DisplayRole, "display");
72 roles.insert(RoutingModel::TurnTypeIconRole, "turnTypeIcon");
73 roles.insert(RoutingModel::LongitudeRole, "longitude");
74 roles.insert(RoutingModel::LatitudeRole, "latitude");
75 d->m_roleNames = roles;
76}
77
78RoutingModel::~RoutingModel()
79{
80 delete d;
81}
82
83int RoutingModel::rowCount(const QModelIndex &parent) const
84{
85 return parent.isValid() ? 0 : d->m_route.turnPoints().size();
86}
87
88QVariant RoutingModel::headerData(int section, Qt::Orientation orientation, int role) const
89{
90 if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
91 return QStringLiteral("Instruction");
92 }
93
94 return QAbstractListModel::headerData(section, orientation, role);
95}
96
97QVariant RoutingModel::data(const QModelIndex &index, int role) const
98{
99 if (!index.isValid()) {
100 return {};
101 }
102
103 if (index.row() < d->m_route.turnPoints().size() && index.column() == 0) {
104 const RouteSegment &segment = d->m_route.at(index.row());
105 switch (role) {
106 case Qt::DisplayRole:
107 case Qt::ToolTipRole:
108 return segment.maneuver().instructionText();
109 case Qt::DecorationRole: {
110 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
111 if (segment.maneuver().hasWaypoint()) {
112 int const size = smallScreen ? 64 : 32;
113 return d->m_request->pixmap(segment.maneuver().waypointIndex(), size, size / 4);
114 }
115
116 QPixmap const pixmap = segment.maneuver().directionPixmap();
117 return smallScreen ? pixmap : pixmap.scaled(32, 32);
118 }
119 case Qt::SizeHintRole: {
120 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
121 int const size = smallScreen ? 64 : 32;
122 return QSize(size, size);
123 }
124 case RoutingModel::CoordinateRole:
125 return QVariant::fromValue(segment.maneuver().position());
126 case RoutingModel::LongitudeRole:
127 return {segment.maneuver().position().longitude(GeoDataCoordinates::Degree)};
128 case RoutingModel::LatitudeRole:
129 return {segment.maneuver().position().latitude(GeoDataCoordinates::Degree)};
130 case RoutingModel::TurnTypeIconRole:
131 return segment.maneuver().directionPixmap();
132 default:
133 return {};
134 }
135 }
136
137 return {};
138}
139
140QHash<int, QByteArray> RoutingModel::roleNames() const
141{
142 return d->m_roleNames;
143}
144
145void RoutingModel::setRoute(const Route &route)
146{
147 d->m_route = route;
148 d->m_deviation = RoutingModelPrivate::Unknown;
149
150 beginResetModel();
151 endResetModel();
152 Q_EMIT currentRouteChanged();
153}
154
155void RoutingModel::exportGpx(QIODevice *device) const
156{
157 QString content = QLatin1StringView(
158 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
159 "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"Marble\" version=\"1.1\" "
160 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
161 "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 "
162 "http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
163 "<metadata>\n <link href=\"http://edu.kde.org/marble\">\n "
164 "<text>Marble Virtual Globe</text>\n </link>\n</metadata>\n"
165 " <rte>\n <name>Route</name>\n");
166 bool hasAltitude = false;
167 for (int i = 0; !hasAltitude && i < d->m_route.size(); ++i) {
168 hasAltitude = d->m_route.at(i).maneuver().position().altitude() != 0.0;
169 }
170 for (int i = 0; i < d->m_route.size(); ++i) {
171 const Maneuver &maneuver = d->m_route.at(i).maneuver();
172 qreal lon = maneuver.position().longitude(GeoDataCoordinates::Degree);
173 qreal lat = maneuver.position().latitude(GeoDataCoordinates::Degree);
174 QString const text = maneuver.instructionText();
175 content += QStringLiteral(" <rtept lat=\"%1\" lon=\"%2\">\n").arg(lat, 0, 'f', 7).arg(lon, 0, 'f', 7);
176 content += QStringLiteral(" <name>%1</name>\n").arg(text);
177 if (hasAltitude) {
178 content += QStringLiteral(" <ele>%1</ele>\n").arg(maneuver.position().altitude(), 0, 'f', 2);
179 }
180 content += QStringLiteral(" </rtept>\n");
181 }
182 content += QLatin1StringView(
183 " </rte>\n"
184 "<trk>\n <name>Route</name>\n <trkseg>\n");
185 GeoDataLineString points = d->m_route.path();
186 hasAltitude = false;
187 for (int i = 0; !hasAltitude && i < points.size(); ++i) {
188 hasAltitude = points[i].altitude() != 0.0;
189 }
190 for (int i = 0; i < points.size(); ++i) {
191 GeoDataCoordinates const &point = points[i];
192 qreal lon = point.longitude(GeoDataCoordinates::Degree);
193 qreal lat = point.latitude(GeoDataCoordinates::Degree);
194 content += QStringLiteral(" <trkpt lat=\"%1\" lon=\"%2\">\n").arg(lat, 0, 'f', 7).arg(lon, 0, 'f', 7);
195 if (hasAltitude) {
196 content += QStringLiteral(" <ele>%1</ele>\n").arg(point.altitude(), 0, 'f', 2);
197 }
198 content += QStringLiteral(" </trkpt>\n");
199 }
200 content += QLatin1StringView(
201 " </trkseg>\n </trk>\n"
202 "</gpx>\n");
203
204 device->write(content.toUtf8());
205}
206
207void RoutingModel::clear()
208{
209 d->m_route = Route();
210 beginResetModel();
211 endResetModel();
212 Q_EMIT currentRouteChanged();
213}
214
215int RoutingModel::rightNeighbor(const GeoDataCoordinates &position, RouteRequest const *const route) const
216{
217 Q_ASSERT(route && "Must not pass a null route ");
218
219 // Quick result for trivial cases
220 if (route->size() < 3) {
221 return route->size() - 1;
222 }
223
224 // Generate an ordered list of all waypoints
225 GeoDataLineString points = d->m_route.path();
226 QMap<int, int> mapping;
227
228 // Force first mapping point to match the route start
229 mapping[0] = 0;
230
231 // Calculate the mapping between waypoints and via points
232 // Need two for loops to avoid getting stuck in local minima
233 for (int j = 1; j < route->size() - 1; ++j) {
234 qreal minDistance = -1.0;
235 for (int i = mapping[j - 1]; i < points.size(); ++i) {
236 const qreal distance = points[i].sphericalDistanceTo(route->at(j));
237 if (minDistance < 0.0 || distance < minDistance) {
238 mapping[j] = i;
239 minDistance = distance;
240 }
241 }
242 }
243
244 // Determine waypoint with minimum distance to the provided position
245 qreal minWaypointDistance = -1.0;
246 int waypoint = 0;
247 for (int i = 0; i < points.size(); ++i) {
248 const qreal waypointDistance = points[i].sphericalDistanceTo(position);
249 if (minWaypointDistance < 0.0 || waypointDistance < minWaypointDistance) {
250 minWaypointDistance = waypointDistance;
251 waypoint = i;
252 }
253 }
254
255 // Force last mapping point to match the route destination
256 mapping[route->size() - 1] = points.size() - 1;
257
258 // Determine neighbor based on the mapping
260 for (; iter != mapping.constEnd(); ++iter) {
261 if (iter.value() > waypoint) {
262 int index = iter.key();
263 Q_ASSERT(index >= 0 && index <= route->size());
264 return index;
265 }
266 }
267
268 return route->size() - 1;
269}
270
271void RoutingModel::updatePosition(const GeoDataCoordinates &location, qreal speed)
272{
273 d->m_route.setPosition(location);
274
275 d->updateViaPoints(location);
276 const qreal planetRadius = PlanetFactory::construct(QStringLiteral("earth")).radius();
277 const qreal distance = planetRadius * location.sphericalDistanceTo(d->m_route.positionOnRoute());
278 Q_EMIT positionChanged();
279
280 qreal deviation = 0.0;
281 if (d->m_positionTracking && d->m_positionTracking->accuracy().vertical > 0.0) {
282 deviation = qMax<qreal>(d->m_positionTracking->accuracy().vertical, d->m_positionTracking->accuracy().horizontal);
283 }
284 qreal const threshold = deviation + qBound(10.0, speed * 10.0, 150.0);
285
286 RoutingModelPrivate::RouteDeviation const deviated = distance < threshold ? RoutingModelPrivate::OnRoute : RoutingModelPrivate::OffRoute;
287 if (d->m_deviation != deviated) {
288 d->m_deviation = deviated;
289 Q_EMIT deviatedFromRoute(deviated == RoutingModelPrivate::OffRoute);
290 }
291}
292
293bool RoutingModel::deviatedFromRoute() const
294{
295 return d->m_deviation == RoutingModelPrivate::OffRoute;
296}
297
298const Route &RoutingModel::route() const
299{
300 return d->m_route;
301}
302
303} // namespace Marble
304
305#include "moc_RoutingModel.cpp"
QVariant location(const QVariant &res)
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const const
iterator insert(const Key &key, const T &value)
qint64 write(const QByteArray &data)
const_iterator constBegin() const const
const_iterator constEnd() const const
size_type size() const const
int column() const const
bool isValid() const const
int row() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QString arg(Args &&... args) const const
QByteArray toUtf8() const const
DisplayRole
Orientation
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.