File indexing completed on 2024-04-28 03:49:29
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "RoutingModel.h" 0007 0008 #include "Planet.h" 0009 #include "PlanetFactory.h" 0010 #include "Route.h" 0011 #include "RouteRequest.h" 0012 #include "PositionTracking.h" 0013 #include "MarbleGlobal.h" 0014 #include "GeoDataAccuracy.h" 0015 0016 #include <QPixmap> 0017 0018 namespace Marble 0019 { 0020 0021 class RoutingModelPrivate 0022 { 0023 public: 0024 enum RouteDeviation 0025 { 0026 Unknown, 0027 OnRoute, 0028 OffRoute 0029 }; 0030 0031 explicit RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request); 0032 0033 Route m_route; 0034 0035 PositionTracking *const m_positionTracking; 0036 RouteRequest* const m_request; 0037 QHash<int, QByteArray> m_roleNames; 0038 RouteDeviation m_deviation; 0039 0040 void updateViaPoints( const GeoDataCoordinates &position ); 0041 }; 0042 0043 RoutingModelPrivate::RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request) : 0044 m_positionTracking(positionTracking), 0045 m_request(request), 0046 m_deviation(Unknown) 0047 { 0048 // nothing to do 0049 } 0050 0051 void RoutingModelPrivate::updateViaPoints( const GeoDataCoordinates &position ) 0052 { 0053 // Mark via points visited after approaching them in a range of 500m or less 0054 qreal const threshold = 500 / EARTH_RADIUS; 0055 for( int i=0; i<m_request->size(); ++i ) { 0056 if ( !m_request->visited( i ) ) { 0057 if (position.sphericalDistanceTo(m_request->at(i)) < threshold) { 0058 m_request->setVisited( i, true ); 0059 } 0060 } 0061 } 0062 } 0063 0064 RoutingModel::RoutingModel(RouteRequest *request, PositionTracking *positionTracking, QObject *parent) : 0065 QAbstractListModel(parent), 0066 d(new RoutingModelPrivate(positionTracking, request)) 0067 { 0068 QObject::connect( d->m_positionTracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), 0069 this, SLOT(updatePosition(GeoDataCoordinates,qreal)) ); 0070 0071 QHash<int, QByteArray> roles; 0072 roles.insert( Qt::DisplayRole, "display" ); 0073 roles.insert( RoutingModel::TurnTypeIconRole, "turnTypeIcon" ); 0074 roles.insert( RoutingModel::LongitudeRole, "longitude" ); 0075 roles.insert( RoutingModel::LatitudeRole, "latitude" ); 0076 d->m_roleNames = roles; 0077 } 0078 0079 RoutingModel::~RoutingModel() 0080 { 0081 delete d; 0082 } 0083 0084 int RoutingModel::rowCount ( const QModelIndex &parent ) const 0085 { 0086 return parent.isValid() ? 0 : d->m_route.turnPoints().size(); 0087 } 0088 0089 QVariant RoutingModel::headerData ( int section, Qt::Orientation orientation, int role ) const 0090 { 0091 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0 ) { 0092 return QString( "Instruction" ); 0093 } 0094 0095 return QAbstractListModel::headerData( section, orientation, role ); 0096 } 0097 0098 QVariant RoutingModel::data ( const QModelIndex & index, int role ) const 0099 { 0100 if ( !index.isValid() ) { 0101 return QVariant(); 0102 } 0103 0104 if ( index.row() < d->m_route.turnPoints().size() && index.column() == 0 ) { 0105 const RouteSegment &segment = d->m_route.at( index.row() ); 0106 switch ( role ) { 0107 case Qt::DisplayRole: 0108 case Qt::ToolTipRole: 0109 return segment.maneuver().instructionText(); 0110 case Qt::DecorationRole: 0111 { 0112 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; 0113 if ( segment.maneuver().hasWaypoint() ) { 0114 int const size = smallScreen ? 64 : 32; 0115 return d->m_request->pixmap( segment.maneuver().waypointIndex(), size, size/4 ); 0116 } 0117 0118 QPixmap const pixmap = segment.maneuver().directionPixmap(); 0119 return smallScreen ? pixmap : pixmap.scaled( 32, 32 ); 0120 } 0121 case Qt::SizeHintRole: 0122 { 0123 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; 0124 int const size = smallScreen ? 64 : 32; 0125 return QSize( size, size ); 0126 } 0127 case RoutingModel::CoordinateRole: 0128 return QVariant::fromValue( segment.maneuver().position() ); 0129 case RoutingModel::LongitudeRole: 0130 return QVariant(segment.maneuver().position().longitude(GeoDataCoordinates::Degree)); 0131 case RoutingModel::LatitudeRole: 0132 return QVariant(segment.maneuver().position().latitude(GeoDataCoordinates::Degree)); 0133 case RoutingModel::TurnTypeIconRole: 0134 return segment.maneuver().directionPixmap(); 0135 default: 0136 return QVariant(); 0137 } 0138 } 0139 0140 return QVariant(); 0141 } 0142 0143 QHash<int, QByteArray> RoutingModel::roleNames() const 0144 { 0145 return d->m_roleNames; 0146 } 0147 0148 void RoutingModel::setRoute( const Route &route ) 0149 { 0150 d->m_route = route; 0151 d->m_deviation = RoutingModelPrivate::Unknown; 0152 0153 beginResetModel(); 0154 endResetModel(); 0155 emit currentRouteChanged(); 0156 } 0157 0158 void RoutingModel::exportGpx( QIODevice *device ) const 0159 { 0160 QString content = QLatin1String("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" 0161 "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"Marble\" version=\"1.1\" " 0162 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 0163 "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 " 0164 "http://www.topografix.com/GPX/1/1/gpx.xsd\">\n" 0165 "<metadata>\n <link href=\"http://edu.kde.org/marble\">\n " 0166 "<text>Marble Virtual Globe</text>\n </link>\n</metadata>\n" 0167 " <rte>\n <name>Route</name>\n"); 0168 bool hasAltitude = false; 0169 for ( int i=0; !hasAltitude && i<d->m_route.size(); ++i ) { 0170 hasAltitude = d->m_route.at( i ).maneuver().position().altitude() != 0.0; 0171 } 0172 for ( int i=0; i<d->m_route.size(); ++i ) { 0173 const Maneuver &maneuver = d->m_route.at( i ).maneuver(); 0174 qreal lon = maneuver.position().longitude( GeoDataCoordinates::Degree ); 0175 qreal lat = maneuver.position().latitude( GeoDataCoordinates::Degree ); 0176 QString const text = maneuver.instructionText(); 0177 content += QString( " <rtept lat=\"%1\" lon=\"%2\">\n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 ); 0178 content += QString( " <name>%1</name>\n").arg( text ); 0179 if ( hasAltitude ) { 0180 content += QString( " <ele>%1</ele>\n" ).arg( maneuver.position().altitude(), 0, 'f', 2 ); 0181 } 0182 content += QString( " </rtept>\n" ); 0183 } 0184 content += QLatin1String(" </rte>\n" 0185 "<trk>\n <name>Route</name>\n <trkseg>\n"); 0186 GeoDataLineString points = d->m_route.path(); 0187 hasAltitude = false; 0188 for ( int i=0; !hasAltitude && i<points.size(); ++i ) { 0189 hasAltitude = points[i].altitude() != 0.0; 0190 } 0191 for ( int i=0; i<points.size(); ++i ) { 0192 GeoDataCoordinates const &point = points[i]; 0193 qreal lon = point.longitude( GeoDataCoordinates::Degree ); 0194 qreal lat = point.latitude( GeoDataCoordinates::Degree ); 0195 content += QString( " <trkpt lat=\"%1\" lon=\"%2\">\n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 ); 0196 if ( hasAltitude ) { 0197 content += QString( " <ele>%1</ele>\n" ).arg( point.altitude(), 0, 'f', 2 ); 0198 } 0199 content += QString( " </trkpt>\n" ); 0200 } 0201 content += QLatin1String(" </trkseg>\n </trk>\n" 0202 "</gpx>\n"); 0203 0204 device->write( content.toUtf8() ); 0205 } 0206 0207 void RoutingModel::clear() 0208 { 0209 d->m_route = Route(); 0210 beginResetModel(); 0211 endResetModel(); 0212 emit currentRouteChanged(); 0213 } 0214 0215 int RoutingModel::rightNeighbor( const GeoDataCoordinates &position, RouteRequest const *const route ) const 0216 { 0217 Q_ASSERT( route && "Must not pass a null route "); 0218 0219 // Quick result for trivial cases 0220 if ( route->size() < 3 ) { 0221 return route->size() - 1; 0222 } 0223 0224 // Generate an ordered list of all waypoints 0225 GeoDataLineString points = d->m_route.path(); 0226 QMap<int,int> mapping; 0227 0228 // Force first mapping point to match the route start 0229 mapping[0] = 0; 0230 0231 // Calculate the mapping between waypoints and via points 0232 // Need two for loops to avoid getting stuck in local minima 0233 for ( int j=1; j<route->size()-1; ++j ) { 0234 qreal minDistance = -1.0; 0235 for ( int i=mapping[j-1]; i<points.size(); ++i ) { 0236 const qreal distance = points[i].sphericalDistanceTo(route->at(j)); 0237 if (minDistance < 0.0 || distance < minDistance ) { 0238 mapping[j] = i; 0239 minDistance = distance; 0240 } 0241 } 0242 } 0243 0244 // Determine waypoint with minimum distance to the provided position 0245 qreal minWaypointDistance = -1.0; 0246 int waypoint=0; 0247 for ( int i=0; i<points.size(); ++i ) { 0248 const qreal waypointDistance = points[i].sphericalDistanceTo(position); 0249 if ( minWaypointDistance < 0.0 || waypointDistance < minWaypointDistance ) { 0250 minWaypointDistance = waypointDistance; 0251 waypoint = i; 0252 } 0253 } 0254 0255 // Force last mapping point to match the route destination 0256 mapping[route->size()-1] = points.size()-1; 0257 0258 // Determine neighbor based on the mapping 0259 QMap<int, int>::const_iterator iter = mapping.constBegin(); 0260 for ( ; iter != mapping.constEnd(); ++iter ) { 0261 if ( iter.value() > waypoint ) { 0262 int index = iter.key(); 0263 Q_ASSERT( index >= 0 && index <= route->size() ); 0264 return index; 0265 } 0266 } 0267 0268 return route->size()-1; 0269 } 0270 0271 void RoutingModel::updatePosition( const GeoDataCoordinates& location, qreal speed ) 0272 { 0273 d->m_route.setPosition( location ); 0274 0275 d->updateViaPoints( location ); 0276 const qreal planetRadius = PlanetFactory::construct("earth").radius(); 0277 const qreal distance = planetRadius * location.sphericalDistanceTo(d->m_route.positionOnRoute()); 0278 emit positionChanged(); 0279 0280 qreal deviation = 0.0; 0281 if ( d->m_positionTracking && d->m_positionTracking->accuracy().vertical > 0.0 ) { 0282 deviation = qMax<qreal>( d->m_positionTracking->accuracy().vertical, d->m_positionTracking->accuracy().horizontal ); 0283 } 0284 qreal const threshold = deviation + qBound(10.0, speed*10.0, 150.0); 0285 0286 RoutingModelPrivate::RouteDeviation const deviated = distance < threshold ? RoutingModelPrivate::OnRoute : RoutingModelPrivate::OffRoute; 0287 if ( d->m_deviation != deviated ) { 0288 d->m_deviation = deviated; 0289 emit deviatedFromRoute( deviated == RoutingModelPrivate::OffRoute ); 0290 } 0291 } 0292 0293 bool RoutingModel::deviatedFromRoute() const 0294 { 0295 return d->m_deviation == RoutingModelPrivate::OffRoute; 0296 } 0297 0298 const Route & RoutingModel::route() const 0299 { 0300 return d->m_route; 0301 } 0302 0303 } // namespace Marble 0304 0305 #include "moc_RoutingModel.cpp"