QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 <nlohmann/json.hpp>
28
30{
31 const QString trimmed = type.trimmed();
32 if ( trimmed.compare( QLatin1String( "Thing" ), Qt::CaseInsensitive ) == 0 )
34 if ( trimmed.compare( QLatin1String( "Location" ), Qt::CaseInsensitive ) == 0 )
36 if ( trimmed.compare( QLatin1String( "HistoricalLocation" ), Qt::CaseInsensitive ) == 0 )
38 if ( trimmed.compare( QLatin1String( "Datastream" ), Qt::CaseInsensitive ) == 0 )
40 if ( trimmed.compare( QLatin1String( "Sensor" ), Qt::CaseInsensitive ) == 0 )
42 if ( trimmed.compare( QLatin1String( "ObservedProperty" ), Qt::CaseInsensitive ) == 0 )
44 if ( trimmed.compare( QLatin1String( "Observation" ), Qt::CaseInsensitive ) == 0 )
46 if ( trimmed.compare( QLatin1String( "FeatureOfInterest" ), Qt::CaseInsensitive ) == 0 )
48
50}
51
53{
54 switch ( type )
55 {
57 return QString();
59 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
61 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
63 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
65 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
67 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
69 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
71 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
73 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
74 }
76}
77
79{
80 const QString trimmed = type.trimmed();
81 if ( trimmed.compare( QLatin1String( "Things" ), Qt::CaseInsensitive ) == 0 )
83 if ( trimmed.compare( QLatin1String( "Locations" ), Qt::CaseInsensitive ) == 0 )
85 if ( trimmed.compare( QLatin1String( "HistoricalLocations" ), Qt::CaseInsensitive ) == 0 )
87 if ( trimmed.compare( QLatin1String( "Datastreams" ), Qt::CaseInsensitive ) == 0 )
89 if ( trimmed.compare( QLatin1String( "Sensors" ), Qt::CaseInsensitive ) == 0 )
91 if ( trimmed.compare( QLatin1String( "ObservedProperties" ), Qt::CaseInsensitive ) == 0 )
93 if ( trimmed.compare( QLatin1String( "Observations" ), Qt::CaseInsensitive ) == 0 )
95 if ( trimmed.compare( QLatin1String( "FeaturesOfInterest" ), Qt::CaseInsensitive ) == 0 )
97
99}
100
102{
103 QgsFields fields;
104
105 // common fields: https://docs.ogc.org/is/18-088/18-088.html#common-control-information
106 fields.append( QgsField( QStringLiteral( "id" ), QVariant::String ) );
107 fields.append( QgsField( QStringLiteral( "selfLink" ), QVariant::String ) );
108
109 switch ( type )
110 {
112 break;
113
115 // https://docs.ogc.org/is/18-088/18-088.html#thing
116 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
117 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
118 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
119 break;
120
122 // https://docs.ogc.org/is/18-088/18-088.html#location
123 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
124 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
125 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
126 break;
127
129 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
130 fields.append( QgsField( QStringLiteral( "time" ), QVariant::DateTime ) );
131 break;
132
134 // https://docs.ogc.org/is/18-088/18-088.html#datastream
135 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
136 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
137 fields.append( QgsField( QStringLiteral( "unitOfMeasurement" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
138 fields.append( QgsField( QStringLiteral( "observationType" ), QVariant::String ) );
139 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
140 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QVariant::DateTime ) );
141 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QVariant::DateTime ) );
142 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QVariant::DateTime ) );
143 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QVariant::DateTime ) );
144 break;
145
147 // https://docs.ogc.org/is/18-088/18-088.html#sensor
148 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
149 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
150 fields.append( QgsField( QStringLiteral( "metadata" ), QVariant::String ) );
151 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
152 break;
153
155 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
156 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
157 fields.append( QgsField( QStringLiteral( "definition" ), QVariant::String ) );
158 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
159 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
160 break;
161
163 // https://docs.ogc.org/is/18-088/18-088.html#observation
164 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QVariant::DateTime ) );
165 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QVariant::DateTime ) );
166
167 // TODO -- handle type correctly
168 fields.append( QgsField( QStringLiteral( "result" ), QVariant::String ) );
169
170 fields.append( QgsField( QStringLiteral( "resultTime" ), QVariant::DateTime ) );
171 fields.append( QgsField( QStringLiteral( "resultQuality" ), QVariant::StringList, QString(), 0, 0, QString(), QVariant::String ) );
172 fields.append( QgsField( QStringLiteral( "validTimeStart" ), QVariant::DateTime ) );
173 fields.append( QgsField( QStringLiteral( "validTimeEnd" ), QVariant::DateTime ) );
174 fields.append( QgsField( QStringLiteral( "parameters" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
175 break;
176
178 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
179 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
180 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
181 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
182 break;
183 }
184
185 return fields;
186}
187
189{
190 switch ( type )
191 {
199 return QString();
200
202 return QStringLiteral( "location" );
203
205 return QStringLiteral( "feature" );
206 }
208}
209
211{
212 switch ( type )
213 {
221 return false;
222
225 return true;
226 }
228}
229
231{
232 QString geometryTypeString;
233 switch ( QgsWkbTypes::geometryType( wkbType ) )
234 {
236 geometryTypeString = QStringLiteral( "Point" );
237 break;
239 geometryTypeString = QStringLiteral( "Polygon" );
240 break;
242 geometryTypeString = QStringLiteral( "LineString" );
243 break;
244
247 return QString();
248 }
249
250 const QString filterTarget = geometryFieldForEntityType( entityType );
251 if ( filterTarget.isEmpty() )
252 return QString();
253
254 return QStringLiteral( "%1/type eq '%2' or %1/geometry/type eq '%2'" ).arg( filterTarget, geometryTypeString );
255}
256
257QString QgsSensorThingsUtils::filterForExtent( const QString &geometryField, const QgsRectangle &extent )
258{
259 // TODO -- confirm using 'geography' is always correct here
260 return ( extent.isNull() || geometryField.isEmpty() )
261 ? QString()
262 : QStringLiteral( "geo.intersects(%1, geography'%2')" ).arg( geometryField, extent.asWktPolygon() );
263}
264
265QString QgsSensorThingsUtils::combineFilters( const QStringList &filters )
266{
267 QStringList nonEmptyFilters;
268 for ( const QString &filter : filters )
269 {
270 if ( !filter.isEmpty() )
271 nonEmptyFilters.append( filter );
272 }
273 if ( nonEmptyFilters.empty() )
274 return QString();
275 if ( nonEmptyFilters.size() == 1 )
276 return nonEmptyFilters.at( 0 );
277
278 return QStringLiteral( "(" ) + nonEmptyFilters.join( QLatin1String( ") and (" ) ) + QStringLiteral( ")" );
279}
280
281QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
282{
283 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
284 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsUtils" ) )
285
286 QgsBlockingNetworkRequest networkRequest;
287 networkRequest.setAuthCfg( authCfg );
288
289 switch ( networkRequest.get( request ) )
290 {
292 break;
293
297 QgsDebugError( QStringLiteral( "Connection failed: %1" ).arg( networkRequest.errorMessage() ) );
298 return {};
299 }
300
301 QString entityBaseUri;
302 const QgsNetworkReplyContent content = networkRequest.reply();
303 try
304 {
305 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
306 if ( !rootContent.contains( "value" ) )
307 {
308 QgsDebugError( QStringLiteral( "No 'value' array in response" ) );
309 return {};
310 }
311
312 bool foundMatchingEntity = false;
313 for ( const auto &valueJson : rootContent["value"] )
314 {
315 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
316 {
317 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
319 if ( entityType == type )
320 {
321 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
322 if ( !url.isEmpty() )
323 {
324 foundMatchingEntity = true;
325 entityBaseUri = url;
326 break;
327 }
328 }
329 }
330 }
331
332 if ( !foundMatchingEntity )
333 {
334 QgsDebugError( QStringLiteral( "Could not find url for %1" ).arg( qgsEnumValueToKey( type ) ) );
335 return {};
336 }
337 }
338 catch ( const nlohmann::json::parse_error &ex )
339 {
340 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
341 return {};
342 }
343
344 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
345 {
346 // return no features, just the total count
347 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( entityBaseUri );
349 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
350 if ( !typeFilter.isEmpty() )
351 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
352
353 const QUrl url( countUri );
354
355 QNetworkRequest request( url );
356 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
357
358 QgsBlockingNetworkRequest networkRequest;
359 networkRequest.setAuthCfg( authCfg );
360 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
361
362 if ( feedback && feedback->isCanceled() )
363 return -1;
364
365 // Handle network errors
367 {
368 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
369 return -1;
370 }
371 else
372 {
373 const QgsNetworkReplyContent content = networkRequest.reply();
374 try
375 {
376 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
377 if ( !rootContent.contains( "@iot.count" ) )
378 {
379 QgsDebugError( QStringLiteral( "No '@iot.count' value in response" ) );
380 return -1;
381 }
382
383 return rootContent["@iot.count"].get<long long>();
384 }
385 catch ( const nlohmann::json::parse_error &ex )
386 {
387 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
388 return -1;
389 }
390 }
391 };
392
393 QList<Qgis::GeometryType> types;
394 for ( Qgis::GeometryType geometryType :
395 {
399 } )
400 {
401 const long long matchCount = getCountForType( geometryType );
402 if ( matchCount < 0 )
403 return {};
404 else if ( matchCount > 0 )
405 types.append( geometryType );
406 }
407 return types;
408}
SensorThingsEntity
OGC SensorThings API entity types.
Definition: qgis.h:4865
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
@ 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:255
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
@ LineString
LineString.
@ Polygon
Polygon.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
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.
@ 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
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
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.
Definition: qgsrectangle.h:42
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).
Definition: qgsrectangle.h:505
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 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 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 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 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...
Definition: qgswkbtypes.h:862
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:5398
#define BUILTIN_UNREACHABLE
Definition: qgis.h:5853
#define QgsDebugError(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)
QgsSQLStatement::Node * parse(const QString &str, QString &parserErrorMsg, bool allowFragments)