QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 <QUrl>
33
34//
35// QgsSensorThingsExpansionDefinition
36//
38 : mChildEntity( childEntity )
39 , mOrderBy( orderBy )
40 , mSortOrder( sortOrder )
41 , mLimit( limit )
42 , mFilter( filter )
43{
44
45}
46
48{
49 switch ( entity )
50 {
53
59 // no special defaults for these entities
61 entity
62 );
63
65 // default to descending sort by phenomenonTime
68 QStringLiteral( "phenomenonTime" ), Qt::SortOrder::DescendingOrder
69 );
70
74 // use smaller limit by default
76 entity,
77 QString(), Qt::SortOrder::AscendingOrder, 10
78 );
79 }
81}
82
87
92
97
99{
100 return mSortOrder;
101}
102
104{
105 mSortOrder = order;
106}
107
109{
110 return mLimit;
111}
112
114{
115 mLimit = limit;
116}
117
119{
120 return mFilter;
121}
122
124{
125 mFilter = filter;
126}
127
129{
130 if ( !isValid() )
131 return QString();
132
133 QStringList parts;
134 parts.append( qgsEnumValueToKey( mChildEntity ) );
135 if ( !mOrderBy.isEmpty() )
136 parts.append( QStringLiteral( "orderby=%1,%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) ) );
137 if ( mLimit >= 0 )
138 parts.append( QStringLiteral( "limit=%1" ).arg( mLimit ) );
139 if ( !mFilter.trimmed().isEmpty() )
140 {
141 QString escapedFilter = mFilter;
142 escapedFilter.replace( ':', QLatin1String( "\\colon" ) );
143 parts.append( QStringLiteral( "filter=%1" ).arg( escapedFilter ) );
144 }
145 return parts.join( ':' );
146}
147
149{
150 const QStringList parts = string.split( ':', Qt::SkipEmptyParts );
151 if ( parts.empty() )
153
155 definition.setLimit( -1 );
156 for ( int i = 1; i < parts.count(); ++i )
157 {
158 const QString &part = parts.at( i );
159 const thread_local QRegularExpression orderByRegEx( QStringLiteral( "^orderby=(.*),(.*?)$" ) );
160 const thread_local QRegularExpression orderLimitRegEx( QStringLiteral( "^limit=(\\d+)$" ) );
161 const thread_local QRegularExpression filterRegEx( QStringLiteral( "^filter=(.*)$" ) );
162
163 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
164 if ( orderByMatch.hasMatch() )
165 {
166 definition.setOrderBy( orderByMatch.captured( 1 ) );
167 definition.setSortOrder( orderByMatch.captured( 2 ) == QLatin1String( "asc" ) ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
168 continue;
169 }
170
171 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
172 if ( limitMatch.hasMatch() )
173 {
174 definition.setLimit( limitMatch.captured( 1 ).toInt() );
175 continue;
176 }
177
178 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
179 if ( filterMatch.hasMatch() )
180 {
181 QString filter = filterMatch.captured( 1 );
182 filter.replace( QLatin1String( "\\colon" ), QLatin1String( ":" ) );
183 definition.setFilter( filter );
184 continue;
185 }
186 }
187 return definition;
188}
189
190QString QgsSensorThingsExpansionDefinition::asQueryString( Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions ) const
191{
192 if ( !isValid() )
193 return QString();
194
195 bool ok = false;
196 // From the specifications, it SOMETIMES the plural form is used for the expansion query, and sometimes singular.
197 // The choice depends on the cardinality of the relationship between the involved entities.
198 const Qgis::RelationshipCardinality cardinality = QgsSensorThingsUtils::relationshipCardinality( parentEntityType, mChildEntity, ok );
199 QString childEntityString;
200 if ( !ok )
201 {
202 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
203 }
204 else
205 {
206 switch ( cardinality )
207 {
210 // use singular strings, eg "Thing"
211 childEntityString = qgsEnumValueToKey( mChildEntity );
212 break;
213
216 // use plural strings, eg "Things"
217 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
218 break;
219 }
220 }
221
222 QString res = QStringLiteral( "$expand=%1" ).arg( childEntityString );
223
224 QStringList queryOptions;
225 if ( !mOrderBy.isEmpty() )
226 queryOptions.append( QStringLiteral( "$orderby=%1%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : QStringLiteral( " desc" ) ) );
227
228 if ( mLimit > -1 )
229 queryOptions.append( QStringLiteral( "$top=%1" ).arg( mLimit ) );
230
231 if ( !mFilter.isEmpty() )
232 queryOptions.append( QStringLiteral( "$filter=%1" ).arg( mFilter ) );
233
234 queryOptions.append( additionalOptions );
235
236 if ( !queryOptions.isEmpty() )
237 res.append( QStringLiteral( "(%1)" ).arg( queryOptions.join( ';' ) ) );
238
239 return res;
240}
241
243{
244 if ( mChildEntity == Qgis::SensorThingsEntity::Invalid )
245 return other.mChildEntity == Qgis::SensorThingsEntity::Invalid;
246
247 return mChildEntity == other.mChildEntity
248 && mSortOrder == other.mSortOrder
249 && mLimit == other.mLimit
250 && mOrderBy == other.mOrderBy
251 && mFilter == other.mFilter;
252}
253
255{
256 return !( *this == other );
257}
258
260{
261 return mOrderBy;
262}
263
265{
266 mOrderBy = field;
267}
268
269//
270// QgsSensorThingsUtils
271//
272
274{
275 const QString trimmed = type.trimmed();
276 if ( trimmed.compare( QLatin1String( "Thing" ), Qt::CaseInsensitive ) == 0 )
278 if ( trimmed.compare( QLatin1String( "Location" ), Qt::CaseInsensitive ) == 0 )
280 if ( trimmed.compare( QLatin1String( "HistoricalLocation" ), Qt::CaseInsensitive ) == 0 )
282 if ( trimmed.compare( QLatin1String( "Datastream" ), Qt::CaseInsensitive ) == 0 )
284 if ( trimmed.compare( QLatin1String( "Sensor" ), Qt::CaseInsensitive ) == 0 )
286 if ( trimmed.compare( QLatin1String( "ObservedProperty" ), Qt::CaseInsensitive ) == 0 )
288 if ( trimmed.compare( QLatin1String( "Observation" ), Qt::CaseInsensitive ) == 0 )
290 if ( trimmed.compare( QLatin1String( "FeatureOfInterest" ), Qt::CaseInsensitive ) == 0 )
292 if ( trimmed.compare( QLatin1String( "MultiDatastream" ), Qt::CaseInsensitive ) == 0 )
294
296}
297
299{
300 switch ( type )
301 {
303 return QString();
305 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
307 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
309 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
311 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
313 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
315 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
317 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
319 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
321 return plural ? QObject::tr( "MultiDatastreams" ) : QObject::tr( "MultiDatastream" );
322 }
324}
325
327{
328 const QString trimmed = type.trimmed();
329 if ( trimmed.compare( QLatin1String( "Things" ), Qt::CaseInsensitive ) == 0 )
331 if ( trimmed.compare( QLatin1String( "Locations" ), Qt::CaseInsensitive ) == 0 )
333 if ( trimmed.compare( QLatin1String( "HistoricalLocations" ), Qt::CaseInsensitive ) == 0 )
335 if ( trimmed.compare( QLatin1String( "Datastreams" ), Qt::CaseInsensitive ) == 0 )
337 if ( trimmed.compare( QLatin1String( "Sensors" ), Qt::CaseInsensitive ) == 0 )
339 if ( trimmed.compare( QLatin1String( "ObservedProperties" ), Qt::CaseInsensitive ) == 0 )
341 if ( trimmed.compare( QLatin1String( "Observations" ), Qt::CaseInsensitive ) == 0 )
343 if ( trimmed.compare( QLatin1String( "FeaturesOfInterest" ), Qt::CaseInsensitive ) == 0 )
345 if ( trimmed.compare( QLatin1String( "MultiDatastreams" ), Qt::CaseInsensitive ) == 0 )
347
349}
350
352{
353 switch ( type )
354 {
356 return QString();
358 return QStringLiteral( "Things" );
360 return QStringLiteral( "Locations" );
362 return QStringLiteral( "HistoricalLocations" );
364 return QStringLiteral( "Datastreams" );
366 return QStringLiteral( "Sensors" );
368 return QStringLiteral( "ObservedProperties" );
370 return QStringLiteral( "Observations" );
372 return QStringLiteral( "FeaturesOfInterest" );
374 return QStringLiteral( "MultiDatastreams" );
375 }
377}
378
380{
381 switch ( type )
382 {
384 return {};
385
387 // https://docs.ogc.org/is/18-088/18-088.html#thing
388 return { QStringLiteral( "id" ),
389 QStringLiteral( "selfLink" ),
390 QStringLiteral( "name" ),
391 QStringLiteral( "description" ),
392 QStringLiteral( "properties" ),
393 };
394
396 // https://docs.ogc.org/is/18-088/18-088.html#location
397 return { QStringLiteral( "id" ),
398 QStringLiteral( "selfLink" ),
399 QStringLiteral( "name" ),
400 QStringLiteral( "description" ),
401 QStringLiteral( "properties" ),
402 };
403
405 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
406 return { QStringLiteral( "id" ),
407 QStringLiteral( "selfLink" ),
408 QStringLiteral( "time" ),
409 };
410
412 // https://docs.ogc.org/is/18-088/18-088.html#datastream
413 return { QStringLiteral( "id" ),
414 QStringLiteral( "selfLink" ),
415 QStringLiteral( "name" ),
416 QStringLiteral( "description" ),
417 QStringLiteral( "unitOfMeasurement" ),
418 QStringLiteral( "observationType" ),
419 QStringLiteral( "properties" ),
420 QStringLiteral( "phenomenonTime" ),
421 QStringLiteral( "resultTime" ),
422 };
423
425 // https://docs.ogc.org/is/18-088/18-088.html#sensor
426 return { QStringLiteral( "id" ),
427 QStringLiteral( "selfLink" ),
428 QStringLiteral( "name" ),
429 QStringLiteral( "description" ),
430 QStringLiteral( "metadata" ),
431 QStringLiteral( "properties" ),
432 };
433
435 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
436 return { QStringLiteral( "id" ),
437 QStringLiteral( "selfLink" ),
438 QStringLiteral( "name" ),
439 QStringLiteral( "definition" ),
440 QStringLiteral( "description" ),
441 QStringLiteral( "properties" ),
442 };
443
445 // https://docs.ogc.org/is/18-088/18-088.html#observation
446 return { QStringLiteral( "id" ),
447 QStringLiteral( "selfLink" ),
448 QStringLiteral( "phenomenonTime" ),
449 QStringLiteral( "result" ),
450 QStringLiteral( "resultTime" ),
451 QStringLiteral( "resultQuality" ),
452 QStringLiteral( "validTime" ),
453 QStringLiteral( "parameters" ),
454 };
455
457 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
458 return { QStringLiteral( "id" ),
459 QStringLiteral( "selfLink" ),
460 QStringLiteral( "name" ),
461 QStringLiteral( "description" ),
462 QStringLiteral( "properties" ),
463 };
464
466 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
467 return { QStringLiteral( "id" ),
468 QStringLiteral( "selfLink" ),
469 QStringLiteral( "name" ),
470 QStringLiteral( "description" ),
471 QStringLiteral( "unitOfMeasurements" ),
472 QStringLiteral( "observationType" ),
473 QStringLiteral( "multiObservationDataTypes" ),
474 QStringLiteral( "properties" ),
475 QStringLiteral( "phenomenonTime" ),
476 QStringLiteral( "resultTime" ),
477 };
478 }
479
480 return {};
481}
482
484{
485 QgsFields fields;
486
487 // common fields: https://docs.ogc.org/is/18-088/18-088.html#common-control-information
488 fields.append( QgsField( QStringLiteral( "id" ), QMetaType::Type::QString ) );
489 fields.append( QgsField( QStringLiteral( "selfLink" ), QMetaType::Type::QString ) );
490
491 switch ( type )
492 {
494 break;
495
497 // https://docs.ogc.org/is/18-088/18-088.html#thing
498 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
499 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
500 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
501 break;
502
504 // https://docs.ogc.org/is/18-088/18-088.html#location
505 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
506 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
507 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
508 break;
509
511 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
512 fields.append( QgsField( QStringLiteral( "time" ), QMetaType::Type::QDateTime ) );
513 break;
514
516 // https://docs.ogc.org/is/18-088/18-088.html#datastream
517 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
518 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
519 fields.append( QgsField( QStringLiteral( "unitOfMeasurement" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
520 fields.append( QgsField( QStringLiteral( "observationType" ), QMetaType::Type::QString ) );
521 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
522 if ( includeRangeFieldProxies )
523 {
524 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
525 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
526 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
527 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QMetaType::Type::QDateTime ) );
528 }
529 break;
530
532 // https://docs.ogc.org/is/18-088/18-088.html#sensor
533 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
534 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
535 fields.append( QgsField( QStringLiteral( "metadata" ), QMetaType::Type::QString ) );
536 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
537 break;
538
540 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
541 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
542 fields.append( QgsField( QStringLiteral( "definition" ), QMetaType::Type::QString ) );
543 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
544 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
545 break;
546
548 // https://docs.ogc.org/is/18-088/18-088.html#observation
549 if ( includeRangeFieldProxies )
550 {
551 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
552 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
553 }
554
555 // TODO -- handle type correctly
556 fields.append( QgsField( QStringLiteral( "result" ), QMetaType::Type::QString ) );
557
558 fields.append( QgsField( QStringLiteral( "resultTime" ), QMetaType::Type::QDateTime ) );
559 fields.append( QgsField( QStringLiteral( "resultQuality" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
560 if ( includeRangeFieldProxies )
561 {
562 fields.append( QgsField( QStringLiteral( "validTimeStart" ), QMetaType::Type::QDateTime ) );
563 fields.append( QgsField( QStringLiteral( "validTimeEnd" ), QMetaType::Type::QDateTime ) );
564 }
565 fields.append( QgsField( QStringLiteral( "parameters" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
566 break;
567
569 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
570 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
571 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
572 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
573 break;
574
576 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
577 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
578 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
579 fields.append( QgsField( QStringLiteral( "unitOfMeasurements" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
580 fields.append( QgsField( QStringLiteral( "observationType" ), QMetaType::Type::QString ) );
581 fields.append( QgsField( QStringLiteral( "multiObservationDataTypes" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
582 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
583 if ( includeRangeFieldProxies )
584 {
585 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
586 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
587 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
588 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QMetaType::Type::QDateTime ) );
589 }
590 break;
591 }
592
593 return fields;
594}
595
596QgsFields QgsSensorThingsUtils::fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList<Qgis::SensorThingsEntity> &expandedTypes )
597{
598 if ( expandedTypes.empty() )
599 return fieldsForEntityType( baseType );
600
601 QgsFields fields = fieldsForEntityType( baseType );
602 QString path;
603 for ( const Qgis::SensorThingsEntity expandedType : expandedTypes )
604 {
605 path = ( path.isEmpty() ? QString() : ( path + '_' ) ) + qgsEnumValueToKey( expandedType );
606 const QgsFields expandedFields = fieldsForEntityType( expandedType );
607 for ( const QgsField &expandedField : expandedFields )
608 {
609 QgsField renamedExpandedField = expandedField;
610 renamedExpandedField.setName( QStringLiteral( "%1_%2" ).arg( path, expandedField.name() ) );
611 fields.append( renamedExpandedField );
612 }
613 }
614 return fields;
615}
616
618{
619 switch ( type )
620 {
627 return QString();
628
630 return QStringLiteral( "location" );
631
633 return QStringLiteral( "feature" );
634
637 return QStringLiteral( "observedArea" );
638 }
640}
641
662
683
685{
686 QString geometryTypeString;
687 switch ( QgsWkbTypes::geometryType( wkbType ) )
688 {
690 geometryTypeString = QStringLiteral( "Point" );
691 break;
693 geometryTypeString = QStringLiteral( "Polygon" );
694 break;
696 geometryTypeString = QStringLiteral( "LineString" );
697 break;
698
701 return QString();
702 }
703
704 const QString filterTarget = geometryFieldForEntityType( entityType );
705 if ( filterTarget.isEmpty() )
706 return QString();
707
708 return QStringLiteral( "%1/type eq '%2' or %1/geometry/type eq '%2'" ).arg( filterTarget, geometryTypeString );
709}
710
711QString QgsSensorThingsUtils::filterForExtent( const QString &geometryField, const QgsRectangle &extent )
712{
713 // TODO -- confirm using 'geography' is always correct here
714 return ( extent.isNull() || geometryField.isEmpty() )
715 ? QString()
716 : QStringLiteral( "geo.intersects(%1, geography'%2')" ).arg( geometryField, extent.asWktPolygon() );
717}
718
719QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
720{
721 QStringList nonEmptyFilters;
722 for ( const QString &filter : filters )
723 {
724 if ( !filter.isEmpty() )
725 nonEmptyFilters.append( filter );
726 }
727 if ( nonEmptyFilters.empty() )
728 return QString();
729 if ( nonEmptyFilters.size() == 1 )
730 return nonEmptyFilters.at( 0 );
731
732 return QStringLiteral( "(" ) + nonEmptyFilters.join( QLatin1String( ") and (" ) ) + QStringLiteral( ")" );
733}
734
735QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
736{
737 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
738 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsUtils" ) )
739
740 QgsBlockingNetworkRequest networkRequest;
741 networkRequest.setAuthCfg( authCfg );
742
743 switch ( networkRequest.get( request ) )
744 {
746 break;
747
751 QgsDebugError( QStringLiteral( "Connection failed: %1" ).arg( networkRequest.errorMessage() ) );
752 return {};
753 }
754
755 QString entityBaseUri;
756 const QgsNetworkReplyContent content = networkRequest.reply();
757 try
758 {
759 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
760 if ( !rootContent.contains( "value" ) )
761 {
762 QgsDebugError( QStringLiteral( "No 'value' array in response" ) );
763 return {};
764 }
765
766 bool foundMatchingEntity = false;
767 for ( const auto &valueJson : rootContent["value"] )
768 {
769 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
770 {
771 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
773 if ( entityType == type )
774 {
775 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
776 if ( !url.isEmpty() )
777 {
778 foundMatchingEntity = true;
779 entityBaseUri = url;
780 break;
781 }
782 }
783 }
784 }
785
786 if ( !foundMatchingEntity )
787 {
788 QgsDebugError( QStringLiteral( "Could not find url for %1" ).arg( qgsEnumValueToKey( type ) ) );
789 return {};
790 }
791 }
792 catch ( const nlohmann::json::parse_error &ex )
793 {
794 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
795 return {};
796 }
797
798 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
799 {
800 // return no features, just the total count
801 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( entityBaseUri );
803 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
804 if ( !typeFilter.isEmpty() )
805 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
806
807 const QUrl url( countUri );
808
809 QNetworkRequest request( url );
810 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
811
812 QgsBlockingNetworkRequest networkRequest;
813 networkRequest.setAuthCfg( authCfg );
814 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
815
816 if ( feedback && feedback->isCanceled() )
817 return -1;
818
819 // Handle network errors
821 {
822 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
823 return -1;
824 }
825 else
826 {
827 const QgsNetworkReplyContent content = networkRequest.reply();
828 try
829 {
830 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
831 if ( !rootContent.contains( "@iot.count" ) )
832 {
833 QgsDebugError( QStringLiteral( "No '@iot.count' value in response" ) );
834 return -1;
835 }
836
837 return rootContent["@iot.count"].get<long long>();
838 }
839 catch ( const nlohmann::json::parse_error &ex )
840 {
841 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
842 return -1;
843 }
844 }
845 };
846
847 QList<Qgis::GeometryType> types;
848 for ( Qgis::GeometryType geometryType :
849 {
853 } )
854 {
855 const long long matchCount = getCountForType( geometryType );
856 if ( matchCount < 0 )
857 return {};
858 else if ( matchCount > 0 )
859 types.append( geometryType );
860 }
861 return types;
862}
863
865{
866 // note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
867
868 // NOLINTBEGIN(bugprone-branch-clone)
869 switch ( type )
870 {
872 return {};
873
875 return
876 {
880 };
881
883 return
884 {
887 };
888
890 return
891 {
893 };
894
896 return
897 {
902 };
903
905 return
906 {
909 };
910
912 return
913 {
916 };
917
919 return
920 {
923 };
924
926 return
927 {
929 };
930
932 return
933 {
938 };
939 }
940 // NOLINTEND(bugprone-branch-clone)
941
943}
944
946{
947 valid = true;
948 switch ( baseType )
949 {
951 break;
952
954 {
955 switch ( relatedType )
956 {
959
964
971 break;
972 }
973 break;
974 }
976 {
977 switch ( relatedType )
978 {
982
991 break;
992 }
993 break;
994 }
996 {
997 switch ( relatedType )
998 {
1000 // The SensorThings specification MAY be wrong here. There's an inconsistency between
1001 // the inheritance graph which shows HistoricalLocation linking to "location", when
1002 // the text description describes the relationship as linking to "locationS".
1003 // We assume the text description is correct and the graph is wrong, as the reverse
1004 // relationship between Location and HistoricalLocation is many-to-many.
1006
1009
1018 break;
1019 }
1020
1021 break;
1022 }
1023
1025 {
1026 switch ( relatedType )
1027 {
1032
1035
1042 break;
1043 }
1044
1045 break;
1046 }
1047
1049 {
1050 switch ( relatedType )
1051 {
1055
1064 break;
1065 }
1066
1067 break;
1068 }
1069
1071 {
1072 switch ( relatedType )
1073 {
1076
1079
1088 break;
1089 }
1090 break;
1091 }
1092
1094 {
1095 switch ( relatedType )
1096 {
1101
1109 break;
1110 }
1111 break;
1112 }
1113
1115 {
1116 switch ( relatedType )
1117 {
1120
1130 break;
1131 }
1132
1133 break;
1134 }
1135
1137 {
1138 switch ( relatedType )
1139 {
1143
1146
1149
1156 break;
1157 }
1158 break;
1159 }
1160 }
1161
1162 valid = false;
1164}
1165
1166QString QgsSensorThingsUtils::asQueryString( Qgis::SensorThingsEntity baseType, const QList<QgsSensorThingsExpansionDefinition> &expansions )
1167{
1168 QString res;
1169 for ( int i = expansions.size() - 1; i >= 0 ; i-- )
1170 {
1171 const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
1172 if ( !expansion.isValid() )
1173 continue;
1174
1175 const Qgis::SensorThingsEntity parentType = i > 0 ? expansions.at( i - 1 ).childEntity() : baseType;
1176
1177 res = expansion.asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList{ res } );
1178 }
1179
1180 return res;
1181}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:5966
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
Definition qgis.h:5972
@ MultiDatastream
A MultiDatastream groups a collection of Observations and the Observations in a MultiDatastream have ...
Definition qgis.h:5976
@ ObservedProperty
An ObservedProperty specifies the phenomenon of an Observation.
Definition qgis.h:5973
@ Invalid
An invalid/unknown entity.
Definition qgis.h:5967
@ FeatureOfInterest
In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of...
Definition qgis.h:5975
@ Datastream
A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by ...
Definition qgis.h:5971
@ Observation
An Observation is the act of measuring or otherwise determining the value of a property.
Definition qgis.h:5974
@ Location
A Location entity locates the Thing or the Things it associated with. A Thing’s Location entity is de...
Definition qgis.h:5969
@ Thing
A Thing is an object of the physical world (physical things) or the information world (virtual things...
Definition qgis.h:5968
@ HistoricalLocation
A Thing’s HistoricalLocation entity set provides the times of the current (i.e., last known) and prev...
Definition qgis.h:5970
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:358
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
@ Unknown
Unknown types.
Definition qgis.h:362
@ Null
No geometry.
Definition qgis.h:363
RelationshipCardinality
Relationship cardinality.
Definition qgis.h:4417
@ ManyToMany
Many to many relationship.
Definition qgis.h:4421
@ ManyToOne
Many to one relationship.
Definition qgis.h:4420
@ OneToOne
One to one relationship.
Definition qgis.h:4418
@ OneToMany
One to many relationship.
Definition qgis.h:4419
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
@ Point
Point.
Definition qgis.h:279
@ LineString
LineString.
Definition qgis.h:280
@ Polygon
Polygon.
Definition qgis.h:281
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:53
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
void setName(const QString &name)
Set the field name.
Definition qgsfield.cpp:229
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:73
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 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 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:6817
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6798
#define BUILTIN_UNREACHABLE
Definition qgis.h:7208
#define QgsDebugError(str)
Definition qgslogger.h:57
#define QgsSetRequestInitiatorClass(request, _class)