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 
52 QT_BEGIN_NAMESPACE
53 
54 namespace {
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 */
checkLocation(const QJsonObject & loc,QString * errorString)110 bool checkLocation(const QJsonObject &loc, QString *errorString)
111 {
112     QJsonObject::const_iterator ait = loc.constFind(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(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(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(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(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(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(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(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(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(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(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 */
checkDocument(const QJsonDocument & doc,QString * errorString)247 bool 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(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(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(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("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(location, errorString)) {
310                 return false;
311             }
312         }
313     }
314 
315     return true;
316 }
317 
parseLocation(const QJsonObject & obj,const QGeoShape & bounds,QGeoLocation * loc)318 bool parseLocation(const QJsonObject &obj, const QGeoShape &bounds, QGeoLocation *loc)
319 {
320     QJsonObject displayPosition = obj.value("DisplayPosition").toObject();
321     QGeoCoordinate coordinate = QGeoCoordinate(displayPosition.value("Latitude").toDouble(), displayPosition.value("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("Address").toObject();
329     address.setCountryCode(addr.value("Country").toString());
330     address.setState(addr.value("State").toString());
331     address.setCounty(addr.value("County").toString());
332     address.setCity(addr.value("City").toString());
333     address.setDistrict(addr.value("District").toString());
334     QString houseNumber = addr.value("HouseNumber").toString();
335     QString street = addr.value("Street").toString();
336     address.setStreet(houseNumber.isEmpty() ? street : QString("%1 %2").arg(houseNumber, street));
337     address.setPostalCode(addr.value("PostalCode").toString());
338     QString label = addr.value("Label").toString().trimmed();
339     if (!label.isEmpty()) {
340         address.setText(label);
341     }
342     QJsonArray additionalData = addr.value("AdditionalData").toArray();
343     Q_FOREACH (const QJsonValue &adv, additionalData) {
344         if (adv.isObject()) {
345             const QJsonObject &ado(adv.toObject());
346             if (ado.value("key").toString() == QLatin1String("CountryName")) {
347                 address.setCountry(ado.value("value").toString());
348             }
349         }
350     }
351 
352     QGeoRectangle boundingBox;
353     QJsonObject mapView = obj.value("MapView").toObject();
354     QJsonObject bottomRight = mapView.value("BottomRight").toObject();
355     QJsonObject topLeft = mapView.value("TopLeft").toObject();
356     boundingBox.setBottomRight(QGeoCoordinate(bottomRight.value("Latitude").toDouble(), bottomRight.value("Longitude").toDouble()));
357     boundingBox.setTopLeft(QGeoCoordinate(topLeft.value("Latitude").toDouble(), topLeft.value("Longitude").toDouble()));
358 
359     loc->setAddress(address);
360     loc->setCoordinate(coordinate);
361     loc->setBoundingBox(boundingBox);
362 
363     return true;
364 }
365 
parseDocument(const QJsonDocument & doc,const QGeoShape & bounds,QList<QGeoLocation> * locs)366 void parseDocument(const QJsonDocument &doc, const QGeoShape &bounds, QList<QGeoLocation> *locs)
367 {
368     QJsonArray view = doc.object().value("Response").toObject().value("View").toArray();
369     Q_FOREACH (const QJsonValue &viewElement, view) {
370         QJsonArray result = viewElement.toObject().value("Result").toArray();
371         Q_FOREACH (const QJsonValue &resultElement, result) {
372             QGeoLocation location;
373             if (parseLocation(resultElement.toObject().value("Location").toObject(), bounds, &location)) {
374                 locs->append(location);
375             }
376         }
377     }
378 }
379 
380 } // namespace
381 
setBounds(const QGeoShape & bounds)382 void QGeoCodeJsonParser::setBounds(const QGeoShape &bounds)
383 {
384     m_bounds = bounds;
385 }
386 
parse(const QByteArray & data)387 void QGeoCodeJsonParser::parse(const QByteArray &data)
388 {
389     m_data = data;
390     QThreadPool::globalInstance()->start(this);
391 }
392 
run()393 void QGeoCodeJsonParser::run()
394 {
395     // parse the document.
396     QJsonParseError perror;
397     m_document = QJsonDocument::fromJson(m_data, &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(m_document, &m_errorString)) {
403             // extract the location results from the response.
404             parseDocument(m_document, m_bounds, &m_results);
405             emit results(m_results);
406             return;
407         }
408     }
409 
410     emit error(m_errorString);
411 }
412 
413 QT_END_NAMESPACE
414