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