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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Siddharth Srivastava <akssps011@gmail.com>
0004 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0005 //
0006 
0007 #include "RoutingPlugin.h"
0008 
0009 #include "ui_RoutingPlugin.h"
0010 #include "ui_RoutingConfigDialog.h"
0011 
0012 #include "Planet.h"
0013 #include "AudioOutput.h"
0014 #include "GeoDataCoordinates.h"
0015 #include "GeoDataLookAt.h"
0016 #include "GeoPainter.h"
0017 #include "MarbleGraphicsGridLayout.h"
0018 #include "MarbleModel.h"
0019 #include "MarbleWidget.h"
0020 #include "MarbleLocale.h"
0021 #include "MarbleDirs.h"
0022 #include "PluginManager.h"
0023 #include "PositionTracking.h"
0024 #include "PositionProviderPlugin.h"
0025 #include "routing/Route.h"
0026 #include "routing/RoutingManager.h"
0027 #include "routing/RoutingModel.h"
0028 #include "routing/RouteRequest.h"
0029 #include "routing/SpeakersModel.h"
0030 #include "ViewportParams.h"
0031 #include "WidgetGraphicsItem.h"
0032 
0033 #include <QDialog>
0034 #include <QPushButton>
0035 
0036 namespace Marble
0037 {
0038 
0039 namespace
0040 {
0041 int const thresholdDistance = 1000; // in meter
0042 }
0043 
0044 class RoutingPluginPrivate
0045 {
0046 public:
0047     MarbleWidget* m_marbleWidget;
0048     WidgetGraphicsItem* m_widgetItem;
0049     RoutingModel* m_routingModel;
0050     Ui::RoutingPlugin m_widget;
0051     bool m_nearNextInstruction;
0052     bool m_guidanceModeEnabled;
0053     AudioOutput* m_audio;
0054     QDialog *m_configDialog;
0055     Ui::RoutingConfigDialog m_configUi;
0056     bool m_routeCompleted;
0057     SpeakersModel* m_speakersModel;
0058 
0059     RoutingPluginPrivate( RoutingPlugin* parent );
0060 
0061     void updateZoomButtons( int zoomValue );
0062 
0063     void updateZoomButtons();
0064 
0065     void updateGuidanceModeButton();
0066 
0067     void forceRepaint();
0068 
0069     void updateButtonVisibility();
0070 
0071     void reverseRoute();
0072 
0073     void toggleGuidanceMode( bool enabled );
0074 
0075     void updateDestinationInformation();
0076 
0077     void updateGpsButton( PositionProviderPlugin *activePlugin );
0078 
0079     void togglePositionTracking( bool enabled );
0080 
0081     static QString richText( const QString &source );
0082 
0083     static QString fuzzyDistance( qreal distanceMeter );
0084 
0085     void readSettings();
0086 
0087     qreal nextInstructionDistance() const;
0088 
0089     qreal remainingDistance() const;
0090 
0091 private:
0092     RoutingPlugin* m_parent;
0093 };
0094 
0095 RoutingPluginPrivate::RoutingPluginPrivate( RoutingPlugin *parent ) :
0096     m_marbleWidget( nullptr ),
0097     m_widgetItem( nullptr ),
0098     m_routingModel( nullptr ),
0099     m_nearNextInstruction( false ),
0100     m_guidanceModeEnabled( false ),
0101     m_audio( new AudioOutput( parent ) ),
0102     m_configDialog( nullptr ),
0103     m_routeCompleted( false ),
0104     m_speakersModel( nullptr ),
0105     m_parent( parent )
0106 {
0107     m_audio->setMuted( false );
0108     m_audio->setSoundEnabled( true );
0109 }
0110 
0111 QString RoutingPluginPrivate::richText( const QString &source )
0112 {
0113     return QLatin1String("<font size=\"+1\" color=\"black\">") + source + QLatin1String("</font>");
0114 }
0115 
0116 QString RoutingPluginPrivate::fuzzyDistance( qreal length )
0117 {
0118     int precision = 0;
0119     QString distanceUnit = QLatin1String( "m" );
0120 
0121     if ( MarbleGlobal::getInstance()->locale()->measurementSystem() != MarbleLocale::MetricSystem ) {
0122         precision = 1;
0123         distanceUnit = "mi";
0124         length *= METER2KM;
0125         length *= KM2MI;
0126     } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() ==
0127                MarbleLocale::MetricSystem) {
0128         if ( length >= 1000 ) {
0129             length /= 1000;
0130             distanceUnit = "km";
0131             precision = 1;
0132         } else if ( length >= 200 ) {
0133             length = 50 * qRound( length / 50 );
0134         } else if ( length >= 100 ) {
0135             length = 25 * qRound( length / 25 );
0136         } else {
0137             length = 10 * qRound( length / 10 );
0138         }
0139     } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() ==
0140                MarbleLocale::NauticalSystem) {
0141         precision = 2;
0142         distanceUnit = "nm";
0143         length *= METER2KM;
0144         length *= KM2NM;
0145     }
0146 
0147     return QString( "%1 %2" ).arg( length, 0, 'f', precision ).arg( distanceUnit );
0148 }
0149 
0150 void RoutingPluginPrivate::updateZoomButtons( int zoomValue )
0151 {
0152     int const minZoom = m_marbleWidget ? m_marbleWidget->minimumZoom() : 900;
0153     int const maxZoom = m_marbleWidget ? m_marbleWidget->maximumZoom() : 2400;
0154 
0155     bool const zoomInEnabled = zoomValue < maxZoom;
0156     bool const zoomOutEnabled = zoomValue > minZoom;
0157 
0158     if ( ( zoomInEnabled != m_widget.zoomInButton->isEnabled() ) ||
0159          ( zoomOutEnabled != m_widget.zoomOutButton->isEnabled() ) ) {
0160         m_widget.zoomInButton->setEnabled( zoomInEnabled );
0161         m_widget.zoomOutButton->setEnabled( zoomOutEnabled );
0162         forceRepaint();
0163     }
0164 }
0165 
0166 void RoutingPluginPrivate::updateGuidanceModeButton()
0167 {
0168     bool const hasRoute = m_routingModel->rowCount() > 0;
0169     m_widget.routingButton->setEnabled( hasRoute );
0170     forceRepaint();
0171 }
0172 
0173 void RoutingPluginPrivate::forceRepaint()
0174 {
0175     m_parent->update();
0176     emit m_parent->repaintNeeded();
0177 }
0178 
0179 void RoutingPluginPrivate::updateButtonVisibility()
0180 {
0181     bool const show = m_guidanceModeEnabled;
0182     bool const near = show && m_nearNextInstruction;
0183     m_widget.progressBar->setVisible( near );
0184     m_widget.instructionIconLabel->setVisible( show );
0185     m_widget.spacer->changeSize( show ? 10 : 0, 20 );
0186     m_widget.instructionLabel->setVisible( show );
0187 
0188     // m_widget.followingInstructionIconLabel->setVisible( show );
0189     // Disabling the next instruction turn icon for now, it seems to confuse first time users.
0190     m_widget.followingInstructionIconLabel->setVisible( false );
0191 
0192     m_widget.destinationDistanceLabel->setVisible( show );
0193 
0194     m_widget.gpsButton->setVisible( !show );
0195     m_widget.zoomOutButton->setVisible( !show );
0196     m_widget.zoomInButton->setVisible( !show );
0197 
0198     m_widgetItem->widget()->updateGeometry();
0199     QSize const size = m_widgetItem->widget()->sizeHint();
0200     m_widgetItem->widget()->resize( size );
0201     m_widgetItem->setContentSize( size );
0202 
0203     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0204     if ( smallScreen ) {
0205         qreal const pluginWidth = size.width();
0206         int x = -10;
0207         if ( m_guidanceModeEnabled ) {
0208             int const parentWidth = m_marbleWidget->width();
0209             x = qRound( ( parentWidth - pluginWidth ) / 2.0 );
0210         }
0211         m_parent->setPosition( QPointF( x, m_parent->position().y() ) );
0212     }
0213 }
0214 
0215 void RoutingPluginPrivate::updateZoomButtons()
0216 {
0217     if ( m_marbleWidget ) {
0218         updateZoomButtons( m_marbleWidget->zoom() );
0219     }
0220 }
0221 
0222 void RoutingPluginPrivate::toggleGuidanceMode( bool enabled )
0223 {
0224     if( !m_marbleWidget || m_guidanceModeEnabled == enabled ) {
0225         return;
0226     }
0227 
0228     m_guidanceModeEnabled = enabled;
0229     updateButtonVisibility();
0230 
0231     if( enabled ) {
0232         QObject::connect( m_routingModel, SIGNAL(positionChanged()),
0233                  m_parent, SLOT(updateDestinationInformation()) );
0234     } else {
0235         QObject::disconnect( m_routingModel, SIGNAL(positionChanged()),
0236                     m_parent, SLOT(updateDestinationInformation()) );
0237     }
0238 
0239     if ( enabled ) {
0240         QString const text = QObject::tr( "Starting guidance mode, please wait..." );
0241         m_widget.instructionLabel->setText( richText( "%1" ).arg( text ) );
0242     }
0243 
0244     if ( enabled ) {
0245         RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest();
0246         if ( request && request->size() > 0 ) {
0247             GeoDataCoordinates source = request->source();
0248             if ( source.isValid() ) {
0249                 GeoDataLookAt view;
0250                 view.setCoordinates( source );
0251                 // By happy coincidence this equals OpenStreetMap tile level 15
0252                 view.setRange( 851.807 );
0253                 m_marbleWidget->flyTo( view );
0254             }
0255         }
0256     }
0257 
0258     m_marbleWidget->model()->routingManager()->setGuidanceModeEnabled( enabled );
0259 
0260     if ( enabled ) {
0261         m_routeCompleted = false;
0262     }
0263 
0264     forceRepaint();
0265 }
0266 
0267 void RoutingPluginPrivate::updateDestinationInformation()
0268 {
0269     if ( m_routingModel->route().currentSegment().isValid() ) {
0270         qreal remaining = remainingDistance();
0271         qreal distanceLeft = nextInstructionDistance();
0272         m_audio->update( m_routingModel->route(), distanceLeft, remaining, m_routingModel->deviatedFromRoute() );
0273 
0274         m_nearNextInstruction = distanceLeft < thresholdDistance;
0275 
0276         QString pixmapHtml = "<img src=\":/flag.png\" /><br />";
0277         m_widget.destinationDistanceLabel->setText( pixmapHtml + richText( fuzzyDistance( remaining ) ) );
0278 
0279         m_widget.instructionIconLabel->setEnabled( m_nearNextInstruction );
0280         m_widget.progressBar->setMaximum( thresholdDistance );
0281         m_widget.progressBar->setValue( qRound( distanceLeft ) );
0282 
0283         updateButtonVisibility();
0284 
0285         QString pixmap = MarbleDirs::path(QStringLiteral("bitmaps/routing_step.png"));
0286         pixmapHtml = QString( "<img src=\"%1\" />" ).arg( pixmap );
0287 
0288         qreal planetRadius = m_marbleWidget->model()->planet()->radius();
0289         GeoDataCoordinates const onRoute = m_routingModel->route().positionOnRoute();
0290         GeoDataCoordinates const ego = m_routingModel->route().position();
0291         qreal const distanceToRoute = planetRadius * ego.sphericalDistanceTo(onRoute);
0292 
0293         if ( !m_routingModel->route().currentSegment().isValid() ) {
0294             m_widget.instructionLabel->setText( richText( QObject::tr( "Calculate a route to get directions." ) ) );
0295             m_widget.instructionIconLabel->setText( pixmapHtml );
0296         } else if ( distanceToRoute > 300.0 ) {
0297             m_widget.instructionLabel->setText( richText( QObject::tr( "Route left." ) ) );
0298             m_widget.instructionIconLabel->setText( pixmapHtml );
0299         } else if ( !m_routingModel->route().currentSegment().nextRouteSegment().isValid() ) {
0300             m_widget.instructionLabel->setText( richText( QObject::tr( "Destination ahead." ) ) );
0301             m_widget.instructionIconLabel->setText( pixmapHtml );
0302         } else {
0303             pixmap = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().directionPixmap();
0304             QString const instructionText = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().instructionText();
0305             m_widget.instructionLabel->setText( richText( "%1" ).arg( instructionText ) );
0306             pixmapHtml = QString( "<p align=\"center\"><img src=\"%1\" /><br />%2</p>" ).arg( pixmap );
0307             m_widget.instructionIconLabel->setText( pixmapHtml.arg( richText( fuzzyDistance( distanceLeft ) ) ) );
0308 
0309             if( remaining > 50 ) {
0310                 m_routeCompleted = false;
0311             } else {
0312                 if ( !m_routeCompleted ) {
0313                     QString content = QObject::tr( "Arrived at destination. <a href=\"#reverse\">Calculate the way back.</a>" );
0314                     m_widget.instructionLabel->setText( richText( "%1" ).arg( content ) );
0315                 }
0316                 m_routeCompleted = true;
0317             }
0318         }
0319 
0320         forceRepaint();
0321     }
0322 }
0323 
0324 void RoutingPluginPrivate::updateGpsButton( PositionProviderPlugin *activePlugin )
0325 {
0326     m_widget.gpsButton->setChecked( activePlugin != nullptr );
0327     forceRepaint();
0328 }
0329 
0330 void RoutingPluginPrivate::togglePositionTracking( bool enabled )
0331 {
0332     PositionProviderPlugin* plugin = nullptr;
0333     if ( enabled ) {
0334         const PluginManager* pluginManager = m_marbleWidget->model()->pluginManager();
0335         QList<const PositionProviderPlugin*> plugins = pluginManager->positionProviderPlugins();
0336         if ( plugins.size() > 0 ) {
0337             plugin = plugins.first()->newInstance();
0338         }
0339     }
0340     m_parent->marbleModel()->positionTracking()->setPositionProviderPlugin( plugin );
0341 }
0342 
0343 void RoutingPluginPrivate::reverseRoute()
0344 {
0345     if ( m_marbleWidget ) {
0346         m_marbleWidget->model()->routingManager()->reverseRoute();
0347     }
0348 }
0349 
0350 void RoutingPluginPrivate::readSettings()
0351 {
0352     if ( m_configDialog ) {
0353         if ( !m_speakersModel ) {
0354             m_speakersModel = new SpeakersModel( m_parent );
0355         }
0356         int const index = m_speakersModel->indexOf( m_audio->speaker() );
0357         m_configUi.speakerComboBox->setModel( m_speakersModel );
0358         m_configUi.speakerComboBox->setCurrentIndex( index );
0359         m_configUi.voiceNavigationCheckBox->setChecked( !m_audio->isMuted() );
0360         m_configUi.soundRadioButton->setChecked( m_audio->isSoundEnabled() );
0361         m_configUi.speakerRadioButton->setChecked( !m_audio->isSoundEnabled() );
0362     }
0363 }
0364 
0365 qreal RoutingPluginPrivate::nextInstructionDistance() const
0366 {
0367     GeoDataCoordinates position = m_routingModel->route().position();
0368     GeoDataCoordinates interpolated = m_routingModel->route().positionOnRoute();
0369     GeoDataCoordinates onRoute = m_routingModel->route().currentWaypoint();
0370     qreal planetRadius = m_marbleWidget->model()->planet()->radius();
0371     const qreal distance = planetRadius * (position.sphericalDistanceTo(interpolated) + interpolated.sphericalDistanceTo(onRoute));
0372     const RouteSegment &segment = m_routingModel->route().currentSegment();
0373     for (int i=0; i<segment.path().size(); ++i) {
0374         if (segment.path()[i] == onRoute) {
0375             return distance + segment.path().length( planetRadius, i );
0376         }
0377     }
0378 
0379     return distance;
0380 }
0381 
0382 qreal RoutingPluginPrivate::remainingDistance() const
0383 {
0384     GeoDataCoordinates position = m_routingModel->route().currentSegment().maneuver().position();
0385     bool foundSegment = false;
0386     qreal distance = nextInstructionDistance();
0387     for ( int i=0; i<m_routingModel->route().size(); ++i ) {
0388         if ( foundSegment ) {
0389             distance += m_routingModel->route().at( i ).distance();
0390         } else {
0391             foundSegment =  m_routingModel->route().at( i ).maneuver().position() == position;
0392         }
0393     }
0394 
0395     return distance;
0396 }
0397 
0398 void RoutingPlugin::writeSettings()
0399 {
0400     Q_ASSERT( d->m_configDialog );
0401     int const index = d->m_configUi.speakerComboBox->currentIndex();
0402     if ( index >= 0 ) {
0403         QModelIndex const idx = d->m_speakersModel->index( index );
0404         d->m_audio->setSpeaker( d->m_speakersModel->data( idx, SpeakersModel::Path ).toString() );
0405         if ( !d->m_speakersModel->data( idx, SpeakersModel::IsLocal ).toBool() ) {
0406             d->m_speakersModel->install( index );
0407         }
0408     }
0409     d->m_audio->setMuted( !d->m_configUi.voiceNavigationCheckBox->isChecked() );
0410     d->m_audio->setSoundEnabled( d->m_configUi.soundRadioButton->isChecked() );
0411     d->readSettings();
0412     emit settingsChanged( nameId() );
0413 }
0414 
0415 
0416 RoutingPlugin::RoutingPlugin() :
0417     AbstractFloatItem( nullptr ),
0418     d( nullptr )
0419 {
0420 }
0421 
0422 RoutingPlugin::RoutingPlugin( const MarbleModel *marbleModel ) :
0423     AbstractFloatItem( marbleModel, QPointF( -10, -10 ) ),
0424     d( new RoutingPluginPrivate( this ) )
0425 {
0426     setEnabled( true );
0427     //plugin is visible by default on small screen devices
0428     setVisible( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen );
0429     setPadding( 0.5 );
0430     setBorderWidth( 1 );
0431     setBackground( QBrush( QColor( "white" ) ) );
0432 }
0433 
0434 RoutingPlugin::~RoutingPlugin()
0435 {
0436     delete d;
0437 }
0438 
0439 QStringList RoutingPlugin::backendTypes() const
0440 {
0441     return QStringList(QStringLiteral("routing"));
0442 }
0443 
0444 QString RoutingPlugin::name() const
0445 {
0446     return tr( "Routing" );
0447 }
0448 
0449 QString RoutingPlugin::guiString() const
0450 {
0451     return tr( "&Routing" );
0452 }
0453 
0454 QString RoutingPlugin::nameId() const
0455 {
0456     return QStringLiteral("routing");
0457 }
0458 
0459 QString RoutingPlugin::version() const
0460 {
0461     return QStringLiteral("1.0");
0462 }
0463 
0464 QString RoutingPlugin::description() const
0465 {
0466     return tr( "Routing information and navigation controls" );
0467 }
0468 
0469 QString RoutingPlugin::copyrightYears() const
0470 {
0471     return QStringLiteral("2010");
0472 }
0473 
0474 QVector<PluginAuthor> RoutingPlugin::pluginAuthors() const
0475 {
0476     return QVector<PluginAuthor>()
0477             << PluginAuthor(QStringLiteral("Siddharth Srivastava"), QStringLiteral("akssps011@gmail.com"))
0478             << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org"));
0479 }
0480 
0481 QIcon RoutingPlugin::icon() const
0482 {
0483     return QIcon(QStringLiteral(":/icons/routeplanning.png"));
0484 }
0485 
0486 void RoutingPlugin::initialize()
0487 {
0488     QWidget *widget = new QWidget;
0489     d->m_widget.setupUi( widget );
0490     d->m_widgetItem = new WidgetGraphicsItem( this );
0491     d->m_widgetItem->setWidget( widget );
0492 
0493     PositionProviderPlugin* activePlugin = marbleModel()->positionTracking()->positionProviderPlugin();
0494     d->updateGpsButton( activePlugin );
0495     connect( marbleModel()->positionTracking(),
0496              SIGNAL(positionProviderPluginChanged(PositionProviderPlugin*)),
0497              this, SLOT(updateGpsButton(PositionProviderPlugin*)) );
0498 
0499     d->m_widget.routingButton->setEnabled( false );
0500     connect( d->m_widget.instructionLabel, SIGNAL(linkActivated(QString)),
0501              this, SLOT(reverseRoute()) );
0502 
0503     MarbleGraphicsGridLayout *layout = new MarbleGraphicsGridLayout( 1, 1 );
0504     layout->addItem( d->m_widgetItem, 0, 0 );
0505     setLayout( layout );
0506     d->updateButtonVisibility();
0507 }
0508 
0509 bool RoutingPlugin::isInitialized() const
0510 {
0511     return d->m_widgetItem;
0512 }
0513 
0514 bool RoutingPlugin::eventFilter( QObject *object, QEvent *e )
0515 {
0516     if ( d->m_marbleWidget || !enabled() || !visible() ) {
0517         return AbstractFloatItem::eventFilter( object, e );
0518     }
0519 
0520     MarbleWidget *widget = dynamic_cast<MarbleWidget*> ( object );
0521 
0522     if ( widget && !d->m_marbleWidget ) {
0523         d->m_marbleWidget = widget;
0524         d->m_routingModel = d->m_marbleWidget->model()->routingManager()->routingModel();
0525 
0526         connect( d->m_widget.routingButton, SIGNAL(clicked(bool)),
0527                  this, SLOT(toggleGuidanceMode(bool)) );
0528         connect( d->m_widget.gpsButton, SIGNAL(clicked(bool)),
0529                  this, SLOT(togglePositionTracking(bool)) );
0530         connect( d->m_widget.zoomInButton, SIGNAL(clicked()),
0531                  d->m_marbleWidget, SLOT(zoomIn()) );
0532         connect( d->m_widget.zoomOutButton, SIGNAL(clicked()),
0533                  d->m_marbleWidget, SLOT(zoomOut()) );
0534         connect( d->m_marbleWidget, SIGNAL(themeChanged(QString)),
0535                  this, SLOT(updateZoomButtons()) );
0536         connect( d->m_marbleWidget, SIGNAL(zoomChanged(int)),
0537                  this, SLOT(updateZoomButtons(int)) );
0538         connect( d->m_routingModel, SIGNAL(currentRouteChanged()),
0539                 this, SLOT(updateGuidanceModeButton()) );
0540         d->updateGuidanceModeButton();
0541     }
0542     return AbstractFloatItem::eventFilter( object, e );
0543 }
0544 
0545 QHash<QString,QVariant> RoutingPlugin::settings() const
0546 {
0547     QHash<QString, QVariant> result = AbstractFloatItem::settings();
0548 
0549     result.insert(QStringLiteral("muted"), d->m_audio->isMuted());
0550     result.insert(QStringLiteral("sound"), d->m_audio->isSoundEnabled());
0551     result.insert(QStringLiteral("speaker"), d->m_audio->speaker());
0552 
0553     return result;
0554 }
0555 
0556 void RoutingPlugin::setSettings( const QHash<QString,QVariant> &settings )
0557 {
0558     AbstractFloatItem::setSettings( settings );
0559 
0560     d->m_audio->setMuted(settings.value(QStringLiteral("muted"), false).toBool());
0561     d->m_audio->setSoundEnabled(settings.value(QStringLiteral("sound"), true).toBool());
0562     d->m_audio->setSpeaker(settings.value(QStringLiteral("speaker")).toString());
0563 
0564     d->readSettings();
0565 }
0566 
0567 QDialog *RoutingPlugin::configDialog()
0568 {
0569     if ( !d->m_configDialog ) {
0570         d->m_configDialog = new QDialog;
0571         d->m_configUi.setupUi( d->m_configDialog );
0572         d->readSettings();
0573 
0574         connect( d->m_configDialog, SIGNAL(accepted()), this, SLOT(writeSettings()) );
0575         connect( d->m_configDialog, SIGNAL(rejected()), this, SLOT(readSettings()) );
0576         connect( d->m_configUi.buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()),
0577                  SLOT(restoreDefaultSettings()) );
0578     }
0579 
0580     return d->m_configDialog;
0581 }
0582 
0583 }
0584 
0585 #include "moc_RoutingPlugin.cpp"