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 "qgeocodejsonparser.h"
38
39#include <QtPositioning/QGeoShape>
40#include <QtPositioning/QGeoRectangle>
41#include <QtPositioning/QGeoAddress>
42#include <QtPositioning/QGeoCoordinate>
43
44#include <QtCore/QThreadPool>
45#include <QtCore/QJsonObject>
46#include <QtCore/QJsonArray>
47#include <QtCore/QJsonParseError>
48#include <QtCore/QVariantMap>
49
50#include <QtDebug>
51
52QT_BEGIN_NAMESPACE
53
54namespace {
55
56/*
57 Checks that the given Location object contains the information
58 we need and is not malformed in any way. We expect a Location
59 object of the following form:
60
61 "Location": {
62 "Address": {
63 "AdditionalData": [
64 {
65 "key": "CountryName",
66 "value": "Australia"
67 },
68 {
69 "key": "StateName",
70 "value": "New South Wales"
71 }
72 ],
73 "City": "Sydney",
74 "Country": "AUS",
75 "District": "Casula",
76 "Label": "Casula, Sydney, NSW, Australia",
77 "PostalCode": "2170",
78 "State": "NSW"
79 },
80 "DisplayPosition": {
81 "Latitude": -33.949509999999997,
82 "Longitude": 150.90386000000001
83 },
84 "LocationId": "NT_5UQ89lKoiI4DIYbOrIR0-D",
85 "LocationType": "area",
86 "MapReference": {
87 "CityId": "1469266800",
88 "CountryId": "1469256839",
89 "DistrictId": "1469267758",
90 "MapId": "NXAM16130",
91 "MapReleaseDate": "2016-10-05",
92 "MapVersion": "Q1/2016",
93 "ReferenceId": "868383156",
94 "SideOfStreet": "neither",
95 "StateId": "1469256831"
96 },
97 "MapView": {
98 "BottomRight": {
99 "Latitude": -33.966839999999998,
100 "Longitude": 150.91875999999999
101 },
102 "TopLeft": {
103 "Latitude": -33.937440000000002,
104 "Longitude": 150.87457000000001
105 }
106 }
107 }
108
109*/
110bool checkLocation(const QJsonObject &loc, QString *errorString)
111{
112 QJsonObject::const_iterator ait = loc.constFind(key: QLatin1String("Address"));
113 if (ait == loc.constEnd()) {
114 *errorString = QLatin1String("Expected Address element within Location object");
115 return false;
116 } else if (!ait.value().isObject()) {
117 *errorString = QLatin1String("Expected Address object within Location object");
118 return false;
119 }
120
121 QJsonObject::const_iterator dpit = loc.constFind(key: QLatin1String("DisplayPosition"));
122 if (dpit == loc.constEnd()) {
123 *errorString = QLatin1String("Expected DisplayPosition element within Location object");
124 return false;
125 } else if (!dpit.value().isObject()) {
126 *errorString = QLatin1String("Expected DisplayPosition object within Location object");
127 return false;
128 }
129 QJsonObject displayPosition = dpit.value().toObject();
130 QJsonObject::const_iterator latit = displayPosition.constFind(key: QLatin1String("Latitude"));
131 if (latit == displayPosition.constEnd()) {
132 *errorString = QLatin1String("Expected Latitude element within Location.DisplayPosition object");
133 return false;
134 } else if (!latit.value().isDouble()) {
135 *errorString = QLatin1String("Expected Latitude double within Location.DisplayPosition object");
136 return false;
137 }
138 QJsonObject::const_iterator lonit = displayPosition.constFind(key: QLatin1String("Longitude"));
139 if (lonit == displayPosition.constEnd()) {
140 *errorString = QLatin1String("Expected Longitude element within Location.DisplayPosition object");
141 return false;
142 } else if (!lonit.value().isDouble()) {
143 *errorString = QLatin1String("Expected Longitude double within Location.DisplayPosition object");
144 return false;
145 }
146
147 QJsonObject::const_iterator mvit = loc.constFind(key: QLatin1String("MapView"));
148 if (mvit == loc.constEnd()) {
149 *errorString = QLatin1String("Expected MapView element within Location object");
150 return false;
151 } else if (!mvit.value().isObject()) {
152 *errorString = QLatin1String("Expected MapView object within Location object");
153 return false;
154 }
155 QJsonObject mapView = mvit.value().toObject();
156 QJsonObject::const_iterator brit = mapView.constFind(key: QLatin1String("BottomRight"));
157 if (brit == mapView.constEnd()) {
158 *errorString = QLatin1String("Expected BottomRight element within Location.MapView object");
159 return false;
160 } else if (!brit.value().isObject()) {
161 *errorString = QLatin1String("Expected BottomRight object within Location.MapView object");
162 return false;
163 }
164 QJsonObject bottomRight = brit.value().toObject();
165 QJsonObject::const_iterator brlatit = bottomRight.constFind(key: QLatin1String("Latitude"));
166 if (brlatit == bottomRight.constEnd()) {
167 *errorString = QLatin1String("Expected Latitude element within Location.MapView.BottomRight object");
168 return false;
169 } else if (!brlatit.value().isDouble()) {
170 *errorString = QLatin1String("Expected Latitude double within Location.MapView.BottomRight object");
171 return false;
172 }
173 QJsonObject::const_iterator brlonit = bottomRight.constFind(key: QLatin1String("Longitude"));
174 if (brlonit == bottomRight.constEnd()) {
175 *errorString = QLatin1String("Expected Longitude element within Location.MapView.BottomRight object");
176 return false;
177 } else if (!brlonit.value().isDouble()) {
178 *errorString = QLatin1String("Expected Longitude double within Location.MapView.BottomRight object");
179 return false;
180 }
181 QJsonObject::const_iterator tlit = mapView.constFind(key: QLatin1String("TopLeft"));
182 if (tlit == mapView.constEnd()) {
183 *errorString = QLatin1String("Expected TopLeft element within Location.MapView object");
184 return false;
185 } else if (!tlit.value().isObject()) {
186 *errorString = QLatin1String("Expected TopLeft object within Location.MapView object");
187 return false;
188 }
189 QJsonObject topLeft = tlit.value().toObject();
190 QJsonObject::const_iterator tllatit = topLeft.constFind(key: QLatin1String("Latitude"));
191 if (tllatit == topLeft.constEnd()) {
192 *errorString = QLatin1String("Expected Latitude element within Location.MapView.TopLeft object");
193 return false;
194 } else if (!tllatit.value().isDouble()) {
195 *errorString = QLatin1String("Expected Latitude double within Location.MapView.TopLeft object");
196 return false;
197 }
198 QJsonObject::const_iterator tllonit = topLeft.constFind(key: QLatin1String("Longitude"));
199 if (tllonit == bottomRight.constEnd()) {
200 *errorString = QLatin1String("Expected Longitude element within Location.MapView.TopLeft object");
201 return false;
202 } else if (!tllonit.value().isDouble()) {
203 *errorString = QLatin1String("Expected Longitude double within Location.MapView.TopLeft object");
204 return false;
205 }
206
207 return true;
208}
209
210/*
211 Checks that the given document contains the required information
212 and is not malformed in any way. We expect a document like the
213 following:
214
215 {
216 "Response": {
217 "MetaInfo": {
218 "Timestamp": "2016-10-18T08:42:04.369+0000"
219 },
220 "View": [
221 {
222 "ViewId": 0,
223 "_type": "SearchResultsViewType",
224 "Result": [
225 {
226 "Direction": 72.099999999999994,
227 "Distance": -1885.2,
228 "Location": {
229 // OMITTED FOR BREVITY
230 },
231 "MatchLevel": "district",
232 "MatchQuality": {
233 "City": 1,
234 "Country": 1,
235 "District": 1,
236 "PostalCode": 1,
237 "State": 1
238 },
239 "Relevance": 1
240 }
241 ]
242 }
243 ]
244 }
245 }
246*/
247bool checkDocument(const QJsonDocument &doc, QString *errorString)
248{
249 if (!doc.isObject()) {
250 *errorString = QLatin1String("Expected JSON document containing object");
251 return false;
252 }
253
254 QJsonObject rootObject = doc.object();
255 QJsonObject::const_iterator it = rootObject.constFind(key: QLatin1String("Response"));
256 if (it == rootObject.constEnd()) {
257 *errorString = QLatin1String("Expected Response element within root object");
258 return false;
259 } else if (!it.value().isObject()) {
260 *errorString = QLatin1String("Expected Response object within root object");
261 return false;
262 }
263
264 QJsonObject response = it.value().toObject();
265 QJsonObject::const_iterator rit = response.constFind(key: QLatin1String("View"));
266 if (rit == response.constEnd()) {
267 *errorString = QLatin1String("Expected View element within Response object");
268 return false;
269 } else if (!rit.value().isArray()) {
270 *errorString = QLatin1String("Expected View array within Response object");
271 return false;
272 }
273
274 QJsonArray view = rit.value().toArray();
275 Q_FOREACH (const QJsonValue &viewElement, view) {
276 if (!viewElement.isObject()) {
277 *errorString = QLatin1String("Expected View array element to be object");
278 return false;
279 }
280
281 QJsonObject viewObject = viewElement.toObject();
282 QJsonObject::const_iterator voit = viewObject.constFind(key: QLatin1String("Result"));
283 if (voit == viewObject.constEnd()) {
284 *errorString = QLatin1String("Expected Result element within View array object element");
285 return false;
286 } else if (!voit.value().isArray()) {
287 *errorString = QLatin1String("Expected Result array within View array object element");
288 return false;
289 }
290
291 QJsonArray result = voit.value().toArray();
292 Q_FOREACH (const QJsonValue &resultElement, result) {
293 if (!resultElement.isObject()) {
294 *errorString = QLatin1String("Expected Result array element to be object");
295 return false;
296 }
297
298 QJsonObject resultObject = resultElement.toObject();
299 QJsonObject::const_iterator roit = resultObject.constFind(key: "Location");
300 if (roit == resultObject.constEnd()) {
301 *errorString = QLatin1String("Expected Location element in Result array element object");
302 return false;
303 } else if (!roit.value().isObject()) {
304 *errorString = QLatin1String("Expected Location object in Result array element object");
305 return false;
306 }
307
308 QJsonObject location = roit.value().toObject();
309 if (!checkLocation(loc: location, errorString)) {
310 return false;
311 }
312 }
313 }
314
315 return true;
316}
317
318bool parseLocation(const QJsonObject &obj, const QGeoShape &bounds, QGeoLocation *loc)
319{
320 QJsonObject displayPosition = obj.value(key: "DisplayPosition").toObject();
321 QGeoCoordinate coordinate = QGeoCoordinate(displayPosition.value(key: "Latitude").toDouble(), displayPosition.value(key: "Longitude").toDouble());
322 if (bounds.isValid() && !bounds.contains(coordinate)) {
323 // manual bounds check failed, location can be omitted from results.
324 return false;
325 }
326
327 QGeoAddress address;
328 QJsonObject addr = obj.value(key: "Address").toObject();
329 address.setCountryCode(addr.value(key: "Country").toString());
330 address.setState(addr.value(key: "State").toString());
331 address.setCounty(addr.value(key: "County").toString());
332 address.setCity(addr.value(key: "City").toString());
333 address.setDistrict(addr.value(key: "District").toString());
334 QString houseNumber = addr.value(key: "HouseNumber").toString();
335 QString street = addr.value(key: "Street").toString();
336 address.setStreet(houseNumber.isEmpty() ? street : QString("%1 %2").arg(args&: houseNumber, args&: street));
337 address.setPostalCode(addr.value(key: "PostalCode").toString());
338 QString label = addr.value(key: "Label").toString().trimmed();
339 if (!label.isEmpty()) {
340 address.setText(label);
341 }
342 QJsonArray additionalData = addr.value(key: "AdditionalData").toArray();
343 Q_FOREACH (const QJsonValue &adv, additionalData) {
344 if (adv.isObject()) {
345 const QJsonObject &ado(adv.toObject());
346 if (ado.value(key: "key").toString() == QLatin1String("CountryName")) {
347 address.setCountry(ado.value(key: "value").toString());
348 }
349 }
350 }
351
352 QGeoRectangle boundingBox;
353 QJsonObject mapView = obj.value(key: "MapView").toObject();
354 QJsonObject bottomRight = mapView.value(key: "BottomRight").toObject();
355 QJsonObject topLeft = mapView.value(key: "TopLeft").toObject();
356 boundingBox.setBottomRight(QGeoCoordinate(bottomRight.value(key: "Latitude").toDouble(), bottomRight.value(key: "Longitude").toDouble()));
357 boundingBox.setTopLeft(QGeoCoordinate(topLeft.value(key: "Latitude").toDouble(), topLeft.value(key: "Longitude").toDouble()));
358
359 loc->setAddress(address);
360 loc->setCoordinate(coordinate);
361 loc->setBoundingBox(boundingBox);
362
363 return true;
364}
365
366void parseDocument(const QJsonDocument &doc, const QGeoShape &bounds, QList<QGeoLocation> *locs)
367{
368 QJsonArray view = doc.object().value(key: "Response").toObject().value(key: "View").toArray();
369 Q_FOREACH (const QJsonValue &viewElement, view) {
370 QJsonArray result = viewElement.toObject().value(key: "Result").toArray();
371 Q_FOREACH (const QJsonValue &resultElement, result) {
372 QGeoLocation location;
373 if (parseLocation(obj: resultElement.toObject().value(key: "Location").toObject(), bounds, loc: &location)) {
374 locs->append(t: location);
375 }
376 }
377 }
378}
379
380} // namespace
381
382void QGeoCodeJsonParser::setBounds(const QGeoShape &bounds)
383{
384 m_bounds = bounds;
385}
386
387void QGeoCodeJsonParser::parse(const QByteArray &data)
388{
389 m_data = data;
390 QThreadPool::globalInstance()->start(runnable: this);
391}
392
393void QGeoCodeJsonParser::run()
394{
395 // parse the document.
396 QJsonParseError perror;
397 m_document = QJsonDocument::fromJson(json: m_data, error: &perror);
398 if (perror.error != QJsonParseError::NoError) {
399 m_errorString = perror.errorString();
400 } else {
401 // ensure that the response is valid and contains the information we need.
402 if (checkDocument(doc: m_document, errorString: &m_errorString)) {
403 // extract the location results from the response.
404 parseDocument(doc: m_document, bounds: m_bounds, locs: &m_results);
405 emit results(locations: m_results);
406 return;
407 }
408 }
409
410 emit error(errorString: m_errorString);
411}
412
413QT_END_NAMESPACE
414

source code of qtlocation/src/plugins/geoservices/nokia/qgeocodejsonparser.cpp