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)
97 : PositionProviderPlugin(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 moveByBearing(qreal bearing, qreal distance) const
Returns the coordinates of the resulting point after moving this point according to the distance and ...
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-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.