Skip to content

Commit

Permalink
[Feature] Add 'OGC API - Features' provider, shortnamed as OAPIF. Onl…
Browse files Browse the repository at this point in the history
…y non-GUI elements in this commit

Funded by Planet
  • Loading branch information
rouault authored and nyalldawson committed Oct 25, 2019
1 parent 05eee42 commit 4a6b49f
Show file tree
Hide file tree
Showing 16 changed files with 2,257 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/providers/wfs/CMakeLists.txt
Expand Up @@ -3,6 +3,7 @@
# Files

SET(WFS_SRCS
${CMAKE_SOURCE_DIR}/external/nlohmann/json.hpp
qgswfsprovider.cpp
qgswfscapabilities.cpp
qgswfsdataitems.cpp
Expand All @@ -19,6 +20,12 @@ SET(WFS_SRCS
qgsbackgroundcachedshareddata.cpp
qgscachedirectorymanager.cpp
qgsbasenetworkrequest.cpp
qgsoapiflandingpagerequest.cpp
qgsoapifapirequest.cpp
qgsoapifcollection.cpp
qgsoapifitemsrequest.cpp
qgsoapifprovider.cpp
qgsoapifutils.cpp
)

SET (WFS_MOC_HDRS
Expand All @@ -35,6 +42,11 @@ SET (WFS_MOC_HDRS
qgswfsutils.h
qgsbackgroundcachedfeatureiterator.h
qgscachedirectorymanager.h
qgsoapiflandingpagerequest.h
qgsoapifapirequest.h
qgsoapifcollection.h
qgsoapifitemsrequest.h
qgsoapifprovider.h
)

IF (WITH_GUI)
Expand Down
132 changes: 132 additions & 0 deletions src/providers/wfs/qgsoapifapirequest.cpp
@@ -0,0 +1,132 @@
/***************************************************************************
qgsoapifapirequest.cpp
---------------------
begin : October 2019
copyright : (C) 2019 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 "qgsoapifapirequest.h"

#include <QTextCodec>

QgsOapifApiRequest::QgsOapifApiRequest( const QgsDataSourceUri &baseUri, const QString &url ):
QgsBaseNetworkRequest( QgsAuthorizationSettings( baseUri.username(), baseUri.password(), baseUri.authConfigId() ), tr( "OAPIF" ) ),
mUrl( url )
{
// 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, &QgsOapifApiRequest::processReply, Qt::DirectConnection );
}

bool QgsOapifApiRequest::request( bool synchronous, bool forceRefresh )
{
if ( !sendGET( QUrl( mUrl ), QStringLiteral( "application/vnd.oai.openapi+json;version=3.0, application/openapi+json;version=3.0, application/json" ), synchronous, forceRefresh ) )
{
emit gotResponse();
return false;
}
return true;
}

QString QgsOapifApiRequest::errorMessageWithReason( const QString &reason )
{
return tr( "Download of API page failed: %1" ).arg( reason );
}

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

QgsDebugMsgLevel( QStringLiteral( "parsing API 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;
mAppLevelError = ApplicationLevelError::JsonError;
mErrorMessage = errorMessageWithReason( tr( "Invalid UTF-8 content" ) );
emit gotResponse();
return;
}

try
{
const json j = json::parse( utf8Text.toStdString() );
if ( j.is_object() && j.contains( "components" ) )
{
const auto components = j["components"];
if ( components.is_object() && components.contains( "parameters" ) )
{
const auto parameters = components["parameters"];
if ( parameters.is_object() && parameters.contains( "limit" ) )
{
const auto limit = parameters["limit"];
if ( limit.is_object() && limit.contains( "schema" ) )
{
const auto schema = limit["schema"];
if ( schema.is_object() )
{
if ( schema.contains( "maximum" ) )
{
const auto maximum = schema["maximum"];
if ( maximum.is_number_integer() )
{
mMaxLimit = maximum.get<int>();
}
}

if ( schema.contains( "default" ) )
{
const auto defaultL = schema["default"];
if ( defaultL.is_number_integer() )
{
mDefaultLimit = defaultL.get<int>();
}
}
}
}
}
}
}
}
catch ( const json::parse_error &ex )
{
mErrorCode = QgsBaseNetworkRequest::ApplicationLevelError;
mAppLevelError = ApplicationLevelError::JsonError;
mErrorMessage = errorMessageWithReason( tr( "Cannot decode JSon document: %1" ).arg( QString::fromStdString( ex.what() ) ) );
emit gotResponse();
return;
}

emit gotResponse();
}
72 changes: 72 additions & 0 deletions src/providers/wfs/qgsoapifapirequest.h
@@ -0,0 +1,72 @@
/***************************************************************************
qgsoapifapirequest.h
---------------------
begin : October 2019
copyright : (C) 2019 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 QGSOAPIFAPIREQUEST_H
#define QGSOAPIFAPIREQUEST_H

#include <QObject>

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

//! Manages the /api request
class QgsOapifApiRequest : public QgsBaseNetworkRequest
{
Q_OBJECT
public:
explicit QgsOapifApiRequest( const QgsDataSourceUri &baseUri, const QString &url );

//! Issue the request
bool request( bool synchronous, bool forceRefresh );

//! Application level error
enum class ApplicationLevelError
{
NoError,
JsonError,
IncompleteInformation
};

//! Returns application level error
ApplicationLevelError applicationLevelError() const { return mAppLevelError; }

//! Return the maximum number of features that can be requested at once (-1 if unknown)
int maxLimit() const { return mMaxLimit; }

//! Return the default number of features that are requested at once (-1 if unknown)
int defaultLimit() const { return mDefaultLimit; }

signals:
//! emitted when the capabilities have been fully parsed, or an error occurred */
void gotResponse();

private slots:
void processReply();

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

private:
QString mUrl;

int mMaxLimit = -1;

int mDefaultLimit = -1;

ApplicationLevelError mAppLevelError = ApplicationLevelError::NoError;

};

#endif // QGSOAPIFAPIREQUEST_H

0 comments on commit 4a6b49f

Please sign in to comment.