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
qdeclarativegeocodemodel.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
6
7#include <QtCore/QCoreApplication>
8#include <QtQml/QQmlInfo>
9#include <QtPositioning/QGeoCircle>
10#include <QtLocation/QGeoServiceProvider>
11#include <QtLocation/QGeoCodingManager>
12#include <QtLocation/private/qgeocodereply_p.h>
13#include <QtPositioning/QGeoPolygon>
14
16
17/*!
18 \qmltype GeocodeModel
19 \nativetype QDeclarativeGeocodeModel
20 \inqmlmodule QtLocation
21 \ingroup qml-QtLocation5-geocoding
22 \since QtLocation 5.5
23
24 \brief The GeocodeModel type provides support for searching operations
25 related to geographic information.
26
27 The GeocodeModel type is used as part of a model/view grouping to
28 match addresses or search strings with geographic locations. How the
29 geographic locations generated are used or displayed is decided by any
30 Views attached to the GeocodeModel (for example a \l MapItemView or \l{ListView}).
31
32 Like \l Map and \l RouteModel, all the data for a GeocodeModel to work
33 comes from a services plugin. This is contained in the \l{plugin} property,
34 and this must be set before the GeocodeModel can do any useful work.
35
36 Once the plugin is set, the \l{query} property can be used to specify the
37 address or search string to match. If \l{autoUpdate} is enabled, the Model
38 will update its output automatically. Otherwise, the \l{update} method may
39 be used. By default, autoUpdate is disabled.
40
41 The data stored and returned in the GeocodeModel consists of \l [QML] {Location}
42 objects, as a list with the role name "locationData". See the documentation
43 for \l [QML] {Location} for further details on its structure and contents.
44
45 \section2 Example Usage
46
47 The following snippet is two-part, showing firstly the declaration of
48 objects, and secondly a short piece of procedural code using it. We set
49 the geocodeModel's \l{autoUpdate} property to false, and call \l{update} once
50 the query is set up. In this case, as we use a string value in \l{query},
51 only one update would occur, even with autoUpdate enabled. However, if we
52 provided an \l{Address} object we may inadvertently trigger multiple
53 requests whilst setting its properties.
54
55 \code
56 Plugin {
57 id: aPlugin
58 }
59
60 GeocodeModel {
61 id: geocodeModel
62 plugin: aPlugin
63 autoUpdate: false
64 }
65 \endcode
66
67 \code
68 {
69 geocodeModel.query = "53 Brandl St, Eight Mile Plains, Australia"
70 geocodeModel.update()
71 }
72 \endcode
73*/
74
75/*!
76 \qmlsignal QtLocation::GeocodeModel::locationsChanged()
77
78 This signal is emitted when locations in the model have changed.
79
80 \sa count
81*/
82
83
84QDeclarativeGeocodeModel::QDeclarativeGeocodeModel(QObject *parent)
85 : QAbstractListModel(parent)
86{
87}
88
89QDeclarativeGeocodeModel::~QDeclarativeGeocodeModel()
90{
91 qDeleteAll(declarativeLocations_);
92 declarativeLocations_.clear();
93 delete reply_;
94}
95
96/*!
97 \internal
98 From QQmlParserStatus
99*/
100void QDeclarativeGeocodeModel::componentComplete()
101{
102 complete_ = true;
103 if (autoUpdate_)
104 update();
105}
106
107/*!
108 \qmlmethod void QtLocation::GeocodeModel::update()
109
110 Instructs the GeocodeModel to update its data. This is most useful
111 when \l autoUpdate is disabled, to force a refresh when the query
112 has been changed.
113*/
114void QDeclarativeGeocodeModel::update()
115{
116 if (!complete_)
117 return;
118
119 if (!plugin_) {
120 setError(EngineNotSetError, tr("Cannot geocode, plugin not set."));
121 return;
122 }
123
124 QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider();
125 if (!serviceProvider)
126 return;
127
128 QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager();
129 if (!geocodingManager) {
130 setError(EngineNotSetError, tr("Cannot geocode, geocode manager not set."));
131 return;
132 }
133 if (!coordinate_.isValid() && (!address_ || address_->address().isEmpty()) &&
134 (searchString_.isEmpty())) {
135 setError(ParseError, tr("Cannot geocode, valid query not set."));
136 return;
137 }
138 abortRequest(); // abort possible previous requests
139 setError(NoError, QString());
140
141 if (coordinate_.isValid()) {
142 setStatus(QDeclarativeGeocodeModel::Loading);
143 reply_ = geocodingManager->reverseGeocode(coordinate_, boundingArea_);
144 if (reply_->isFinished()) {
145 if (reply_->error() == QGeoCodeReply::NoError) {
146 geocodeFinished(reply_);
147 } else {
148 geocodeError(reply_, reply_->error(), reply_->errorString());
149 }
150 }
151 } else if (address_) {
152 setStatus(QDeclarativeGeocodeModel::Loading);
153 reply_ = geocodingManager->geocode(address_->address(), boundingArea_);
154 if (reply_->isFinished()) {
155 if (reply_->error() == QGeoCodeReply::NoError) {
156 geocodeFinished(reply_);
157 } else {
158 geocodeError(reply_, reply_->error(), reply_->errorString());
159 }
160 }
161 } else if (!searchString_.isEmpty()) {
162 setStatus(QDeclarativeGeocodeModel::Loading);
163 reply_ = geocodingManager->geocode(searchString_, limit_, offset_, boundingArea_);
164 if (reply_->isFinished()) {
165 if (reply_->error() == QGeoCodeReply::NoError) {
166 geocodeFinished(reply_);
167 } else {
168 geocodeError(reply_, reply_->error(), reply_->errorString());
169 }
170 }
171 }
172}
173
174/*!
175 \internal
176*/
177void QDeclarativeGeocodeModel::abortRequest()
178{
179 if (reply_) {
180 reply_->abort();
181 }
182 if (reply_) {
183 reply_->deleteLater();
184 reply_ = 0;
185 }
186}
187
188/*!
189 \internal
190*/
191void QDeclarativeGeocodeModel::queryContentChanged()
192{
193 if (autoUpdate_)
194 update();
195}
196
197/*!
198 \internal
199 From QAbstractListModel
200*/
201int QDeclarativeGeocodeModel::rowCount(const QModelIndex &parent) const
202{
203 Q_UNUSED(parent);
204 return declarativeLocations_.count();
205}
206
207/*!
208 \internal
209*/
210QVariant QDeclarativeGeocodeModel::data(const QModelIndex &index, int role) const
211{
212 if (!index.isValid())
213 return QVariant();
214 if (index.row() >= declarativeLocations_.count())
215 return QVariant();
216 if (role == QDeclarativeGeocodeModel::LocationRole) {
217 QObject *locationObject = declarativeLocations_.at(index.row());
218 Q_ASSERT(locationObject);
219 return QVariant::fromValue(locationObject);
220 }
221 return QVariant();
222}
223
224QHash<int, QByteArray> QDeclarativeGeocodeModel::roleNames() const
225{
226 QHash<int, QByteArray> roleNames = QAbstractItemModel::roleNames();
227 roleNames.insert(LocationRole, "locationData");
228 return roleNames;
229}
230
231/*!
232 \internal
233*/
234void QDeclarativeGeocodeModel::setPlugin(QDeclarativeGeoServiceProvider *plugin)
235{
236 if (plugin_ == plugin)
237 return;
238
239 reset(); // reset the model
240 plugin_ = plugin;
241 if (complete_)
242 emit pluginChanged();
243
244 if (!plugin)
245 return;
246
247 if (plugin_->isAttached()) {
248 pluginReady();
249 } else {
250 connect(plugin_, &QDeclarativeGeoServiceProvider::attached,
251 this, &QDeclarativeGeocodeModel::pluginReady);
252 }
253}
254
255/*!
256 \internal
257*/
258void QDeclarativeGeocodeModel::pluginReady()
259{
260 QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider();
261 QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager();
262
263 if (serviceProvider->geocodingError() != QGeoServiceProvider::NoError) {
264 QDeclarativeGeocodeModel::GeocodeError newError = UnknownError;
265 switch (serviceProvider->geocodingError()) {
266 case QGeoServiceProvider::NotSupportedError:
267 newError = EngineNotSetError; break;
268 case QGeoServiceProvider::UnknownParameterError:
269 newError = UnknownParameterError; break;
270 case QGeoServiceProvider::MissingRequiredParameterError:
271 newError = MissingRequiredParameterError; break;
272 case QGeoServiceProvider::ConnectionError:
273 newError = CommunicationError; break;
274 default:
275 break;
276 }
277
278 setError(newError, serviceProvider->geocodingErrorString());
279 return;
280 }
281
282 if (!geocodingManager) {
283 setError(EngineNotSetError,tr("Plugin does not support (reverse) geocoding."));
284 return;
285 }
286
287 connect(geocodingManager, &QGeoCodingManager::finished,
288 this, &QDeclarativeGeocodeModel::geocodeFinished);
289 connect(geocodingManager, &QGeoCodingManager::errorOccurred,
290 this, &QDeclarativeGeocodeModel::geocodeError);
291
292 if (complete_ && autoUpdate_)
293 update();
294}
295
296/*!
297 \qmlproperty Plugin QtLocation::GeocodeModel::plugin
298
299 This property holds the plugin that provides the actual geocoding service.
300 Note that all plugins do not necessarily provide geocoding (could for example provide
301 only routing or maps).
302
303 \sa Plugin
304*/
305
306QDeclarativeGeoServiceProvider *QDeclarativeGeocodeModel::plugin() const
307{
308 return plugin_;
309}
310
311void QDeclarativeGeocodeModel::setBounds(const QVariant &boundingArea)
312{
313 QGeoShape s;
314
315 if (boundingArea.userType() == qMetaTypeId<QGeoRectangle>())
316 s = boundingArea.value<QGeoRectangle>();
317 else if (boundingArea.userType() == qMetaTypeId<QGeoCircle>())
318 s = boundingArea.value<QGeoCircle>();
319 else if (boundingArea.userType() == qMetaTypeId<QGeoShape>())
320 s = boundingArea.value<QGeoShape>();
321
322
323 if (boundingArea_ == s)
324 return;
325
326 boundingArea_ = s;
327 emit boundsChanged();
328}
329
330/*!
331 \qmlproperty geoshape QtLocation::GeocodeModel::bounds
332
333 This property holds the bounding area used to limit the results to those
334 within the area. This is particularly useful if query is only partially filled out,
335 as the service will attempt to (reverse) geocode all matches for the specified data.
336
337 Accepted types are \l {georectangle} and
338 \l {geocircle}.
339*/
340QVariant QDeclarativeGeocodeModel::bounds() const
341{
342 if (boundingArea_.type() == QGeoShape::RectangleType)
343 return QVariant::fromValue(QGeoRectangle(boundingArea_));
344 else if (boundingArea_.type() == QGeoShape::CircleType)
345 return QVariant::fromValue(QGeoCircle(boundingArea_));
346 else if (boundingArea_.type() == QGeoShape::PolygonType)
347 return QVariant::fromValue(QGeoPolygon(boundingArea_));
348 else
349 return QVariant::fromValue(boundingArea_);
350}
351
352void QDeclarativeGeocodeModel::geocodeFinished(QGeoCodeReply *reply)
353{
354 if (reply != reply_ || reply->error() != QGeoCodeReply::NoError)
355 return;
356
357 reply->deleteLater();
358 reply_ = 0;
359 int oldCount = declarativeLocations_.count();
360 setLocations(reply->locations());
361 setError(NoError, QString());
362 setStatus(QDeclarativeGeocodeModel::Ready);
363 emit locationsChanged();
364 if (oldCount != declarativeLocations_.count())
365 emit countChanged();
366}
367
368/*!
369 \internal
370*/
371void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply,
372 QGeoCodeReply::Error error,
373 const QString &errorString)
374{
375 if (reply != reply_)
376 return;
377
378 reply->deleteLater();
379 reply_ = 0;
380 int oldCount = declarativeLocations_.count();
381 if (oldCount > 0) {
382 // Reset the model
383 setLocations(reply->locations());
384 emit locationsChanged();
385 emit countChanged();
386 }
387 setError(static_cast<QDeclarativeGeocodeModel::GeocodeError>(error), errorString);
388 setStatus(QDeclarativeGeocodeModel::Error);
389}
390
391/*!
392 \qmlproperty enumeration QtLocation::GeocodeModel::status
393
394 This read-only property holds the current status of the model.
395
396 \list
397 \li GeocodeModel.Null - No geocode requests have been issued or \l reset has been called.
398 \li GeocodeModel.Ready - Geocode request(s) have finished successfully.
399 \li GeocodeModel.Loading - Geocode request has been issued but not yet finished
400 \li GeocodeModel.Error - Geocoding error has occurred, details are in \l error and \l errorString
401 \endlist
402*/
403
404QDeclarativeGeocodeModel::Status QDeclarativeGeocodeModel::status() const
405{
406 return status_;
407}
408
409void QDeclarativeGeocodeModel::setStatus(QDeclarativeGeocodeModel::Status status)
410{
411 if (status_ == status)
412 return;
413 status_ = status;
414 emit statusChanged();
415}
416
417/*!
418 \qmlproperty enumeration QtLocation::GeocodeModel::error
419
420 This read-only property holds the latest error value of the geocoding request.
421
422 \list
423 \li GeocodeModel.NoError - No error has occurred.
424 \li GeocodeModel.CombinationError - An error occurred while results where being combined from multiple sources.
425 \li GeocodeModel.CommunicationError - An error occurred while communicating with the service provider.
426 \li GeocodeModel.EngineNotSetError - The model's plugin property was not set or there is no geocoding manager associated with the plugin.
427 \li GeocodeModel.MissingRequiredParameterError - A required parameter was not specified.
428 \li GeocodeModel.ParseError - The response from the service provider was in an unrecognizable format.
429 \li GeocodeModel.UnknownError - An error occurred which does not fit into any of the other categories.
430 \li GeocodeModel.UnknownParameterError - The plugin did not recognize one of the parameters it was given.
431 \li GeocodeModel.UnsupportedOptionError - The requested operation is not supported by the geocoding provider.
432 This may happen when the loaded engine does not support a particular geocoding request
433 such as reverse geocoding.
434 \endlist
435*/
436
437QDeclarativeGeocodeModel::GeocodeError QDeclarativeGeocodeModel::error() const
438{
439 return error_;
440}
441
442void QDeclarativeGeocodeModel::setError(GeocodeError error, const QString &errorString)
443{
444 if (error_ == error && errorString_ == errorString)
445 return;
446 error_ = error;
447 errorString_ = errorString;
448 emit errorChanged();
449}
450
451/*!
452 \qmlproperty string QtLocation::GeocodeModel::errorString
453
454 This read-only property holds the textual presentation of the latest geocoding error.
455 If no error has occurred or the model has been reset, an empty string is returned.
456
457 An empty string may also be returned if an error occurred which has no associated
458 textual representation.
459*/
460
461QString QDeclarativeGeocodeModel::errorString() const
462{
463 return errorString_;
464}
465
466/*!
467 \internal
468*/
469void QDeclarativeGeocodeModel::setLocations(const QList<QGeoLocation> &locations)
470{
471 beginResetModel();
472 qDeleteAll(declarativeLocations_);
473 declarativeLocations_.clear();
474 for (const auto &location : locations)
475 declarativeLocations_.append(new QDeclarativeGeoLocation(location, this));
476 endResetModel();
477}
478
479/*!
480 \qmlproperty int QtLocation::GeocodeModel::count
481
482 This property holds how many locations the model currently has.
483 Amongst other uses, you can use this value when accessing locations
484 via the GeocodeModel::get -method.
485*/
486
487int QDeclarativeGeocodeModel::count() const
488{
489 return declarativeLocations_.count();
490}
491
492/*!
493 \qmlmethod Location QtLocation::GeocodeModel::get(int index)
494
495 Returns the \l [QML] {Location} at given \a index. Use \l count property to check the
496 amount of locations available. The locations are indexed from zero, so the accessible range
497 is 0...(count - 1).
498
499 If you access out of bounds, a zero (null object) is returned and a warning is issued.
500*/
501
502QDeclarativeGeoLocation *QDeclarativeGeocodeModel::get(int index)
503{
504 if (index < 0 || index >= declarativeLocations_.count()) {
505 qmlWarning(this) << QStringLiteral("Index '%1' out of range").arg(index);
506 return nullptr;
507 }
508 return declarativeLocations_.at(index);
509}
510
511/*!
512 \qmlproperty int QtLocation::GeocodeModel::limit
513
514 This property holds the maximum number of results. The limit and \l offset values are only
515 applicable with free string geocoding (that is they are not considered when using addresses
516 or coordinates in the search query).
517
518 If limit is -1 the entire result set will be returned, otherwise at most limit results will be
519 returned. The limit and \l offset results can be used together to implement paging.
520*/
521
522int QDeclarativeGeocodeModel::limit() const
523{
524 return limit_;
525}
526
527void QDeclarativeGeocodeModel::setLimit(int limit)
528{
529 if (limit == limit_)
530 return;
531 limit_ = limit;
532 if (autoUpdate_) {
533 update();
534 }
535 emit limitChanged();
536}
537
538/*!
539 \qmlproperty int QtLocation::GeocodeModel::offset
540
541 This property tells not to return the first 'offset' number of the results. The \l limit and
542 offset values are only applicable with free string geocoding (that is they are not considered
543 when using addresses or coordinates in the search query).
544
545 The \l limit and offset results can be used together to implement paging.
546*/
547
548int QDeclarativeGeocodeModel::offset() const
549{
550 return offset_;
551}
552
553void QDeclarativeGeocodeModel::setOffset(int offset)
554{
555 if (offset == offset_)
556 return;
557 offset_ = offset;
558 if (autoUpdate_) {
559 update();
560 }
561 emit offsetChanged();
562}
563
564/*!
565 \qmlmethod void QtLocation::GeocodeModel::reset()
566
567 Resets the model. All location data is cleared, any outstanding requests
568 are aborted and possible errors are cleared. Model status will be set
569 to GeocodeModel.Null
570*/
571
572void QDeclarativeGeocodeModel::reset()
573{
574 beginResetModel();
575 if (!declarativeLocations_.isEmpty()) {
576 setLocations(QList<QGeoLocation>());
577 emit countChanged();
578 }
579 endResetModel();
580
581 abortRequest();
582 setError(NoError, QString());
583 setStatus(QDeclarativeGeocodeModel::Null);
584}
585
586/*!
587 \qmlmethod void QtLocation::GeocodeModel::cancel()
588
589 Cancels any outstanding requests and clears errors. Model status will be set to either
590 GeocodeModel.Null or GeocodeModel.Ready.
591*/
592void QDeclarativeGeocodeModel::cancel()
593{
594 abortRequest();
595 setError(NoError, QString());
596 setStatus(declarativeLocations_.isEmpty() ? Null : Ready);
597}
598
599/*!
600 \qmlproperty QVariant QtLocation::GeocodeModel::query
601
602 This property holds the data of the geocoding request.
603 The property accepts three types of queries which determine both the data and
604 the type of action to be performed:
605
606 \list
607 \li Address - Geocoding (address to coordinate)
608 \li \l {coordinate} - Reverse geocoding (coordinate to address)
609 \li string - Geocoding (address to coordinate)
610 \endlist
611*/
612
613QVariant QDeclarativeGeocodeModel::query() const
614{
615 return queryVariant_;
616}
617
618void QDeclarativeGeocodeModel::setQuery(const QVariant &query)
619{
620 if (query == queryVariant_)
621 return;
622
623 if (query.userType() == qMetaTypeId<QGeoCoordinate>()) {
624 if (address_) {
625 address_->disconnect(this);
626 address_ = 0;
627 }
628 searchString_.clear();
629
630 coordinate_ = query.value<QGeoCoordinate>();
631 } else if (query.typeId() == QMetaType::QString) {
632 searchString_ = query.toString();
633 if (address_) {
634 address_->disconnect(this);
635 address_ = 0;
636 }
637 coordinate_ = QGeoCoordinate();
638 } else if (QObject *object = query.value<QObject *>()) {
639 if (QDeclarativeGeoAddress *address = qobject_cast<QDeclarativeGeoAddress *>(object)) {
640 if (address_)
641 address_->disconnect(this);
642 coordinate_ = QGeoCoordinate();
643 searchString_.clear();
644
645 address_ = address;
646 connect(address_, &QDeclarativeGeoAddress::countryChanged,
647 this, &QDeclarativeGeocodeModel::queryContentChanged);
648 connect(address_, &QDeclarativeGeoAddress::countryCodeChanged,
649 this, &QDeclarativeGeocodeModel::queryContentChanged);
650 connect(address_, &QDeclarativeGeoAddress::stateChanged,
651 this, &QDeclarativeGeocodeModel::queryContentChanged);
652 connect(address_, &QDeclarativeGeoAddress::countyChanged,
653 this, &QDeclarativeGeocodeModel::queryContentChanged);
654 connect(address_, &QDeclarativeGeoAddress::cityChanged,
655 this, &QDeclarativeGeocodeModel::queryContentChanged);
656 connect(address_, &QDeclarativeGeoAddress::districtChanged,
657 this, &QDeclarativeGeocodeModel::queryContentChanged);
658 connect(address_, &QDeclarativeGeoAddress::streetChanged,
659 this, &QDeclarativeGeocodeModel::queryContentChanged);
660 connect(address_, &QDeclarativeGeoAddress::postalCodeChanged,
661 this, &QDeclarativeGeocodeModel::queryContentChanged);
662 } else {
663 qmlWarning(this) << QStringLiteral("Unsupported query type for geocode model ")
664 << QStringLiteral("(coordinate, string and Address supported).");
665 return;
666 }
667 } else {
668 qmlWarning(this) << QStringLiteral("Unsupported query type for geocode model ")
669 << QStringLiteral("(coordinate, string and Address supported).");
670 return;
671 }
672
673 queryVariant_ = query;
674 emit queryChanged();
675 if (autoUpdate_)
676 update();
677}
678
679/*!
680 \qmlproperty bool QtLocation::GeocodeModel::autoUpdate
681
682 This property controls whether the Model automatically updates in response
683 to changes in its attached query. The default value of this property
684 is false.
685
686 If setting this value to 'true' and using an Address or
687 \l {coordinate} as the query, note that any change at all in the
688 object's properties will trigger a new request to be sent. If you are adjusting many
689 properties of the object whilst autoUpdate is enabled, this can generate large numbers of
690 useless (and later discarded) requests.
691*/
692
693bool QDeclarativeGeocodeModel::autoUpdate() const
694{
695 return autoUpdate_;
696}
697
698void QDeclarativeGeocodeModel::setAutoUpdate(bool update)
699{
700 if (autoUpdate_ == update)
701 return;
702 autoUpdate_ = update;
703 emit autoUpdateChanged();
704}
705
706QT_END_NAMESPACE