Marble

RouteSimulationPositionProviderPlugin.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2011 Konrad Enzensberger <e.konrad@mpegcode.com>
4// SPDX-FileCopyrightText: 2011 Dennis Nienhüser <nienhueser@kde.org>
5// SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6//
7
8#include "RouteSimulationPositionProviderPlugin.h"
9
10#include "GeoDataAccuracy.h"
11#include "MarbleModel.h"
12#include "routing/Route.h"
13#include "routing/RoutingManager.h"
14#include "routing/RoutingModel.h"
15
16#include <QIcon>
17#include <QRandomGenerator>
18
19namespace Marble
20{
21
22namespace
23{
24qreal const c_frequency = 4.0; // Hz
25}
26
27QString RouteSimulationPositionProviderPlugin::name() const
28{
29 return tr("Current Route Position Provider Plugin");
30}
31
32QString RouteSimulationPositionProviderPlugin::nameId() const
33{
34 return QStringLiteral("RouteSimulationPositionProviderPlugin");
35}
36
37QString RouteSimulationPositionProviderPlugin::guiString() const
38{
39 return tr("Current Route");
40}
41
42QString RouteSimulationPositionProviderPlugin::version() const
43{
44 return QStringLiteral("1.1");
45}
46
47QString RouteSimulationPositionProviderPlugin::description() const
48{
49 return tr("Simulates traveling along the current route.");
50}
51
52QString RouteSimulationPositionProviderPlugin::copyrightYears() const
53{
54 return QStringLiteral("2011, 2012");
55}
56
57QList<PluginAuthor> RouteSimulationPositionProviderPlugin::pluginAuthors() const
58{
59 return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Konrad Enzensberger"), QStringLiteral("e.konrad@mpegcode.com"))
60 << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org"))
61 << PluginAuthor(QStringLiteral("Bernhard Beschow"), QStringLiteral("bbeschow@cs.tu-berlin.de"));
62}
63
64QIcon RouteSimulationPositionProviderPlugin::icon() const
65{
66 return {};
67}
68
69PositionProviderPlugin *RouteSimulationPositionProviderPlugin::newInstance() const
70{
71 return new RouteSimulationPositionProviderPlugin(m_marbleModel);
72}
73
74PositionProviderStatus RouteSimulationPositionProviderPlugin::status() const
75{
76 return m_status;
77}
78
79GeoDataCoordinates RouteSimulationPositionProviderPlugin::position() const
80{
81 return m_currentPositionWithNoise;
82}
83
84GeoDataAccuracy RouteSimulationPositionProviderPlugin::accuracy() const
85{
86 GeoDataAccuracy result;
87
88 // faked values
89 result.level = GeoDataAccuracy::Detailed;
90 result.horizontal = 10.0;
91 result.vertical = 10.0;
92
93 return result;
94}
95
96RouteSimulationPositionProviderPlugin::RouteSimulationPositionProviderPlugin(MarbleModel *marbleModel, QObject *parent)
98 , m_marbleModel(marbleModel)
99 , m_currentIndex(-2)
100 , m_status(PositionProviderStatusUnavailable)
101 , m_currentDateTime()
102 , m_speed(0.0)
103 , m_direction(0.0)
104 , m_directionWithNoise(0.0)
105{
106 connect(&m_updateTimer, &QTimer::timeout, this, &RouteSimulationPositionProviderPlugin::update);
107}
108
109RouteSimulationPositionProviderPlugin::~RouteSimulationPositionProviderPlugin() = default;
110
111void RouteSimulationPositionProviderPlugin::initialize()
112{
113 updateRoute();
114 connect(m_marbleModel->routingManager()->routingModel(), SIGNAL(currentRouteChanged()), this, SLOT(updateRoute()));
115}
116
117bool RouteSimulationPositionProviderPlugin::isInitialized() const
118{
119 return (m_currentIndex > -2);
120}
121
122qreal RouteSimulationPositionProviderPlugin::speed() const
123{
124 return m_speed;
125}
126
127qreal RouteSimulationPositionProviderPlugin::direction() const
128{
129 return m_directionWithNoise;
130}
131
132QDateTime RouteSimulationPositionProviderPlugin::timestamp() const
133{
134 return m_currentDateTime;
135}
136
137void RouteSimulationPositionProviderPlugin::updateRoute()
138{
139 m_currentIndex = -1;
140 m_lineString = m_lineStringInterpolated = m_marbleModel->routingManager()->routingModel()->route().path();
141 m_speed = 0; // initialize speed to be around 25 m/s;
142 bool const canWork = !m_lineString.isEmpty() || m_currentPosition.isValid();
143 if (canWork) {
144 changeStatus(PositionProviderStatusAcquiring);
145 m_updateTimer.start(1000.0 / c_frequency);
146 } else {
147 changeStatus(PositionProviderStatusUnavailable);
148 m_updateTimer.stop();
149 }
150}
151
152void RouteSimulationPositionProviderPlugin::update()
153{
154 if (m_lineString.isEmpty() && m_currentPosition.isValid()) {
155 m_currentPositionWithNoise = addNoise(m_currentPosition, accuracy());
156 changeStatus(PositionProviderStatusAvailable);
157 Q_EMIT positionChanged(position(), accuracy());
158 return;
159 }
160
161 if (m_currentIndex >= 0 && m_currentIndex < m_lineStringInterpolated.size()) {
162 changeStatus(PositionProviderStatusAvailable);
163 GeoDataCoordinates newPosition = m_lineStringInterpolated.at(m_currentIndex);
164 const QDateTime newDateTime = QDateTime::currentDateTime();
165 qreal time = m_currentDateTime.msecsTo(newDateTime) / 1000.0;
166 if (m_currentPosition.isValid()) {
167 // speed calculations
168 // Max speed is set on points (m_lineStringInterpolated) based on formula. (max speed before points is calculated so the acceleration won't be
169 // exceeded)
170 const qreal acceleration = 1.5;
171 const qreal lookForwardDistance = 1000;
172 qreal checkedDistance = m_currentPosition.sphericalDistanceTo(m_lineStringInterpolated.at(m_currentIndex)) * m_marbleModel->planetRadius();
173 const qreal maxSpeed = 25;
174 const qreal minSpeed = 2;
175 qreal newSpeed = qMin((m_speed + acceleration * time), maxSpeed);
176 for (int i = qMax(1, m_currentIndex); i < m_lineStringInterpolated.size() - 1 && checkedDistance < lookForwardDistance; ++i) {
177 qreal previousHeading =
178 m_lineStringInterpolated.at(i - 1).bearing(m_lineStringInterpolated.at(i), GeoDataCoordinates::Degree, GeoDataCoordinates::FinalBearing);
179 qreal curveLength = 10; // we treat one point turn as a curve of length 10
180 qreal angleSum = 0; // sum of turn angles in a curve
181 for (int j = i + 1; j < m_lineStringInterpolated.size() && curveLength < 35; ++j) {
182 qreal newHeading = m_lineStringInterpolated.at(j - 1).bearing(m_lineStringInterpolated.at(j),
183 GeoDataCoordinates::Degree,
184 GeoDataCoordinates::FinalBearing);
185 qreal differenceHeading = qAbs(previousHeading - newHeading); // angle of turn
186 if (differenceHeading > 180) {
187 differenceHeading = 360 - differenceHeading;
188 }
189 angleSum += differenceHeading;
190 qreal maxSpeedAtTurn = qMax((1 - (static_cast<qreal>(angleSum / 60.0 / curveLength * 10.0)) * maxSpeed), minSpeed); // speed limit at turn
191 if (checkedDistance < 25 && maxSpeedAtTurn < newSpeed) // if we are near turn don't accelerate, if we will have to slow down
192 newSpeed = qMin(newSpeed, qMax(m_speed, maxSpeedAtTurn));
193 // formulas:
194 // s = Vc * t + a*t*t/2
195 // V0 = Vc + a*t
196 // V0 = maxCurrentSpeed
197 // Vc = maxSpeedAtTurn
198 // s = checkedDistance
199 // a = acceleration
200 qreal delta = maxSpeedAtTurn * maxSpeedAtTurn - 4.0 * acceleration / 2.0 * (-checkedDistance); // delta = b*b-4*a*c
201 qreal t = (-maxSpeedAtTurn + sqrt(delta)) / (2.0 * acceleration / 2.0); //(-b+sqrt(delta))/(2*c)
202 qreal maxCurrentSpeed = maxSpeedAtTurn + acceleration * t;
203 newSpeed = qMin(newSpeed, maxCurrentSpeed);
204 previousHeading = newHeading;
205 curveLength += m_lineStringInterpolated.at(j - 1).sphericalDistanceTo(m_lineStringInterpolated.at(j)) * m_marbleModel->planetRadius();
206 }
207 checkedDistance += m_lineStringInterpolated.at(i).sphericalDistanceTo(m_lineStringInterpolated.at(i + 1)) * m_marbleModel->planetRadius();
208 }
209 m_speed = newSpeed;
210
211 // Assume the car's moving at m_speed m/s. The distance moved will be speed*time which is equal to the speed of the car if time is equal to one.
212 // If the function isn't called once exactly after a second, multiplying by the time will compensate for the error and maintain the speed.
213 qreal fraction = m_speed * time / (m_currentPosition.sphericalDistanceTo(newPosition) * m_marbleModel->planetRadius());
214
215 // Interpolate and find the next point to move to if needed.
216 if (fraction > 0 && fraction < 1) {
217 GeoDataCoordinates newPoint = m_currentPosition.interpolate(newPosition, fraction);
218 newPosition = newPoint;
219 } else if (fraction > 1) {
220 bool isCurrentIndexValid = true;
221 while (fraction > 1) {
222 ++m_currentIndex;
223 if (m_currentIndex >= m_lineStringInterpolated.size()) {
224 isCurrentIndexValid = false;
225 break;
226 }
227
228 newPosition = m_lineStringInterpolated.at(m_currentIndex);
229 fraction = m_speed * time / (m_currentPosition.sphericalDistanceTo(newPosition) * m_marbleModel->planetRadius());
230 }
231
232 if (isCurrentIndexValid) {
233 GeoDataCoordinates newPoint = m_currentPosition.interpolate(newPosition, fraction);
234 newPosition = newPoint;
235 }
236 } else {
237 m_currentIndex++;
238 }
239
240 m_direction = m_currentPosition.bearing(newPosition, GeoDataCoordinates::Degree, GeoDataCoordinates::FinalBearing);
241 m_directionWithNoise = addNoise(m_direction);
242 }
243 m_currentPosition = newPosition;
244 m_currentPositionWithNoise = addNoise(m_currentPosition, accuracy());
245 m_currentDateTime = newDateTime;
246 Q_EMIT positionChanged(position(), accuracy());
247 } else {
248 // Repeat from start
249 m_currentIndex = 0;
250 m_lineStringInterpolated = m_lineString;
251 m_currentPosition = GeoDataCoordinates(); // Reset the current position so that the simulation starts from the correct starting point.
252 m_currentPositionWithNoise = GeoDataCoordinates();
253 m_speed = 0;
254 changeStatus(PositionProviderStatusUnavailable);
255 }
256}
257
258GeoDataCoordinates RouteSimulationPositionProviderPlugin::addNoise(const Marble::GeoDataCoordinates &position, const Marble::GeoDataAccuracy &accuracy) const
259{
260 qreal randomBearing = static_cast<qreal>(QRandomGenerator::global()->generate()) / (static_cast<qreal>(RAND_MAX / M_PI));
261 qreal randomDistance = static_cast<qreal>(QRandomGenerator::global()->generate())
262 / (static_cast<qreal>(RAND_MAX / (accuracy.horizontal / 2.0 / m_marbleModel->planetRadius())));
263 return position.moveByBearing(randomBearing, randomDistance);
264}
265
266qreal RouteSimulationPositionProviderPlugin::addNoise(qreal bearing)
267{
268 qreal const maxBearingError = 30.0;
269 return bearing + static_cast<qreal>(QRandomGenerator::global()->generate()) / (static_cast<qreal>(RAND_MAX / maxBearingError / 2.0))
270 - maxBearingError / 2.0;
271}
272
273void RouteSimulationPositionProviderPlugin::changeStatus(PositionProviderStatus status)
274{
275 if (m_status != status) {
276 m_status = status;
277 Q_EMIT statusChanged(m_status);
278 }
279}
280
281} // namespace Marble
282
283#include "moc_RouteSimulationPositionProviderPlugin.cpp"
This file contains the headers for MarbleModel.
A 3d point representation.
GeoDataCoordinates interpolate(const GeoDataCoordinates &target, double t) const
slerp (spherical linear) interpolation between this coordinate and the given target coordinate
A 3d point representation.
GeoDataCoordinates moveByBearing(qreal bearing, qreal distance) const
Returns the coordinates of the resulting point after moving this point according to the distance and ...
The data model (not based on QAbstractModel) for a MarbleWidget.
Definition MarbleModel.h:84
The abstract class that provides position information.
Q_SCRIPTABLE CaptureState status()
Binds a QML item to a specific geodetic location in screen coordinates.
QDateTime currentDateTime()
bool isValid() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
quint32 generate()
QRandomGenerator * global()
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:10 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.