Source code
Revision control
Copy as Markdown
// Copyright (C) 2015 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qgeoroutingmanagerengine_nokia.h"
#include "qgeoroutereply_nokia.h"
#include "qgeonetworkaccessmanager.h"
#include "qgeouriprovider.h"
#include "uri_constants.h"
#include <QStringList>
#include <QUrl>
#include <QLocale>
#include <QtPositioning/QGeoRectangle>
QT_BEGIN_NAMESPACE
QGeoRoutingManagerEngineNokia::QGeoRoutingManagerEngineNokia(
QGeoNetworkAccessManager *networkManager,
const QVariantMap ¶meters,
QGeoServiceProvider::Error *error,
QString *errorString)
: QGeoRoutingManagerEngine(parameters)
, m_networkManager(networkManager)
, m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.routing.host"), ROUTING_HOST))
{
Q_ASSERT(networkManager);
m_networkManager->setParent(this);
m_apiKey = parameters.value(QStringLiteral("here.apiKey")).toString();
QGeoRouteRequest::FeatureTypes featureTypes;
featureTypes |= QGeoRouteRequest::TollFeature;
featureTypes |= QGeoRouteRequest::HighwayFeature;
featureTypes |= QGeoRouteRequest::FerryFeature;
featureTypes |= QGeoRouteRequest::TunnelFeature;
featureTypes |= QGeoRouteRequest::DirtRoadFeature;
featureTypes |= QGeoRouteRequest::ParksFeature;
setSupportedFeatureTypes(featureTypes);
QGeoRouteRequest::FeatureWeights featureWeights;
featureWeights |= QGeoRouteRequest::DisallowFeatureWeight;
featureWeights |= QGeoRouteRequest::AvoidFeatureWeight;
featureWeights |= QGeoRouteRequest::PreferFeatureWeight;
setSupportedFeatureWeights(featureWeights);
QGeoRouteRequest::ManeuverDetails maneuverDetails;
maneuverDetails |= QGeoRouteRequest::BasicManeuvers;
setSupportedManeuverDetails(maneuverDetails);
QGeoRouteRequest::RouteOptimizations optimizations;
optimizations |= QGeoRouteRequest::ShortestRoute;
optimizations |= QGeoRouteRequest::FastestRoute;
setSupportedRouteOptimizations(optimizations);
QGeoRouteRequest::TravelModes travelModes;
travelModes |= QGeoRouteRequest::CarTravel;
travelModes |= QGeoRouteRequest::PedestrianTravel;
travelModes |= QGeoRouteRequest::PublicTransitTravel;
travelModes |= QGeoRouteRequest::BicycleTravel;
setSupportedTravelModes(travelModes);
QGeoRouteRequest::SegmentDetails segmentDetails;
segmentDetails |= QGeoRouteRequest::BasicSegmentData;
setSupportedSegmentDetails(segmentDetails);
if (error)
*error = QGeoServiceProvider::NoError;
if (errorString)
*errorString = QString();
}
QGeoRoutingManagerEngineNokia::~QGeoRoutingManagerEngineNokia() {}
QGeoRouteReply *QGeoRoutingManagerEngineNokia::calculateRoute(const QGeoRouteRequest &request)
{
const QStringList reqStrings = calculateRouteRequestString(request);
if (reqStrings.isEmpty()) {
QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this);
emit errorOccurred(reply, reply->error(), reply->errorString());
return reply;
}
QList<QNetworkReply*> replies;
for (const QString &reqString : reqStrings)
replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString))));
QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(request, replies, this);
connect(reply, &QGeoRouteReplyNokia::finished,
this, &QGeoRoutingManagerEngineNokia::routeFinished);
connect(reply, &QGeoRouteReplyNokia::errorOccurred,
this, &QGeoRoutingManagerEngineNokia::routeError);
return reply;
}
QGeoRouteReply *QGeoRoutingManagerEngineNokia::updateRoute(const QGeoRoute &route, const QGeoCoordinate &position)
{
const QStringList reqStrings = updateRouteRequestString(route, position);
if (reqStrings.isEmpty()) {
QGeoRouteReply *reply = new QGeoRouteReply(QGeoRouteReply::UnsupportedOptionError, "The given route request options are not supported by this service provider.", this);
emit errorOccurred(reply, reply->error(), reply->errorString());
return reply;
}
QList<QNetworkReply*> replies;
for (const QString &reqString : reqStrings)
replies.append(m_networkManager->get(QNetworkRequest(QUrl(reqString))));
QGeoRouteRequest updateRequest(route.request());
updateRequest.setTravelModes(route.travelMode());
QGeoRouteReplyNokia *reply = new QGeoRouteReplyNokia(updateRequest, replies, this);
connect(reply, &QGeoRouteReplyNokia::finished,
this, &QGeoRoutingManagerEngineNokia::routeFinished);
connect(reply, &QGeoRouteReplyNokia::errorOccurred,
this, &QGeoRoutingManagerEngineNokia::routeError);
return reply;
}
bool QGeoRoutingManagerEngineNokia::checkEngineSupport(const QGeoRouteRequest &request,
QGeoRouteRequest::TravelModes travelModes) const
{
const QList<QGeoRouteRequest::FeatureType> featureTypeList = request.featureTypes();
QGeoRouteRequest::FeatureTypes featureTypeFlag = QGeoRouteRequest::NoFeature;
QGeoRouteRequest::FeatureWeights featureWeightFlag = QGeoRouteRequest::NeutralFeatureWeight;
for (const auto &featureType : featureTypeList) {
featureTypeFlag |= featureType;
featureWeightFlag |= request.featureWeight(featureType);
}
if ((featureTypeFlag & supportedFeatureTypes()) != featureTypeFlag)
return false;
if ((featureWeightFlag & supportedFeatureWeights()) != featureWeightFlag)
return false;
if ((request.maneuverDetail() & supportedManeuverDetails()) != request.maneuverDetail())
return false;
if ((request.segmentDetail() & supportedSegmentDetails()) != request.segmentDetail())
return false;
if ((request.routeOptimization() & supportedRouteOptimizations()) != request.routeOptimization())
return false;
if ((travelModes & supportedTravelModes()) != travelModes)
return false;
// Count the number of set bits (= number of travel modes) (popcount)
int count = 0;
for (unsigned bits = travelModes; bits; bits >>= 1)
count += (bits & 1);
// We only allow one travel mode at a time
if (count != 1)
return false;
return true;
}
QStringList QGeoRoutingManagerEngineNokia::calculateRouteRequestString(const QGeoRouteRequest &request) const
{
bool supported = checkEngineSupport(request, request.travelModes());
if (!supported)
return QStringList();
QStringList requests;
QString baseRequest = QStringLiteral("https://");
baseRequest += m_uriProvider->getCurrentHost();
baseRequest += QStringLiteral("/routing/7.2/calculateroute.xml");
baseRequest += QStringLiteral("?alternatives=");
baseRequest += QString::number(request.numberAlternativeRoutes());
if (!m_apiKey.isEmpty()) {
baseRequest += QStringLiteral("&apiKey=");
baseRequest += m_apiKey;
}
const QList<QGeoCoordinate> waypoints = request.waypoints();
int numWaypoints = waypoints.size();
if (numWaypoints < 2)
return QStringList();
// Details: https://developer.here.com/documentation/routing/topics/resource-param-type-waypoint.html
for (int i = 0;i < numWaypoints;++i) {
const QGeoCoordinate &c = waypoints.at(i);
baseRequest += QStringLiteral("&waypoint");
baseRequest += QString::number(i);
baseRequest += QStringLiteral("=geo!");
baseRequest += trimDouble(c.latitude());
baseRequest += ',';
baseRequest += trimDouble(c.longitude());
baseRequest += QStringLiteral(";;"); // ;<TransitRadius>;<UserLabel>
}
QGeoRouteRequest::RouteOptimizations optimization = request.routeOptimization();
QStringList types;
if (optimization.testFlag(QGeoRouteRequest::ShortestRoute))
types.append("shortest");
if (optimization.testFlag(QGeoRouteRequest::FastestRoute))
types.append("fastest");
for (const QString &optimization : types) {
QString requestString = baseRequest;
requestString += modesRequestString(request, request.travelModes(), optimization);
requestString += routeRequestString(request);
requests << requestString;
}
return requests;
}
QStringList QGeoRoutingManagerEngineNokia::updateRouteRequestString(const QGeoRoute &route, const QGeoCoordinate &position)
{
if (!checkEngineSupport(route.request(), route.travelMode()))
return QStringList();
QStringList requests;
QString baseRequest = "https://";
baseRequest += m_uriProvider->getCurrentHost();
baseRequest += "/routing/7.2/getroute.xml";
baseRequest += "?routeid=";
baseRequest += route.routeId();
baseRequest += "&pos=";
baseRequest += QString::number(position.latitude());
baseRequest += ',';
baseRequest += QString::number(position.longitude());
QGeoRouteRequest::RouteOptimizations optimization = route.request().routeOptimization();
QStringList types;
if (optimization.testFlag(QGeoRouteRequest::ShortestRoute))
types.append("shortest");
if (optimization.testFlag(QGeoRouteRequest::FastestRoute))
types.append("fastest");
for (const QString &optimization : types) {
QString requestString = baseRequest;
requestString += modesRequestString(route.request(), route.travelMode(), optimization);
requestString += routeRequestString(route.request());
requests << requestString;
}
return requests;
}
QString QGeoRoutingManagerEngineNokia::modesRequestString(const QGeoRouteRequest &request,
QGeoRouteRequest::TravelModes travelModes, const QString &optimization) const
{
QString requestString;
QStringList modes;
if (travelModes.testFlag(QGeoRouteRequest::CarTravel))
modes.append("car");
if (travelModes.testFlag(QGeoRouteRequest::PedestrianTravel))
modes.append("pedestrian");
if (travelModes.testFlag(QGeoRouteRequest::PublicTransitTravel))
modes.append("publicTransport");
QStringList featureStrings;
QList<QGeoRouteRequest::FeatureType> featureTypes = request.featureTypes();
for (int i = 0; i < featureTypes.size(); ++i) {
QGeoRouteRequest::FeatureWeight weight = request.featureWeight(featureTypes.at(i));
if (weight == QGeoRouteRequest::NeutralFeatureWeight)
continue;
QString weightString = "";
switch (weight) {
case QGeoRouteRequest::PreferFeatureWeight:
weightString = '1';
break;
case QGeoRouteRequest::AvoidFeatureWeight:
weightString = "-1";
break;
case QGeoRouteRequest::DisallowFeatureWeight:
weightString = "-3";
break;
case QGeoRouteRequest::NeutralFeatureWeight:
case QGeoRouteRequest::RequireFeatureWeight:
break;
}
if (weightString.isEmpty())
continue;
switch (featureTypes.at(i)) {
case QGeoRouteRequest::TollFeature:
featureStrings.append("tollroad:" + weightString);
break;
case QGeoRouteRequest::HighwayFeature:
featureStrings.append("motorway:" + weightString);
break;
case QGeoRouteRequest::FerryFeature:
featureStrings.append("boatFerry:" + weightString);
featureStrings.append("railFerry:" + weightString);
break;
case QGeoRouteRequest::TunnelFeature:
featureStrings.append("tunnel:" + weightString);
break;
case QGeoRouteRequest::DirtRoadFeature:
featureStrings.append("dirtRoad:" + weightString);
break;
case QGeoRouteRequest::PublicTransitFeature:
case QGeoRouteRequest::ParksFeature:
case QGeoRouteRequest::MotorPoolLaneFeature:
case QGeoRouteRequest::TrafficFeature:
case QGeoRouteRequest::NoFeature:
break;
}
}
requestString += "&mode=";
requestString += optimization + ';' + modes.join(',');
if (featureStrings.count())
requestString += ';' + featureStrings.join(',');
return requestString;
}
QString QGeoRoutingManagerEngineNokia::routeRequestString(const QGeoRouteRequest &request) const
{
QString requestString;
for (const QGeoRectangle &area : request.excludeAreas()) {
requestString += QLatin1String("&avoidareas=");
requestString += trimDouble(area.topLeft().latitude());
requestString += QLatin1String(",");
requestString += trimDouble(area.topLeft().longitude());
requestString += QLatin1String(";");
requestString += trimDouble(area.bottomRight().latitude());
requestString += QLatin1String(",");
requestString += trimDouble(area.bottomRight().longitude());
}
QStringList legAttributes;
// if (request.segmentDetail() & QGeoRouteRequest::BasicSegmentData) // QTBUG-70501, this code expects to find links
{
requestString += "&linkattributes=sh,le"; //shape,length
legAttributes.append("links");
}
// if (request.maneuverDetail() & QGeoRouteRequest::BasicManeuvers) // QTBUG-70501, this code expects to find maneuvers
{
legAttributes.append("maneuvers");
//requestString += "&maneuverattributes=po,tt,le,di"; //position,traveltime,length,direction
requestString += "&maneuverattributes=all";
if (!(request.segmentDetail() & QGeoRouteRequest::NoSegmentData))
requestString += ",li"; //link
}
// Handle QTBUG-70502, when API fixes it
requestString += "&routeattributes=sm,sh,bb,lg"; //summary,shape,boundingBox,legs
if (legAttributes.count() > 0) {
requestString += "&legattributes=";
requestString += legAttributes.join(",");
}
// Handle QTBUG-70503, when API fixes it
requestString += "&departure=";
requestString += QDateTime::currentDateTime().toUTC().toString("yyyy-MM-ddThh:mm:ssZ");
requestString += "&instructionformat=text";
// ToDo: make this request-able
requestString += "&metricSystem=";
if (QLocale::MetricSystem == measurementSystem())
requestString += "metric";
else
requestString += "imperial";
const QLocale loc(locale());
// ToDo: make this request-able
if (QLocale::C != loc.language() && QLocale::AnyLanguage != loc.language()) {
requestString += "&language=";
requestString += loc.name();
//If the first language isn't supported, english will be selected automatically
if (QLocale::English != loc.language())
requestString += ",en_US";
}
return requestString;
}
QString QGeoRoutingManagerEngineNokia::trimDouble(double degree, int decimalDigits)
{
QString sDegree = QString::number(degree, 'g', decimalDigits);
int index = sDegree.indexOf('.');
if (index == -1)
return sDegree;
else
return QString::number(degree, 'g', decimalDigits + index);
}
void QGeoRoutingManagerEngineNokia::routeFinished()
{
QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
if (!reply)
return;
if (receivers(SIGNAL(finished(QGeoRouteReply*))) == 0) {
reply->deleteLater();
return;
}
emit finished(reply);
}
void QGeoRoutingManagerEngineNokia::routeError(QGeoRouteReply::Error error, const QString &errorString)
{
QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
if (!reply)
return;
if (receivers(SIGNAL(errorOccurred(QGeoRouteReply*,QGeoRouteReply::Error,QString))) == 0) {
reply->deleteLater();
return;
}
emit errorOccurred(reply, error, errorString);
}
QT_END_NAMESPACE