1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Vlad Seryakov <vseryakov@gmail.com>
4 ** Copyright (C) 2016 Aaron McCarthy <mccarthy.aaron@gmail.com>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtLocation module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "qgeoroutingmanagerenginemapbox.h"
42 #include "qgeoroutereplymapbox.h"
43 #include "qmapboxcommon.h"
44 #include <QtLocation/private/qgeorouteparserosrmv5_p.h>
45 #include <QtLocation/qgeoroutesegment.h>
46 #include <QtLocation/qgeomaneuver.h>
47
48 #include <QtCore/QJsonDocument>
49 #include <QtCore/QJsonObject>
50 #include <QtCore/QJsonArray>
51 #include <QtCore/QUrlQuery>
52 #include <QtCore/QDebug>
53
54 QT_BEGIN_NAMESPACE
55
56 class QGeoRouteParserOsrmV5ExtensionMapbox: public QGeoRouteParserOsrmV5Extension
57 {
58 public:
59 QGeoRouteParserOsrmV5ExtensionMapbox(const QString &accessToken, bool useMapboxTextInstructions);
60 void updateQuery(QUrlQuery &query) const override;
61 void updateSegment(QGeoRouteSegment &segment, const QJsonObject &step, const QJsonObject &maneuver) const override;
62
63 QString m_accessToken;
64 bool m_useMapboxTextInstructions = false;
65 };
66
QGeoRouteParserOsrmV5ExtensionMapbox(const QString & accessToken,bool useMapboxTextInstructions)67 QGeoRouteParserOsrmV5ExtensionMapbox::QGeoRouteParserOsrmV5ExtensionMapbox(const QString &accessToken, bool useMapboxTextInstructions)
68 : QGeoRouteParserOsrmV5Extension(), m_accessToken(accessToken), m_useMapboxTextInstructions(useMapboxTextInstructions)
69 {
70
71 }
72
updateQuery(QUrlQuery & query) const73 void QGeoRouteParserOsrmV5ExtensionMapbox::updateQuery(QUrlQuery &query) const
74 {
75 if (!m_accessToken.isEmpty())
76 query.addQueryItem(QLatin1String("access_token"), m_accessToken);
77
78 query.addQueryItem(QLatin1String("annotations"), QLatin1String("duration,distance,speed,congestion"));
79
80 query.addQueryItem(QLatin1String("voice_instructions"), QLatin1String("true"));
81 query.addQueryItem(QLatin1String("banner_instructions"), QLatin1String("true"));
82 query.addQueryItem(QLatin1String("roundabout_exits"), QLatin1String("true"));
83
84 QLocale::MeasurementSystem unit = QLocale::system().measurementSystem();
85 query.addQueryItem(QLatin1String("voice_units"), unit == QLocale::MetricSystem ? QLatin1String("metric") : QLatin1String("imperial"));
86 }
87
parseMapboxVoiceInstruction(const QJsonObject & voiceInstruction)88 static QVariantMap parseMapboxVoiceInstruction(const QJsonObject &voiceInstruction)
89 {
90 QVariantMap map;
91
92 if (voiceInstruction.value(QLatin1String("distanceAlongGeometry")).isDouble())
93 map.insert(QLatin1String("distance_along_geometry"), voiceInstruction.value(QLatin1String("distanceAlongGeometry")).toDouble());
94
95 if (voiceInstruction.value(QLatin1String("announcement")).isString())
96 map.insert(QLatin1String("announcement"), voiceInstruction.value(QLatin1String("announcement")).toString());
97
98 if (voiceInstruction.value(QLatin1String("ssmlAnnouncement")).isString())
99 map.insert(QLatin1String("ssml_announcement"), voiceInstruction.value(QLatin1String("ssmlAnnouncement")).toString());
100
101 return map;
102 }
103
parseMapboxVoiceInstructions(const QJsonArray & voiceInstructions)104 static QVariantList parseMapboxVoiceInstructions(const QJsonArray &voiceInstructions)
105 {
106 QVariantList list;
107 for (const QJsonValue &voiceInstructionValue : voiceInstructions) {
108 if (voiceInstructionValue.isObject())
109 list << parseMapboxVoiceInstruction(voiceInstructionValue.toObject());
110 }
111 return list;
112 }
113
parseMapboxBannerComponent(const QJsonObject & bannerComponent)114 static QVariantMap parseMapboxBannerComponent(const QJsonObject &bannerComponent)
115 {
116 QVariantMap map;
117
118 if (bannerComponent.value(QLatin1String("type")).isString())
119 map.insert(QLatin1String("type"), bannerComponent.value(QLatin1String("type")).toString());
120
121 if (bannerComponent.value(QLatin1String("text")).isString())
122 map.insert(QLatin1String("text"), bannerComponent.value(QLatin1String("text")).toString());
123
124 if (bannerComponent.value(QLatin1String("abbr")).isString())
125 map.insert(QLatin1String("abbr"), bannerComponent.value(QLatin1String("abbr")).toString());
126
127 if (bannerComponent.value(QLatin1String("abbr_priority")).isDouble())
128 map.insert(QLatin1String("abbr_priority"), bannerComponent.value(QLatin1String("abbr_priority")).toInt());
129
130 return map;
131 }
132
parseMapboxBannerComponents(const QJsonArray & bannerComponents)133 static QVariantList parseMapboxBannerComponents(const QJsonArray &bannerComponents)
134 {
135 QVariantList list;
136 for (const QJsonValue &bannerComponentValue : bannerComponents) {
137 if (bannerComponentValue.isObject())
138 list << parseMapboxBannerComponent(bannerComponentValue.toObject());
139 }
140 return list;
141 }
142
parseMapboxBanner(const QJsonObject & banner)143 static QVariantMap parseMapboxBanner(const QJsonObject &banner)
144 {
145 QVariantMap map;
146
147 if (banner.value(QLatin1String("text")).isString())
148 map.insert(QLatin1String("text"), banner.value(QLatin1String("text")).toString());
149
150 if (banner.value(QLatin1String("components")).isArray())
151 map.insert(QLatin1String("components"), parseMapboxBannerComponents(banner.value(QLatin1String("components")).toArray()));
152
153 if (banner.value(QLatin1String("type")).isString())
154 map.insert(QLatin1String("type"), banner.value(QLatin1String("type")).toString());
155
156 if (banner.value(QLatin1String("modifier")).isString())
157 map.insert(QLatin1String("modifier"), banner.value(QLatin1String("modifier")).toString());
158
159 if (banner.value(QLatin1String("degrees")).isDouble())
160 map.insert(QLatin1String("degrees"), banner.value(QLatin1String("degrees")).toDouble());
161
162 if (banner.value(QLatin1String("driving_side")).isString())
163 map.insert(QLatin1String("driving_side"), banner.value(QLatin1String("driving_side")).toString());
164
165 return map;
166 }
167
parseMapboxBannerInstruction(const QJsonObject & bannerInstruction)168 static QVariantMap parseMapboxBannerInstruction(const QJsonObject &bannerInstruction)
169 {
170 QVariantMap map;
171
172 if (bannerInstruction.value(QLatin1String("distanceAlongGeometry")).isDouble())
173 map.insert(QLatin1String("distance_along_geometry"), bannerInstruction.value(QLatin1String("distanceAlongGeometry")).toDouble());
174
175 if (bannerInstruction.value(QLatin1String("primary")).isObject())
176 map.insert(QLatin1String("primary"), parseMapboxBanner(bannerInstruction.value(QLatin1String("primary")).toObject()));
177
178 if (bannerInstruction.value(QLatin1String("secondary")).isObject())
179 map.insert(QLatin1String("secondary"), parseMapboxBanner(bannerInstruction.value(QLatin1String("secondary")).toObject()));
180
181 if (bannerInstruction.value(QLatin1String("then")).isObject())
182 map.insert(QLatin1String("then"), parseMapboxBanner(bannerInstruction.value(QLatin1String("then")).toObject()));
183
184 return map;
185 }
186
parseMapboxBannerInstructions(const QJsonArray & bannerInstructions)187 static QVariantList parseMapboxBannerInstructions(const QJsonArray &bannerInstructions)
188 {
189 QVariantList list;
190 for (const QJsonValue &bannerInstructionValue : bannerInstructions) {
191 if (bannerInstructionValue.isObject())
192 list << parseMapboxBannerInstruction(bannerInstructionValue.toObject());
193 }
194 return list;
195 }
196
updateSegment(QGeoRouteSegment & segment,const QJsonObject & step,const QJsonObject & maneuver) const197 void QGeoRouteParserOsrmV5ExtensionMapbox::updateSegment(QGeoRouteSegment &segment, const QJsonObject &step, const QJsonObject &maneuver) const
198 {
199 QGeoManeuver m = segment.maneuver();
200 QVariantMap extendedAttributes = m.extendedAttributes();
201 if (m_useMapboxTextInstructions && maneuver.value(QLatin1String("instruction")).isString()) {
202 QString maneuverInstructionText = maneuver.value(QLatin1String("instruction")).toString();
203 if (!maneuverInstructionText.isEmpty())
204 m.setInstructionText(maneuverInstructionText);
205 }
206
207 if (step.value(QLatin1String("voiceInstructions")).isArray())
208 extendedAttributes.insert(QLatin1String("mapbox.voice_instructions"),
209 parseMapboxVoiceInstructions(step.value(QLatin1String("voiceInstructions")).toArray()));
210 if (step.value(QLatin1String("bannerInstructions")).isArray())
211 extendedAttributes.insert(QLatin1String("mapbox.banner_instructions"),
212 parseMapboxBannerInstructions(step.value(QLatin1String("bannerInstructions")).toArray()));
213
214 m.setExtendedAttributes(extendedAttributes);
215 segment.setManeuver(m);
216 }
217
218
QGeoRoutingManagerEngineMapbox(const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)219 QGeoRoutingManagerEngineMapbox::QGeoRoutingManagerEngineMapbox(const QVariantMap ¶meters,
220 QGeoServiceProvider::Error *error,
221 QString *errorString)
222 : QGeoRoutingManagerEngine(parameters),
223 m_networkManager(new QNetworkAccessManager(this)),
224 m_userAgent(mapboxDefaultUserAgent)
225 {
226 if (parameters.contains(QStringLiteral("mapbox.useragent"))) {
227 m_userAgent = parameters.value(QStringLiteral("mapbox.useragent")).toString().toLatin1();
228 }
229
230 if (parameters.contains(QStringLiteral("mapbox.access_token"))) {
231 m_accessToken = parameters.value(QStringLiteral("mapbox.access_token")).toString();
232 }
233
234 bool use_mapbox_text_instructions = true;
235 if (parameters.contains(QStringLiteral("mapbox.routing.use_mapbox_text_instructions"))) {
236 use_mapbox_text_instructions = parameters.value(QStringLiteral("mapbox.routing.use_mapbox_text_instructions")).toBool();
237 }
238
239 QGeoRouteParserOsrmV5 *parser = new QGeoRouteParserOsrmV5(this);
240 parser->setExtension(new QGeoRouteParserOsrmV5ExtensionMapbox(m_accessToken, use_mapbox_text_instructions));
241 if (parameters.contains(QStringLiteral("mapbox.routing.traffic_side"))) {
242 QString trafficSide = parameters.value(QStringLiteral("mapbox.routing.traffic_side")).toString();
243 if (trafficSide == QStringLiteral("right"))
244 parser->setTrafficSide(QGeoRouteParser::RightHandTraffic);
245 else if (trafficSide == QStringLiteral("left"))
246 parser->setTrafficSide(QGeoRouteParser::LeftHandTraffic);
247 }
248 m_routeParser = parser;
249
250 *error = QGeoServiceProvider::NoError;
251 errorString->clear();
252 }
253
~QGeoRoutingManagerEngineMapbox()254 QGeoRoutingManagerEngineMapbox::~QGeoRoutingManagerEngineMapbox()
255 {
256 }
257
calculateRoute(const QGeoRouteRequest & request)258 QGeoRouteReply* QGeoRoutingManagerEngineMapbox::calculateRoute(const QGeoRouteRequest &request)
259 {
260 QNetworkRequest networkRequest;
261 networkRequest.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent);
262
263 QString url = mapboxDirectionsApiPath;
264
265 QGeoRouteRequest::TravelModes travelModes = request.travelModes();
266 if (travelModes.testFlag(QGeoRouteRequest::PedestrianTravel)) {
267 url += QStringLiteral("walking/");
268 } else if (travelModes.testFlag(QGeoRouteRequest::BicycleTravel)) {
269 url += QStringLiteral("cycling/");
270 } else if (travelModes.testFlag(QGeoRouteRequest::CarTravel)) {
271 const QList<QGeoRouteRequest::FeatureType> &featureTypes = request.featureTypes();
272 int trafficFeatureIdx = featureTypes.indexOf(QGeoRouteRequest::TrafficFeature);
273 QGeoRouteRequest::FeatureWeight trafficWeight = request.featureWeight(QGeoRouteRequest::TrafficFeature);
274 if (trafficFeatureIdx >= 0 &&
275 (trafficWeight == QGeoRouteRequest::AvoidFeatureWeight || trafficWeight == QGeoRouteRequest::DisallowFeatureWeight)) {
276 url += QStringLiteral("driving-traffic/");
277 } else {
278 url += QStringLiteral("driving/");
279 }
280 }
281
282 networkRequest.setUrl(m_routeParser->requestUrl(request, url));
283
284 QNetworkReply *reply = m_networkManager->get(networkRequest);
285
286 QGeoRouteReplyMapbox *routeReply = new QGeoRouteReplyMapbox(reply, request, this);
287
288 connect(routeReply, SIGNAL(finished()), this, SLOT(replyFinished()));
289 connect(routeReply, SIGNAL(error(QGeoRouteReply::Error,QString)),
290 this, SLOT(replyError(QGeoRouteReply::Error,QString)));
291
292 return routeReply;
293 }
294
routeParser() const295 const QGeoRouteParser *QGeoRoutingManagerEngineMapbox::routeParser() const
296 {
297 return m_routeParser;
298 }
299
replyFinished()300 void QGeoRoutingManagerEngineMapbox::replyFinished()
301 {
302 QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
303 if (reply)
304 emit finished(reply);
305 }
306
replyError(QGeoRouteReply::Error errorCode,const QString & errorString)307 void QGeoRoutingManagerEngineMapbox::replyError(QGeoRouteReply::Error errorCode,
308 const QString &errorString)
309 {
310 QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
311 if (reply)
312 emit error(reply, errorCode, errorString);
313 }
314
315 QT_END_NAMESPACE
316