File indexing completed on 2024-07-14 10:58:48

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