QGIS API Documentation 3.41.0-Master (25ec5511245)
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#include "qgsfield.h"
18#include "qgsfields.h"
19#include "qgswkbtypes.h"
23#include "qgslogger.h"
24#include "qgsrectangle.h"
25#include <QUrl>
26#include <QNetworkRequest>
27#include <QRegularExpression>
28#include <QRegularExpressionMatch>
29#include <nlohmann/json.hpp>
30
31
32//
33// QgsSensorThingsExpansionDefinition
34//
35QgsSensorThingsExpansionDefinition::QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity childEntity, const QString &orderBy, Qt::SortOrder sortOrder, int limit, const QString &filter )
36 : mChildEntity( childEntity )
37 , mOrderBy( orderBy )
38 , mSortOrder( sortOrder )
39 , mLimit( limit )
40 , mFilter( filter )
41{
42
43}
44
46{
47 switch ( entity )
48 {
51
57 // no special defaults for these entities
59 entity
60 );
61
63 // default to descending sort by phenomenonTime
66 QStringLiteral( "phenomenonTime" ), Qt::SortOrder::DescendingOrder
67 );
68
72 // use smaller limit by default
74 entity,
75 QString(), Qt::SortOrder::AscendingOrder, 10
76 );
77 }
79}
80
85
90
95
97{
98 return mSortOrder;
99}
100
102{
103 mSortOrder = order;
104}
105
107{
108 return mLimit;
109}
110
112{
113 mLimit = limit;
114}
115
117{
118 return mFilter;
119}
120
122{
123 mFilter = filter;
124}
125
127{
128 if ( !isValid() )
129 return QString();
130
131 QStringList parts;
132 parts.append( qgsEnumValueToKey( mChildEntity ) );
133 if ( !mOrderBy.isEmpty() )
134 parts.append( QStringLiteral( "orderby=%1,%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) ) );
135 if ( mLimit >= 0 )
136 parts.append( QStringLiteral( "limit=%1" ).arg( mLimit ) );
137 if ( !mFilter.trimmed().isEmpty() )
138 {
139 QString escapedFilter = mFilter;
140 escapedFilter.replace( ':', QLatin1String( "\\colon" ) );
141 parts.append( QStringLiteral( "filter=%1" ).arg( escapedFilter ) );
142 }
143 return parts.join( ':' );
144}
145
147{
148 const QStringList parts = string.split( ':', Qt::SkipEmptyParts );
149 if ( parts.empty() )
151
153 definition.setLimit( -1 );
154 for ( int i = 1; i < parts.count(); ++i )
155 {
156 const QString &part = parts.at( i );
157 const thread_local QRegularExpression orderByRegEx( QStringLiteral( "^orderby=(.*),(.*?)$" ) );
158 const thread_local QRegularExpression orderLimitRegEx( QStringLiteral( "^limit=(\\d+)$" ) );
159 const thread_local QRegularExpression filterRegEx( QStringLiteral( "^filter=(.*)$" ) );
160
161 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
162 if ( orderByMatch.hasMatch() )
163 {
164 definition.setOrderBy( orderByMatch.captured( 1 ) );
165 definition.setSortOrder( orderByMatch.captured( 2 ) == QLatin1String( "asc" ) ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
166 continue;
167 }
168
169 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
170 if ( limitMatch.hasMatch() )
171 {
172 definition.setLimit( limitMatch.captured( 1 ).toInt() );
173 continue;
174 }
175
176 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
177 if ( filterMatch.hasMatch() )
178 {
179 QString filter = filterMatch.captured( 1 );
180 filter.replace( QLatin1String( "\\colon" ), QLatin1String( ":" ) );
181 definition.setFilter( filter );
182 continue;
183 }
184 }
185 return definition;
186}
187
188QString QgsSensorThingsExpansionDefinition::asQueryString( Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions ) const
189{
190 if ( !isValid() )
191 return QString();
192
193 bool ok = false;
194 // From the specifications, it SOMETIMES the plural form is used for the expansion query, and sometimes singular.
195 // The choice depends on the cardinality of the relationship between the involved entities.
196 const Qgis::RelationshipCardinality cardinality = QgsSensorThingsUtils::relationshipCardinality( parentEntityType, mChildEntity, ok );
197 QString childEntityString;
198 if ( !ok )
199 {
200 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
201 }
202 else
203 {
204 switch ( cardinality )
205 {
208 // use singular strings, eg "Thing"
209 childEntityString = qgsEnumValueToKey( mChildEntity );
210 break;
211
214 // use plural strings, eg "Things"
215 childEntityString = QgsSensorThingsUtils::entityToSetString( mChildEntity );
216 break;
217 }
218 }
219
220 QString res = QStringLiteral( "$expand=%1" ).arg( childEntityString );
221
222 QStringList queryOptions;
223 if ( !mOrderBy.isEmpty() )
224 queryOptions.append( QStringLiteral( "$orderby=%1%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : QStringLiteral( " desc" ) ) );
225
226 if ( mLimit > -1 )
227 queryOptions.append( QStringLiteral( "$top=%1" ).arg( mLimit ) );
228
229 if ( !mFilter.isEmpty() )
230 queryOptions.append( QStringLiteral( "$filter=%1" ).arg( mFilter ) );
231
232 queryOptions.append( additionalOptions );
233
234 if ( !queryOptions.isEmpty() )
235 res.append( QStringLiteral( "(%1)" ).arg( queryOptions.join( ';' ) ) );
236
237 return res;
238}
239
241{
242 if ( mChildEntity == Qgis::SensorThingsEntity::Invalid )
243 return other.mChildEntity == Qgis::SensorThingsEntity::Invalid;
244
245 return mChildEntity == other.mChildEntity
246 && mSortOrder == other.mSortOrder
247 && mLimit == other.mLimit
248 && mOrderBy == other.mOrderBy
249 && mFilter == other.mFilter;
250}
251
253{
254 return !( *this == other );
255}
256
258{
259 return mOrderBy;
260}
261
263{
264 mOrderBy = field;
265}
266
267//
268// QgsSensorThingsUtils
269//
270
272{
273 const QString trimmed = type.trimmed();
274 if ( trimmed.compare( QLatin1String( "Thing" ), Qt::CaseInsensitive ) == 0 )
276 if ( trimmed.compare( QLatin1String( "Location" ), Qt::CaseInsensitive ) == 0 )
278 if ( trimmed.compare( QLatin1String( "HistoricalLocation" ), Qt::CaseInsensitive ) == 0 )
280 if ( trimmed.compare( QLatin1String( "Datastream" ), Qt::CaseInsensitive ) == 0 )
282 if ( trimmed.compare( QLatin1String( "Sensor" ), Qt::CaseInsensitive ) == 0 )
284 if ( trimmed.compare( QLatin1String( "ObservedProperty" ), Qt::CaseInsensitive ) == 0 )
286 if ( trimmed.compare( QLatin1String( "Observation" ), Qt::CaseInsensitive ) == 0 )
288 if ( trimmed.compare( QLatin1String( "FeatureOfInterest" ), Qt::CaseInsensitive ) == 0 )
290 if ( trimmed.compare( QLatin1String( "MultiDatastream" ), Qt::CaseInsensitive ) == 0 )
292
294}
295
297{
298 switch ( type )
299 {
301 return QString();
303 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
305 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
307 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
309 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
311 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
313 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
315 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
317 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
319 return plural ? QObject::tr( "MultiDatastreams" ) : QObject::tr( "MultiDatastream" );
320 }
322}
323
325{
326 const QString trimmed = type.trimmed();
327 if ( trimmed.compare( QLatin1String( "Things" ), Qt::CaseInsensitive ) == 0 )
329 if ( trimmed.compare( QLatin1String( "Locations" ), Qt::CaseInsensitive ) == 0 )
331 if ( trimmed.compare( QLatin1String( "HistoricalLocations" ), Qt::CaseInsensitive ) == 0 )
333 if ( trimmed.compare( QLatin1String( "Datastreams" ), Qt::CaseInsensitive ) == 0 )
335 if ( trimmed.compare( QLatin1String( "Sensors" ), Qt::CaseInsensitive ) == 0 )
337 if ( trimmed.compare( QLatin1String( "ObservedProperties" ), Qt::CaseInsensitive ) == 0 )
339 if ( trimmed.compare( QLatin1String( "Observations" ), Qt::CaseInsensitive ) == 0 )
341 if ( trimmed.compare( QLatin1String( "FeaturesOfInterest" ), Qt::CaseInsensitive ) == 0 )
343 if ( trimmed.compare( QLatin1String( "MultiDatastreams" ), Qt::CaseInsensitive ) == 0 )
345
347}
348
350{
351 switch ( type )
352 {
354 return QString();
356 return QStringLiteral( "Things" );
358 return QStringLiteral( "Locations" );
360 return QStringLiteral( "HistoricalLocations" );
362 return QStringLiteral( "Datastreams" );
364 return QStringLiteral( "Sensors" );
366 return QStringLiteral( "ObservedProperties" );
368 return QStringLiteral( "Observations" );
370 return QStringLiteral( "FeaturesOfInterest" );
372 return QStringLiteral( "MultiDatastreams" );
373 }
375}
376
378{
379 switch ( type )
380 {
382 return {};
383
385 // https://docs.ogc.org/is/18-088/18-088.html#thing
386 return { QStringLiteral( "id" ),
387 QStringLiteral( "selfLink" ),
388 QStringLiteral( "name" ),
389 QStringLiteral( "description" ),
390 QStringLiteral( "properties" ),
391 };
392
394 // https://docs.ogc.org/is/18-088/18-088.html#location
395 return { QStringLiteral( "id" ),
396 QStringLiteral( "selfLink" ),
397 QStringLiteral( "name" ),
398 QStringLiteral( "description" ),
399 QStringLiteral( "properties" ),
400 };
401
403 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
404 return { QStringLiteral( "id" ),
405 QStringLiteral( "selfLink" ),
406 QStringLiteral( "time" ),
407 };
408
410 // https://docs.ogc.org/is/18-088/18-088.html#datastream
411 return { QStringLiteral( "id" ),
412 QStringLiteral( "selfLink" ),
413 QStringLiteral( "name" ),
414 QStringLiteral( "description" ),
415 QStringLiteral( "unitOfMeasurement" ),
416 QStringLiteral( "observationType" ),
417 QStringLiteral( "properties" ),
418 QStringLiteral( "phenomenonTime" ),
419 QStringLiteral( "resultTime" ),
420 };
421
423 // https://docs.ogc.org/is/18-088/18-088.html#sensor
424 return { QStringLiteral( "id" ),
425 QStringLiteral( "selfLink" ),
426 QStringLiteral( "name" ),
427 QStringLiteral( "description" ),
428 QStringLiteral( "metadata" ),
429 QStringLiteral( "properties" ),
430 };
431
433 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
434 return { QStringLiteral( "id" ),
435 QStringLiteral( "selfLink" ),
436 QStringLiteral( "name" ),
437 QStringLiteral( "definition" ),
438 QStringLiteral( "description" ),
439 QStringLiteral( "properties" ),
440 };
441
443 // https://docs.ogc.org/is/18-088/18-088.html#observation
444 return { QStringLiteral( "id" ),
445 QStringLiteral( "selfLink" ),
446 QStringLiteral( "phenomenonTime" ),
447 QStringLiteral( "result" ),
448 QStringLiteral( "resultTime" ),
449 QStringLiteral( "resultQuality" ),
450 QStringLiteral( "validTime" ),
451 QStringLiteral( "parameters" ),
452 };
453
455 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
456 return { QStringLiteral( "id" ),
457 QStringLiteral( "selfLink" ),
458 QStringLiteral( "name" ),
459 QStringLiteral( "description" ),
460 QStringLiteral( "properties" ),
461 };
462
464 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
465 return { QStringLiteral( "id" ),
466 QStringLiteral( "selfLink" ),
467 QStringLiteral( "name" ),
468 QStringLiteral( "description" ),
469 QStringLiteral( "unitOfMeasurements" ),
470 QStringLiteral( "observationType" ),
471 QStringLiteral( "multiObservationDataTypes" ),
472 QStringLiteral( "properties" ),
473 QStringLiteral( "phenomenonTime" ),
474 QStringLiteral( "resultTime" ),
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( QStringLiteral( "id" ), QMetaType::Type::QString ) );
487 fields.append( QgsField( QStringLiteral( "selfLink" ), 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( QStringLiteral( "name" ), QMetaType::Type::QString ) );
497 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
498 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "name" ), QMetaType::Type::QString ) );
504 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
505 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "time" ), QMetaType::Type::QDateTime ) );
511 break;
512
514 // https://docs.ogc.org/is/18-088/18-088.html#datastream
515 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
516 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
517 fields.append( QgsField( QStringLiteral( "unitOfMeasurement" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
518 fields.append( QgsField( QStringLiteral( "observationType" ), QMetaType::Type::QString ) );
519 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
520 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
521 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
522 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
523 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QMetaType::Type::QDateTime ) );
524 break;
525
527 // https://docs.ogc.org/is/18-088/18-088.html#sensor
528 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
529 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
530 fields.append( QgsField( QStringLiteral( "metadata" ), QMetaType::Type::QString ) );
531 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
532 break;
533
535 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
536 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
537 fields.append( QgsField( QStringLiteral( "definition" ), QMetaType::Type::QString ) );
538 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
539 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
540 break;
541
543 // https://docs.ogc.org/is/18-088/18-088.html#observation
544 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
545 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
546
547 // TODO -- handle type correctly
548 fields.append( QgsField( QStringLiteral( "result" ), QMetaType::Type::QString ) );
549
550 fields.append( QgsField( QStringLiteral( "resultTime" ), QMetaType::Type::QDateTime ) );
551 fields.append( QgsField( QStringLiteral( "resultQuality" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
552 fields.append( QgsField( QStringLiteral( "validTimeStart" ), QMetaType::Type::QDateTime ) );
553 fields.append( QgsField( QStringLiteral( "validTimeEnd" ), QMetaType::Type::QDateTime ) );
554 fields.append( QgsField( QStringLiteral( "parameters" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
555 break;
556
558 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
559 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
560 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
561 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
562 break;
563
565 // https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
566 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
567 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
568 fields.append( QgsField( QStringLiteral( "unitOfMeasurements" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
569 fields.append( QgsField( QStringLiteral( "observationType" ), QMetaType::Type::QString ) );
570 fields.append( QgsField( QStringLiteral( "multiObservationDataTypes" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
571 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
572 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
573 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
574 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
575 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QMetaType::Type::QDateTime ) );
576 break;
577 }
578
579 return fields;
580}
581
582QgsFields QgsSensorThingsUtils::fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList<Qgis::SensorThingsEntity> &expandedTypes )
583{
584 if ( expandedTypes.empty() )
585 return fieldsForEntityType( baseType );
586
587 QgsFields fields = fieldsForEntityType( baseType );
588 QString path;
589 for ( const Qgis::SensorThingsEntity expandedType : expandedTypes )
590 {
591 path = ( path.isEmpty() ? QString() : ( path + '_' ) ) + qgsEnumValueToKey( expandedType );
592 const QgsFields expandedFields = fieldsForEntityType( expandedType );
593 for ( const QgsField &expandedField : expandedFields )
594 {
595 QgsField renamedExpandedField = expandedField;
596 renamedExpandedField.setName( QStringLiteral( "%1_%2" ).arg( path, expandedField.name() ) );
597 fields.append( renamedExpandedField );
598 }
599 }
600 return fields;
601}
602
604{
605 switch ( type )
606 {
614 return QString();
615
617 return QStringLiteral( "location" );
618
620 return QStringLiteral( "feature" );
621
623 return QStringLiteral( "observedArea" );
624 }
626}
627
648
671
673{
674 QString geometryTypeString;
675 switch ( QgsWkbTypes::geometryType( wkbType ) )
676 {
678 geometryTypeString = QStringLiteral( "Point" );
679 break;
681 geometryTypeString = QStringLiteral( "Polygon" );
682 break;
684 geometryTypeString = QStringLiteral( "LineString" );
685 break;
686
689 return QString();
690 }
691
692 const QString filterTarget = geometryFieldForEntityType( entityType );
693 if ( filterTarget.isEmpty() )
694 return QString();
695
696 return QStringLiteral( "%1/type eq '%2' or %1/geometry/type eq '%2'" ).arg( filterTarget, geometryTypeString );
697}
698
699QString QgsSensorThingsUtils::filterForExtent( const QString &geometryField, const QgsRectangle &extent )
700{
701 // TODO -- confirm using 'geography' is always correct here
702 return ( extent.isNull() || geometryField.isEmpty() )
703 ? QString()
704 : QStringLiteral( "geo.intersects(%1, geography'%2')" ).arg( geometryField, extent.asWktPolygon() );
705}
706
707QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
708{
709 QStringList nonEmptyFilters;
710 for ( const QString &filter : filters )
711 {
712 if ( !filter.isEmpty() )
713 nonEmptyFilters.append( filter );
714 }
715 if ( nonEmptyFilters.empty() )
716 return QString();
717 if ( nonEmptyFilters.size() == 1 )
718 return nonEmptyFilters.at( 0 );
719
720 return QStringLiteral( "(" ) + nonEmptyFilters.join( QLatin1String( ") and (" ) ) + QStringLiteral( ")" );
721}
722
723QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
724{
725 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
726 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsUtils" ) )
727
728 QgsBlockingNetworkRequest networkRequest;
729 networkRequest.setAuthCfg( authCfg );
730
731 switch ( networkRequest.get( request ) )
732 {
734 break;
735
739 QgsDebugError( QStringLiteral( "Connection failed: %1" ).arg( networkRequest.errorMessage() ) );
740 return {};
741 }
742
743 QString entityBaseUri;
744 const QgsNetworkReplyContent content = networkRequest.reply();
745 try
746 {
747 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
748 if ( !rootContent.contains( "value" ) )
749 {
750 QgsDebugError( QStringLiteral( "No 'value' array in response" ) );
751 return {};
752 }
753
754 bool foundMatchingEntity = false;
755 for ( const auto &valueJson : rootContent["value"] )
756 {
757 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
758 {
759 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
761 if ( entityType == type )
762 {
763 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
764 if ( !url.isEmpty() )
765 {
766 foundMatchingEntity = true;
767 entityBaseUri = url;
768 break;
769 }
770 }
771 }
772 }
773
774 if ( !foundMatchingEntity )
775 {
776 QgsDebugError( QStringLiteral( "Could not find url for %1" ).arg( qgsEnumValueToKey( type ) ) );
777 return {};
778 }
779 }
780 catch ( const nlohmann::json::parse_error &ex )
781 {
782 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
783 return {};
784 }
785
786 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
787 {
788 // return no features, just the total count
789 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( entityBaseUri );
791 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
792 if ( !typeFilter.isEmpty() )
793 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
794
795 const QUrl url( countUri );
796
797 QNetworkRequest request( url );
798 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
799
800 QgsBlockingNetworkRequest networkRequest;
801 networkRequest.setAuthCfg( authCfg );
802 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
803
804 if ( feedback && feedback->isCanceled() )
805 return -1;
806
807 // Handle network errors
809 {
810 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
811 return -1;
812 }
813 else
814 {
815 const QgsNetworkReplyContent content = networkRequest.reply();
816 try
817 {
818 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
819 if ( !rootContent.contains( "@iot.count" ) )
820 {
821 QgsDebugError( QStringLiteral( "No '@iot.count' value in response" ) );
822 return -1;
823 }
824
825 return rootContent["@iot.count"].get<long long>();
826 }
827 catch ( const nlohmann::json::parse_error &ex )
828 {
829 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
830 return -1;
831 }
832 }
833 };
834
835 QList<Qgis::GeometryType> types;
836 for ( Qgis::GeometryType geometryType :
837 {
841 } )
842 {
843 const long long matchCount = getCountForType( geometryType );
844 if ( matchCount < 0 )
845 return {};
846 else if ( matchCount > 0 )
847 types.append( geometryType );
848 }
849 return types;
850}
851
853{
854 // note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
855
856 // NOLINTBEGIN(bugprone-branch-clone)
857 switch ( type )
858 {
860 return {};
861
863 return
864 {
867 };
868
870 return
871 {
874 };
875
877 return
878 {
880 };
881
883 return
884 {
889 };
890
892 return
893 {
895 };
896
898 return
899 {
901 };
902
904 return
905 {
907 };
908
910 return
911 {
913 };
914
916 return
917 {
922 };
923 }
924 // NOLINTEND(bugprone-branch-clone)
925
927}
928
930{
931 valid = true;
932 switch ( baseType )
933 {
935 break;
936
938 {
939 switch ( relatedType )
940 {
943
948
955 break;
956 }
957 break;
958 }
960 {
961 switch ( relatedType )
962 {
966
975 break;
976 }
977 break;
978 }
980 {
981 switch ( relatedType )
982 {
984 // The SensorThings specification MAY be wrong here. There's an inconsistency between
985 // the inheritance graph which shows HistoricalLocation linking to "location", when
986 // the text description describes the relationship as linking to "locationS".
987 // We assume the text description is correct and the graph is wrong, as the reverse
988 // relationship between Location and HistoricalLocation is many-to-many.
990
993
1002 break;
1003 }
1004
1005 break;
1006 }
1007
1009 {
1010 switch ( relatedType )
1011 {
1016
1019
1026 break;
1027 }
1028
1029 break;
1030 }
1031
1033 {
1034 switch ( relatedType )
1035 {
1039
1048 break;
1049 }
1050
1051 break;
1052 }
1053
1055 {
1056 switch ( relatedType )
1057 {
1060
1063
1072 break;
1073 }
1074 break;
1075 }
1076
1078 {
1079 switch ( relatedType )
1080 {
1085
1093 break;
1094 }
1095 break;
1096 }
1097
1099 {
1100 switch ( relatedType )
1101 {
1104
1114 break;
1115 }
1116
1117 break;
1118 }
1119
1121 {
1122 switch ( relatedType )
1123 {
1127
1130
1133
1140 break;
1141 }
1142 break;
1143 }
1144 }
1145
1146 valid = false;
1148}
1149
1150QString QgsSensorThingsUtils::asQueryString( Qgis::SensorThingsEntity baseType, const QList<QgsSensorThingsExpansionDefinition> &expansions )
1151{
1152 QString res;
1153 for ( int i = expansions.size() - 1; i >= 0 ; i-- )
1154 {
1155 const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
1156 if ( !expansion.isValid() )
1157 continue;
1158
1159 const Qgis::SensorThingsEntity parentType = i > 0 ? expansions.at( i - 1 ).childEntity() : baseType;
1160
1161 res = expansion.asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList{ res } );
1162 }
1163
1164 return res;
1165}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:5556
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
@ MultiDatastream
A MultiDatastream groups a collection of Observations and the Observations in a MultiDatastream have ...
@ ObservedProperty
An ObservedProperty specifies the phenomenon of an Observation.
@ Invalid
An invalid/unknown entity.
@ FeatureOfInterest
In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of...
@ Datastream
A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by ...
@ Observation
An Observation is the act of measuring or otherwise determining the value of a property.
@ Location
A Location entity locates the Thing or the Things it associated with. A Thing’s Location entity is de...
@ Thing
A Thing is an object of the physical world (physical things) or the information world (virtual things...
@ HistoricalLocation
A Thing’s HistoricalLocation entity set provides the times of the current (i.e., last known) and prev...
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
RelationshipCardinality
Relationship cardinality.
Definition qgis.h:4112
@ ManyToMany
Many to many relationship.
@ ManyToOne
Many to one relationship.
@ OneToOne
One to one relationship.
@ OneToMany
One to many relationship.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:256
@ LineString
LineString.
@ Polygon
Polygon.
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:53
void setName(const QString &name)
Set the field name.
Definition qgsfield.cpp:227
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:70
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.
QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
bool isNull() const
Test if the rectangle is null (holding no spatial information).
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 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 QgsFields fieldsForEntityType(Qgis::SensorThingsEntity type)
Returns the fields which correspond to 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:6159
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6140
#define BUILTIN_UNREACHABLE
Definition qgis.h:6603
#define QgsDebugError(str)
Definition qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)