File indexing completed on 2025-03-23 09:47:40
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