QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgssensorthingsutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssensorthingsutils.cpp
3 --------------------
4 begin : November 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include <nlohmann/json.hpp>
19
21#include "qgsfield.h"
22#include "qgsfields.h"
23#include "qgslogger.h"
25#include "qgsrectangle.h"
27#include "qgswkbtypes.h"
28
29#include <QNetworkRequest>
30#include <QRegularExpression>
31#include <QRegularExpressionMatch>
32#include <QString>
33#include <QUrl>
34
35using namespace Qt::StringLiterals;
36
37//
38// QgsSensorThingsExpansionDefinition
39//
41 : mChildEntity( childEntity )
42 , mOrderBy( orderBy )
43 , mSortOrder( sortOrder )
44 , mLimit( limit )
45 , mFilter( filter )
46{
47
48}
49
51{
52 switch ( entity )
53 {
56
62 // no special defaults for these entities
64 entity
65 );
66
68 // default to descending sort by phenomenonTime
71 u"phenomenonTime"_s, Qt::SortOrder::DescendingOrder
72 );
73
77 // use smaller limit by default
79 entity,
80 QString(), Qt::SortOrder::AscendingOrder, 10
81 );
82 }
84}
85
90
95
100
102{
103 return mSortOrder;
104}
105
107{
108 mSortOrder = order;
109}
110
112{
113 return mLimit;
114}
115
117{
118 mLimit = limit;
119}
120
122{
123 return mFilter;
124}
125
127{
128 mFilter = filter;
129}
130
132{
133 if ( !isValid() )
134 return QString();
135
136 QStringList parts;
137 parts.append( qgsEnumValueToKey( mChildEntity ) );
138 if ( !mOrderBy.isEmpty() )
139 parts.append( u"orderby=%1,%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? u"asc"_s : u"desc"_s ) );
140 if ( mLimit >= 0 )
141 parts.append( u"limit=%1"_s.arg( mLimit ) );
142 if ( !mFilter.trimmed().isEmpty() )
143 {
144 QString escapedFilter = mFilter;
145 escapedFilter.replace( ':', "\\colon"_L1 );
146 parts.append( u"filter=%1"_s.arg( escapedFilter ) );
147 }
148 return parts.join( ':' );
149}
150
152{
153 const QStringList parts = string.split( ':', Qt::SkipEmptyParts );
154 if ( parts.empty() )
156
158 definition.setLimit( -1 );
159 for ( int i = 1; i < parts.count(); ++i )
160 {
161 const QString &part = parts.at( i );
162 const thread_local QRegularExpression orderByRegEx( u"^orderby=(.*),(.*?)$"_s );
163 const thread_local QRegularExpression orderLimitRegEx( u"^limit=(\\d+)$"_s );
164 const thread_local QRegularExpression filterRegEx( u"^filter=(.*)$"_s );
165
166 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
167 if ( orderByMatch.hasMatch() )
168 {
169 definition.setOrderBy( orderByMatch.captured( 1 ) );
170 definition.setSortOrder( orderByMatch.captured( 2 ) == "asc"_L1 ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
171 continue;
172 }
173
174 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
175 if ( limitMatch.hasMatch() )
176 {
177 definition.setLimit( limitMatch.captured( 1 ).toInt() );
178 continue;
179 }
180
181 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
182 if ( filterMatch.hasMatch() )
183 {
184 QString filter = filterMatch.captured( 1 );
185 filter.replace( "\\colon"_L1, ":"_L1 );
186 definition.setFilter( filter );
187 continue;
188 }
189 }
190 return definition;
191}
192
193QString QgsSensorThingsExpansionDefinition::asQueryString( Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions ) const
194{
195 if ( !isValid() )
196 return QString();
197
198 bool ok = false;
199 // From the specifications, it SOMETIMES the plural form is used for the expansion query, and sometimes singular.
200 // The choice depends on the cardinality of the relationship between the involved entities.
201 const Qgis::RelationshipCardinality cardinality = QgsSensorThingsUtils::relationshipCardinality( parentEntityType, mChildEntity, ok );
202 QString childEntityString;
203 if ( !ok )
204 {
205 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
206 }
207 else
208 {
209 switch ( cardinality )
210 {
213 // use singular strings, eg "Thing"
214 childEntityString = qgsEnumValueToKey( mChildEntity );
215 break;
216
219 // use plural strings, eg "Things"
220 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
221 break;
222 }
223 }
224
225 QString res = u"$expand=%1"_s.arg( childEntityString );
226
227 QStringList queryOptions;
228 if ( !mOrderBy.isEmpty() )
229 queryOptions.append( u"$orderby=%1%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : u" desc"_s ) );
230
231 if ( mLimit > -1 )
232 queryOptions.append( u"$top=%1"_s.arg( mLimit ) );
233
234 if ( !mFilter.isEmpty() )
235 queryOptions.append( u"$filter=%1"_s.arg( mFilter ) );
236
237 queryOptions.append( additionalOptions );
238
239 if ( !queryOptions.isEmpty() )
240 res.append( u"(%1)"_s.arg( queryOptions.join( ';' ) ) );
241
242 return res;
243}
244
246{
247 if ( mChildEntity == Qgis::SensorThingsEntity::Invalid )
248 return other.mChildEntity == Qgis::SensorThingsEntity::Invalid;
249
250 return mChildEntity == other.mChildEntity
251 && mSortOrder == other.mSortOrder
252 && mLimit == other.mLimit
253 && mOrderBy == other.mOrderBy
254 && mFilter == other.mFilter;
255}
256
258{
259 return !( *this == other );
260}
261
263{
264 return mOrderBy;
265}
266
268{
269 mOrderBy = field;
270}
271
272//
273// QgsSensorThingsUtils
274//
275
277{
278 const QString trimmed = type.trimmed();
279 if ( trimmed.compare( "Thing"_L1, Qt::CaseInsensitive ) == 0 )
281 if ( trimmed.compare( "Location"_L1, Qt::CaseInsensitive ) == 0 )
283 if ( trimmed.compare( "HistoricalLocation"_L1, Qt::CaseInsensitive ) == 0 )
285 if ( trimmed.compare( "Datastream"_L1, Qt::CaseInsensitive ) == 0 )
287 if ( trimmed.compare( "Sensor"_L1, Qt::CaseInsensitive ) == 0 )
289 if ( trimmed.compare( "ObservedProperty"_L1, Qt::CaseInsensitive ) == 0 )
291 if ( trimmed.compare( "Observation"_L1, Qt::CaseInsensitive ) == 0 )
293 if ( trimmed.compare( "FeatureOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
295 if ( trimmed.compare( "MultiDatastream"_L1, Qt::CaseInsensitive ) == 0 )
297
299}
300
302{
303 switch ( type )
304 {
306 return QString();
308 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
310 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
312 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
314 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
316 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
318 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
320 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
322 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
324 return plural ? QObject::tr( "MultiDatastreams" ) : QObject::tr( "MultiDatastream" );
325 }
327}
328
330{
331 const QString trimmed = type.trimmed();
332 if ( trimmed.compare( "Things"_L1, Qt::CaseInsensitive ) == 0 )
334 if ( trimmed.compare( "Locations"_L1, Qt::CaseInsensitive ) == 0 )
336 if ( trimmed.compare( "HistoricalLocations"_L1, Qt::CaseInsensitive ) == 0 )
338 if ( trimmed.compare( "Datastreams"_L1, Qt::CaseInsensitive ) == 0 )
340 if ( trimmed.compare( "Sensors"_L1, Qt::CaseInsensitive ) == 0 )
342 if ( trimmed.compare( "ObservedProperties"_L1, Qt::CaseInsensitive ) == 0 )
344 if ( trimmed.compare( "Observations"_L1, Qt::CaseInsensitive ) == 0 )
346 if ( trimmed.compare( "FeaturesOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
348 if ( trimmed.compare( "MultiDatastreams"_L1, Qt::CaseInsensitive ) == 0 )
350
352}
353
355{
356 switch ( type )
357 {
359 return QString();
361 return u"Things"_s;
363 return u"Locations"_s;
365 return u"HistoricalLocations"_s;
367 return u"Datastreams"_s;
369 return u"Sensors"_s;
371 return u"ObservedProperties"_s;
373 return u"Observations"_s;
375 return u"FeaturesOfInterest"_s;
377 return u"MultiDatastreams"_s;
378 }
380}
381
383{
384 switch ( type )
385 {
387 return {};
388
390 // https://docs.ogc.org/is/18-088/18-088.html#thing
391 return { u"id"_s,
392 u"selfLink"_s,
393 u"name"_s,
394 u"description"_s,
395 u"properties"_s,
396 };
397
399 // https://docs.ogc.org/is/18-088/18-088.html#location
400 return { u"id"_s,
401 u"selfLink"_s,
402 u"name"_s,
403 u"description"_s,
404 u"properties"_s,
405 };
406
408 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
409 return { u"id"_s,
410 u"selfLink"_s,
411 u"time"_s,
412 };
413
415 // https://docs.ogc.org/is/18-088/18-088.html#datastream
416 return { u"id"_s,
417 u"selfLink"_s,
418 u"name"_s,
419 u"description"_s,
420 u"unitOfMeasurement"_s,
421 u"observationType"_s,
422 u"properties"_s,
423 u"phenomenonTime"_s,
424 u"resultTime"_s,
425 };
426
428 // https://docs.ogc.org/is/18-088/18-088.html#sensor
429 return { u"id"_s,
430 u"selfLink"_s,
431 u"name"_s,
432 u"description"_s,
433 u"metadata"_s,
434 u"properties"_s,
435 };
436
438 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
439 return { u"id"_s,
440 u"selfLink"_s,
441 u"name"_s,
442 u"definition"_s,
443 u"description"_s,
444 u"properties"_s,
445 };
446
448 // https://docs.ogc.org/is/18-088/18-088.html#observation
449 return { u"id"_s,
450 u"selfLink"_s,
451 u"phenomenonTime"_s,
452 u"result"_s,
453 u"resultTime"_s,
454 u"resultQuality"_s,
455 u"validTime"_s,
456 u"parameters"_s,
457 };
458
460 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
461 return { u"id"_s,
462 u"selfLink"_s,
463 u"name"_s,
464 u"description"_s,
465 u"properties"_s,
466 };
467
469 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
470 return { u"id"_s,
471 u"selfLink"_s,
472 u"name"_s,
473 u"description"_s,
474 u"unitOfMeasurements"_s,
475 u"observationType"_s,
476 u"multiObservationDataTypes"_s,
477 u"properties"_s,
478 u"phenomenonTime"_s,
479 u"resultTime"_s,
480 };
481 }
482
483 return {};
484}
485
487{
488 QgsFields fields;
489
490 // common fields: https://docs.ogc.org/is/18-088/18-088.html#common-control-information
491 fields.append( QgsField( u"id"_s, QMetaType::Type::QString ) );
492 fields.append( QgsField( u"selfLink"_s, QMetaType::Type::QString ) );
493
494 switch ( type )
495 {
497 break;
498
500 // https://docs.ogc.org/is/18-088/18-088.html#thing
501 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
502 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
503 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
504 break;
505
507 // https://docs.ogc.org/is/18-088/18-088.html#location
508 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
509 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
510 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
511 break;
512
514 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
515 fields.append( QgsField( u"time"_s, QMetaType::Type::QDateTime ) );
516 break;
517
519 // https://docs.ogc.org/is/18-088/18-088.html#datastream
520 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
521 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
522 fields.append( QgsField( u"unitOfMeasurement"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
523 fields.append( QgsField( u"observationType"_s, QMetaType::Type::QString ) );
524 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
525 if ( includeRangeFieldProxies )
526 {
527 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
528 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
529 fields.append( QgsField( u"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
530 fields.append( QgsField( u"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
531 }
532 break;
533
535 // https://docs.ogc.org/is/18-088/18-088.html#sensor
536 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
537 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
538 fields.append( QgsField( u"metadata"_s, QMetaType::Type::QString ) );
539 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
540 break;
541
543 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
544 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
545 fields.append( QgsField( u"definition"_s, QMetaType::Type::QString ) );
546 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
547 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
548 break;
549
551 // https://docs.ogc.org/is/18-088/18-088.html#observation
552 if ( includeRangeFieldProxies )
553 {
554 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
555 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
556 }
557
558 // TODO -- handle type correctly
559 fields.append( QgsField( u"result"_s, QMetaType::Type::QString ) );
560
561 fields.append( QgsField( u"resultTime"_s, QMetaType::Type::QDateTime ) );
562 fields.append( QgsField( u"resultQuality"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
563 if ( includeRangeFieldProxies )
564 {
565 fields.append( QgsField( u"validTimeStart"_s, QMetaType::Type::QDateTime ) );
566 fields.append( QgsField( u"validTimeEnd"_s, QMetaType::Type::QDateTime ) );
567 }
568 fields.append( QgsField( u"parameters"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
569 break;
570
572 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
573 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
574 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
575 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
576 break;
577
579 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
580 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
581 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
582 fields.append( QgsField( u"unitOfMeasurements"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
583 fields.append( QgsField( u"observationType"_s, QMetaType::Type::QString ) );
584 fields.append( QgsField( u"multiObservationDataTypes"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
585 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
586 if ( includeRangeFieldProxies )
587 {
588 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
589 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
590 fields.append( QgsField( u"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
591 fields.append( QgsField( u"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
592 }
593 break;
594 }
595
596 return fields;
597}
598
599QgsFields QgsSensorThingsUtils::fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList<Qgis::SensorThingsEntity> &expandedTypes )
600{
601 if ( expandedTypes.empty() )
602 return fieldsForEntityType( baseType );
603
604 QgsFields fields = fieldsForEntityType( baseType );
605 QString path;
606 for ( const Qgis::SensorThingsEntity expandedType : expandedTypes )
607 {
608 path = ( path.isEmpty() ? QString() : ( path + '_' ) ) + qgsEnumValueToKey( expandedType );
609 const QgsFields expandedFields = fieldsForEntityType( expandedType );
610 for ( const QgsField &expandedField : expandedFields )
611 {
612 QgsField renamedExpandedField = expandedField;
613 renamedExpandedField.setName( u"%1_%2"_s.arg( path, expandedField.name() ) );
614 fields.append( renamedExpandedField );
615 }
616 }
617 return fields;
618}
619
644
665
686
688{
689 QString geometryTypeString;
690 switch ( QgsWkbTypes::geometryType( wkbType ) )
691 {
693 geometryTypeString = u"Point"_s;
694 break;
696 geometryTypeString = u"Polygon"_s;
697 break;
699 geometryTypeString = u"LineString"_s;
700 break;
701
704 return QString();
705 }
706
707 const QString filterTarget = geometryFieldForEntityType( entityType );
708 if ( filterTarget.isEmpty() )
709 return QString();
710
711 return u"%1/type eq '%2' or %1/geometry/type eq '%2'"_s.arg( filterTarget, geometryTypeString );
712}
713
714QString QgsSensorThingsUtils::filterForExtent( const QString &geometryField, const QgsRectangle &extent )
715{
716 // TODO -- confirm using 'geography' is always correct here
717 return ( extent.isNull() || geometryField.isEmpty() )
718 ? QString()
719 : u"geo.intersects(%1, geography'%2')"_s.arg( geometryField, extent.asWktPolygon() );
720}
721
722QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
723{
724 QStringList nonEmptyFilters;
725 for ( const QString &filter : filters )
726 {
727 if ( !filter.isEmpty() )
728 nonEmptyFilters.append( filter );
729 }
730 if ( nonEmptyFilters.empty() )
731 return QString();
732 if ( nonEmptyFilters.size() == 1 )
733 return nonEmptyFilters.at( 0 );
734
735 return u"("_s + nonEmptyFilters.join( ") and ("_L1 ) + u")"_s;
736}
737
738QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
739{
740 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
741 QgsSetRequestInitiatorClass( request, u"QgsSensorThingsUtils"_s )
742
743 QgsBlockingNetworkRequest networkRequest;
744 networkRequest.setAuthCfg( authCfg );
745
746 switch ( networkRequest.get( request ) )
747 {
749 break;
750
754 QgsDebugError( u"Connection failed: %1"_s.arg( networkRequest.errorMessage() ) );
755 return {};
756 }
757
758 QString entityBaseUri;
759 const QgsNetworkReplyContent content = networkRequest.reply();
760 try
761 {
762 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
763 if ( !rootContent.contains( "value" ) )
764 {
765 QgsDebugError( u"No 'value' array in response"_s );
766 return {};
767 }
768
769 bool foundMatchingEntity = false;
770 for ( const auto &valueJson : rootContent["value"] )
771 {
772 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
773 {
774 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
776 if ( entityType == type )
777 {
778 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
779 if ( !url.isEmpty() )
780 {
781 foundMatchingEntity = true;
782 entityBaseUri = url;
783 break;
784 }
785 }
786 }
787 }
788
789 if ( !foundMatchingEntity )
790 {
791 QgsDebugError( u"Could not find url for %1"_s.arg( qgsEnumValueToKey( type ) ) );
792 return {};
793 }
794 }
795 catch ( const nlohmann::json::parse_error &ex )
796 {
797 QgsDebugError( u"Error parsing response: %1"_s.arg( ex.what() ) );
798 return {};
799 }
800
801 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
802 {
803 // return no features, just the total count
804 QString countUri = u"%1?$top=0&$count=true"_s.arg( entityBaseUri );
806 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
807 if ( !typeFilter.isEmpty() )
808 countUri += u"&$filter="_s + typeFilter;
809
810 const QUrl url( countUri );
811
812 QNetworkRequest request( url );
813 QgsSetRequestInitiatorClass( request, u"QgsSensorThingsSharedData"_s );
814
815 QgsBlockingNetworkRequest networkRequest;
816 networkRequest.setAuthCfg( authCfg );
817 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
818
819 if ( feedback && feedback->isCanceled() )
820 return -1;
821
822 // Handle network errors
824 {
825 QgsDebugError( u"Network error: %1"_s.arg( networkRequest.errorMessage() ) );
826 return -1;
827 }
828 else
829 {
830 const QgsNetworkReplyContent content = networkRequest.reply();
831 try
832 {
833 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
834 if ( !rootContent.contains( "@iot.count" ) )
835 {
836 QgsDebugError( u"No '@iot.count' value in response"_s );
837 return -1;
838 }
839
840 return rootContent["@iot.count"].get<long long>();
841 }
842 catch ( const nlohmann::json::parse_error &ex )
843 {
844 QgsDebugError( u"Error parsing response: %1"_s.arg( ex.what() ) );
845 return -1;
846 }
847 }
848 };
849
850 QList<Qgis::GeometryType> types;
851 for ( Qgis::GeometryType geometryType :
852 {
856 } )
857 {
858 const long long matchCount = getCountForType( geometryType );
859 if ( matchCount < 0 )
860 return {};
861 else if ( matchCount > 0 )
862 types.append( geometryType );
863 }
864 return types;
865}
866
868{
869 // note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
870
871 // NOLINTBEGIN(bugprone-branch-clone)
872 switch ( type )
873 {
875 return {};
876
878 return
879 {
883 };
884
886 return
887 {
890 };
891
893 return
894 {
896 };
897
899 return
900 {
905 };
906
908 return
909 {
912 };
913
915 return
916 {
919 };
920
922 return
923 {
926 };
927
929 return
930 {
932 };
933
935 return
936 {
941 };
942 }
943 // NOLINTEND(bugprone-branch-clone)
944
946}
947
949{
950 valid = true;
951 switch ( baseType )
952 {
954 break;
955
957 {
958 switch ( relatedType )
959 {
962
967
974 break;
975 }
976 break;
977 }
979 {
980 switch ( relatedType )
981 {
985
994 break;
995 }
996 break;
997 }
999 {
1000 switch ( relatedType )
1001 {
1003 // The SensorThings specification MAY be wrong here. There's an inconsistency between
1004 // the inheritance graph which shows HistoricalLocation linking to "location", when
1005 // the text description describes the relationship as linking to "locationS".
1006 // We assume the text description is correct and the graph is wrong, as the reverse
1007 // relationship between Location and HistoricalLocation is many-to-many.
1009
1012
1021 break;
1022 }
1023
1024 break;
1025 }
1026
1028 {
1029 switch ( relatedType )
1030 {
1035
1038
1045 break;
1046 }
1047
1048 break;
1049 }
1050
1052 {
1053 switch ( relatedType )
1054 {
1058
1067 break;
1068 }
1069
1070 break;
1071 }
1072
1074 {
1075 switch ( relatedType )
1076 {
1079
1082
1091 break;
1092 }
1093 break;
1094 }
1095
1097 {
1098 switch ( relatedType )
1099 {
1104
1112 break;
1113 }
1114 break;
1115 }
1116
1118 {
1119 switch ( relatedType )
1120 {
1123
1133 break;
1134 }
1135
1136 break;
1137 }
1138
1140 {
1141 switch ( relatedType )
1142 {
1146
1149
1152
1159 break;
1160 }
1161 break;
1162 }
1163 }
1164
1165 valid = false;
1167}
1168
1169QString QgsSensorThingsUtils::asQueryString( Qgis::SensorThingsEntity baseType, const QList<QgsSensorThingsExpansionDefinition> &expansions )
1170{
1171 QString res;
1172 for ( int i = expansions.size() - 1; i >= 0 ; i-- )
1173 {
1174 const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
1175 if ( !expansion.isValid() )
1176 continue;
1177
1178 const Qgis::SensorThingsEntity parentType = i > 0 ? expansions.at( i - 1 ).childEntity() : baseType;
1179
1180 res = expansion.asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList{ res } );
1181 }
1182
1183 return res;
1184}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:6232
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
Definition qgis.h:6238
@ MultiDatastream
A MultiDatastream groups a collection of Observations and the Observations in a MultiDatastream have ...
Definition qgis.h:6242
@ ObservedProperty
An ObservedProperty specifies the phenomenon of an Observation.
Definition qgis.h:6239
@ Invalid
An invalid/unknown entity.
Definition qgis.h:6233
@ FeatureOfInterest
In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of...
Definition qgis.h:6241
@ Datastream
A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by ...
Definition qgis.h:6237
@ Observation
An Observation is the act of measuring or otherwise determining the value of a property.
Definition qgis.h:6240
@ Location
A Location entity locates the Thing or the Things it associated with. A Thing’s Location entity is de...
Definition qgis.h:6235
@ Thing
A Thing is an object of the physical world (physical things) or the information world (virtual things...
Definition qgis.h:6234
@ HistoricalLocation
A Thing’s HistoricalLocation entity set provides the times of the current (i.e., last known) and prev...
Definition qgis.h:6236
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
@ Polygon
Polygons.
Definition qgis.h:368
@ Unknown
Unknown types.
Definition qgis.h:369
@ Null
No geometry.
Definition qgis.h:370
RelationshipCardinality
Relationship cardinality.
Definition qgis.h:4476
@ ManyToMany
Many to many relationship.
Definition qgis.h:4480
@ ManyToOne
Many to one relationship.
Definition qgis.h:4479
@ OneToOne
One to one relationship.
Definition qgis.h:4477
@ OneToMany
One to many relationship.
Definition qgis.h:4478
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ Point
Point.
Definition qgis.h:282
@ LineString
LineString.
Definition qgis.h:283
@ Polygon
Polygon.
Definition qgis.h:284
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
void setName(const QString &name)
Set the field name.
Definition qgsfield.cpp:233
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:76
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
A rectangle specified with double values.
Q_INVOKABLE QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
Encapsulates information about how relationships in a SensorThings API service should be expanded.
Qgis::SensorThingsEntity childEntity() const
Returns the target child entity which should be expanded.
void setLimit(int limit)
Sets the limit on the number of child features to fetch.
void setChildEntity(Qgis::SensorThingsEntity entity)
Sets the target child entity which should be expanded.
void setSortOrder(Qt::SortOrder order)
Sets the sort order for the expanded child entities.
static QgsSensorThingsExpansionDefinition defaultDefinitionForEntity(Qgis::SensorThingsEntity entity)
Returns an expansion definition for the specified entity type, populated with reasonable defaults whi...
static QgsSensorThingsExpansionDefinition fromString(const QString &string)
Returns a QgsSensorThingsExpansionDefinition from a string representation.
bool operator!=(const QgsSensorThingsExpansionDefinition &other) const
void setFilter(const QString &filter)
Returns the string filter to filter expanded child entities by.
QString asQueryString(Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions=QStringList()) const
Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phe...
bool operator==(const QgsSensorThingsExpansionDefinition &other) const
int limit() const
Returns the limit on the number of child features to fetch.
void setOrderBy(const QString &field)
Sets the field name to order the expanded child entities by.
Qt::SortOrder sortOrder() const
Returns the sort order for the expanded child entities.
QString toString() const
Returns a string encapsulation of the expansion definition.
QString orderBy() const
Returns the field name to order the expanded child entities by.
QString filter() const
Returns the string filter to filter expanded child entities by.
QgsSensorThingsExpansionDefinition(Qgis::SensorThingsEntity childEntity=Qgis::SensorThingsEntity::Invalid, const QString &orderBy=QString(), Qt::SortOrder sortOrder=Qt::SortOrder::AscendingOrder, int limit=QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT, const QString &filter=QString())
Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
bool isValid() const
Returns true if the definition is valid.
static QStringList propertiesForEntityType(Qgis::SensorThingsEntity type)
Returns the SensorThings properties which correspond to a specified entity type.
static QList< Qgis::GeometryType > availableGeometryTypes(const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback=nullptr, const QString &authCfg=QString())
Returns a list of available geometry types for the server at the specified uri and entity type.
static Qgis::SensorThingsEntity stringToEntity(const QString &type)
Converts a string value to a Qgis::SensorThingsEntity type.
static QString entityToSetString(Qgis::SensorThingsEntity type)
Converts a SensorThings entity set to a SensorThings entity set string.
static QString asQueryString(Qgis::SensorThingsEntity baseType, const QList< QgsSensorThingsExpansionDefinition > &expansions)
Returns a list of expansions as a valid SensorThings API query string, eg "$expand=Locations($orderby...
static Qgis::SensorThingsEntity entitySetStringToEntity(const QString &type)
Converts a string value corresponding to a SensorThings entity set to a Qgis::SensorThingsEntity type...
static QString combineFilters(const QStringList &filters)
Combines a set of SensorThings API filter operators.
static QString filterForWkbType(Qgis::SensorThingsEntity entityType, Qgis::WkbType wkbType)
Returns a filter string which restricts results to those matching the specified entityType and wkbTyp...
static Qgis::RelationshipCardinality relationshipCardinality(Qgis::SensorThingsEntity baseType, Qgis::SensorThingsEntity relatedType, bool &valid)
Returns the cardinality of the relationship between a base entity type and a related entity type.
static Qgis::GeometryType geometryTypeForEntity(Qgis::SensorThingsEntity type)
Returns the geometry type for if the specified entity type.
static QgsFields fieldsForEntityType(Qgis::SensorThingsEntity type, bool includeRangeFieldProxies=true)
Returns the fields which correspond to a specified entity type.
static QString displayString(Qgis::SensorThingsEntity type, bool plural=false)
Converts a Qgis::SensorThingsEntity type to a user-friendly translated string.
static bool entityTypeHasGeometry(Qgis::SensorThingsEntity type)
Returns true if the specified entity type can have geometry attached.
static QgsFields fieldsForExpandedEntityType(Qgis::SensorThingsEntity baseType, const QList< Qgis::SensorThingsEntity > &expandedTypes)
Returns the fields which correspond to a specified entity baseType, expanded using the specified list...
static QString geometryFieldForEntityType(Qgis::SensorThingsEntity type)
Returns the geometry field for a specified entity type.
static QList< Qgis::SensorThingsEntity > expandableTargets(Qgis::SensorThingsEntity type)
Returns a list of permissible expand targets for a given base entity type.
static QString filterForExtent(const QString &geometryField, const QgsRectangle &extent)
Returns a filter string which restricts results to those within the specified extent.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:7110
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7091
#define BUILTIN_UNREACHABLE
Definition qgis.h:7489
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)