1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2011 Dennis Nienhüser <nienhueser@kde.org>
4 //
5 
6 #include "Routing.h"
7 
8 #include <MarbleMap.h>
9 #include <MarbleModel.h>
10 #include "MarbleDirs.h"
11 #include "routing/AlternativeRoutesModel.h"
12 #include "routing/RoutingManager.h"
13 #include "routing/RouteRequest.h"
14 #include "routing/RoutingProfilesModel.h"
15 #include <GeoDataLatLonAltBox.h>
16 #include <GeoPainter.h>
17 #include <routing/Route.h>
18 #include <declarative/RouteRequestModel.h>
19 #include <ViewportParams.h>
20 #include <PositionTracking.h>
21 
22 #include <QDebug>
23 #include <QQmlContext>
24 #include <QOpenGLPaintDevice>
25 #include <QSGGeometryNode>
26 #include <QSGFlatColorMaterial>
27 
28 namespace Marble {
29 
30 class RoutingPrivate
31 {
32 public:
33     explicit RoutingPrivate(QObject * parent = nullptr);
34 
35     MarbleMap* m_marbleMap;
36     QMap<QString, Marble::RoutingProfile> m_profiles;
37     QString m_routingProfile;
38     QQmlComponent * m_waypointDelegate;
39     QMap<int,QQuickItem*> m_waypointItems;
40     RouteRequestModel* m_routeRequestModel;
41     QObject * m_parent;
42     QVector<Placemark *> m_searchResultPlacemarks;
43     QMap<int, QQuickItem*> m_searchResultItems;
44 };
45 
RoutingPrivate(QObject * parent)46 RoutingPrivate::RoutingPrivate(QObject *parent) :
47     m_marbleMap( nullptr ),
48     m_waypointDelegate( nullptr ),
49     m_routeRequestModel( new RouteRequestModel(parent) ),
50     m_parent( parent )
51 {
52     // nothing to do
53 }
54 
Routing(QQuickItem * parent)55 Routing::Routing( QQuickItem *parent) :
56     QQuickItem( parent ), d( new RoutingPrivate(this) )
57 {
58     setFlag(ItemHasContents, true);
59     d->m_routeRequestModel->setRouting(this);
60     connect(d->m_routeRequestModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateWaypointItems()));
61     connect(d->m_routeRequestModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateWaypointItems()));
62     connect(d->m_routeRequestModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateWaypointItems()));
63 
64     emit routeRequestModelChanged(d->m_routeRequestModel);
65 }
66 
~Routing()67 Routing::~Routing()
68 {
69     delete d;
70 }
71 
updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData *)72 QSGNode * Routing::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
73     if (!d->m_marbleMap) {
74         return nullptr;
75     }
76 
77     QOpenGLPaintDevice paintDevice(QSize(width(), height()));
78     Marble::GeoPainter geoPainter(&paintDevice, d->m_marbleMap->viewport(), d->m_marbleMap->mapQuality());
79 
80     RoutingManager const * const routingManager = d->m_marbleMap->model()->routingManager();
81     GeoDataLineString const & waypoints = routingManager->routingModel()->route().path();
82 
83     if (waypoints.isEmpty()) {
84       return nullptr;
85     }
86 
87     int const dpi = qMax(paintDevice.logicalDpiX(), paintDevice.logicalDpiY());
88     qreal const halfWidth = 0.5 * 2.5 * MM2M * M2IN * dpi;
89 
90     QColor standardRouteColor = routingManager->state() == RoutingManager::Downloading ?
91                                 routingManager->routeColorStandard() :
92                                 routingManager->routeColorStandard().darker( 200 );
93 
94     QVector<QPolygonF*> polygons;
95     geoPainter.polygonsFromLineString( waypoints, polygons);
96 
97     if (!polygons.isEmpty()) {
98         delete oldNode;
99         oldNode = new QSGNode;
100         for(const QPolygonF* itPolygon: polygons) {
101             QPolygonF const & polygon = *itPolygon;
102             QVector<QVector2D> normals;
103             int segmentCount = itPolygon->size() - 1;
104             normals.reserve(segmentCount);
105             for(int i = 0; i < segmentCount; ++i) {
106                 normals << QVector2D(polygon[i+1] - polygon[i]).normalized();
107             }
108             QSGGeometryNode* lineNode = new QSGGeometryNode;
109 
110             QSGGeometry * lineNodeGeo = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), segmentCount*4);
111             lineNodeGeo->setDrawingMode(GL_TRIANGLE_STRIP);
112             lineNodeGeo->allocate(segmentCount*4);
113 
114             QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
115             material->setColor(standardRouteColor);
116 
117             lineNode->setGeometry(lineNodeGeo);
118             lineNode->setFlag(QSGNode::OwnsGeometry);
119             lineNode->setMaterial(material);
120             lineNode->setFlag(QSGNode::OwnsMaterial);
121 
122             auto points = lineNodeGeo->vertexDataAsPoint2D();
123             int k = -1;
124             for(int i = 0; i < segmentCount; ++i) {
125                 auto const & a = polygon[i];
126                 auto const & b = polygon[i+1];
127                 auto const & n = normals[i];
128                 points[++k].set(a.x() - halfWidth * n.y(), a.y() + halfWidth * n.x());
129                 points[++k].set(a.x() + halfWidth * n.y(), a.y() - halfWidth * n.x());
130                 points[++k].set(b.x() - halfWidth * n.y(), b.y() + halfWidth * n.x());
131                 points[++k].set(b.x() + halfWidth * n.y(), b.y() - halfWidth * n.x());
132             }
133 
134             oldNode->appendChildNode(lineNode);
135         }
136     } else {
137         if (oldNode && oldNode->childCount() > 0) {
138             delete oldNode;
139             oldNode = new QSGNode;
140         }
141     }
142 
143     qDeleteAll(polygons);
144     return oldNode;
145 }
146 
waypointModel()147 QObject* Routing::waypointModel()
148 {
149     return d->m_marbleMap ? d->m_marbleMap->model()->routingManager()->routingModel() : nullptr;
150 }
151 
setWaypointDelegate(QQmlComponent * waypointDelegate)152 void Routing::setWaypointDelegate(QQmlComponent *waypointDelegate)
153 {
154     if (d->m_waypointDelegate == waypointDelegate) {
155         return;
156     }
157 
158     d->m_waypointDelegate = waypointDelegate;
159     emit waypointDelegateChanged(waypointDelegate);
160 }
161 
updateWaypointItems()162 void Routing::updateWaypointItems()
163 {
164     if ( d->m_marbleMap && d->m_routeRequestModel ) {
165         for (int i = d->m_waypointItems.keys().size(); i < d->m_routeRequestModel->rowCount(); i++ ) {
166             QQmlContext * context = new QQmlContext( qmlContext( d->m_waypointDelegate ) );
167             QObject * component = d->m_waypointDelegate->create(context);
168             QQuickItem* item = qobject_cast<QQuickItem*>( component );
169             if ( item ) {
170                 item->setParentItem( this );
171                 item->setProperty("index", i);
172                 d->m_waypointItems[i] = item;
173             } else {
174                 delete component;
175             }
176         }
177 
178         for (int i = d->m_waypointItems.keys().size()-1; i >= d->m_routeRequestModel->rowCount(); i--) {
179             QQuickItem* item = d->m_waypointItems[i];
180             item->setProperty("visible", QVariant(false) );
181             d->m_waypointItems.erase(d->m_waypointItems.find(i));
182             item->deleteLater();
183         }
184 
185         QMap<int, QQuickItem*>::iterator iter = d->m_waypointItems.begin();
186         while ( iter != d->m_waypointItems.end() ) {
187             qreal x = 0;
188             qreal y = 0;
189             const qreal lon = d->m_routeRequestModel->data(d->m_routeRequestModel->index( iter.key() ), RouteRequestModel::LongitudeRole).toFloat();
190             const qreal lat = d->m_routeRequestModel->data(d->m_routeRequestModel->index( iter.key() ), RouteRequestModel::LatitudeRole).toFloat();
191             const bool visible = d->m_marbleMap->viewport()->screenCoordinates(lon * DEG2RAD, lat * DEG2RAD, x, y);
192 
193             QQuickItem * item = iter.value();
194             if ( item ) {
195                 item->setVisible( visible );
196                 if ( visible ) {
197                     item->setProperty("xPos", QVariant(x));
198                     item->setProperty("yPos", QVariant(y));
199                     if (iter.key() == 0 && waypointCount() == 1) {
200                         item->setProperty("type", QVariant(QStringLiteral("departure")));
201                     }
202                     else if (iter.key() == d->m_waypointItems.keys().size()-1) {
203                         item->setProperty("type", QVariant(QStringLiteral("destination")));
204                     }
205                     else if (iter.key() > 0) {
206                         item->setProperty("type", QVariant(QStringLiteral("waypoint")));
207                     }
208                     else {
209                         item->setProperty("type", QVariant(QStringLiteral("departure")));
210                     }
211                 }
212             }
213             ++iter;
214         }
215     }
216 }
217 
addSearchResultPlacemark(Placemark * placemark)218 int Routing::addSearchResultPlacemark(Placemark *placemark)
219 {
220     if ( d->m_marbleMap ) {
221         for (int i = 0; i < d->m_searchResultItems.size(); i++) {
222             if (d->m_searchResultPlacemarks[i]->placemark().coordinate() == placemark->placemark().coordinate()) {
223                 return i;
224             }
225         }
226         Placemark * newPlacemark = new Placemark(this);
227         newPlacemark->setGeoDataPlacemark(placemark->placemark());
228         d->m_searchResultPlacemarks.push_back(newPlacemark);
229     }
230 
231     updateSearchResultPlacemarks();
232     return d->m_searchResultPlacemarks.size()-1;
233 }
234 
clearSearchResultPlacemarks()235 void Routing::clearSearchResultPlacemarks()
236 {
237     for(Placemark* placemark: d->m_searchResultPlacemarks) {
238         placemark->deleteLater();
239     }
240     d->m_searchResultPlacemarks.clear();
241 
242     for(QQuickItem* item: d->m_searchResultItems) {
243         item->deleteLater();
244     }
245     d->m_searchResultItems.clear();
246 }
247 
updateSearchResultPlacemarks()248 void Routing::updateSearchResultPlacemarks()
249 {
250     for (int i = d->m_searchResultItems.keys().size(); i < d->m_searchResultPlacemarks.size(); i++ ) {
251         QQmlContext * context = new QQmlContext( qmlContext( d->m_waypointDelegate ) );
252         QObject * component = d->m_waypointDelegate->create(context);
253         QQuickItem* item = qobject_cast<QQuickItem*>( component );
254         if ( item ) {
255             item->setParentItem( this );
256             item->setProperty("index", i);
257             item->setProperty("type", QVariant(QStringLiteral("searchResult")));
258             item->setProperty("placemark", QVariant::fromValue(d->m_searchResultPlacemarks[i]));
259             d->m_searchResultItems[i] = item;
260         } else {
261             delete component;
262         }
263     }
264 
265     for (int i = d->m_searchResultItems.keys().size()-1; i >= d->m_searchResultPlacemarks.size(); i--) {
266         QQuickItem* item = d->m_searchResultItems[i];
267         item->setProperty("visible", QVariant(false) );
268         d->m_searchResultItems.erase(d->m_searchResultItems.find(i));
269         item->deleteLater();
270     }
271 
272     for (int i = 0; i < d->m_searchResultItems.keys().size() && i < d->m_searchResultPlacemarks.size(); i++) {
273         qreal x = 0;
274         qreal y = 0;
275         const qreal lon = d->m_searchResultPlacemarks[i]->placemark().coordinate().longitude();
276         const qreal lat = d->m_searchResultPlacemarks[i]->placemark().coordinate().latitude();
277         const bool visible = d->m_marbleMap->viewport()->screenCoordinates(lon, lat, x, y);
278 
279         QQuickItem * item = d->m_searchResultItems[i];
280         if ( item ) {
281             item->setVisible( visible );
282             if ( visible ) {
283                 item->setProperty("xPos", QVariant(x));
284                 item->setProperty("yPos", QVariant(y));
285             }
286         }
287     }
288 }
289 
setMarbleMap(MarbleMap * marbleMap)290 void Routing::setMarbleMap( MarbleMap* marbleMap )
291 {
292     d->m_marbleMap = marbleMap;
293 
294     if ( d->m_marbleMap ) {
295         connect(d->m_marbleMap, SIGNAL(repaintNeeded(QRegion)), this, SLOT(update()));
296         RoutingManager* routingManager = d->m_marbleMap->model()->routingManager();
297         if (routingManager->profilesModel()->rowCount() == 0) {
298             routingManager->profilesModel()->loadDefaultProfiles();
299             routingManager->readSettings();
300         }
301 
302         connect( routingManager, SIGNAL(stateChanged(RoutingManager::State)), this, SLOT(update()));
303         connect( routingManager, SIGNAL(routeRetrieved(GeoDataDocument*)), this, SLOT(update()));
304         connect( routingManager, SIGNAL(stateChanged(RoutingManager::State)),
305                  this, SIGNAL(hasRouteChanged()) );
306         connect( routingModel(), SIGNAL(currentRouteChanged()),
307                  this, SIGNAL(hasRouteChanged()) );
308         connect( routingManager, SIGNAL(stateChanged(RoutingManager::State)),
309                  this, SIGNAL(hasWaypointsChanged()) );
310         connect( routingModel(), SIGNAL(currentRouteChanged()),
311                  this, SIGNAL(hasWaypointsChanged()) );
312         connect( routingModel(), SIGNAL(currentRouteChanged()),
313                  this, SLOT(update()) );
314         connect( d->m_marbleMap, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)),
315                  this, SLOT(updateWaypointItems()) );
316         connect( d->m_marbleMap, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)),
317                  this, SLOT(updateSearchResultPlacemarks()) );
318 
319         emit routingModelChanged();
320 
321         QList<Marble::RoutingProfile> profiles = routingManager->profilesModel()->profiles();
322         if ( profiles.size() == 4 ) {
323             /** @todo FIXME: Restrictive assumptions on available plugins and certain profile loading implementation */
324             d->m_profiles[QStringLiteral("Motorcar")] = profiles.at( 0 );
325             d->m_profiles[QStringLiteral("Bicycle")] = profiles.at( 2 );
326             d->m_profiles[QStringLiteral("Pedestrian")] = profiles.at( 3 );
327         } else {
328             qDebug() << "Unexpected size of default routing profiles: " << profiles.size();
329         }
330     }
331 
332     emit marbleMapChanged();
333     emit routingProfileChanged();
334     emit hasRouteChanged();
335     emit hasWaypointsChanged();
336 }
337 
marbleMap()338 MarbleMap *Routing::marbleMap()
339 {
340     return d->m_marbleMap;
341 }
342 
routingProfile() const343 QString Routing::routingProfile() const
344 {
345     return d->m_routingProfile;
346 }
347 
setRoutingProfile(const QString & profile)348 void Routing::setRoutingProfile( const QString & profile )
349 {
350     if ( d->m_routingProfile != profile ) {
351         d->m_routingProfile = profile;
352         if ( d->m_marbleMap ) {
353             d->m_marbleMap->model()->routingManager()->routeRequest()->setRoutingProfile( d->m_profiles[profile] );
354         }
355         emit routingProfileChanged();
356     }
357 }
358 
hasRoute() const359 bool Routing::hasRoute() const
360 {
361     return d->m_marbleMap && !d->m_marbleMap->model()->routingManager()->routingModel()->route().path().isEmpty();
362 }
363 
hasWaypoints() const364 bool Routing::hasWaypoints() const
365 {
366     return d->m_marbleMap && d->m_marbleMap->model()->routingManager()->routingModel()->rowCount() > 0;
367 }
368 
routingModel()369 RoutingModel *Routing::routingModel()
370 {
371     return d->m_marbleMap == nullptr ? nullptr : d->m_marbleMap->model()->routingManager()->routingModel();
372 }
373 
waypointDelegate() const374 QQmlComponent *Routing::waypointDelegate() const
375 {
376     return d->m_waypointDelegate;
377 }
378 
waypointCount() const379 int Routing::waypointCount() const
380 {
381     return d->m_routeRequestModel ? d->m_routeRequestModel->rowCount() : 0;
382 }
383 
routeRequestModel()384 RouteRequestModel *Routing::routeRequestModel()
385 {
386     return d->m_routeRequestModel;
387 }
388 
addVia(qreal lon,qreal lat)389 void Routing::addVia( qreal lon, qreal lat )
390 {
391     if ( d->m_marbleMap ) {
392         Marble::RouteRequest* request = d->m_marbleMap->model()->routingManager()->routeRequest();
393         request->addVia( Marble::GeoDataCoordinates( lon, lat, 0.0, Marble::GeoDataCoordinates::Degree ) );
394         updateRoute();
395     }
396 }
397 
addViaAtIndex(int index,qreal lon,qreal lat)398 void Routing::addViaAtIndex(int index, qreal lon, qreal lat)
399 {
400     if ( d->m_marbleMap ) {
401         Marble::RouteRequest * request = d->m_marbleMap->model()->routingManager()->routeRequest();
402         request->insert(index, Marble::GeoDataCoordinates( lon, lat, 0.0, Marble::GeoDataCoordinates::Degree) );
403         updateRoute();
404     }
405 }
406 
addViaByPlacemark(Placemark * placemark)407 void Routing::addViaByPlacemark(Placemark *placemark)
408 {
409     if (d->m_marbleMap && placemark) {
410         Marble::RouteRequest * request = d->m_marbleMap->model()->routingManager()->routeRequest();
411         request->addVia(placemark->placemark());
412         updateRoute();
413     }
414 }
415 
addViaByPlacemarkAtIndex(int index,Placemark * placemark)416 void Routing::addViaByPlacemarkAtIndex(int index, Placemark *placemark)
417 {
418     if (d->m_marbleMap && placemark) {
419         Marble::RouteRequest * request = d->m_marbleMap->model()->routingManager()->routeRequest();
420         request->insert(index, placemark->placemark());
421         updateRoute();
422     }
423 }
424 
setVia(int index,qreal lon,qreal lat)425 void Routing::setVia( int index, qreal lon, qreal lat )
426 {
427     if ( index < 0 || index > 200 || !d->m_marbleMap ) {
428         return;
429     }
430 
431     Marble::RouteRequest* request = d->m_marbleMap->model()->routingManager()->routeRequest();
432     Q_ASSERT( request );
433     if ( index < request->size() ) {
434         request->setPosition( index, Marble::GeoDataCoordinates( lon, lat, 0.0, Marble::GeoDataCoordinates::Degree ) );
435     } else {
436         for ( int i=request->size(); i<index; ++i ) {
437             request->append( Marble::GeoDataCoordinates( 0.0, 0.0 ) );
438         }
439         request->append( Marble::GeoDataCoordinates( lon, lat, 0.0, Marble::GeoDataCoordinates::Degree ) );
440     }
441 
442     updateRoute();
443 }
444 
removeVia(int index)445 void Routing::removeVia( int index )
446 {
447     if ( index < 0 || !d->m_marbleMap ) {
448         return;
449     }
450 
451     Marble::RouteRequest* request = d->m_marbleMap->model()->routingManager()->routeRequest();
452     if ( index < request->size() ) {
453         d->m_marbleMap->model()->routingManager()->routeRequest()->remove( index );
454     }
455 
456     updateRoute();
457 }
458 
swapVias(int index1,int index2)459 void Routing::swapVias(int index1, int index2)
460 {
461     if ( !d->m_marbleMap || !d->m_routeRequestModel ) {
462         return;
463     }
464 
465     Marble::RouteRequest* request = d->m_marbleMap->model()->routingManager()->routeRequest();
466     request->swap(index1, index2);
467     updateRoute();
468     updateWaypointItems();
469 }
470 
reverseRoute()471 void Routing::reverseRoute()
472 {
473     if ( d->m_marbleMap ) {
474         d->m_marbleMap->model()->routingManager()->reverseRoute();
475     }
476 }
477 
clearRoute()478 void Routing::clearRoute()
479 {
480     if ( d->m_marbleMap ) {
481         d->m_marbleMap->model()->routingManager()->clearRoute();
482     }
483 }
484 
updateRoute()485 void Routing::updateRoute()
486 {
487     if ( d->m_marbleMap ) {
488         d->m_marbleMap->model()->routingManager()->retrieveRoute();
489     }
490 }
491 
openRoute(const QString & fileName)492 void Routing::openRoute( const QString &fileName )
493 {
494     if ( d->m_marbleMap ) {
495         Marble::RoutingManager * const routingManager = d->m_marbleMap->model()->routingManager();
496         /** @todo FIXME: replace the file:// prefix on QML side */
497         routingManager->clearRoute();
498         QString target = fileName.startsWith( QLatin1String( "file://" ) ) ? fileName.mid( 7 ) : fileName;
499         routingManager->loadRoute( target );
500         const Marble::GeoDataDocument *route = routingManager->alternativeRoutesModel()->currentRoute();
501         if ( route ) {
502             const Marble::GeoDataLineString* waypoints = Marble::AlternativeRoutesModel::waypoints( route );
503             if ( waypoints ) {
504                 GeoDataCoordinates const center = waypoints->latLonAltBox().center();
505                 GeoDataCoordinates::Unit const inDegree = GeoDataCoordinates::Degree;
506                 d->m_marbleMap->centerOn( center.longitude(inDegree), center.latitude(inDegree) );
507             }
508         }
509     }
510 }
511 
saveRoute(const QString & fileName)512 void Routing::saveRoute( const QString &fileName )
513 {
514     if ( d->m_marbleMap ) {
515         /** @todo FIXME: replace the file:// prefix on QML side */
516         QString target = fileName.startsWith( QLatin1String( "file://" ) ) ? fileName.mid( 7 ) : fileName;
517         d->m_marbleMap->model()->routingManager()->saveRoute( target );
518     }
519 }
520 
521 }
522 
523 #include "moc_Routing.cpp"
524