28using namespace Qt::StringLiterals;
36 mData = nlohmann::json::parse( data.data() );
37 if ( mData.contains(
"stac_version" ) )
39 const QString ver( QString::fromStdString( mData.at(
"stac_version" ) ) );
40 if ( !isSupportedStacVersion( ver ) )
42 mError = u
"Unsupported STAC version: %1"_s.arg( ver );
47 catch ( nlohmann::json::exception &ex )
49 mError = u
"Error parsing JSON"_s;
50 QgsDebugError( u
"Error parsing JSON : %1"_s.arg( ex.what() ) );
56 if ( mData.at(
"type" ) ==
"Catalog" )
60 else if ( mData.at(
"type" ) ==
"Collection" )
64 else if ( mData.at(
"type" ) ==
"Feature" )
69 catch ( nlohmann::json::exception &ex )
92 return parseCatalog( mData );
95std::unique_ptr<QgsStacCatalog> QgsStacParser::parseCatalog(
const nlohmann::json &data )
99 const QString ver( QString::fromStdString( data.at(
"stac_version" ) ) );
100 if ( !isSupportedStacVersion( ver ) )
102 mError = u
"Unsupported STAC version: %1"_s.arg( ver );
106 const QString id( QString::fromStdString( data.at(
"id" ) ) );
107 const QString description( getString( data.at(
"description" ) ) );
109 QVector< QgsStacLink > links = parseLinks( data.at(
"links" ) );
111 auto catalog = std::make_unique< QgsStacCatalog >(
id, ver, description, links );
113 if ( data.contains(
"title" ) )
114 catalog->setTitle( getString( data[
"title"] ) );
116 if ( data.contains(
"conformsTo" ) )
118 for (
const auto &conformanceClass : data[
"conformsTo"] )
120 if ( conformanceClass.is_string() )
121 catalog->addConformanceClass( QString::fromStdString( conformanceClass ) );
125 if ( data.contains(
"stac_extensions" ) )
127 QStringList extensions;
128 for (
const auto &extension : data[
"stac_extensions"] )
130 if ( extension.is_string() )
131 extensions.append( QString::fromStdString( extension ) );
133 catalog->setStacExtensions( extensions );
138 catch ( nlohmann::json::exception &ex )
140 mError = u
"Error parsing Collection"_s;
141 QgsDebugError( u
"Error parsing Collection: %1"_s.arg( ex.what() ) );
148 return parseCollection( mData );
151std::unique_ptr<QgsStacCollection> QgsStacParser::parseCollection(
const nlohmann::json &data )
155 const QString ver( QString::fromStdString( data.at(
"stac_version" ) ) );
156 if ( !isSupportedStacVersion( ver ) )
158 mError = u
"Unsupported STAC version: %1"_s.arg( ver );
162 const QString id( QString::fromStdString( data.at(
"id" ) ) );
163 const QString description( getString( data.at(
"description" ) ) );
164 const QString license( getString( data.at(
"license" ) ) );
166 QgsStacExtent stacExtent;
167 int totalExtents = 0;
168 for (
const auto &e : data.at(
"extent" ).at(
"spatial" ).at(
"bbox" ) )
173 extent = QgsBox3D( e[0].get<double>(), e[1].get<double>(), 0., e[2].get<double>(), e[3].get<double>(), 0. );
175 else if ( e.size() == 6 )
177 extent = QgsBox3D( e[0].get<double>(), e[1].get<double>(), e[2].get<double>(), e[3].get<double>(), e[4].get<double>(), e[5].get<double>() );
181 mError = u
"Malformed STAC collection spatial extents"_s;
185 if ( ++totalExtents == 1 )
192 for (
const auto &e : data.at(
"extent" ).at(
"temporal" ).at(
"interval" ) )
194 if ( !e.is_array() || e.size() != 2 )
196 mError = u
"Malformed STAC collection temporal extents"_s;
200 const QDateTime start = QDateTime::fromString( getString( e[0] ), Qt::ISODateWithMs );
201 const QDateTime end = QDateTime::fromString( getString( e[1] ), Qt::ISODateWithMs );
203 if ( ++totalExtents == 1 )
209 QVector< QgsStacLink > links = parseLinks( data.at(
"links" ) );
211 auto collection = std::make_unique< QgsStacCollection >(
id, ver, description, links, license, stacExtent );
213 if ( data.contains(
"title" ) )
214 collection->setTitle( getString( data[
"title"] ) );
216 if ( data.contains(
"stac_extensions" ) )
218 QStringList extensions;
219 for (
const auto &extension : data[
"stac_extensions"] )
221 if ( extension.is_string() )
222 extensions.append( QString::fromStdString( extension ) );
227 if ( data.contains(
"keywords" ) )
229 QStringList keywords;
230 for (
const auto &kw : data[
"keywords"] )
232 if ( kw.is_string() )
233 keywords.append( QString::fromStdString( kw ) );
238 if ( data.contains(
"providers" ) )
240 QVector< QgsStacProvider > providers;
241 for (
const auto &p : data[
"providers"] )
243 if ( !p.contains(
"name" ) || ( p.contains(
"roles" ) && !p[
"roles"].is_array() ) )
250 if ( p.contains(
"roles" ) )
252 for (
const auto &role : p[
"roles"] )
254 if ( role.is_string() )
255 roles.append( QString::fromStdString( role ) );
258 const QgsStacProvider
259 provider( QString::fromStdString( p[
"name"] ), p.contains(
"description" ) ? getString( p[
"description"] ) : QString(), roles, p.contains(
"url" ) ? getString( p[
"url"] ) : QString() );
262 providers.append( provider );
267 if ( data.contains(
"summaries" ) )
273 if ( data.contains(
"assets" ) )
275 QMap< QString, QgsStacAsset > assets = parseAssets( data[
"assets"] );
281 catch ( nlohmann::json::exception &ex )
283 mError = u
"Error parsing Collection"_s;
284 QgsDebugError( u
"Error parsing Collection: %1"_s.arg( ex.what() ) );
291 return parseItem( mData );
294std::unique_ptr<QgsStacItem> QgsStacParser::parseItem(
const nlohmann::json &data )
298 const QString ver = QString::fromStdString( data.at(
"stac_version" ) );
299 if ( !isSupportedStacVersion( ver ) )
301 mError = u
"Unsupported STAC version: %1"_s.arg( ver );
305 const QString
id = QString::fromStdString( data.at(
"id" ) );
311 auto b = data.at(
"bbox" );
319 else if ( b.size() == 6 )
330 auto datetime = data.at(
"properties" ).at(
"datetime" );
331 if ( !datetime.is_string() || !QDateTime::fromString( QString::fromStdString( datetime ), Qt::ISODate ).isValid() )
333 auto s = data.at(
"properties" ).at(
"start_datetime" );
334 QString ss = QString::fromStdString( s );
335 const QDateTime start = QDateTime::fromString( ss, Qt::ISODate );
337 auto e = data.at(
"properties" ).at(
"end_datetime" );
338 const QString ee = QString::fromStdString( e );
339 const QDateTime end = QDateTime::fromString( ee, Qt::ISODate );
340 if ( start.isNull() || end.isNull() )
343 mError = u
"Invalid STAC item temporal range"_s;
349 QVector< QgsStacLink > links = parseLinks( data.at(
"links" ) );
351 QMap< QString, QgsStacAsset > assets = parseAssets( data.at(
"assets" ) );
353 auto item = std::make_unique< QgsStacItem >(
id, ver, geom, properties, links, assets, bbox );
355 if ( data.contains(
"stac_extensions" ) )
357 QStringList extensions;
358 for (
const auto &extension : data[
"stac_extensions"] )
360 if ( extension.is_string() )
361 extensions.append( QString::fromStdString( extension ) );
363 item->setStacExtensions( extensions );
366 if ( data.contains(
"collection" ) )
367 item->setCollection( getString( data[
"collection"] ) );
371 catch ( nlohmann::json::exception &ex )
373 mError = u
"Error parsing Item"_s;
374 QgsDebugError( u
"Error parsing Item: %1"_s.arg( ex.what() ) );
379QVector<QgsStacLink> QgsStacParser::parseLinks(
const json &data )
381 QVector< QgsStacLink > links;
382 links.reserve(
static_cast<int>( data.size() ) );
383 for (
const auto &link : data )
385 QUrl linkUrl( QString::fromStdString( link.at(
"href" ) ) );
386 if ( linkUrl.isRelative() )
387 linkUrl = mBaseUrl.resolved( linkUrl );
390 l( linkUrl.toString(),
391 QString::fromStdString( link.at(
"rel" ) ),
392 link.contains(
"type" ) ? getString( link[
"type"] ) : QString(),
393 link.contains(
"title" ) ? getString( link[
"title"] ) : QString() );
399QMap<QString, QgsStacAsset> QgsStacParser::parseAssets(
const json &data )
401 QMap< QString, QgsStacAsset > assets;
402 for (
const auto &asset : data.items() )
404 const json value = asset.value();
405 QUrl assetUrl( QString::fromStdString( value.at(
"href" ) ) );
406 if ( assetUrl.isRelative() )
407 assetUrl = mBaseUrl.resolved( assetUrl );
410 a( assetUrl.toString(),
411 value.contains(
"title" ) ? getString( value[
"title"] ) : QString(),
412 value.contains(
"description" ) ? getString( value[
"description"] ) : QString(),
413 value.contains(
"type" ) ? getString( value[
"type"] ) : QString(),
415 assets.insert( QString::fromStdString( asset.key() ), a );
420bool QgsStacParser::isSupportedStacVersion(
const QString &version )
422 const int maxMajor = 1;
423 const int minMajor = 1;
424 const int minMinor = 0;
425 const int minRelease = 0;
427 const thread_local QRegularExpression r( u
"^(\\d+).(\\d+).(\\d+)(-.*)*"_s );
428 const QRegularExpressionMatch match = r.match( version );
430 const QStringList m = match.capturedTexts();
433 if ( m.length() < 4 )
436 const int major = m[1].toInt();
437 const int minor = m[2].toInt();
438 const int release = m[3].toInt();
440 if ( major > maxMajor
442 || ( major == minMajor && minor < minMinor )
443 || ( major == minMajor && minor == minMinor && release < minRelease )
444 || ( major == minMajor && minor == minMinor && release == minRelease && m.length() == 5 ) )
451QString QgsStacParser::getString(
const nlohmann::json &data )
453 return data.is_null() ? QString() : QString::fromStdString( data );
458 std::vector< std::unique_ptr<QgsStacItem> > items;
459 QVector< QgsStacLink > links;
460 int numberMatched = -1;
463 links = parseLinks( mData.at(
"links" ) );
465 items.reserve( mData.at(
"features" ).size() );
466 for (
auto &
item : mData.at(
"features" ) )
468 items.emplace_back( parseItem(
item ) );
471 if ( mData.contains(
"numberMatched" ) )
472 numberMatched = mData[
"numberMatched"].get<
int>();
474 catch ( nlohmann::json::exception &ex )
476 mError = u
"Error parsing ItemCollection"_s;
477 QgsDebugError( u
"Error parsing ItemCollection: %1"_s.arg( ex.what() ) );
481 QVector< QgsStacItem *> rawItems;
482 rawItems.reserve(
static_cast<int>( items.size() ) );
483 for ( std::unique_ptr<QgsStacItem> &i : items )
486 rawItems.append( i.release() );
489 return std::make_unique< QgsStacItemCollection >( rawItems, links, numberMatched );
494 std::vector< std::unique_ptr<QgsStacCollection> > cols;
495 QVector< QgsStacLink > links;
496 int numberMatched = -1;
501 if ( mData.contains(
"links" ) )
502 links = parseLinks( mData.at(
"links" ) );
504 cols.reserve( mData.at(
"collections" ).size() );
505 for (
auto &col : mData.at(
"collections" ) )
507 cols.emplace_back( parseCollection( col ) );
510 if ( mData.contains(
"numberMatched" ) )
511 numberMatched = mData[
"numberMatched"].get<
int>();
513 catch ( nlohmann::json::exception &ex )
515 mError = u
"Error parsing ItemCollection"_s;
516 QgsDebugError( u
"Error parsing ItemCollection: %1"_s.arg( ex.what() ) );
520 QVector< QgsStacCollection * > rawCols;
521 rawCols.reserve(
static_cast<int>( cols.size() ) );
522 for ( std::unique_ptr<QgsStacCollection> &
c : cols )
525 rawCols.append(
c.release() );
StacObjectType
Available types of stac objects.
@ Unknown
Type is not known.
@ Collection
STAC collection.
void setZMinimum(double z)
Sets the minimum z value.
void setYMaximum(double y)
Sets the maximum y value.
void setZMaximum(double z)
Sets the maximum z value.
void setXMaximum(double x)
Sets the maximum x value.
void setYMinimum(double y)
Sets the minimum y value.
void setXMinimum(double x)
Sets the minimum x value.
static QgsGeometry geometryFromGeoJson(const json &geometry)
Parses a GeoJSON "geometry" value to a QgsGeometry object.
static QVariant jsonToVariant(const json &value)
Converts a JSON value to a QVariant, in case of parsing error an invalid QVariant is returned.
Stores a list of STAC Collections, which is typically used to store the data returned by STAC API /co...
void addDetailedSpatialExtent(QgsBox3D extent)
Adds a more detailed spatial extent.
void addDetailedTemporalExtent(QgsDateTimeRange extent)
Adds a more detailed temporal extent.
void setSpatialExtent(QgsBox3D extent)
Sets the overall spatial extent to extent.
void setTemporalExtent(QgsDateTimeRange extent)
Sets the overall temporal extent to extent.
QString error() const
Returns the last parsing error.
QgsStacCollectionList * collections()
Returns the parsed STAC API Collections If parsing failed, nullptr is returned The caller takes owner...
void setBaseUrl(const QUrl &url)
Sets the base url that will be used to resolve relative links.
std::unique_ptr< QgsStacItem > item()
Returns the parsed STAC Item If parsing failed, nullptr is returned The caller takes ownership of the...
std::unique_ptr< QgsStacItemCollection > itemCollection()
Returns the parsed STAC API Item Collection If parsing failed, nullptr is returned The caller takes o...
std::unique_ptr< QgsStacCatalog > catalog()
Returns the parsed STAC Catalog If parsing failed, nullptr is returned The caller takes ownership of ...
Qgis::StacObjectType type() const
Returns the type of the parsed object.
std::unique_ptr< QgsStacCollection > collection()
Returns the parsed STAC Collection If parsing failed, nullptr is returned The caller takes ownership ...
void setData(const QByteArray &data)
Sets the JSON data to be parsed.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define QgsDebugError(str)
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.