Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #52366 from rouault/fix_52228
[OAPIF provider] OGC API features 2 / CRS related fixes (fixes #52228)
  • Loading branch information
rouault committed Apr 3, 2023
2 parents a2b3a0c + 50a5c8f commit 65402bb
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 82 deletions.
1 change: 1 addition & 0 deletions src/providers/wfs/CMakeLists.txt
Expand Up @@ -26,6 +26,7 @@ set(WFS_SRCS
oapif/qgsoapiflandingpagerequest.cpp
oapif/qgsoapifapirequest.cpp
oapif/qgsoapifcollection.cpp
oapif/qgsoapifconformancerequest.cpp
oapif/qgsoapifitemsrequest.cpp
oapif/qgsoapifprovider.cpp
oapif/qgsoapifutils.cpp
Expand Down
72 changes: 61 additions & 11 deletions src/providers/wfs/oapif/qgsoapifcollection.cpp
Expand Up @@ -25,7 +25,7 @@ using namespace nlohmann;

#include <QTextCodec>

bool QgsOapifCollection::deserialize( const json &j )
bool QgsOapifCollection::deserialize( const json &j, const json &jCollections )
{
if ( !j.is_object() )
return false;
Expand Down Expand Up @@ -121,7 +121,6 @@ bool QgsOapifCollection::deserialize( const json &j )
crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QString::fromStdString( jCrs.get<std::string>() ) );
}
}
mLayerMetadata.setCrs( crs );

const auto jBboxes = spatial["bbox"];
if ( jBboxes.is_array() )
Expand All @@ -148,6 +147,7 @@ bool QgsOapifCollection::deserialize( const json &j )
{
if ( firstBbox )
{
mBboxCrs = crs;
mBbox.set( values[0], values[1], values[2], values[3] );
}
spatialExtent.bounds = QgsBox3d( mBbox );
Expand All @@ -156,6 +156,7 @@ bool QgsOapifCollection::deserialize( const json &j )
{
if ( firstBbox )
{
mBboxCrs = crs;
mBbox.set( values[0], values[1], values[3], values[4] );
}
spatialExtent.bounds = QgsBox3d( values[0], values[1], values[2],
Expand Down Expand Up @@ -298,25 +299,74 @@ bool QgsOapifCollection::deserialize( const json &j )
}
}

// Usage storageCrs from Part 2 in priority
bool layerCrsSet = false;
if ( j.contains( "storageCrs" ) )
{
const auto crsUrl = j["storageCrs"];
if ( crsUrl.is_string() )
{
QString crsStr = QString::fromStdString( crsUrl.get<std::string>() );
QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsStr );

if ( j.contains( "storageCrsCoordinateEpoch" ) )
{
const auto storageCrsCoordinateEpoch = j["storageCrsCoordinateEpoch"];
if ( storageCrsCoordinateEpoch.is_number() )
{
crs.setCoordinateEpoch( storageCrsCoordinateEpoch.get<double>() );
}
}

layerCrsSet = true;
mLayerMetadata.setCrs( crs );
mCrsList.append( crs.authid() );
}
}

if ( j.contains( "crs" ) )
{
const auto crsUrls = j["crs"];
if ( crsUrls.is_array() )
json jCrs = j["crs"];
// Resolve "#/crs" link
if ( jCrs.is_array() && jCrs.size() == 1 &&
jCrs[0].is_string() && jCrs[0].get<std::string>() == "#/crs" &&
jCollections.is_object() && jCollections.contains( "crs" ) )
{
for ( const auto &crsUrl : crsUrls )
jCrs = jCollections["crs"];
}

if ( jCrs.is_array() )
{
for ( const auto &crsUrl : jCrs )
{
if ( crsUrl.is_string() )
{
QString crs = QString::fromStdString( crsUrl.get<std::string>() );
mLayerMetadata.setCrs( QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs ) );
QString crsStr = QString::fromStdString( crsUrl.get<std::string>() );
QgsCoordinateReferenceSystem crs( QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsStr ) );
if ( !layerCrsSet )
{
// Take the first CRS of the list
layerCrsSet = true;
mLayerMetadata.setCrs( crs );
}

// Take the first CRS of the list
break;
if ( !mCrsList.contains( crs.authid() ) )
{
mCrsList.append( crs.authid() );
}
}
}
}
}

