QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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"
22#include "qgslogger.h"
23#include <QUrl>
24#include <QNetworkRequest>
25#include <nlohmann/json.hpp>
26
28{
29 const QString trimmed = type.trimmed();
30 if ( trimmed.compare( QLatin1String( "Thing" ), Qt::CaseInsensitive ) == 0 )
32 if ( trimmed.compare( QLatin1String( "Location" ), Qt::CaseInsensitive ) == 0 )
34 if ( trimmed.compare( QLatin1String( "HistoricalLocation" ), Qt::CaseInsensitive ) == 0 )
36 if ( trimmed.compare( QLatin1String( "Datastream" ), Qt::CaseInsensitive ) == 0 )
38 if ( trimmed.compare( QLatin1String( "Sensor" ), Qt::CaseInsensitive ) == 0 )
40 if ( trimmed.compare( QLatin1String( "ObservedProperty" ), Qt::CaseInsensitive ) == 0 )
42 if ( trimmed.compare( QLatin1String( "Observation" ), Qt::CaseInsensitive ) == 0 )
44 if ( trimmed.compare( QLatin1String( "FeatureOfInterest" ), Qt::CaseInsensitive ) == 0 )
46
48}
49
51{
52 switch ( type )
53 {
55 return QString();
57 return plural ? QObject::tr( "Things" ) : QObject::tr( "Thing" );
59 return plural ? QObject::tr( "Locations" ) : QObject::tr( "Location" );
61 return plural ? QObject::tr( "Historical Locations" ) : QObject::tr( "Historical Location" );
63 return plural ? QObject::tr( "Datastreams" ) : QObject::tr( "Datastream" );
65 return plural ? QObject::tr( "Sensors" ) : QObject::tr( "Sensor" );
67 return plural ? QObject::tr( "Observed Properties" ) : QObject::tr( "Observed Property" );
69 return plural ? QObject::tr( "Observations" ) : QObject::tr( "Observation" );
71 return plural ? QObject::tr( "Features of Interest" ) : QObject::tr( "Feature of Interest" );
72 }
74}
75
77{
78 const QString trimmed = type.trimmed();
79 if ( trimmed.compare( QLatin1String( "Things" ), Qt::CaseInsensitive ) == 0 )
81 if ( trimmed.compare( QLatin1String( "Locations" ), Qt::CaseInsensitive ) == 0 )
83 if ( trimmed.compare( QLatin1String( "HistoricalLocations" ), Qt::CaseInsensitive ) == 0 )
85 if ( trimmed.compare( QLatin1String( "Datastreams" ), Qt::CaseInsensitive ) == 0 )
87 if ( trimmed.compare( QLatin1String( "Sensors" ), Qt::CaseInsensitive ) == 0 )
89 if ( trimmed.compare( QLatin1String( "ObservedProperties" ), Qt::CaseInsensitive ) == 0 )
91 if ( trimmed.compare( QLatin1String( "Observations" ), Qt::CaseInsensitive ) == 0 )
93 if ( trimmed.compare( QLatin1String( "FeaturesOfInterest" ), Qt::CaseInsensitive ) == 0 )
95
97}
98
100{
101 QgsFields fields;
102
103 // common fields: https://docs.ogc.org/is/18-088/18-088.html#common-control-information
104 fields.append( QgsField( QStringLiteral( "id" ), QVariant::String ) );
105 fields.append( QgsField( QStringLiteral( "selfLink" ), QVariant::String ) );
106
107 switch ( type )
108 {
110 break;
111
113 // https://docs.ogc.org/is/18-088/18-088.html#thing
114 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
115 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
116 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
117 break;
118
120 // https://docs.ogc.org/is/18-088/18-088.html#location
121 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
122 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
123 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
124 break;
125
127 // https://docs.ogc.org/is/18-088/18-088.html#historicallocation
128 fields.append( QgsField( QStringLiteral( "time" ), QVariant::DateTime ) );
129 break;
130
132 // https://docs.ogc.org/is/18-088/18-088.html#datastream
133 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
134 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
135 fields.append( QgsField( QStringLiteral( "unitOfMeasurement" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
136 fields.append( QgsField( QStringLiteral( "observationType" ), QVariant::String ) );
137 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
138 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QVariant::DateTime ) );
139 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QVariant::DateTime ) );
140 fields.append( QgsField( QStringLiteral( "resultTimeStart" ), QVariant::DateTime ) );
141 fields.append( QgsField( QStringLiteral( "resultTimeEnd" ), QVariant::DateTime ) );
142 break;
143
145 // https://docs.ogc.org/is/18-088/18-088.html#sensor
146 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
147 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
148 fields.append( QgsField( QStringLiteral( "metadata" ), QVariant::String ) );
149 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
150 break;
151
153 // https://docs.ogc.org/is/18-088/18-088.html#observedproperty
154 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
155 fields.append( QgsField( QStringLiteral( "definition" ), QVariant::String ) );
156 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
157 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
158 break;
159
161 // https://docs.ogc.org/is/18-088/18-088.html#observation
162 fields.append( QgsField( QStringLiteral( "phenomenonTimeStart" ), QVariant::DateTime ) );
163 fields.append( QgsField( QStringLiteral( "phenomenonTimeEnd" ), QVariant::DateTime ) );
164
165 // TODO -- handle type correctly
166 fields.append( QgsField( QStringLiteral( "result" ), QVariant::String ) );
167
168 fields.append( QgsField( QStringLiteral( "resultTime" ), QVariant::DateTime ) );
169 fields.append( QgsField( QStringLiteral( "resultQuality" ), QVariant::StringList, QString(), 0, 0, QString(), QVariant::String ) );
170 fields.append( QgsField( QStringLiteral( "validTimeStart" ), QVariant::DateTime ) );
171 fields.append( QgsField( QStringLiteral( "validTimeEnd" ), QVariant::DateTime ) );
172 fields.append( QgsField( QStringLiteral( "parameters" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
173 break;
174
176 // https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
177 fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
178 fields.append( QgsField( QStringLiteral( "description" ), QVariant::String ) );
179 fields.append( QgsField( QStringLiteral( "properties" ), QVariant::Map, QStringLiteral( "json" ), 0, 0, QString(), QVariant::String ) );
180 break;
181 }
182
183 return fields;
184}
185
207
227
229{
230 QString filterTarget;
231 switch ( entityType )
232 {
234 filterTarget = QStringLiteral( "location/type" );
235 break;
236
238 filterTarget = QStringLiteral( "feature/type" );
239 break;
240
248 break;
249 }
250
251 switch ( QgsWkbTypes::geometryType( wkbType ) )
252 {
254 return QStringLiteral( "%1 eq 'Point'" ).arg( filterTarget );
256 return QStringLiteral( "%1 eq 'Polygon'" ).arg( filterTarget );
258 // TODO -- confirm
259 return QStringLiteral( "%1 eq 'LineString'" ).arg( filterTarget );
262 break;
263 }
264 return QString();
265}
266
267QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback, const QString &authCfg )
268{
269 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
270 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsUtils" ) )
271
272 QgsBlockingNetworkRequest networkRequest;
273 networkRequest.setAuthCfg( authCfg );
274
275 switch ( networkRequest.get( request ) )
276 {
278 break;
279
283 QgsDebugError( QStringLiteral( "Connection failed: %1" ).arg( networkRequest.errorMessage() ) );
284 return {};
285 }
286
287 QString entityBaseUri;
288 const QgsNetworkReplyContent content = networkRequest.reply();
289 try
290 {
291 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
292 if ( !rootContent.contains( "value" ) )
293 {
294 QgsDebugError( QStringLiteral( "No 'value' array in response" ) );
295 return {};
296 }
297
298 bool foundMatchingEntity = false;
299 for ( const auto &valueJson : rootContent["value"] )
300 {
301 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
302 {
303 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
305 if ( entityType == type )
306 {
307 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
308 if ( !url.isEmpty() )
309 {
310 foundMatchingEntity = true;
311 entityBaseUri = url;
312 break;
313 }
314 }
315 }
316 }
317
318 if ( !foundMatchingEntity )
319 {
320 QgsDebugError( QStringLiteral( "Could not find url for %1" ).arg( qgsEnumValueToKey( type ) ) );
321 return {};
322 }
323 }
324 catch ( const nlohmann::json::parse_error &ex )
325 {
326 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
327 return {};
328 }
329
330 auto getCountForType = [entityBaseUri, type, authCfg, feedback]( Qgis::GeometryType geometryType ) -> long long
331 {
332 // return no features, just the total count
333 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( entityBaseUri );
335 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( type, wkbType );
336 if ( !typeFilter.isEmpty() )
337 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
338
339 const QUrl url( countUri );
340
341 QNetworkRequest request( url );
342 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
343
344 QgsBlockingNetworkRequest networkRequest;
345 networkRequest.setAuthCfg( authCfg );
346 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
347
348 if ( feedback && feedback->isCanceled() )
349 return -1;
350
351 // Handle network errors
353 {
354 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
355 return -1;
356 }
357 else
358 {
359 const QgsNetworkReplyContent content = networkRequest.reply();
360 try
361 {
362 auto rootContent = nlohmann::json::parse( content.content().toStdString() );
363 if ( !rootContent.contains( "@iot.count" ) )
364 {
365 QgsDebugError( QStringLiteral( "No '@iot.count' value in response" ) );
366 return -1;
367 }
368
369 return rootContent["@iot.count"].get<long long>();
370 }
371 catch ( const nlohmann::json::parse_error &ex )
372 {
373 QgsDebugError( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ) );
374 return -1;
375 }
376 }
377 };
378
379 QList<Qgis::GeometryType> types;
380 for ( Qgis::GeometryType geometryType :
381 {
385 } )
386 {
387 const long long matchCount = getCountForType( geometryType );
388 if ( matchCount < 0 )
389 return {};
390 else if ( matchCount > 0 )
391 types.append( geometryType );
392 }
393 return types;
394}
SensorThingsEntity
OGC SensorThings API entity types.
Definition qgis.h:4803
@ 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.
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 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 Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:5335
#define BUILTIN_UNREACHABLE
Definition qgis.h:5790
#define QgsDebugError(str)
Definition qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)