Marble

RoutingModel.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <[email protected]>
4 //
5 
6 #include "RoutingModel.h"
7 
8 #include "Planet.h"
9 #include "PlanetFactory.h"
10 #include "Route.h"
11 #include "RouteRequest.h"
12 #include "PositionTracking.h"
13 #include "MarbleGlobal.h"
14 #include "GeoDataAccuracy.h"
15 
16 #include <QPixmap>
17 
18 namespace Marble
19 {
20 
21 class RoutingModelPrivate
22 {
23 public:
24  enum RouteDeviation
25  {
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 
43 RoutingModelPrivate::RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request) :
44  m_positionTracking(positionTracking),
45  m_request(request),
46  m_deviation(Unknown)
47 {
48  // nothing to do
49 }
50 
51 void 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 
64 RoutingModel::RoutingModel(RouteRequest *request, PositionTracking *positionTracking, QObject *parent) :
65  QAbstractListModel(parent),
66  d(new RoutingModelPrivate(positionTracking, request))
67 {
68  QObject::connect( d->m_positionTracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)),
69  this, SLOT(updatePosition(GeoDataCoordinates,qreal)) );
70 
72  roles.insert( Qt::DisplayRole, "display" );
73  roles.insert( RoutingModel::TurnTypeIconRole, "turnTypeIcon" );
74  roles.insert( RoutingModel::LongitudeRole, "longitude" );
75  roles.insert( RoutingModel::LatitudeRole, "latitude" );
76  d->m_roleNames = roles;
77 }
78 
79 RoutingModel::~RoutingModel()
80 {
81  delete d;
82 }
83 
84 int RoutingModel::rowCount ( const QModelIndex &parent ) const
85 {
86  return parent.isValid() ? 0 : d->m_route.turnPoints().size();
87 }
88 
89 QVariant RoutingModel::headerData ( int section, Qt::Orientation orientation, int role ) const
90 {
91  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0 ) {
92  return QString( "Instruction" );
93  }
94 
95  return QAbstractListModel::headerData( section, orientation, role );
96 }
97 
98 QVariant RoutingModel::data ( const QModelIndex & index, int role ) const
99 {
100  if ( !index.isValid() ) {
101  return QVariant();
102  }
103 
104  if ( index.row() < d->m_route.turnPoints().size() && index.column() == 0 ) {
105  const RouteSegment &segment = d->m_route.at( index.row() );
106  switch ( role ) {
107  case Qt::DisplayRole:
108  case Qt::ToolTipRole:
109  return segment.maneuver().instructionText();
110  case Qt::DecorationRole:
111  {
112  bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
113  if ( segment.maneuver().hasWaypoint() ) {
114  int const size = smallScreen ? 64 : 32;
115  return d->m_request->pixmap( segment.maneuver().waypointIndex(), size, size/4 );
116  }
117 
118  QPixmap const pixmap = segment.maneuver().directionPixmap();
119  return smallScreen ? pixmap : pixmap.scaled( 32, 32 );
120  }
121  case Qt::SizeHintRole:
122  {
123  bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
124  int const size = smallScreen ? 64 : 32;
125  return QSize( size, size );
126  }
127  case RoutingModel::CoordinateRole:
128  return QVariant::fromValue( segment.maneuver().position() );
129  case RoutingModel::LongitudeRole:
130  return QVariant(segment.maneuver().position().longitude(GeoDataCoordinates::Degree));
131  case RoutingModel::LatitudeRole:
132  return QVariant(segment.maneuver().position().latitude(GeoDataCoordinates::Degree));
133  case RoutingModel::TurnTypeIconRole:
134  return segment.maneuver().directionPixmap();
135  default:
136  return QVariant();
137  }
138  }
139 
140  return QVariant();
141 }
142 
143 QHash<int, QByteArray> RoutingModel::roleNames() const
144 {
145  return d->m_roleNames;
146 }
147 
148 void RoutingModel::setRoute( const Route &route )
149 {
150  d->m_route = route;
151  d->m_deviation = RoutingModelPrivate::Unknown;
152 
153  beginResetModel();
154  endResetModel();
155  emit currentRouteChanged();
156 }
157 
158 void RoutingModel::exportGpx( QIODevice *device ) const
159 {
160  QString content = QLatin1String("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
161  "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"Marble\" version=\"1.1\" "
162  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
163  "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 "
164  "http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
165  "<metadata>\n <link href=\"http://edu.kde.org/marble\">\n "
166  "<text>Marble Virtual Globe</text>\n </link>\n</metadata>\n"
167  " <rte>\n <name>Route</name>\n");
168  bool hasAltitude = false;
169  for ( int i=0; !hasAltitude && i<d->m_route.size(); ++i ) {
170  hasAltitude = d->m_route.at( i ).maneuver().position().altitude() != 0.0;
171  }
172  for ( int i=0; i<d->m_route.size(); ++i ) {
173  const Maneuver &maneuver = d->m_route.at( i ).maneuver();
174  qreal lon = maneuver.position().longitude( GeoDataCoordinates::Degree );
175  qreal lat = maneuver.position().latitude( GeoDataCoordinates::Degree );
176  QString const text = maneuver.instructionText();
177  content += QString( " <rtept lat=\"%1\" lon=\"%2\">\n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 );
178  content += QString( " <name>%1</name>\n").arg( text );
179  if ( hasAltitude ) {
180  content += QString( " <ele>%1</ele>\n" ).arg( maneuver.position().altitude(), 0, 'f', 2 );
181  }
182  content += QString( " </rtept>\n" );
183  }
184  content += QLatin1String(" </rte>\n"
185  "<trk>\n <name>Route</name>\n <trkseg>\n");
186  GeoDataLineString points = d->m_route.path();
187  hasAltitude = false;
188  for ( int i=0; !hasAltitude && i<points.size(); ++i ) {
189  hasAltitude = points[i].altitude() != 0.0;
190  }
191  for ( int i=0; i<points.size(); ++i ) {
192  GeoDataCoordinates const &point = points[i];
193  qreal lon = point.longitude( GeoDataCoordinates::Degree );
194  qreal lat = point.latitude( GeoDataCoordinates::Degree );
195  content += QString( " <trkpt lat=\"%1\" lon=\"%2\">\n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 );
196  if ( hasAltitude ) {
197  content += QString( " <ele>%1</ele>\n" ).arg( point.altitude(), 0, 'f', 2 );
198  }
199  content += QString( " </trkpt>\n" );
200  }
201  content += QLatin1String(" </trkseg>\n </trk>\n"
202  "</gpx>\n");
203 
204  device->write( content.toUtf8() );
205 }
206 
207 void RoutingModel::clear()
208 {
209  d->m_route = Route();
210  beginResetModel();
211  endResetModel();
212  emit currentRouteChanged();
213 }
214 
215 int 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 
271 void 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("earth").radius();
277  const qreal distance = planetRadius * location.sphericalDistanceTo(d->m_route.positionOnRoute());
278  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  emit deviatedFromRoute( deviated == RoutingModelPrivate::OffRoute );
290  }
291 }
292 
293 bool RoutingModel::deviatedFromRoute() const
294 {
295  return d->m_deviation == RoutingModelPrivate::OffRoute;
296 }
297 
298 const Route & RoutingModel::route() const
299 {
300  return d->m_route;
301 }
302 
303 } // namespace Marble
304 
305 #include "moc_RoutingModel.cpp"
QMap::const_iterator constBegin() const const
DisplayRole
QVariant location(const QVariant &res)
int size() const const
QVariant fromValue(const T &value)
int column() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QHash::iterator insert(const Key &key, const T &value)
static Planet construct(const QString &id)
Creates the planet with the given ID, or one with default values if ID is not among planetList()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
QMap::const_iterator constEnd() const const
Orientation
QByteArray toUtf8() const const
Binds a QML item to a specific geodetic location in screen coordinates.
bool isValid() const const
int row() const const
QPixmap scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
qint64 write(const char *data, qint64 maxSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Oct 2 2023 03:52:09 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.