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 &parameters,
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