File indexing completed on 2024-04-28 03:50:38
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "TravelingSalesmanRunner.h" 0007 0008 #include "MarbleDebug.h" 0009 #include "MarbleDirs.h" 0010 #include "GeoDataDocument.h" 0011 #include "GeoDataParser.h" 0012 0013 #include <QProcess> 0014 #include <QMap> 0015 #include <QTemporaryFile> 0016 0017 namespace Marble 0018 { 0019 0020 class TravelingSalesmanRunnerPrivate 0021 { 0022 public: 0023 QFileInfo m_travelingsalesmanJar; 0024 0025 GeoDataLineString retrieveWaypoints( const QPair<QString, QString> &query ) const; 0026 0027 GeoDataDocument* createDocument( GeoDataLineString* routeWaypoints ) const; 0028 0029 GeoDataLineString parseTravelingSalesmanOutput( QFile &file ) const; 0030 0031 void merge( GeoDataLineString* one, const GeoDataLineString& two ) const; 0032 0033 /** Static to share the cache among all instances */ 0034 static QMap<QString, GeoDataLineString> m_partialRoutes; 0035 }; 0036 0037 QMap<QString, GeoDataLineString> TravelingSalesmanRunnerPrivate::m_partialRoutes; 0038 0039 void TravelingSalesmanRunnerPrivate::merge( GeoDataLineString* one, const GeoDataLineString& two ) const 0040 { 0041 Q_ASSERT( one ); 0042 0043 QVector<GeoDataCoordinates>::const_iterator iter = two.constBegin(); 0044 for ( ; iter != two.constEnd(); ++iter ) { 0045 /** @todo: It might be needed to cut off some points at the start or end */ 0046 one->append( *iter ); 0047 } 0048 } 0049 0050 GeoDataLineString TravelingSalesmanRunnerPrivate::retrieveWaypoints( const QPair<QString, QString> &query ) const 0051 { 0052 QString cacheItem = query.first + query.second; 0053 if ( m_partialRoutes.contains( cacheItem ) ) { 0054 return m_partialRoutes[cacheItem]; 0055 } 0056 0057 QTemporaryFile gpxFile; 0058 if ( !gpxFile.open() ) { 0059 mDebug() << "Unable to create a temporary work file"; 0060 return GeoDataLineString(); 0061 } 0062 0063 QProcess travelingsalesman; 0064 QStringList arguments = QStringList() << "-jar" << m_travelingsalesmanJar.absoluteFilePath(); 0065 arguments << "route" << "-gpx" << gpxFile.fileName(); 0066 arguments << query.first << query.second; 0067 travelingsalesman.start( "java", arguments ); 0068 if ( !travelingsalesman.waitForStarted( 5000 ) ) { 0069 mDebug() << "Couldn't start travelingsalesman from the current PATH. Is java setup correctly?"; 0070 return GeoDataLineString(); 0071 } 0072 0073 if ( travelingsalesman.waitForFinished( 60 * 1000 ) ) { 0074 m_partialRoutes[cacheItem] = parseTravelingSalesmanOutput( gpxFile ); 0075 return m_partialRoutes[cacheItem]; 0076 } else { 0077 mDebug() << "Couldn't stop travelingsalesman"; 0078 } 0079 0080 return GeoDataLineString(); 0081 } 0082 0083 GeoDataLineString TravelingSalesmanRunnerPrivate::parseTravelingSalesmanOutput( QFile &file ) const 0084 { 0085 GeoDataParser parser( GeoData_GPX ); 0086 if ( !parser.read( &file ) ) { 0087 mDebug() << "Could not parse gpx file " << file.fileName(); 0088 return GeoDataLineString(); 0089 } 0090 0091 GeoDataLineString result; 0092 GeoDocument* document = parser.releaseDocument(); 0093 GeoDataDocument* route = dynamic_cast<GeoDataDocument*>( document ); 0094 0095 if ( route ) { 0096 if ( route->placemarkList().size() == 1 ) { 0097 GeoDataPlacemark* placemark = route->placemarkList().first(); 0098 GeoDataMultiGeometry* multi = dynamic_cast<GeoDataMultiGeometry*>( placemark->geometry() ); 0099 if ( multi && multi->size() == 1 ) { 0100 GeoDataLineString* lineString = dynamic_cast<GeoDataLineString*>( &multi->first() ); 0101 if ( lineString ) { 0102 return *lineString; 0103 } 0104 } 0105 } 0106 } 0107 delete document; 0108 return result; 0109 } 0110 0111 GeoDataDocument* TravelingSalesmanRunnerPrivate::createDocument( GeoDataLineString* routeWaypoints ) const 0112 { 0113 if ( !routeWaypoints || routeWaypoints->isEmpty() ) { 0114 return 0; 0115 } 0116 0117 GeoDataDocument* result = new GeoDataDocument(); 0118 GeoDataPlacemark* routePlacemark = new GeoDataPlacemark; 0119 routePlacemark->setName(QStringLiteral("Route")); 0120 routePlacemark->setGeometry( routeWaypoints ); 0121 result->append( routePlacemark ); 0122 0123 QString name = "%1 %2 (Traveling Salesman)"; 0124 QString unit = QStringLiteral("m"); 0125 qreal length = routeWaypoints->length( EARTH_RADIUS ); 0126 if ( length >= 1000 ) { 0127 length /= 1000.0; 0128 unit = "km"; 0129 } 0130 result->setName( name.arg( length, 0, 'f', 1 ).arg( unit ) ); 0131 return result; 0132 } 0133 0134 TravelingSalesmanRunner::TravelingSalesmanRunner( QObject *parent ) : 0135 MarbleAbstractRunner( parent ), 0136 d( new TravelingSalesmanRunnerPrivate ) 0137 { 0138 // Check installation 0139 QDir mapDir(MarbleDirs::localPath() + QLatin1String("/maps/earth/traveling-salesman/")); 0140 d->m_travelingsalesmanJar = QFileInfo ( mapDir, "traveling-salesman.jar" ); 0141 } 0142 0143 TravelingSalesmanRunner::~TravelingSalesmanRunner() 0144 { 0145 delete d; 0146 } 0147 0148 GeoDataFeature::GeoDataVisualCategory TravelingSalesmanRunner::category() const 0149 { 0150 return GeoDataFeature::OsmSite; 0151 } 0152 0153 void TravelingSalesmanRunner::retrieveRoute( RouteRequest *route ) 0154 { 0155 if ( !d->m_travelingsalesmanJar.exists() ) { 0156 emit routeCalculated( 0 ); 0157 return; 0158 } 0159 0160 GeoDataLineString* wayPoints = new GeoDataLineString; 0161 0162 for ( int i = 0; i < route->size() - 1; ++i ) { 0163 QPair<QString, QString> queryString; 0164 GeoDataCoordinates source = route->at( i ); 0165 double fLon = source.longitude( GeoDataCoordinates::Degree ); 0166 double fLat = source.latitude( GeoDataCoordinates::Degree ); 0167 queryString.first = QString( "[%1,%2]" ).arg( fLat, 0, 'f', 8 ).arg( fLon, 0, 'f', 8 ); 0168 GeoDataCoordinates destination = route->at( i + 1 ); 0169 double tLon = destination.longitude( GeoDataCoordinates::Degree ); 0170 double tLat = destination.latitude( GeoDataCoordinates::Degree ); 0171 queryString.second = QString( "[%1,%2]" ).arg( tLat, 0, 'f', 8 ).arg( tLon, 0, 'f', 8 ); 0172 0173 d->merge( wayPoints, d->retrieveWaypoints( queryString ) ); 0174 } 0175 0176 GeoDataDocument* result = d->createDocument( wayPoints ); 0177 emit routeCalculated( result ); 0178 } 0179 0180 } // namespace Marble