if ( mCrsList.isEmpty() )
{
QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs(
QgsOapifProvider::OAPIF_PROVIDER_DEFAULT_CRS );
mLayerMetadata.setCrs( QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs.authid() ) );
mCrsList.append( crs.authid() );
}

return true;
}

Expand Down Expand Up @@ -408,7 +458,7 @@ void QgsOapifCollectionsRequest::processReply()
for ( const auto &jCollection : collections )
{
QgsOapifCollection collection;
if ( collection.deserialize( jCollection ) )
if ( collection.deserialize( jCollection, j ) )
{
if ( collection.mLayerMetadata.licenses().isEmpty() )
{
Expand Down Expand Up @@ -502,7 +552,7 @@ void QgsOapifCollectionRequest::processReply()
try
{
const json j = json::parse( utf8Text.toStdString() );
mCollection.deserialize( j );
mCollection.deserialize( j, json() );
}
catch ( const json::parse_error &ex )
{
Expand Down
10 changes: 8 additions & 2 deletions src/providers/wfs/oapif/qgsoapifcollection.h
Expand Up @@ -39,14 +39,20 @@ struct QgsOapifCollection
//! Description
QString mDescription;

//! Bounding box (in CRS84)
//! Bounding box
QgsRectangle mBbox;

//! Bounding box Crs
QgsCoordinateReferenceSystem mBboxCrs;

//! List of available CRS
QList<QString> mCrsList;

//! Layer metadata
QgsLayerMetadata mLayerMetadata;

//! Fills a collection from its JSON serialization
bool deserialize( const json &j );
bool deserialize( const json &j, const json &jCollections );
};

//! Manages the /collections request
Expand Down
100 changes: 100 additions & 0 deletions src/providers/wfs/oapif/qgsoapifconformancerequest.cpp
@@ -0,0 +1,100 @@
/***************************************************************************
qgsoapifconformancerequest.cpp
------------------------------
begin : April 2023
copyright : (C) 2023 by Even Rouault
email : even.rouault at spatialys.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include <nlohmann/json.hpp>
using namespace nlohmann;

#include "qgslogger.h"
#include "qgsoapifconformancerequest.h"
#include "qgsoapifutils.h"
#include "qgswfsconstants.h"

#include <QTextCodec>

QgsOapifConformanceRequest::QgsOapifConformanceRequest( const QgsDataSourceUri &uri ):
QgsBaseNetworkRequest( QgsAuthorizationSettings( uri.username(), uri.password(), uri.authConfigId() ), "OAPIF" )
{
// Using Qt::DirectConnection since the download might be running on a different thread.
// In this case, the request was sent from the main thread and is executed with the main
// thread being blocked in future.waitForFinished() so we can run code on this object which
// lives in the main thread without risking havoc.
connect( this, &QgsBaseNetworkRequest::downloadFinished, this, &QgsOapifConformanceRequest::processReply, Qt::DirectConnection );
}

QStringList QgsOapifConformanceRequest::conformanceClasses( const QUrl &conformanceUrl )
{
sendGET( conformanceUrl, QString( "application/json" ), /*synchronous=*/true, /*forceRefresh=*/false );
return mConformanceClasses;
}

QString QgsOapifConformanceRequest::errorMessageWithReason( const QString &reason )
{
return tr( "Download of conformance classes failed: %1" ).arg( reason );
}

void QgsOapifConformanceRequest::processReply()
{
if ( mErrorCode != QgsBaseNetworkRequest::NoError )
{
return;
}
const QByteArray &buffer = mResponse;
if ( buffer.isEmpty() )
{
mErrorMessage = tr( "empty response" );
mErrorCode = QgsBaseNetworkRequest::ServerExceptionError;
return;
}

QgsDebugMsgLevel( QStringLiteral( "parsing Conformance response: " ) + buffer, 4 );

QTextCodec::ConverterState state;
QTextCodec *codec = QTextCodec::codecForName( "UTF-8" );
Q_ASSERT( codec );

const QString utf8Text = codec->toUnicode( buffer.constData(), buffer.size(), &state );
if ( state.invalidChars != 0 )
{
mErrorCode = QgsBaseNetworkRequest::ApplicationLevelError;
mErrorMessage = errorMessageWithReason( tr( "Invalid UTF-8 content" ) );
return;
}

try
{
const json j = json::parse( utf8Text.toStdString() );

if ( j.is_object() && j.contains( "conformsTo" ) )
{
const json jConformsTo = j["conformsTo"];
if ( jConformsTo.is_array() )
{
for ( const auto &subj : jConformsTo )
{
if ( subj.is_string() )
{
mConformanceClasses.append( QString::fromStdString( subj.get<std::string>() ) );
}
}
}
}
}
catch ( const json::parse_error &ex )
{
mErrorCode = QgsBaseNetworkRequest::ApplicationLevelError;
mErrorMessage = errorMessageWithReason( tr( "Cannot decode JSON document: %1" ).arg( QString::fromStdString( ex.what() ) ) );
return;
}
}
44 changes: 44 additions & 0 deletions src/providers/wfs/oapif/qgsoapifconformancerequest.h
@@ -0,0 +1,44 @@
/***************************************************************************
qgsoapifconformancerequest.h
-----------------------------
begin : April 2023
copyright : (C) 2023 by Even Rouault
email : even.rouault at spatialys.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSOAPIFCONFORMANCEREQUEST_H
#define QGSOAPIFCONFORMANCEREQUEST_H

#include <QObject>

#include "qgsdatasourceuri.h"
#include "qgsbasenetworkrequest.h"

//! Manages the conformance request
class QgsOapifConformanceRequest : public QgsBaseNetworkRequest
{
Q_OBJECT
public:
explicit QgsOapifConformanceRequest( const QgsDataSourceUri &uri );

//! Issue the request synchronously and return conformance classes
QStringList conformanceClasses( const QUrl &conformanceUrl );

private slots:
void processReply();

private:
QStringList mConformanceClasses;

protected:
QString errorMessageWithReason( const QString &reason ) override;
};

#endif // QGSOAPIFCONFORMANCEREQUEST_H
2 changes: 2 additions & 0 deletions src/providers/wfs/oapif/qgsoapiflandingpagerequest.cpp
Expand Up @@ -124,6 +124,8 @@ void QgsOapifLandingPageRequest::processReply()
apiTypes );
}
#endif

mConformanceUrl = QgsOAPIFJson::findLink( links, QStringLiteral( "conformance" ) );
}
catch ( const json::parse_error &ex )
{
Expand Down
8 changes: 7 additions & 1 deletion src/providers/wfs/oapif/qgsoapiflandingpagerequest.h
Expand Up @@ -45,9 +45,12 @@ class QgsOapifLandingPageRequest : public QgsBaseNetworkRequest
//! Return URL of the api endpoint
const QString &apiUrl() const { return mApiUrl; }

//! Return URL of the api endpoint
//! Return URL of the collections endpoint
const QString &collectionsUrl() const { return mCollectionsUrl; }

//! Return URL of the conformance endpoint
const QString &conformanceUrl() const { return mConformanceUrl; }

signals:
//! emitted when the capabilities have been fully parsed, or an error occurred
void gotResponse();
Expand All @@ -67,6 +70,9 @@ class QgsOapifLandingPageRequest : public QgsBaseNetworkRequest
//! URL of the collections endpoint.
QString mCollectionsUrl;

//! URL of the conformance endpoint.
QString mConformanceUrl;

ApplicationLevelError mAppLevelError = ApplicationLevelError::NoError;

};
Expand Down

0 comments on commit 65402bb

Please sign in to comment.