1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qgeoroutingmanagerengine_nokia.h"
38 #include "qgeoroutereply_nokia.h"
39 #include "qgeonetworkaccessmanager.h"
40 #include "qgeouriprovider.h"
41 #include "uri_constants.h"
42 
43 #include <QStringList>
44 #include <QUrl>
45 #include <QLocale>
46 #include <QtPositioning/QGeoRectangle>
47 
48 QT_BEGIN_NAMESPACE
49 
QGeoRoutingManagerEngineNokia(QGeoNetworkAccessManager * networkManager,const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)50 QGeoRoutingManagerEngineNokia::QGeoRoutingManagerEngineNokia(
51         QGeoNetworkAccessManager *networkManager,
52         const QVariantMap &parameters,
53         QGeoServiceProvider::Error *error,
54         QString *errorString)
55         : QGeoRoutingManagerEngine(parameters)
56         , m_networkManager(networkManager)
57         , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.routing.host"), ROUTING_HOST))
58 
59 {
60     Q_ASSERT(networkManager);
61     m_networkManager->setParent(this);
62 
63     m_appId = parameters.value(QStringLiteral("here.app_id")).toString();
64     m_token = parameters.value(QStringLiteral("here.token")).toString();
65 
66     QGeoRouteRequest::FeatureTypes featureTypes;
67     featureTypes |= QGeoRouteRequest::TollFeature;
68     featureTypes |= QGeoRouteRequest::HighwayFeature;
69     featureTypes |= QGeoRouteRequest::FerryFeature;
70     featureTypes |= QGeoRouteRequest::TunnelFeature;
71     featureTypes |= QGeoRouteRequest::DirtRoadFeature;
72     featureTypes |= QGeoRouteRequest::ParksFeature;
73     setSupportedFeatureTypes(featureTypes);
74 
75     QGeoRouteRequest::FeatureWeights featureWeights;
76     featureWeights |= QGeoRouteRequest::DisallowFeatureWeight;
77     featureWeights |= QGeoRouteRequest::AvoidFeatureWeight;
78     featureWeights |= QGeoRouteRequest::PreferFeatureWeight;
79     setSupportedFeatureWeights(featureWeights);
80 
81     QGeoRouteRequest::ManeuverDetails maneuverDetails;
82     maneuverDetails |= QGeoRouteRequest::BasicManeuvers;
83     setSupportedManeuverDetails(maneuverDetails);
84 
85     QGeoRouteRequest::RouteOptimizations optimizations;
86     optimizations |= QGeoRouteRequest::ShortestRoute;
87     optimizations |= QGeoRouteRequest::FastestRoute;
88     setSupportedRouteOptimizations(optimizations);
89 
90     QGeoRouteRequest::TravelModes travelModes;
91     travelModes |= QGeoRouteRequest::CarTravel;
92     travelModes |= QGeoRouteRequest::PedestrianTravel;
93     travelModes |= QGeoRouteRequest::PublicTransitTravel;
94     travelModes |= QGeoRouteRequest::BicycleTravel;
95     setSupportedTravelModes(travelModes);
96 
97     QGeoRouteRequest::SegmentDetails segmentDetails;
98     segmentDetails |= QGeoRouteRequest::BasicSegmentData;
99     setSupportedSegmentDetails(segmentDetails);
100 
101     if (error)
102         *error = QGeoServiceProvider::NoError;
103 
104     if (errorString)
105         *errorString = QString();
106 }
107 
~QGeoRoutingManagerEngineNokia()108 QGeoRoutingManagerEngineNokia::~QGeoRoutingManagerEngineNokia() {}
109 
calculateRoute(const QGeoRouteRequest & request)110 QGeoRouteReply *QGeoRoutingManagerEngineNokia::calculateRoute(const QGeoRouteRequest &request)
111 {
112     const QStringList reqStrings = calculateRouteRequestString(request);
113 
114     if (reqStrings.isEmpty()) {
115         QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this);
116         emit error(reply, reply->error(), reply->errorString());
117         return reply;
118     }
119 
120     QList<QNetworkReply*> replies;
121     foreach (const QString &reqString, reqStrings)
122         replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString))));
123 
124     QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(request, replies, this);
125 
126     connect(reply,
127             SIGNAL(finished()),
128             this,
129             SLOT(routeFinished()));
130 
131     connect(reply,
132             SIGNAL(error(QGeoRouteReply::Error,QString)),
133             this,
134             SLOT(routeError(QGeoRouteReply::Error,QString)));
135 
136     return reply;
137 }
138 
updateRoute(const QGeoRoute & route,const QGeoCoordinate & position)139 QGeoRouteReply *QGeoRoutingManagerEngineNokia::updateRoute(const QGeoRoute &route, const QGeoCoordinate &position)
140 {
141     const QStringList reqStrings = updateRouteRequestString(route, position);
142 
143     if (reqStrings.isEmpty()) {
144         QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this);
145         emit error(reply, reply->error(), reply->errorString());
146         return reply;
147     }
148 
149     QList<QNetworkReply*> replies;
150     foreach (const QString &reqString, reqStrings)
151         replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString))));
152 
153     QGeoRouteRequest updateRequest(route.request());
154     updateRequest.setTravelModes(route.travelMode());
155     QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(updateRequest, replies, this);
156 
157     connect(reply,
158             SIGNAL(finished()),
159             this,
160             SLOT(routeFinished()));
161 
162     connect(reply,
163             SIGNAL(error(QGeoRouteReply::Error,QString)),
164             this,
165             SLOT(routeError(QGeoRouteReply::Error,QString)));
166 
167     return reply;
168 }
169 
checkEngineSupport(const QGeoRouteRequest & request,QGeoRouteRequest::TravelModes travelModes) const170 bool QGeoRoutingManagerEngineNokia::checkEngineSupport(const QGeoRouteRequest &request,
171         QGeoRouteRequest::TravelModes travelModes) const
172 {
173     QList<QGeoRouteRequest::FeatureType> featureTypeList = request.featureTypes();
174     QGeoRouteRequest::FeatureTypes featureTypeFlag = QGeoRouteRequest::NoFeature;
175     QGeoRouteRequest::FeatureWeights featureWeightFlag = QGeoRouteRequest::NeutralFeatureWeight;
176 
177     for (int i = 0; i < featureTypeList.size(); ++i) {
178         featureTypeFlag |= featureTypeList.at(i);
179         featureWeightFlag |= request.featureWeight(featureTypeList.at(i));
180     }
181 
182     if ((featureTypeFlag & supportedFeatureTypes()) != featureTypeFlag)
183         return false;
184 
185     if ((featureWeightFlag & supportedFeatureWeights()) != featureWeightFlag)
186         return false;
187 
188 
189     if ((request.maneuverDetail() & supportedManeuverDetails()) != request.maneuverDetail())
190         return false;
191 
192     if ((request.segmentDetail() & supportedSegmentDetails()) != request.segmentDetail())
193         return false;
194 
195     if ((request.routeOptimization() & supportedRouteOptimizations()) != request.routeOptimization())
196         return false;
197 
198     if ((travelModes & supportedTravelModes()) != travelModes)
199         return false;
200 
201     // Count the number of set bits (= number of travel modes) (popcount)
202     int count = 0;
203 
204     for (unsigned bits = travelModes; bits; bits >>= 1)
205         count += (bits & 1);
206 
207     // We only allow one travel mode at a time
208     if (count != 1)
209         return false;
210 
211     return true;
212 }
213 
calculateRouteRequestString(const QGeoRouteRequest & request)214 QStringList QGeoRoutingManagerEngineNokia::calculateRouteRequestString(const QGeoRouteRequest &request)
215 {
216     bool supported = checkEngineSupport(request, request.travelModes());
217 
218     if (!supported)
219         return QStringList();
220     QStringList requests;
221 
222     QString baseRequest = QStringLiteral("http://");
223     baseRequest += m_uriProvider->getCurrentHost();
224     baseRequest += QStringLiteral("/routing/7.2/calculateroute.xml");
225 
226     baseRequest += QStringLiteral("?alternatives=");
227     baseRequest += QString::number(request.numberAlternativeRoutes());
228 
229     if (!m_appId.isEmpty() && !m_token.isEmpty()) {
230         baseRequest += QStringLiteral("&app_id=");
231         baseRequest += m_appId;
232         baseRequest += QStringLiteral("&token=");
233         baseRequest += m_token;
234     }
235 
236     const QList<QVariantMap> metadata = request.waypointsMetadata();
237     const QList<QGeoCoordinate> waypoints = request.waypoints();
238     int numWaypoints = waypoints.size();
239     if (numWaypoints < 2)
240         return QStringList();
241     // Details: https://developer.here.com/documentation/routing/topics/resource-param-type-waypoint.html
242     for (int i = 0;i < numWaypoints;++i) {
243         const QGeoCoordinate &c = waypoints.at(i);
244         baseRequest += QStringLiteral("&waypoint");
245         baseRequest += QString::number(i);
246         baseRequest += QStringLiteral("=geo!");
247         baseRequest += trimDouble(c.latitude());
248         baseRequest += ',';
249         baseRequest += trimDouble(c.longitude());
250         baseRequest += QStringLiteral(";;"); // ;<TransitRadius>;<UserLabel>
251         if (metadata.size() > i) {
252             const QVariantMap &meta = metadata.at(i);
253             if (meta.contains(QStringLiteral("bearing"))) {
254                 qreal bearing = meta.value(QStringLiteral("bearing")).toDouble();
255                 baseRequest += ';' + QString::number(int(bearing));
256             }
257         }
258     }
259 
260     QGeoRouteRequest::RouteOptimizations optimization = request.routeOptimization();
261 
262     QStringList types;
263     if (optimization.testFlag(QGeoRouteRequest::ShortestRoute))
264         types.append("shortest");
265     if (optimization.testFlag(QGeoRouteRequest::FastestRoute))
266         types.append("fastest");
267 
268     foreach (const QString &optimization, types) {
269         QString requestString = baseRequest;
270         requestString += modesRequestString(request, request.travelModes(), optimization);
271         requestString += routeRequestString(request);
272         requests << requestString;
273     }
274 
275     return requests;
276 }
277 
updateRouteRequestString(const QGeoRoute & route,const QGeoCoordinate & position)278 QStringList QGeoRoutingManagerEngineNokia::updateRouteRequestString(const QGeoRoute &route, const QGeoCoordinate &position)
279 {
280     if (!checkEngineSupport(route.request(), route.travelMode()))
281         return QStringList();
282     QStringList requests;
283 
284     QString baseRequest = "http://";
285     baseRequest += m_uriProvider->getCurrentHost();
286     baseRequest += "/routing/7.2/getroute.xml";
287 
288     baseRequest += "?routeid=";
289     baseRequest += route.routeId();
290 
291     baseRequest += "&pos=";
292     baseRequest += QString::number(position.latitude());
293     baseRequest += ',';
294     baseRequest += QString::number(position.longitude());
295 
296     QGeoRouteRequest::RouteOptimizations optimization = route.request().routeOptimization();
297 
298     QStringList types;
299     if (optimization.testFlag(QGeoRouteRequest::ShortestRoute))
300         types.append("shortest");
301     if (optimization.testFlag(QGeoRouteRequest::FastestRoute))
302         types.append("fastest");
303 
304     foreach (const QString &optimization, types) {
305         QString requestString = baseRequest;
306         requestString += modesRequestString(route.request(), route.travelMode(), optimization);
307         requestString += routeRequestString(route.request());
308         requests << requestString;
309     }
310 
311     return requests;
312 }
313 
modesRequestString(const QGeoRouteRequest & request,QGeoRouteRequest::TravelModes travelModes,const QString & optimization) const314 QString QGeoRoutingManagerEngineNokia::modesRequestString(const QGeoRouteRequest &request,
315         QGeoRouteRequest::TravelModes travelModes, const QString &optimization) const
316 {
317     QString requestString;
318 
319     QStringList modes;
320     if (travelModes.testFlag(QGeoRouteRequest::CarTravel))
321         modes.append("car");
322     if (travelModes.testFlag(QGeoRouteRequest::PedestrianTravel))
323         modes.append("pedestrian");
324     if (travelModes.testFlag(QGeoRouteRequest::PublicTransitTravel))
325         modes.append("publicTransport");
326 
327     QStringList featureStrings;
328     QList<QGeoRouteRequest::FeatureType> featureTypes = request.featureTypes();
329     for (int i = 0; i < featureTypes.size(); ++i) {
330         QGeoRouteRequest::FeatureWeight weight = request.featureWeight(featureTypes.at(i));
331 
332         if (weight == QGeoRouteRequest::NeutralFeatureWeight)
333             continue;
334 
335         QString weightString = "";
336         switch (weight) {
337             case QGeoRouteRequest::PreferFeatureWeight:
338                 weightString = '1';
339                 break;
340             case QGeoRouteRequest::AvoidFeatureWeight:
341                 weightString = "-1";
342                 break;
343             case QGeoRouteRequest::DisallowFeatureWeight:
344                 weightString = "-3";
345                 break;
346             case QGeoRouteRequest::NeutralFeatureWeight:
347             case QGeoRouteRequest::RequireFeatureWeight:
348                 break;
349         }
350 
351         if (weightString.isEmpty())
352             continue;
353 
354         switch (featureTypes.at(i)) {
355             case QGeoRouteRequest::TollFeature:
356                 featureStrings.append("tollroad:" + weightString);
357                 break;
358             case QGeoRouteRequest::HighwayFeature:
359                 featureStrings.append("motorway:" + weightString);
360                 break;
361             case QGeoRouteRequest::FerryFeature:
362                 featureStrings.append("boatFerry:" + weightString);
363                 featureStrings.append("railFerry:" + weightString);
364                 break;
365             case QGeoRouteRequest::TunnelFeature:
366                 featureStrings.append("tunnel:" + weightString);
367                 break;
368             case QGeoRouteRequest::DirtRoadFeature:
369                 featureStrings.append("dirtRoad:" + weightString);
370                 break;
371             case QGeoRouteRequest::PublicTransitFeature:
372             case QGeoRouteRequest::ParksFeature:
373             case QGeoRouteRequest::MotorPoolLaneFeature:
374             case QGeoRouteRequest::TrafficFeature:
375             case QGeoRouteRequest::NoFeature:
376                 break;
377         }
378     }
379 
380     requestString += "&mode=";
381     requestString += optimization + ';' + modes.join(',');
382     if (featureStrings.count())
383         requestString += ';' + featureStrings.join(',');
384     return requestString;
385 }
386 
routeRequestString(const QGeoRouteRequest & request) const387 QString QGeoRoutingManagerEngineNokia::routeRequestString(const QGeoRouteRequest &request) const
388 {
389     QString requestString;
390 
391     foreach (const QGeoRectangle &area, request.excludeAreas()) {
392         requestString += QLatin1String("&avoidareas=");
393         requestString += trimDouble(area.topLeft().latitude());
394         requestString += QLatin1String(",");
395         requestString += trimDouble(area.topLeft().longitude());
396         requestString += QLatin1String(";");
397         requestString += trimDouble(area.bottomRight().latitude());
398         requestString += QLatin1String(",");
399         requestString += trimDouble(area.bottomRight().longitude());
400     }
401 
402     QStringList legAttributes;
403 //    if (request.segmentDetail() & QGeoRouteRequest::BasicSegmentData)  // QTBUG-70501, this code expects to find links
404     {
405         requestString += "&linkattributes=sh,le"; //shape,length
406         legAttributes.append("links");
407     }
408 
409 //    if (request.maneuverDetail() & QGeoRouteRequest::BasicManeuvers) // QTBUG-70501, this code expects to find maneuvers
410     {
411         legAttributes.append("maneuvers");
412         //requestString += "&maneuverattributes=po,tt,le,di"; //position,traveltime,length,direction
413         requestString += "&maneuverattributes=all";
414         if (!(request.segmentDetail() & QGeoRouteRequest::NoSegmentData))
415             requestString += ",li"; //link
416     }
417 
418     // Handle QTBUG-70502, when API fixes it
419     requestString += "&routeattributes=sm,sh,bb,lg"; //summary,shape,boundingBox,legs
420     if (legAttributes.count() > 0) {
421         requestString += "&legattributes=";
422         requestString += legAttributes.join(",");
423     }
424 
425     // Handle QTBUG-70503, when API fixes it
426     requestString += "&departure=";
427     requestString += QDateTime::currentDateTime().toUTC().toString("yyyy-MM-ddThh:mm:ssZ");
428 
429     requestString += "&instructionformat=text";
430 
431     // ToDo: make this request-able
432     requestString += "&metricSystem=";
433     if (QLocale::MetricSystem == measurementSystem())
434         requestString  += "metric";
435     else
436         requestString += "imperial";
437 
438     const QLocale loc(locale());
439 
440     // ToDo: make this request-able
441     if (QLocale::C != loc.language() && QLocale::AnyLanguage != loc.language()) {
442         requestString += "&language=";
443         requestString += loc.name();
444         //If the first language isn't supported, english will be selected automatically
445         if (QLocale::English != loc.language())
446             requestString += ",en_US";
447     }
448 
449     return requestString;
450 }
451 
trimDouble(double degree,int decimalDigits)452 QString QGeoRoutingManagerEngineNokia::trimDouble(double degree, int decimalDigits)
453 {
454     QString sDegree = QString::number(degree, 'g', decimalDigits);
455 
456     int index = sDegree.indexOf('.');
457 
458     if (index == -1)
459         return sDegree;
460     else
461         return QString::number(degree, 'g', decimalDigits + index);
462 }
463 
routeFinished()464 void QGeoRoutingManagerEngineNokia::routeFinished()
465 {
466     QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
467 
468     if (!reply)
469         return;
470 
471     if (receivers(SIGNAL(finished(QGeoRouteReply*))) == 0) {
472         reply->deleteLater();
473         return;
474     }
475 
476     emit finished(reply);
477 }
478 
routeError(QGeoRouteReply::Error error,const QString & errorString)479 void QGeoRoutingManagerEngineNokia::routeError(QGeoRouteReply::Error error, const QString &errorString)
480 {
481     QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
482 
483     if (!reply)
484         return;
485 
486     if (receivers(SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString))) == 0) {
487         reply->deleteLater();
488         return;
489     }
490 
491     emit this->error(reply, error, errorString);
492 }
493 
494 QT_END_NAMESPACE
495