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 "qgeocodingmanagerengine_nokia.h"
38 #include "qgeocodereply_nokia.h"
39 #include "marclanguagecodes.h"
40 #include "qgeonetworkaccessmanager.h"
41 #include "qgeouriprovider.h"
42 #include "uri_constants.h"
43 
44 #include <QtPositioning/QGeoAddress>
45 #include <QtPositioning/QGeoCoordinate>
46 #include <QtPositioning/QGeoCircle>
47 #include <QtPositioning/QGeoRectangle>
48 #include <QtPositioning/QGeoShape>
49 
50 #include <QUrl>
51 #include <QMap>
52 #include <QStringList>
53 
54 QT_BEGIN_NAMESPACE
55 
QGeoCodingManagerEngineNokia(QGeoNetworkAccessManager * networkManager,const QVariantMap & parameters,QGeoServiceProvider::Error * error,QString * errorString)56 QGeoCodingManagerEngineNokia::QGeoCodingManagerEngineNokia(
57         QGeoNetworkAccessManager *networkManager,
58         const QVariantMap &parameters,
59         QGeoServiceProvider::Error *error,
60         QString *errorString)
61         : QGeoCodingManagerEngine(parameters)
62         , m_networkManager(networkManager)
63         , m_uriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.geocoding.host"), GEOCODING_HOST))
64         , m_reverseGeocodingUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.reversegeocoding.host"), REVERSE_GEOCODING_HOST))
65 {
66     Q_ASSERT(networkManager);
67     m_networkManager->setParent(this);
68 
69     if (parameters.contains(QStringLiteral("here.token")))
70         m_token = parameters.value(QStringLiteral("here.token")).toString();
71 
72     if (parameters.contains(QStringLiteral("here.app_id")))
73         m_applicationId = parameters.value(QStringLiteral("here.app_id")).toString();
74 
75     if (error)
76         *error = QGeoServiceProvider::NoError;
77 
78     if (errorString)
79         *errorString = "";
80 }
81 
~QGeoCodingManagerEngineNokia()82 QGeoCodingManagerEngineNokia::~QGeoCodingManagerEngineNokia() {}
83 
getAuthenticationString() const84 QString QGeoCodingManagerEngineNokia::getAuthenticationString() const
85 {
86     QString authenticationString;
87 
88     if (!m_token.isEmpty() && !m_applicationId.isEmpty()) {
89         authenticationString += "?app_code=";
90         authenticationString += m_token;
91 
92         authenticationString += "&app_id=";
93         authenticationString += m_applicationId;
94     }
95 
96     return authenticationString;
97 }
98 
99 
geocode(const QGeoAddress & address,const QGeoShape & bounds)100 QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QGeoAddress &address,
101                                                      const QGeoShape &bounds)
102 {
103     QString requestString = "https://";
104     requestString += m_uriProvider->getCurrentHost();
105     requestString += "/6.2/geocode.json";
106 
107     requestString += getAuthenticationString();
108     requestString += "&gen=9";
109 
110     requestString += "&language=";
111     requestString += languageToMarc(locale().language());
112 
113     bool manualBoundsRequired = false;
114     if (bounds.type() == QGeoShape::UnknownType) {
115         manualBoundsRequired = true;
116     } else if (bounds.type() == QGeoShape::CircleType) {
117         QGeoCircle circ(bounds);
118         if (circ.isValid()) {
119             requestString += "?prox=";
120             requestString += trimDouble(circ.center().latitude());
121             requestString += ",";
122             requestString += trimDouble(circ.center().longitude());
123             requestString += ",";
124             requestString += trimDouble(circ.radius());
125         }
126     } else {
127         QGeoRectangle rect = bounds.boundingGeoRectangle();
128         if (rect.isValid()) {
129             requestString += "&bbox=";
130             requestString += trimDouble(rect.topLeft().latitude());
131             requestString += ",";
132             requestString += trimDouble(rect.topLeft().longitude());
133             requestString += ";";
134             requestString += trimDouble(rect.bottomRight().latitude());
135             requestString += ",";
136             requestString += trimDouble(rect.bottomRight().longitude());
137         }
138     }
139 
140     if (address.country().isEmpty()) {
141         QStringList parts;
142 
143         if (!address.state().isEmpty())
144             parts << address.state();
145 
146         if (!address.city().isEmpty())
147             parts << address.city();
148 
149         if (!address.postalCode().isEmpty())
150             parts << address.postalCode();
151 
152         if (!address.street().isEmpty())
153             parts << address.street();
154 
155         requestString += "&searchtext=";
156         requestString += parts.join("+").replace(' ', '+');
157     } else {
158         requestString += "&country=";
159         requestString += address.country();
160 
161         if (!address.state().isEmpty()) {
162             requestString += "&state=";
163             requestString += address.state();
164         }
165 
166         if (!address.city().isEmpty()) {
167             requestString += "&city=";
168             requestString += address.city();
169         }
170 
171         if (!address.postalCode().isEmpty()) {
172             requestString += "&postalcode=";
173             requestString += address.postalCode();
174         }
175 
176         if (!address.street().isEmpty()) {
177             requestString += "&street=";
178             requestString += address.street();
179         }
180     }
181 
182     return geocode(requestString, bounds, manualBoundsRequired);
183 }
184 
geocode(const QString & address,int limit,int offset,const QGeoShape & bounds)185 QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(const QString &address,
186                                                      int limit,
187                                                      int offset,
188                                                      const QGeoShape &bounds)
189 {
190     QString requestString = "https://";
191     requestString += m_uriProvider->getCurrentHost();
192     requestString += "/6.2/geocode.json";
193 
194     requestString += getAuthenticationString();
195     requestString += "&gen=9";
196 
197     requestString += "&language=";
198     requestString += languageToMarc(locale().language());
199 
200     requestString += "&searchtext=";
201     requestString += QString(address).replace(' ', '+');
202 
203     if (limit > 0) {
204         requestString += "&maxresults=";
205         requestString += QString::number(limit);
206     }
207     if (offset > 0) {
208         // We cannot do this precisely, since HERE doesn't allow
209         // precise result-set offset to be supplied; instead, it
210         // returns "pages" of results at a time.
211         // So, we tell HERE which page of results we want, and the
212         // client has to filter out duplicates if they changed
213         // the limit param since the last call.
214         requestString += "&pageinformation=";
215         requestString += QString::number(offset/limit);
216     }
217 
218     bool manualBoundsRequired = false;
219     if (bounds.type() == QGeoShape::RectangleType) {
220         QGeoRectangle rect(bounds);
221         if (rect.isValid()) {
222             requestString += "&bbox=";
223             requestString += trimDouble(rect.topLeft().latitude());
224             requestString += ",";
225             requestString += trimDouble(rect.topLeft().longitude());
226             requestString += ";";
227             requestString += trimDouble(rect.bottomRight().latitude());
228             requestString += ",";
229             requestString += trimDouble(rect.bottomRight().longitude());
230         }
231     } else if (bounds.type() == QGeoShape::CircleType) {
232         QGeoCircle circ(bounds);
233         if (circ.isValid()) {
234             requestString += "?prox=";
235             requestString += trimDouble(circ.center().latitude());
236             requestString += ",";
237             requestString += trimDouble(circ.center().longitude());
238             requestString += ",";
239             requestString += trimDouble(circ.radius());
240         }
241     } else {
242         manualBoundsRequired = true;
243     }
244 
245     return geocode(requestString, bounds, manualBoundsRequired, limit, offset);
246 }
247 
geocode(QString requestString,const QGeoShape & bounds,bool manualBoundsRequired,int limit,int offset)248 QGeoCodeReply *QGeoCodingManagerEngineNokia::geocode(QString requestString,
249                                                      const QGeoShape &bounds,
250                                                      bool manualBoundsRequired,
251                                                      int limit,
252                                                      int offset)
253 {
254     QGeoCodeReplyNokia *reply = new QGeoCodeReplyNokia(
255                 m_networkManager->get(QNetworkRequest(QUrl(requestString))),
256                 limit, offset, bounds, manualBoundsRequired, this);
257 
258     connect(reply, &QGeoCodeReplyNokia::finished,
259             this, &QGeoCodingManagerEngineNokia::placesFinished);
260 
261     connect(reply, static_cast<void (QGeoCodeReply::*)(QGeoCodeReply::Error, const QString &)>(&QGeoCodeReplyNokia::error),
262             this, &QGeoCodingManagerEngineNokia::placesError);
263 
264     return reply;
265 }
266 
reverseGeocode(const QGeoCoordinate & coordinate,const QGeoShape & bounds)267 QGeoCodeReply *QGeoCodingManagerEngineNokia::reverseGeocode(const QGeoCoordinate &coordinate,
268                                                             const QGeoShape &bounds)
269 {
270     QString requestString = "https://";
271     requestString += m_reverseGeocodingUriProvider->getCurrentHost();
272     requestString += "/6.2/reversegeocode.json";
273 
274     requestString += getAuthenticationString();
275     requestString += "&gen=9";
276 
277     requestString += "&mode=retrieveAddresses";
278 
279     requestString += "&prox=";
280     requestString += trimDouble(coordinate.latitude());
281     requestString += ",";
282     requestString += trimDouble(coordinate.longitude());
283 
284     bool manualBoundsRequired = false;
285     if (bounds.type() == QGeoShape::CircleType) {
286         QGeoCircle circ(bounds);
287         if (circ.isValid() && circ.center() == coordinate) {
288             requestString += ",";
289             requestString += trimDouble(circ.radius());
290         } else {
291             manualBoundsRequired = true;
292         }
293     } else {
294         manualBoundsRequired = true;
295     }
296 
297     requestString += "&language=";
298     requestString += languageToMarc(locale().language());
299 
300     return geocode(requestString, bounds, manualBoundsRequired);
301 }
302 
trimDouble(double degree,int decimalDigits)303 QString QGeoCodingManagerEngineNokia::trimDouble(double degree, int decimalDigits)
304 {
305     QString sDegree = QString::number(degree, 'g', decimalDigits);
306 
307     int index = sDegree.indexOf('.');
308 
309     if (index == -1)
310         return sDegree;
311     else
312         return QString::number(degree, 'g', decimalDigits + index);
313 }
314 
placesFinished()315 void QGeoCodingManagerEngineNokia::placesFinished()
316 {
317     QGeoCodeReply *reply = qobject_cast<QGeoCodeReply *>(sender());
318 
319     if (!reply)
320         return;
321 
322     if (receivers(SIGNAL(finished(QGeoCodeReply*))) == 0) {
323         reply->deleteLater();
324         return;
325     }
326 
327     emit finished(reply);
328 }
329 
placesError(QGeoCodeReply::Error error,const QString & errorString)330 void QGeoCodingManagerEngineNokia::placesError(QGeoCodeReply::Error error, const QString &errorString)
331 {
332     QGeoCodeReply *reply = qobject_cast<QGeoCodeReply *>(sender());
333 
334     if (!reply)
335         return;
336 
337     if (receivers(SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString))) == 0) {
338         reply->deleteLater();
339         return;
340     }
341 
342     emit this->error(reply, error, errorString);
343 }
344 
languageToMarc(QLocale::Language language)345 QString QGeoCodingManagerEngineNokia::languageToMarc(QLocale::Language language)
346 {
347     uint offset = 3 * (uint(language));
348     if (language == QLocale::C || offset + 3 > sizeof(marc_language_code_list))
349         return QLatin1String("eng");
350 
351     const unsigned char *c = marc_language_code_list + offset;
352     if (c[0] == 0)
353         return QLatin1String("eng");
354 
355     QString code(3, Qt::Uninitialized);
356     code[0] = ushort(c[0]);
357     code[1] = ushort(c[1]);
358     code[2] = ushort(c[2]);
359 
360     return code;
361 }
362 
363 QT_END_NAMESPACE
364