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 | |
50 | QGeoRoutingManagerEngineNokia::QGeoRoutingManagerEngineNokia( |
51 | QGeoNetworkAccessManager *networkManager, |
52 | const QVariantMap ¶meters, |
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 | |
108 | QGeoRoutingManagerEngineNokia::~QGeoRoutingManagerEngineNokia() {} |
109 | |
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, error: reply->error(), errorString: reply->errorString()); |
117 | return reply; |
118 | } |
119 | |
120 | QList<QNetworkReply*> replies; |
121 | foreach (const QString &reqString, reqStrings) |
122 | replies.append(t: m_networkManager->get(request: QNetworkRequest(QUrl(reqString)))); |
123 | |
124 | QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(request, replies, this); |
125 | |
126 | connect(sender: reply, |
127 | SIGNAL(finished()), |
128 | receiver: this, |
129 | SLOT(routeFinished())); |
130 | |
131 | connect(sender: reply, |
132 | SIGNAL(error(QGeoRouteReply::Error,QString)), |
133 | receiver: this, |
134 | SLOT(routeError(QGeoRouteReply::Error,QString))); |
135 | |
136 | return reply; |
137 | } |
138 | |
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, error: reply->error(), errorString: reply->errorString()); |
146 | return reply; |
147 | } |
148 | |
149 | QList<QNetworkReply*> replies; |
150 | foreach (const QString &reqString, reqStrings) |
151 | replies.append(t: m_networkManager->get(request: 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(sender: reply, |
158 | SIGNAL(finished()), |
159 | receiver: this, |
160 | SLOT(routeFinished())); |
161 | |
162 | connect(sender: reply, |
163 | SIGNAL(error(QGeoRouteReply::Error,QString)), |
164 | receiver: this, |
165 | SLOT(routeError(QGeoRouteReply::Error,QString))); |
166 | |
167 | return reply; |
168 | } |
169 | |
170 | 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(featureType: 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 | |
214 | QStringList QGeoRoutingManagerEngineNokia::calculateRouteRequestString(const QGeoRouteRequest &request) |
215 | { |
216 | bool supported = checkEngineSupport(request, travelModes: 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(degree: c.latitude()); |
248 | baseRequest += ','; |
249 | baseRequest += trimDouble(degree: 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(flag: QGeoRouteRequest::ShortestRoute)) |
264 | types.append(t: "shortest" ); |
265 | if (optimization.testFlag(flag: QGeoRouteRequest::FastestRoute)) |
266 | types.append(t: "fastest" ); |
267 | |
268 | foreach (const QString &optimization, types) { |
269 | QString requestString = baseRequest; |
270 | requestString += modesRequestString(request, travelModes: request.travelModes(), optimization); |
271 | requestString += routeRequestString(request); |
272 | requests << requestString; |
273 | } |
274 | |
275 | return requests; |
276 | } |
277 | |
278 | QStringList QGeoRoutingManagerEngineNokia::updateRouteRequestString(const QGeoRoute &route, const QGeoCoordinate &position) |
279 | { |
280 | if (!checkEngineSupport(request: route.request(), travelModes: 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(flag: QGeoRouteRequest::ShortestRoute)) |
300 | types.append(t: "shortest" ); |
301 | if (optimization.testFlag(flag: QGeoRouteRequest::FastestRoute)) |
302 | types.append(t: "fastest" ); |
303 | |
304 | foreach (const QString &optimization, types) { |
305 | QString requestString = baseRequest; |
306 | requestString += modesRequestString(request: route.request(), travelModes: route.travelMode(), optimization); |
307 | requestString += routeRequestString(request: route.request()); |
308 | requests << requestString; |
309 | } |
310 | |
311 | return requests; |
312 | } |
313 | |
314 | 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(flag: QGeoRouteRequest::CarTravel)) |
321 | modes.append(t: "car" ); |
322 | if (travelModes.testFlag(flag: QGeoRouteRequest::PedestrianTravel)) |
323 | modes.append(t: "pedestrian" ); |
324 | if (travelModes.testFlag(flag: QGeoRouteRequest::PublicTransitTravel)) |
325 | modes.append(t: "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(featureType: 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(t: "tollroad:" + weightString); |
357 | break; |
358 | case QGeoRouteRequest::HighwayFeature: |
359 | featureStrings.append(t: "motorway:" + weightString); |
360 | break; |
361 | case QGeoRouteRequest::FerryFeature: |
362 | featureStrings.append(t: "boatFerry:" + weightString); |
363 | featureStrings.append(t: "railFerry:" + weightString); |
364 | break; |
365 | case QGeoRouteRequest::TunnelFeature: |
366 | featureStrings.append(t: "tunnel:" + weightString); |
367 | break; |
368 | case QGeoRouteRequest::DirtRoadFeature: |
369 | featureStrings.append(t: "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(sep: ','); |
382 | if (featureStrings.count()) |
383 | requestString += ';' + featureStrings.join(sep: ','); |
384 | return requestString; |
385 | } |
386 | |
387 | 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(degree: area.topLeft().latitude()); |
394 | requestString += QLatin1String("," ); |
395 | requestString += trimDouble(degree: area.topLeft().longitude()); |
396 | requestString += QLatin1String(";" ); |
397 | requestString += trimDouble(degree: area.bottomRight().latitude()); |
398 | requestString += QLatin1String("," ); |
399 | requestString += trimDouble(degree: 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(t: "links" ); |
407 | } |
408 | |
409 | // if (request.maneuverDetail() & QGeoRouteRequest::BasicManeuvers) // QTBUG-70501, this code expects to find maneuvers |
410 | { |
411 | legAttributes.append(t: "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(sep: "," ); |
423 | } |
424 | |
425 | // Handle QTBUG-70503, when API fixes it |
426 | requestString += "&departure=" ; |
427 | requestString += QDateTime::currentDateTime().toUTC().toString(format: "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 | |
452 | QString QGeoRoutingManagerEngineNokia::trimDouble(double degree, int decimalDigits) |
453 | { |
454 | QString sDegree = QString::number(degree, f: 'g', prec: decimalDigits); |
455 | |
456 | int index = sDegree.indexOf(c: '.'); |
457 | |
458 | if (index == -1) |
459 | return sDegree; |
460 | else |
461 | return QString::number(degree, f: 'g', prec: decimalDigits + index); |
462 | } |
463 | |
464 | void QGeoRoutingManagerEngineNokia::routeFinished() |
465 | { |
466 | QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(object: 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 | |
479 | void QGeoRoutingManagerEngineNokia::routeError(QGeoRouteReply::Error error, const QString &errorString) |
480 | { |
481 | QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(object: 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 | |