QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
49{
50 switch ( entity )
51 {
54
60 // no special defaults for these entities
62
64 // default to descending sort by phenomenonTime
65 return QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity::Observation, u"phenomenonTime"_s, Qt::SortOrder::DescendingOrder );
66
70 // use smaller limit by default
71 return QgsSensorThingsExpansionDefinition( entity, QString(), Qt::SortOrder::AscendingOrder, 10 );
72 }
74}
75
80
85
90
92{
93 return mSortOrder;
94}
95
97{
98 mSortOrder = order;
99}
100
102{
103 return mLimit;
104}
105
107{
108 mLimit = limit;
109}
110
112{
113 return mFilter;
114}
115
117{
118 mFilter = filter;
119}
120
122{
123 if ( !isValid() )
124 return QString();
125
126 QStringList parts;
127 parts.append( qgsEnumValueToKey( mChildEntity ) );
128 if ( !mOrderBy.isEmpty() )
129 parts.append( u"orderby=%1,%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? u"asc"_s : u"desc"_s ) );
130 if ( mLimit >= 0 )
131 parts.append( u"limit=%1"_s.arg( mLimit ) );
132 if ( !mFilter.trimmed().isEmpty() )
133 {
134 QString escapedFilter = mFilter;
135 escapedFilter.replace( ':', "\\colon"_L1 );
136 parts.append( u"filter=%1"_s.arg( escapedFilter ) );
137 }
138 return parts.join( ':' );
139}
140
142{
143 const QStringList parts = string.split( ':', Qt::SkipEmptyParts );
144 if ( parts.empty() )
146
148 definition.setLimit( -1 );
149 for ( int i = 1; i < parts.count(); ++i )
150 {
151 const QString &part = parts.at( i );
152 const thread_local QRegularExpression orderByRegEx( u"^orderby=(.*),(.*?)$"_s );
153 const thread_local QRegularExpression orderLimitRegEx( u"^limit=(\\d+)$"_s );
154 const thread_local QRegularExpression filterRegEx( u"^filter=(.*)$"_s );
155
156 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
157 if ( orderByMatch.hasMatch() )
158 {
159 definition.setOrderBy( orderByMatch.captured( 1 ) );
160 definition.setSortOrder( orderByMatch.captured( 2 ) == "asc"_L1 ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
161 continue;
162 }
163
164 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
165 if ( limitMatch.hasMatch() )
166 {
167 definition.setLimit( limitMatch.captured( 1 ).toInt() );
168 continue;
169 }
170
171 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
172 if ( filterMatch.hasMatch() )
173 {
174 QString filter = filterMatch.captured( 1 );
175 filter.replace( "\\colon"_L1, ":"_L1 );
176 definition.setFilter( filter );
177 continue;
178 }
179 }
180 return definition;
181}
182
183QString QgsSensorThingsExpansionDefinition::asQueryString( Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions ) const
184{
185 if ( !isValid() )
186 return QString();
187
188 bool ok = false;
189 // From the specifications, it SOMETIMES the plural form is used for the expansion query, and sometimes singular.
190 // The choice depends on the cardinality of the relationship between the involved entities.
191 const Qgis::RelationshipCardinality cardinality = QgsSensorThingsUtils::relationshipCardinality( parentEntityType, mChildEntity, ok );
192 QString childEntityString;
193 if ( !ok )
194 {
195 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
196 }
197 else
198 {
199 switch ( cardinality )
200 {
203 // use singular strings, eg "Thing"
204 childEntityString = qgsEnumValueToKey( mChildEntity );
205 break;
206
209 // use plural strings, eg "Things"
210 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
211 break;
212 }
213 }
214
215 QString res = u"$expand=%1"_s.arg( childEntityString );
216
217 QStringList queryOptions;
218 if ( !mOrderBy.isEmpty() )
219 queryOptions.append( u"$orderby=%1%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : u" desc"_s ) );
220
221 if ( mLimit > -1 )
222 queryOptions.append( u"$top=%1"_s.arg( mLimit ) );
223
224 if ( !mFilter.isEmpty() )
225 queryOptions.append( u"$filter=%1"_s.arg( mFilter ) );
226
227 queryOptions.append( additionalOptions );
228
229 if ( !queryOptions.isEmpty() )
230 res.append( u"(%1)"_s.arg( queryOptions.join( ';' ) ) );
231
232 return res;
233}
234
236{
237 if ( mChildEntity == Qgis::SensorThingsEntity::Invalid )
238 return other.mChildEntity == Qgis::SensorThingsEntity::Invalid;
239
240 return mChildEntity == other.mChildEntity && mSortOrder == other.mSortOrder && mLimit == other.mLimit && mOrderBy == other.mOrderBy && mFilter == other.mFilter;
241}
242
244{
245 return !( *this == other );
246}
247
249{
250 return mOrderBy;
251}
252
254{
255 mOrderBy = field;
256}
257
258//
259// QgsSensorThingsUtils
260//
261
263{
264 const QString trimmed = type.trimmed();
265 if ( trimmed.compare( "Thing"_L1, Qt::CaseInsensitive ) == 0 )
267 if ( trimmed.compare( "Location"_L1, Qt::CaseInsensitive ) == 0 )
269 if ( trimmed.compare( "HistoricalLocation"_L1, Qt::CaseInsensitive ) == 0 )
271 if ( trimmed.compare( "Datastream"_L1, Qt::CaseInsensitive ) == 0 )
273 if ( trimmed.compare( "Sensor"_L1, Qt::CaseInsensitive ) == 0 )
275 if ( trimmed.compare( "ObservedProperty"_L1, Qt::CaseInsensitive ) == 0 )
277 if ( trimmed.compare( "Observation"_L1, Qt::CaseInsensitive ) == 0 )
279 if ( trimmed.compare( "FeatureOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
281 if ( trimmed.compare( "MultiDatastream"_L1, Qt::CaseInsensitive ) == 0 )
283
285}
286
288{
289 switch ( type )
290 {
292 return QString();
294 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
296 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
298 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
300 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
302 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
304 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
306 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
308 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
310 return plural ? QObject::tr( "MultiDatastreams" ) : QObject::tr( "MultiDatastream" );
311 }
313}
314
316{
317 const QString trimmed = type.trimmed();
318 if ( trimmed.compare( "Things"_L1, Qt::CaseInsensitive ) == 0 )
320 if ( trimmed.compare( "Locations"_L1, Qt::CaseInsensitive ) == 0 )
322 if ( trimmed.compare( "HistoricalLocations"_L1, Qt::CaseInsensitive ) == 0 )
324 if ( trimmed.compare( "Datastreams"_L1, Qt::CaseInsensitive ) == 0 )
326 if ( trimmed.compare( "Sensors"_L1, Qt::CaseInsensitive ) == 0 )
328 if ( trimmed.compare( "ObservedProperties"_L1, Qt::CaseInsensitive ) == 0 )
330 if ( trimmed.compare( "Observations"_L1, Qt::CaseInsensitive ) == 0 )
332 if ( trimmed.compare( "FeaturesOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
334 if ( trimmed.compare( "MultiDatastreams"_L1, Qt::CaseInsensitive ) == 0 )
336
338}
339
341{
342 switch ( type )
343 {
345 return QString();
347 return u"Things"_s;
349 return u"Locations"_s;
351 return u"HistoricalLocations"_s;
353 return u"Datastreams"_s;
355 return u"Sensors"_s;
357 return u"ObservedProperties"_s;
359 return u"Observations"_s;
361 return u"FeaturesOfInterest"_s;
363 return u"MultiDatastreams"_s;
364 }
366}
367
369{
370 switch ( type )
371 {
373 return {};
374
376 // https://docs.ogc.org/is/18-088/18-088.html#thing
377 return {
378 u"id"_s,
379 u"selfLink"_s,
380 u"name"_s,
381 u"description"_s,
382 u"properties"_s,
383 };
384
386 // https://docs.ogc.org/is/18-088/18-088.html#location
387 return {
388 u"id"_s,
389 u"selfLink"_s,
390 u"name"_s,
391 u"description"_s,
392 u"properties"_s,
393 };
394
396 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
397 return {
398 u"id"_s,
399 u"selfLink"_s,
400 u"time"_s,
401 };
402
404 // https://docs.ogc.org/is/18-088/18-088.html#datastream
405 return {
406 u"id"_s,
407 u"selfLink"_s,
408 u"name"_s,
409 u"description"_s,
410 u"unitOfMeasurement"_s,
411 u"observationType"_s,
412 u"properties"_s,
413 u"phenomenonTime"_s,
414 u"resultTime"_s,
415 };
416
418 // https://docs.ogc.org/is/18-088/18-088.html#sensor
419 return {
420 u"id"_s,
421 u"selfLink"_s,
422 u"name"_s,
423 u"description"_s,
424 u"metadata"_s,
425 u"properties"_s,
426 };
427
429 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
430 return {
431 u"id"_s,
432 u"selfLink"_s,
433 u"name"_s,
434 u"definition"_s,
435 u"description"_s,
436 u"properties"_s,
437 };
438
440 // https://docs.ogc.org/is/18-088/18-088.html#observation
441 return {
442 u"id"_s,
443 u"selfLink"_s,
444 u"phenomenonTime"_s,
445 u"result"_s,
446 u"resultTime"_s,
447 u"resultQuality"_s,
448 u"validTime"_s,
449 u"parameters"_s,
450 };
451
453 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
454 return {
455 u"id"_s,
456 u"selfLink"_s,
457 u"name"_s,
458 u"description"_s,
459 u"properties"_s,
460 };
461
463 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
464 return {
465 u"id"_s,
466 u"selfLink"_s,
467 u"name"_s,
468 u"description"_s,
469 u"unitOfMeasurements"_s,
470 u"observationType"_s,
471 u"multiObservationDataTypes"_s,
472 u"properties"_s,
473 u"phenomenonTime"_s,
474 u"resultTime"_s,
475 };
476 }
477
478 return {};
479}
480
482{
483 QgsFields fields;
484
485 // common fields: https://docs.ogc.org/is/18-088/18-088.html#common-control-information
486 fields.append( QgsField( u"id"_s, QMetaType::Type::QString ) );
487 fields.append( QgsField( u"selfLink"_s, QMetaType::Type::QString ) );
488
489 switch ( type )
490 {
492 break;
493
495 // https://docs.ogc.org/is/18-088/18-088.html#thing
496 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
497 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
498 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
499 break;
500
502 // https://docs.ogc.org/is/18-088/18-088.html#location
503 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
504 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
505 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
506 break;
507
509 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
510 fields.append( QgsField( u"time"_s, QMetaType::Type::QDateTime ) );
511 break;
512
514 // https://docs.ogc.org/is/18-088/18-088.html#datastream
515 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
516 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
517 fields.append( QgsField( u"unitOfMeasurement"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
518 fields.append( QgsField( u"observationType"_s, QMetaType::Type::QString ) );
519 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
520 if ( includeRangeFieldProxies )
521 {
522 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
523 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
524 fields.append( QgsField( u"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
525 fields.append( QgsField( u"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
526 }
527 break;
528
530 // https://docs.ogc.org/is/18-088/18-088.html#sensor
531 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
532 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
533 fields.append( QgsField( u"metadata"_s, QMetaType::Type::QString ) );
534 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
535 break;
536
538 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
539 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
540 fields.append( QgsField( u"definition"_s, QMetaType::Type::QString ) );
541 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
542 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
543 break;
544
546 // https://docs.ogc.org/is/18-088/18-088.html#observation
547 if ( includeRangeFieldProxies )
548 {
549 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
550 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
551 }
552
553 // TODO -- handle type correctly
554 fields.append( QgsField( u"result"_s, QMetaType::Type::QString ) );
555
556 fields.append( QgsField( u"resultTime"_s, QMetaType::Type::QDateTime ) );
557 fields.append( QgsField( u"resultQuality"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
558 if ( includeRangeFieldProxies )
559 {
560 fields.append( QgsField( u"validTimeStart"_s, QMetaType::Type::QDateTime ) );
561 fields.append( QgsField( u"validTimeEnd"_s, QMetaType::Type::QDateTime ) );
562 }
563 fields.append( QgsField( u"parameters"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
564 break;
565
567 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
568 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
569 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
570 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
571 break;
572
574 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
575 fields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
576 fields.append( QgsField( u"description"_s, QMetaType::Type::QString ) );
577 fields.append( QgsField( u"unitOfMeasurements"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
578 fields.append( QgsField( u"observationType"_s, QMetaType::Type::QString ) );
579 fields.append( QgsField( u"multiObservationDataTypes"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
580 fields.append( QgsField( u"properties"_s, QMetaType::Type::QVariantMap, u"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
581 if ( includeRangeFieldProxies )
582 {
583 fields.append( QgsField( u"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
584 fields.append( QgsField( u"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
585 fields.append( QgsField( u"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
586 fields.append( QgsField( u"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
587 }
588 break;
589 }
590
591 return fields;
592}
593
594QgsFields QgsSensorThingsUtils::fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList<Qgis::SensorThingsEntity> &expandedTypes )
595{
596 if ( expandedTypes.empty() )
597 return fieldsForEntityType( baseType );
598
599 QgsFields fields = fieldsForEntityType( baseType );
600 QString path;
601 for ( const Qgis::SensorThingsEntity expandedType : expandedTypes )
602 {
603 path = ( path.isEmpty() ? QString() : ( path + '_' ) ) + qgsEnumValueToKey( expandedType );
604 const QgsFields expandedFields = fieldsForEntityType( expandedType );
605 for ( const QgsField &expandedField : expandedFields )
606 {
607 QgsField renamedExpandedField = expandedField;
608 renamedExpandedField.setName( u"%1_%2"_s.arg( path, expandedField.name() ) );
609 fields.append( renamedExpandedField );
610 }
611 }
612 return fields;
613}
614
639
660
681
683{
684 QString geometryTypeString;
685 switch ( QgsWkbTypes::geometryType( wkbType ) )
686 {
688 geometryTypeString = u"Point"_s;
689 break;
691 geometryTypeString = u"Polygon"_s;
692 break;
694 geometryTypeString = u"LineString"_s;
695 break;
696
699 return QString();
700 }
701
702 const QString filterTarget = geometryFieldForEntityType( entityType );
703 if ( filterTarget.isEmpty() )
704 return QString();
705
706 return u"%1/type eq '%2' or %1/geometry/type eq '%2'"_s.arg( filterTarget, geometryTypeString );
707}
708
709QString QgsSensorThingsUtils::filterForExtent( const QString &geometryField, const QgsRectangle &extent )
710{
711 // TODO -- confirm using 'geography' is always correct here
712 return ( extent.isNull() || geometryField.isEmpty() ) ? QString() : u"geo.intersects(%1, geography'%2')"_s.arg( geometryField, extent.asWktPolygon() );
713}
714
715QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
716{
717 QStringList nonEmptyFilters;
718 for ( const QString &filter : filters )
719 {
720 if ( !filter.isEmpty() )
721 nonEmptyFilters.append( filter );
722 }
723 if ( nonEmptyFilters.empty() )
724 return QString();
725 if ( nonEmptyFilters.size() == 1 )
726 return nonEmptyFilters.at( 0 );
727
728 return u"("_s + nonEmptyFilters.join( ") and ("_L1 ) + u")"_s;
729}
730
731QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
732{
733 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
734 QgsSetRequestInitiatorClass( request, u"QgsSensorThingsUtils"_s )
735
736 QgsBlockingNetworkRequest networkRequest;
737 networkRequest.setAuthCfg( authCfg );
738
739 switch ( networkRequest.get( request ) )
740 {
742 break;
743
747 QgsDebugError( u"Connection failed: %1"_s.arg( networkRequest.errorMessage() ) );
748 return {};
749 }
750
751 QString entityBaseUri;
752 const QgsNetworkReplyContent content = networkRequest.reply();
753 try
754 {
755 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
756 if ( !rootContent.contains( "value" ) )
757 {
758 QgsDebugError( u"No 'value' array in response"_s );
759 return {};
760 }
761
762 bool foundMatchingEntity = false;
763 for ( const auto &valueJson : rootContent["value"] )
764 {
765 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
766 {
767 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
769 if ( entityType == type )
770 {
771 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
772 if ( !url.isEmpty() )
773 {
774 foundMatchingEntity = true;
775 entityBaseUri = url;
776 break;
777 }
778 }
779 }
780 }
781
782 if ( !foundMatchingEntity )
783 {
784 QgsDebugError( u"Could not find url for %1"_s.arg( qgsEnumValueToKey( type ) ) );
785 return {};
786 }
787 }
788 catch ( const nlohmann::json::parse_error &ex )
789 {
790 QgsDebugError( u"Error parsing response: %1"_s.arg( ex.what() ) );
791 return {};
792 }
793
794 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long {
795 // return no features, just the total count
796 QString countUri = u"%1?$top=0&$count=true"_s.arg( entityBaseUri );
798 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
799 if ( !typeFilter.isEmpty() )
800 countUri += u"&$filter="_s + typeFilter;
801
802 const QUrl url( countUri );
803
804 QNetworkRequest request( url );
805 QgsSetRequestInitiatorClass( request, u"QgsSensorThingsSharedData"_s );
806
807 QgsBlockingNetworkRequest networkRequest;
808 networkRequest.setAuthCfg( authCfg );
809 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
810
811 if ( feedback && feedback->isCanceled() )
812 return -1;
813
814 // Handle network errors
816 {
817 QgsDebugError( u"Network error: %1"_s.arg( networkRequest.errorMessage() ) );
818 return -1;
819 }
820 else
821 {
822 const QgsNetworkReplyContent content = networkRequest.reply();
823 try
824 {
825 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
826 if ( !rootContent.contains( "@iot.count" ) )
827 {
828 QgsDebugError( u"No '@iot.count' value in response"_s );
829 return -1;
830 }
831
832 return rootContent["@iot.count"].get<long long>();
833 }
834 catch ( const nlohmann::json::parse_error &ex )
835 {
836 QgsDebugError( u"Error parsing response: %1"_s.arg( ex.what() ) );
837 return -1;
838 }
839 }
840 };
841
842 QList<Qgis::GeometryType> types;
844 {
845 const long long matchCount = getCountForType( geometryType );
846 if ( matchCount < 0 )
847 return {};
848 else if ( matchCount > 0 )
849 types.append( geometryType );
850 }
851 return types;
852}
853
855{
856 // note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
857
858 // NOLINTBEGIN(bugprone-branch-clone)
859 switch ( type )
860 {
862 return {};
863
865 return {
869 };
870
872 return {
875 };
876
879
882
884 return {
887 };
888
891
894
897
900 }
901 // NOLINTEND(bugprone-branch-clone)
902
904}
905
907{
908 valid = true;
909 switch ( baseType )
910 {
912 break;
913
915 {
916 switch ( relatedType )
917 {
920
925
932 break;
933 }
934 break;
935 }
937 {
938 switch ( relatedType )
939 {
943
952 break;
953 }
954 break;
955 }
957 {
958 switch ( relatedType )
959 {
961 // The SensorThings specification MAY be wrong here. There's an inconsistency between
962 // the inheritance graph which shows HistoricalLocation linking to "location", when
963 // the text description describes the relationship as linking to "locationS".
964 // We assume the text description is correct and the graph is wrong, as the reverse
965 // relationship between Location and HistoricalLocation is many-to-many.
967
970
979 break;
980 }
981
982 break;
983 }
984
986 {
987 switch ( relatedType )
988 {
993
996
1003 break;
1004 }
1005
1006 break;
1007 }
1008
1010 {
1011 switch ( relatedType )
1012 {
1016
1025 break;
1026 }
1027
1028 break;
1029 }
1030
1032 {
1033 switch ( relatedType )
1034 {
1037
1040
1049 break;
1050 }
1051 break;
1052 }
1053
1055 {
1056 switch ( relatedType )
1057 {
1062
1070 break;
1071 }
1072 break;
1073 }
1074
1076 {
1077 switch ( relatedType )
1078 {
1081
1091 break;
1092 }
1093
1094 break;
1095 }
1096
1098 {
1099 switch ( relatedType )
1100 {
1104
1107
1110
1117 break;
1118 }
1119 break;
1120 }
1121 }
1122
1123 valid = false;
1125}
1126
1127QString QgsSensorThingsUtils::asQueryString( Qgis::SensorThingsEntity baseType, const QList<QgsSensorThingsExpansionDefinition> &expansions )
1128{
1129 QString res;
1130 for ( int i = expansions.size() - 1; i >= 0; i-- )
1131 {
1132 const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
1133 if ( !expansion.isValid() )
1134 continue;
1135
1136 const Qgis::SensorThingsEntity parentType = i > 0 ? expansions.at( i - 1 ).childEntity() : baseType;
1137
1138 res = expansion.asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList { res } );
1139 }
1140
1141 return res;
1142}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:6324
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
Definition qgis.h:6330
@ MultiDatastream
A MultiDatastream groups a collection of Observations and the Observations in a MultiDatastream have ...
Definition qgis.h:6334
@ ObservedProperty
An ObservedProperty specifies the phenomenon of an Observation.
Definition qgis.h:6331
@ Invalid
An invalid/unknown entity.
Definition qgis.h:6325
@ FeatureOfInterest
In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of...
Definition qgis.h:6333
@ Datastream
A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by ...
Definition qgis.h:6329
@ Observation
An Observation is the act of measuring or otherwise determining the value of a property.
Definition qgis.h:6332
@ Location
A Location entity locates the Thing or the Things it associated with. A Thing’s Location entity is de...
Definition qgis.h:6327
@ Thing
A Thing is an object of the physical world (physical things) or the information world (virtual things...
Definition qgis.h:6326
@ HistoricalLocation
A Thing’s HistoricalLocation entity set provides the times of the current (i.e., last known) and prev...
Definition qgis.h:6328
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
RelationshipCardinality
Relationship cardinality.
Definition qgis.h:4561
@ ManyToMany
Many to many relationship.
Definition qgis.h:4565
@ ManyToOne
Many to one relationship.
Definition qgis.h:4564
@ OneToOne
One to one relationship.
Definition qgis.h:4562
@ OneToMany
One to many relationship.
Definition qgis.h:4563
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ Polygon
Polygon.
Definition qgis.h:298
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:56
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:224
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:75
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:7176
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7157
#define BUILTIN_UNREACHABLE
Definition qgis.h:7540
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)