QGIS API Documentation 3.41.0-Master (88383c3d16f)
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 if ( includeRangeFieldProxies )
521 {
522 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
523 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
524 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
525 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QMetaType::Type::QDateTime ) );
526 }
527 break;
528
530 // https://docs.ogc.org/is/18-088/18-088.html#sensor
531 fields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
532 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
533 fields.append( QgsField( QStringLiteral( "metadata" ), QMetaType::Type::QString ) );
534 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "name" ), QMetaType::Type::QString ) );
540 fields.append( QgsField( QStringLiteral( "definition" ), QMetaType::Type::QString ) );
541 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
542 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
550 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
551 }
552
553 // TODO -- handle type correctly
554 fields.append( QgsField( QStringLiteral( "result" ), QMetaType::Type::QString ) );
555
556 fields.append( QgsField( QStringLiteral( "resultTime" ), QMetaType::Type::QDateTime ) );
557 fields.append( QgsField( QStringLiteral( "resultQuality" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
558 if ( includeRangeFieldProxies )
559 {
560 fields.append( QgsField( QStringLiteral( "validTimeStart" ), QMetaType::Type::QDateTime ) );
561 fields.append( QgsField( QStringLiteral( "validTimeEnd" ), QMetaType::Type::QDateTime ) );
562 }
563 fields.append( QgsField( QStringLiteral( "parameters" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "name" ), QMetaType::Type::QString ) );
569 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
570 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 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( QStringLiteral( "name" ), QMetaType::Type::QString ) );
576 fields.append( QgsField( QStringLiteral( "description" ), QMetaType::Type::QString ) );
577 fields.append( QgsField( QStringLiteral( "unitOfMeasurements" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
578 fields.append( QgsField( QStringLiteral( "observationType" ), QMetaType::Type::QString ) );
579 fields.append( QgsField( QStringLiteral( "multiObservationDataTypes" ), QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
580 fields.append( QgsField( QStringLiteral( "properties" ), QMetaType::Type::QVariantMap, QStringLiteral( "json" ), 0, 0, QString(), QMetaType::Type::QString ) );
581 if ( includeRangeFieldProxies )
582 {
583 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QMetaType::Type::QDateTime ) );
584 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QMetaType::Type::QDateTime ) );
585 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QMetaType::Type::QDateTime ) );
586 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), 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( QStringLiteral( "%1_%2" ).arg( path, expandedField.name() ) );
609 fields.append( renamedExpandedField );
610 }
611 }
612 return fields;
613}
614
616{
617 switch ( type )
618 {
625 return QString();
626
628 return QStringLiteral( "location" );
629
631 return QStringLiteral( "feature" );
632
635 return QStringLiteral( "observedArea" );
636 }
638}
639
660
681
683{
684 QString geometryTypeString;
685 switch ( QgsWkbTypes::geometryType( wkbType ) )
686 {
688 geometryTypeString = QStringLiteral( "Point" );
689 break;
691 geometryTypeString = QStringLiteral( "Polygon" );
692 break;
694 geometryTypeString = QStringLiteral( "LineString" );
695 break;
696
699 return QString();
700 }
701
702 const QString filterTarget = geometryFieldForEntityType( entityType );
703 if ( filterTarget.isEmpty() )
704 return QString();
705
706 return QStringLiteral( "%1/type eq '%2' or %1/geometry/type eq '%2'" ).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() )
713 ? QString()
714 : QStringLiteral( "geo.intersects(%1, geography'%2')" ).arg( geometryField, extent.asWktPolygon() );
715}
716
717QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
718{
719 QStringList nonEmptyFilters;
720 for ( const QString &filter : filters )
721 {
722 if ( !filter.isEmpty() )
723 nonEmptyFilters.append( filter );
724 }
725 if ( nonEmptyFilters.empty() )
726 return QString();
727 if ( nonEmptyFilters.size() == 1 )
728 return nonEmptyFilters.at( 0 );
729
730 return QStringLiteral( "(" ) + nonEmptyFilters.join( QLatin1String( ") and (" ) ) + QStringLiteral( ")" );
731}
732
733QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
734{
735 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
736 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsUtils" ) )
737
738 QgsBlockingNetworkRequest networkRequest;
739 networkRequest.setAuthCfg( authCfg );
740
741 switch ( networkRequest.get( request ) )
742 {
744 break;
745
749 QgsDebugError( QStringLiteral( "Connection failed: %1" ).arg( networkRequest.errorMessage() ) );
750 return {};
751 }
752
753 QString entityBaseUri;
754 const QgsNetworkReplyContent content = networkRequest.reply();
755 try
756 {
757 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
758 if ( !rootContent.contains( "value" ) )
759 {
760 QgsDebugError( QStringLiteral( "No 'value' array in response" ) );
761 return {};
762 }
763
764 bool foundMatchingEntity = false;
765 for ( const auto &valueJson : rootContent["value"] )
766 {
767 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
768 {
769 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
771 if ( entityType == type )
772 {
773 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
774 if ( !url.isEmpty() )
775 {
776 foundMatchingEntity = true;
777 entityBaseUri = url;
778 break;
779 }
780 }
781 }
782 }
783
784 if ( !foundMatchingEntity )
785 {
786 QgsDebugError( QStringLiteral( "Could not find url for %1" ).arg( qgsEnumValueToKey( type ) ) );
787 return {};
788 }
789 }
790 catch ( const nlohmann::json::parse_error &ex )
791 {
792 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
793 return {};
794 }
795
796 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
797 {
798 // return no features, just the total count
799 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( entityBaseUri );
801 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
802 if ( !typeFilter.isEmpty() )
803 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
804
805 const QUrl url( countUri );
806
807 QNetworkRequest request( url );
808 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
809
810 QgsBlockingNetworkRequest networkRequest;
811 networkRequest.setAuthCfg( authCfg );
812 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
813
814 if ( feedback && feedback->isCanceled() )
815 return -1;
816
817 // Handle network errors
819 {
820 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
821 return -1;
822 }
823 else
824 {
825 const QgsNetworkReplyContent content = networkRequest.reply();
826 try
827 {
828 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
829 if ( !rootContent.contains( "@iot.count" ) )
830 {
831 QgsDebugError( QStringLiteral( "No '@iot.count' value in response" ) );
832 return -1;
833 }
834
835 return rootContent["@iot.count"].get<long long>();
836 }
837 catch ( const nlohmann::json::parse_error &ex )
838 {
839 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
840 return -1;
841 }
842 }
843 };
844
845 QList<Qgis::GeometryType> types;
846 for ( Qgis::GeometryType geometryType :
847 {
851 } )
852 {
853 const long long matchCount = getCountForType( geometryType );
854 if ( matchCount < 0 )
855 return {};
856 else if ( matchCount > 0 )
857 types.append( geometryType );
858 }
859 return types;
860}
861
863{
864 // note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
865
866 // NOLINTBEGIN(bugprone-branch-clone)
867 switch ( type )
868 {
870 return {};
871
873 return
874 {
878 };
879
881 return
882 {
885 };
886
888 return
889 {
891 };
892
894 return
895 {
900 };
901
903 return
904 {
907 };
908
910 return
911 {
914 };
915
917 return
918 {
921 };
922
924 return
925 {
927 };
928
930 return
931 {
936 };
937 }
938 // NOLINTEND(bugprone-branch-clone)
939
941}
942
944{
945 valid = true;
946 switch ( baseType )
947 {
949 break;
950
952 {
953 switch ( relatedType )
954 {
957
962
969 break;
970 }
971 break;
972 }
974 {
975 switch ( relatedType )
976 {
980
989 break;
990 }
991 break;
992 }
994 {
995 switch ( relatedType )
996 {
998 // The SensorThings specification MAY be wrong here. There's an inconsistency between
999 // the inheritance graph which shows HistoricalLocation linking to "location", when
1000 // the text description describes the relationship as linking to "locationS".
1001 // We assume the text description is correct and the graph is wrong, as the reverse
1002 // relationship between Location and HistoricalLocation is many-to-many.
1004
1007
1016 break;
1017 }
1018
1019 break;
1020 }
1021
1023 {
1024 switch ( relatedType )
1025 {
1030
1033
1040 break;
1041 }
1042
1043 break;
1044 }
1045
1047 {
1048 switch ( relatedType )
1049 {
1053
1062 break;
1063 }
1064
1065 break;
1066 }
1067
1069 {
1070 switch ( relatedType )
1071 {
1074
1077
1086 break;
1087 }
1088 break;
1089 }
1090
1092 {
1093 switch ( relatedType )
1094 {
1099
1107 break;
1108 }
1109 break;
1110 }
1111
1113 {
1114 switch ( relatedType )
1115 {
1118
1128 break;
1129 }
1130
1131 break;
1132 }
1133
1135 {
1136 switch ( relatedType )
1137 {
1141
1144
1147
1154 break;
1155 }
1156 break;
1157 }
1158 }
1159
1160 valid = false;
1162}
1163
1164QString QgsSensorThingsUtils::asQueryString( Qgis::SensorThingsEntity baseType, const QList<QgsSensorThingsExpansionDefinition> &expansions )
1165{
1166 QString res;
1167 for ( int i = expansions.size() - 1; i >= 0 ; i-- )
1168 {
1169 const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
1170 if ( !expansion.isValid() )
1171 continue;
1172
1173 const Qgis::SensorThingsEntity parentType = i > 0 ? expansions.at( i - 1 ).childEntity() : baseType;
1174
1175 res = expansion.asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList{ res } );
1176 }
1177
1178 return res;
1179}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:5647
@ 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:4203
@ 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:228
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.
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:6335
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6316
#define BUILTIN_UNREACHABLE
Definition qgis.h:6779
#define QgsDebugError(str)
Definition qgslogger.h:40
#define QgsSetRequestInitiatorClass(request, _class)