File indexing completed on 2024-04-28 03:50:30

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 // SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0005 //
0006 
0007 #include "GosmoreRoutingRunner.h"
0008 
0009 #include "MarbleDebug.h"
0010 #include "MarbleDirs.h"
0011 #include "routing/RouteRequest.h"
0012 #include "routing/instructions/WaypointParser.h"
0013 #include "routing/instructions/InstructionTransformation.h"
0014 #include "GeoDataDocument.h"
0015 #include "GeoDataExtendedData.h"
0016 #include "GeoDataData.h"
0017 #include "GeoDataPlacemark.h"
0018 #include "GeoDataLineString.h"
0019 
0020 #include <QProcess>
0021 #include <QMap>
0022 
0023 namespace Marble
0024 {
0025 
0026 class GosmoreRunnerPrivate
0027 {
0028 public:
0029     QFileInfo m_gosmoreMapFile;
0030 
0031     WaypointParser m_parser;
0032 
0033     /** Static to share the cache among all instances */
0034     static QMap<QString, QByteArray> m_partialRoutes;
0035 
0036     QByteArray retrieveWaypoints( const QString &query ) const;
0037 
0038     static GeoDataDocument* createDocument( GeoDataLineString* routeWaypoints, const QVector<GeoDataPlacemark*> instructions );
0039 
0040     static GeoDataLineString parseGosmoreOutput( const QByteArray &content );
0041 
0042     static void merge( GeoDataLineString* one, const GeoDataLineString& two );
0043 
0044     QVector<GeoDataPlacemark*> parseGosmoreInstructions( const QByteArray &content );
0045 
0046     GosmoreRunnerPrivate();
0047 };
0048 
0049 GosmoreRunnerPrivate::GosmoreRunnerPrivate()
0050 {
0051     m_parser.setLineSeparator("\r");
0052     m_parser.setFieldSeparator(QLatin1Char(','));
0053     m_parser.setFieldIndex( WaypointParser::RoadName, 4 );
0054     m_parser.addJunctionTypeMapping( "Jr", RoutingWaypoint::Roundabout );
0055 }
0056 
0057 QMap<QString, QByteArray> GosmoreRunnerPrivate::m_partialRoutes;
0058 
0059 void GosmoreRunnerPrivate::merge( GeoDataLineString* one, const GeoDataLineString& two )
0060 {
0061     Q_ASSERT( one );
0062 
0063     QVector<GeoDataCoordinates>::const_iterator iter = two.constBegin();
0064     for( ; iter != two.constEnd(); ++iter ) {
0065         /** @todo: It might be needed to cut off some points at the start or end */
0066         one->append( *iter );
0067     }
0068 }
0069 
0070 QByteArray GosmoreRunnerPrivate::retrieveWaypoints( const QString &query ) const
0071 {
0072     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0073     env.insert("QUERY_STRING", query);
0074     env.insert("LC_ALL", "C");
0075     QProcess gosmore;
0076     gosmore.setProcessEnvironment(env);
0077 
0078     gosmore.start("gosmore", QStringList() << m_gosmoreMapFile.absoluteFilePath() );
0079     if (!gosmore.waitForStarted(5000)) {
0080         mDebug() << "Couldn't start gosmore from the current PATH. Install it to retrieve routing results from gosmore.";
0081         return QByteArray();
0082     }
0083 
0084     if ( gosmore.waitForFinished(15000) ) {
0085         return gosmore.readAllStandardOutput();
0086     }
0087     else {
0088         mDebug() << "Couldn't stop gosmore";
0089     }
0090 
0091     return QByteArray();
0092 }
0093 
0094 GeoDataLineString GosmoreRunnerPrivate::parseGosmoreOutput( const QByteArray &content )
0095 {
0096     GeoDataLineString routeWaypoints;
0097 
0098     QStringList lines = QString::fromLocal8Bit( content ).split(QLatin1Char('\r'));
0099     for( const QString &line: lines ) {
0100         const QStringList fields = line.split(QLatin1Char(','));
0101         if (fields.size() >= 5) {
0102             qreal lon = fields.at(1).toDouble();
0103             qreal lat = fields.at(0).toDouble();
0104             GeoDataCoordinates coordinates( lon, lat, 0.0, GeoDataCoordinates::Degree );
0105             routeWaypoints.append( coordinates );
0106         }
0107     }
0108 
0109     return routeWaypoints;
0110 }
0111 
0112 QVector<GeoDataPlacemark*> GosmoreRunnerPrivate::parseGosmoreInstructions( const QByteArray &content )
0113 {
0114     // Determine gosmore version
0115     QStringList lines = QString::fromUtf8(content).split(QLatin1Char('\r'));
0116     if ( lines.size() > 2 ) {
0117         const QStringList fields = lines.at(lines.size()-2).split(QLatin1Char(','));
0118         m_parser.setFieldIndex( WaypointParser::RoadName, fields.size()-1 );
0119         if ( fields.size() < 5 || fields.size() > 6 ) {
0120             // Can happen when gosmore changes the output format, returns garbage
0121             // or the last street name contains a comma. We may still parse it correctly, just try.
0122             mDebug() << "Unexpected number of fields. This gosmore version may be unsupported.";
0123         }
0124     }
0125 
0126     QVector<GeoDataPlacemark*> result;
0127     QTextStream stream( content );
0128     stream.setCodec("UTF8");
0129     stream.setAutoDetectUnicode( true );
0130 
0131     RoutingInstructions directions = InstructionTransformation::process( m_parser.parse( stream ) );
0132     for( int i=0; i<directions.size(); ++i ) {
0133         GeoDataPlacemark* placemark = new GeoDataPlacemark( directions[i].instructionText() );
0134         GeoDataExtendedData extendedData;
0135         GeoDataData turnType;
0136         turnType.setName(QStringLiteral("turnType"));
0137         turnType.setValue( QVariant::fromValue( int( directions[i].turnType() ) ) );
0138         extendedData.addValue( turnType );
0139         GeoDataData roadName;
0140         roadName.setName(QStringLiteral("roadName"));
0141         roadName.setValue( directions[i].roadName() );
0142         extendedData.addValue( roadName );
0143         placemark->setExtendedData( extendedData );
0144         Q_ASSERT( !directions[i].points().isEmpty() );
0145         GeoDataLineString* geometry = new GeoDataLineString;
0146         QVector<RoutingWaypoint> items = directions[i].points();
0147         for (int j=0; j<items.size(); ++j ) {
0148             RoutingPoint point = items[j].point();
0149             GeoDataCoordinates coordinates( point.lon(), point.lat(), 0.0, GeoDataCoordinates::Degree );
0150             geometry->append( coordinates );
0151         }
0152         placemark->setGeometry( geometry );
0153         result.push_back( placemark );
0154     }
0155 
0156     return result;
0157 }
0158 
0159 GeoDataDocument* GosmoreRunnerPrivate::createDocument( GeoDataLineString* routeWaypoints, const QVector<GeoDataPlacemark*> instructions )
0160 {
0161     if ( !routeWaypoints || routeWaypoints->isEmpty() ) {
0162         return nullptr;
0163     }
0164 
0165     GeoDataDocument* result = new GeoDataDocument();
0166     GeoDataPlacemark* routePlacemark = new GeoDataPlacemark;
0167     routePlacemark->setName(QStringLiteral("Route"));
0168     routePlacemark->setGeometry( routeWaypoints );
0169     result->append( routePlacemark );
0170 
0171     QString name = QStringLiteral("%1 %2 (Gosmore)");
0172     QString unit = QLatin1String( "m" );
0173     qreal length = routeWaypoints->length( EARTH_RADIUS );
0174     if (length >= 1000) {
0175         length /= 1000.0;
0176         unit = "km";
0177     }
0178     result->setName( name.arg( length, 0, 'f', 1 ).arg( unit ) );
0179 
0180     for( GeoDataPlacemark* placemark: instructions )
0181     {
0182         result->append( placemark );
0183     }
0184 
0185     return result;
0186 }
0187 
0188 GosmoreRunner::GosmoreRunner( QObject *parent ) :
0189         RoutingRunner( parent ),
0190         d( new GosmoreRunnerPrivate )
0191 {
0192     // Check installation
0193     QDir mapDir(MarbleDirs::localPath() + QLatin1String("/maps/earth/gosmore/"));
0194     d->m_gosmoreMapFile = QFileInfo ( mapDir, "gosmore.pak" );
0195 }
0196 
0197 GosmoreRunner::~GosmoreRunner()
0198 {
0199     delete d;
0200 }
0201 
0202 void GosmoreRunner::retrieveRoute( const RouteRequest *route )
0203 {
0204     if ( !d->m_gosmoreMapFile.exists() )
0205     {
0206         emit routeCalculated( nullptr );
0207         return;
0208     }
0209 
0210     GeoDataLineString* wayPoints = new GeoDataLineString;
0211     QByteArray completeOutput;
0212 
0213     for( int i=0; i<route->size()-1; ++i )
0214     {
0215         QString queryString = "flat=%1&flon=%2&tlat=%3&tlon=%4&fastest=1&v=motorcar";
0216         GeoDataCoordinates source = route->at(i);
0217         double fLon = source.longitude( GeoDataCoordinates::Degree );
0218         double fLat = source.latitude( GeoDataCoordinates::Degree );
0219         queryString = queryString.arg(fLat, 0, 'f', 8).arg(fLon, 0, 'f', 8);
0220         GeoDataCoordinates destination = route->at(i+1);
0221         double tLon = destination.longitude( GeoDataCoordinates::Degree );
0222         double tLat = destination.latitude( GeoDataCoordinates::Degree );
0223         queryString = queryString.arg(tLat, 0, 'f', 8).arg(tLon, 0, 'f', 8);
0224 
0225         QByteArray output;
0226         if ( d->m_partialRoutes.contains( queryString ) ) {
0227             output = d->m_partialRoutes[queryString];
0228         }
0229         else {
0230             output = d->retrieveWaypoints( queryString );
0231         }
0232 
0233         GeoDataLineString points = d->parseGosmoreOutput( output );
0234         d->merge( wayPoints, points );
0235         completeOutput.append( output );
0236     }
0237 
0238     QVector<GeoDataPlacemark*> instructions = d->parseGosmoreInstructions( completeOutput );
0239 
0240     GeoDataDocument* result = d->createDocument( wayPoints, instructions );
0241     emit routeCalculated( result );
0242 }
0243 
0244 } // namespace Marble