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"