mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
Server landing page plugin
This commit is contained in:
parent
47c7862b66
commit
13a5bc4459
@ -0,0 +1 @@
|
||||
.v-app-bar.v-app-bar--fixed,.v-footer{z-index:10000!important}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
dt[data-v-1d7961f2]{font-weight:700}.leaflet-container[data-v-3630fe7c]{height:20rem}.card-footer .btn[data-v-3630fe7c]{margin-right:.5em}h4.loading[data-v-3630fe7c]{margin-top:.35em}.metadata[data-v-3630fe7c]{z-index:1001}.v-dialog{border-radius:4px;margin:24px;overflow-y:auto;pointer-events:auto;transition:.3s cubic-bezier(.25,.8,.25,1);width:100%;z-index:inherit;box-shadow:0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12)}.v-dialog:not(.v-dialog--fullscreen){max-height:90%}.v-dialog>*{width:100%}.v-dialog>.v-card>.v-card__title{font-size:1.25rem;font-weight:500;letter-spacing:.0125em;padding:16px 24px 10px}.v-dialog>.v-card>.v-card__subtitle,.v-dialog>.v-card>.v-card__text{padding:0 24px 20px}.v-dialog__content{align-items:center;display:flex;height:100%;justify-content:center;left:0;pointer-events:none;position:fixed;top:0;transition:.2s cubic-bezier(.25,.8,.25,1),z-index 1ms;width:100%;z-index:6;outline:none}.v-dialog__container{display:none}.v-dialog__container--attached{display:inline}.v-dialog--animated{-webkit-animation-duration:.15s;animation-duration:.15s;-webkit-animation-name:animate-dialog;animation-name:animate-dialog;-webkit-animation-timing-function:cubic-bezier(.25,.8,.25,1);animation-timing-function:cubic-bezier(.25,.8,.25,1)}.v-dialog--fullscreen{border-radius:0;margin:0;height:100%;position:fixed;overflow-y:auto;top:0;left:0}.v-dialog--fullscreen>.v-card{min-height:100%;min-width:100%;margin:0!important;padding:0!important}.v-dialog--scrollable,.v-dialog--scrollable>form{display:flex}.v-dialog--scrollable>.v-card,.v-dialog--scrollable>form>.v-card{display:flex;flex:1 1 100%;flex-direction:column;max-height:100%;max-width:100%}.v-dialog--scrollable>.v-card>.v-card__actions,.v-dialog--scrollable>.v-card>.v-card__title,.v-dialog--scrollable>form>.v-card>.v-card__actions,.v-dialog--scrollable>form>.v-card>.v-card__title{flex:0 0 auto}.v-dialog--scrollable>.v-card>.v-card__text,.v-dialog--scrollable>form>.v-card>.v-card__text{-webkit-backface-visibility:hidden;backface-visibility:hidden;flex:1 1 auto;overflow-y:auto}@-webkit-keyframes animate-dialog{0%{transform:scale(1)}50%{transform:scale(1.03)}to{transform:scale(1)}}@keyframes animate-dialog{0%{transform:scale(1)}50%{transform:scale(1.03)}to{transform:scale(1)}}
|
File diff suppressed because one or more lines are too long
BIN
resources/server/api/ogc/static/landingpage/favicon.ico
Normal file
BIN
resources/server/api/ogc/static/landingpage/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
1
resources/server/api/ogc/static/landingpage/index.html
Normal file
1
resources/server/api/ogc/static/landingpage/index.html
Normal file
@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><link rel=icon href=/favicon.ico><title>app</title><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css><link href=/css/chunk-17be74ed.d8e3c767.css rel=prefetch><link href=/css/chunk-78671b57.a2bc3c6b.css rel=prefetch><link href=/css/chunk-a84730f4.d8136b79.css rel=prefetch><link href=/js/chunk-17be74ed.28f97f46.js rel=prefetch><link href=/js/chunk-78671b57.5200390a.js rel=prefetch><link href=/js/chunk-a84730f4.db1de6e9.js rel=prefetch><link href=/css/app.ca3f5643.css rel=preload as=style><link href=/css/chunk-vendors.4f60a56b.css rel=preload as=style><link href=/js/app.c7284a81.js rel=preload as=script><link href=/js/chunk-vendors.a274ab4b.js rel=preload as=script><link href=/css/chunk-vendors.4f60a56b.css rel=stylesheet><link href=/css/app.ca3f5643.css rel=stylesheet></head><body><noscript><strong>We're sorry but app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.a274ab4b.js></script><script src=/js/app.c7284a81.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
55
src/server/services/landingpage/CMakeLists.txt
Normal file
55
src/server/services/landingpage/CMakeLists.txt
Normal file
@ -0,0 +1,55 @@
|
||||
########################################################
|
||||
# Files
|
||||
|
||||
SET (LANDINGPAGE_SRCS
|
||||
${CMAKE_SOURCE_DIR}/external/nlohmann/json.hpp
|
||||
${CMAKE_SOURCE_DIR}/external/inja/inja.hpp
|
||||
qgslandingpage.cpp
|
||||
qgslandingpageutils.cpp
|
||||
qgslandingpagehandlers.cpp
|
||||
)
|
||||
|
||||
########################################################
|
||||
# Build
|
||||
|
||||
ADD_LIBRARY (landingpage MODULE ${LANDINGPAGE_SRCS})
|
||||
|
||||
INCLUDE_DIRECTORIES(SYSTEM
|
||||
)
|
||||
|
||||
INCLUDE_DIRECTORIES(
|
||||
${CMAKE_SOURCE_DIR}/external
|
||||
${CMAKE_SOURCE_DIR}/external/nlohmann
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/core
|
||||
${CMAKE_SOURCE_DIR}/src/core/geometry
|
||||
${CMAKE_SOURCE_DIR}/src/core/expression
|
||||
${CMAKE_SOURCE_DIR}/src/core/symbology
|
||||
${CMAKE_SOURCE_DIR}/src/core/metadata
|
||||
${CMAKE_SOURCE_DIR}/src/core/layertree
|
||||
${CMAKE_SOURCE_DIR}/src/server
|
||||
${CMAKE_SOURCE_DIR}/src/server/services
|
||||
${CMAKE_SOURCE_DIR}/src/server/services/landingpage
|
||||
|
||||
${CMAKE_BINARY_DIR}/src/core
|
||||
${CMAKE_BINARY_DIR}/src/python
|
||||
${CMAKE_BINARY_DIR}/src/server
|
||||
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
|
||||
TARGET_LINK_LIBRARIES(landingpage
|
||||
qgis_core
|
||||
qgis_server
|
||||
)
|
||||
|
||||
|
||||
########################################################
|
||||
# Install
|
||||
|
||||
INSTALL(TARGETS landingpage
|
||||
RUNTIME DESTINATION ${QGIS_SERVER_MODULE_DIR}
|
||||
LIBRARY DESTINATION ${QGIS_SERVER_MODULE_DIR}
|
||||
)
|
||||
|
133
src/server/services/landingpage/qgslandingpage.cpp
Normal file
133
src/server/services/landingpage/qgslandingpage.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
/***************************************************************************
|
||||
qgslandingpage.cpp
|
||||
-------------------------
|
||||
begin : August 3, 2020
|
||||
copyright : (C) 2020 by Alessandro Pasotti
|
||||
email : elpaso at itopen dot it
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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 "qgsmodule.h"
|
||||
#include "qgsserverogcapi.h"
|
||||
#include "qgsserverfilter.h"
|
||||
#include "qgslandingpagehandlers.h"
|
||||
#include "qgslandingpageutils.h"
|
||||
#include "qgsserverstatichandler.h"
|
||||
#include "qgsmessagelog.h"
|
||||
|
||||
|
||||
/**
|
||||
* Landing page API
|
||||
* \since QGIS 3.16
|
||||
*/
|
||||
class QgsLandingPageApi: public QgsServerOgcApi
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
QgsLandingPageApi( QgsServerInterface *serverIface,
|
||||
const QString &rootPath,
|
||||
const QString &name,
|
||||
const QString &description = QString(),
|
||||
const QString &version = QString() )
|
||||
: QgsServerOgcApi( serverIface, rootPath, name, description, version )
|
||||
{
|
||||
}
|
||||
|
||||
bool accept( const QUrl &url ) const override
|
||||
{
|
||||
// Mainly for CI testing of legacy OGC XML responses, we offer a way to disable landingpage API.
|
||||
// The plugin installation is optional so this won't be an issue in production.
|
||||
return ! qgetenv( "QGIS_SERVER_DISABLED_APIS" ).contains( name().toUtf8() ) && ( url.path().isEmpty()
|
||||
|| url.path() == '/'
|
||||
|| url.path().startsWith( QStringLiteral( "/map/" ) )
|
||||
|| url.path().startsWith( QStringLiteral( "/index" ) )
|
||||
// Statics:
|
||||
|| url.path().startsWith( QStringLiteral( "/css/" ) )
|
||||
|| url.path().startsWith( QStringLiteral( "/js/" ) )
|
||||
|| url.path().startsWith( QStringLiteral( "/public/" ) ) );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets QGIS_PROJECT_FILE from /project/<hash>/ URL fragment
|
||||
* \since QGIS 3.16
|
||||
*/
|
||||
class QgsProjectLoaderFilter: public QgsServerFilter
|
||||
{
|
||||
|
||||
// QgsServerFilter interface
|
||||
public:
|
||||
|
||||
QgsProjectLoaderFilter( QgsServerInterface *serverIface )
|
||||
: QgsServerFilter( serverIface )
|
||||
{
|
||||
}
|
||||
|
||||
void requestReady() override
|
||||
{
|
||||
const auto handler { serverInterface()->requestHandler() };
|
||||
if ( handler->path().startsWith( QStringLiteral( "/project/" ) ) )
|
||||
{
|
||||
const QString projectPath { QgsLandingPageUtils::projectPathFromUrl( handler->url() ) };
|
||||
if ( ! projectPath.isEmpty() )
|
||||
{
|
||||
qputenv( "QGIS_PROJECT_FILE", projectPath.toUtf8() );
|
||||
serverInterface()->setConfigFilePath( projectPath.toUtf8() );
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Project from URL set to: %1" ).arg( projectPath ), QStringLiteral( "Landing Page Plugin" ), Qgis::MessageLevel::Info );
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Could not get project from URL: %1" ).arg( handler->url() ), QStringLiteral( "Landing Page Plugin" ), Qgis::MessageLevel::Info );
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* \class QgsLandingPageModule
|
||||
* \brief Landing page module for QGIS Server
|
||||
* \since QGIS 3.16
|
||||
*/
|
||||
class QgsLandingPageModule: public QgsServiceModule
|
||||
{
|
||||
public:
|
||||
void registerSelf( QgsServiceRegistry ®istry, QgsServerInterface *serverIface ) override
|
||||
{
|
||||
QgsLandingPageApi *landingPageApi = new QgsLandingPageApi{ serverIface,
|
||||
QStringLiteral( "/" ),
|
||||
QStringLiteral( "Landing Page" ),
|
||||
QStringLiteral( "1.0.0" )
|
||||
};
|
||||
// Register handlers
|
||||
landingPageApi->registerHandler<QgsServerStaticHandler>( QStringLiteral( "/(?<staticFilePath>(public|css|js)/.*)$" ), QStringLiteral( "landingpage" ) );
|
||||
landingPageApi->registerHandler<QgsLandingPageHandler>();
|
||||
landingPageApi->registerHandler<QgsLandingPageMapHandler>();
|
||||
|
||||
// Register API
|
||||
registry.registerApi( landingPageApi );
|
||||
|
||||
// Register filters
|
||||
serverIface->registerFilter( new QgsProjectLoaderFilter( serverIface ) );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Entry points
|
||||
QGISEXTERN QgsServiceModule *QGS_ServiceModule_Init()
|
||||
{
|
||||
static QgsLandingPageModule module;
|
||||
return &module;
|
||||
}
|
||||
QGISEXTERN void QGS_ServiceModule_Exit( QgsServiceModule * )
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
97
src/server/services/landingpage/qgslandingpagehandlers.cpp
Normal file
97
src/server/services/landingpage/qgslandingpagehandlers.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/***************************************************************************
|
||||
qgsLandingPagehandlers.cpp
|
||||
-------------------------
|
||||
begin : May 3, 2019
|
||||
copyright : (C) 2019 by Alessandro Pasotti
|
||||
email : elpaso at itopen dot it
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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 "qgslandingpagehandlers.h"
|
||||
#include "qgslandingpageutils.h"
|
||||
#include "qgsserverinterface.h"
|
||||
#include "qgsserverresponse.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsserverprojectutils.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgslayertreenode.h"
|
||||
#include "qgslayertree.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
QgsLandingPageHandler::QgsLandingPageHandler()
|
||||
{
|
||||
setContentTypes( { QgsServerOgcApi::ContentType::JSON, QgsServerOgcApi::ContentType::HTML } );
|
||||
}
|
||||
|
||||
void QgsLandingPageHandler::handleRequest( const QgsServerApiContext &context ) const
|
||||
{
|
||||
if ( context.request()->url().path( ) == '/' || context.request()->url().path( ).isEmpty() )
|
||||
{
|
||||
QUrl url { context.request()->url() };
|
||||
url.setPath( QStringLiteral( "/index.%1" ).arg( QgsServerOgcApi::contentTypeToExtension( contentTypeFromRequest( context.request() ) ) ) );
|
||||
context.response()->setStatusCode( 302 );
|
||||
context.response()->setHeader( QStringLiteral( "Location" ), url.toString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
const json projects { projectsData( ) };
|
||||
json data
|
||||
{
|
||||
{ "links", links( context ) },
|
||||
{ "projects", projects },
|
||||
{ "projects_count", projects.size() }
|
||||
};
|
||||
write( data, context, {{ "pageTitle", linkTitle() }, { "navigation", json::array() }} );
|
||||
}
|
||||
}
|
||||
|
||||
const QString QgsLandingPageHandler::templatePath( const QgsServerApiContext &context ) const
|
||||
{
|
||||
QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
|
||||
path += QStringLiteral( "/ogc/static/landingpage/index.html" );
|
||||
return path;
|
||||
}
|
||||
|
||||
json QgsLandingPageHandler::projectsData() const
|
||||
{
|
||||
json j { json::array() };
|
||||
const auto availableProjects { QgsLandingPageUtils::projects( ) };
|
||||
const auto constProjectKeys { availableProjects.keys() };
|
||||
for ( const auto &p : constProjectKeys )
|
||||
{
|
||||
auto info { QgsLandingPageUtils::projectInfo( availableProjects[ p ] ) };
|
||||
info[ "id" ] = p.toStdString();
|
||||
j.push_back( info );
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
|
||||
QgsLandingPageMapHandler::QgsLandingPageMapHandler()
|
||||
{
|
||||
setContentTypes( { QgsServerOgcApi::ContentType::JSON } );
|
||||
}
|
||||
|
||||
void QgsLandingPageMapHandler::handleRequest( const QgsServerApiContext &context ) const
|
||||
{
|
||||
json data;
|
||||
data[ "links" ] = json::array();
|
||||
const QString projectPath { QgsLandingPageUtils::projectPathFromUrl( context.request()->url().path() ) };
|
||||
if ( projectPath.isEmpty() )
|
||||
{
|
||||
throw QgsServerApiNotFoundError( QStringLiteral( "Requested project hash not found!" ) );
|
||||
}
|
||||
data[ "project" ] = QgsLandingPageUtils::projectInfo( projectPath );
|
||||
write( data, context, {{ "pageTitle", linkTitle() }, { "navigation", json::array() }} );
|
||||
}
|
||||
|
91
src/server/services/landingpage/qgslandingpagehandlers.h
Normal file
91
src/server/services/landingpage/qgslandingpagehandlers.h
Normal file
@ -0,0 +1,91 @@
|
||||
/***************************************************************************
|
||||
qgslandingpagehandlers.h
|
||||
-------------------------
|
||||
begin : July 30, 2020
|
||||
copyright : (C) 2020 by Alessandro Pasotti
|
||||
email : elpaso at itopen dot it
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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 QGS_LANDINGPAGE_HANDLERS_H
|
||||
#define QGS_LANDINGPAGE_HANDLERS_H
|
||||
|
||||
#include "qgsserverogcapihandler.h"
|
||||
#include "qgsfields.h"
|
||||
|
||||
class QgsFeatureRequest;
|
||||
class QgsServerOgcApi;
|
||||
class QgsFeature;
|
||||
|
||||
/**
|
||||
* The QgsLandingPageHandler implements the landing page handler.
|
||||
*/
|
||||
class QgsLandingPageHandler: public QgsServerOgcApiHandler
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLandingPageHandler( );
|
||||
|
||||
void handleRequest( const QgsServerApiContext &context ) const override;
|
||||
|
||||
// QgsServerOgcApiHandler interface
|
||||
QRegularExpression path() const override { return QRegularExpression( R"re(^/(index.html|index.json)?$)re" ); }
|
||||
std::string operationId() const override { return "getLandingPage"; }
|
||||
QStringList tags() const override { return { QStringLiteral( "Catalog" ) }; }
|
||||
std::string summary() const override
|
||||
{
|
||||
return "Server Landing Page";
|
||||
}
|
||||
std::string description() const override
|
||||
{
|
||||
return "The landing page provides information about available projects and services.";
|
||||
}
|
||||
std::string linkTitle() const override { return "Landing page"; }
|
||||
QgsServerOgcApi::Rel linkType() const override { return QgsServerOgcApi::Rel::self; }
|
||||
const QString templatePath( const QgsServerApiContext &context ) const override;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
json projectsData() const;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The QgsLandingPageMapHandler implements the landing page map handler (JSON only).
|
||||
*/
|
||||
class QgsLandingPageMapHandler: public QgsServerOgcApiHandler
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLandingPageMapHandler( );
|
||||
|
||||
void handleRequest( const QgsServerApiContext &context ) const override;
|
||||
|
||||
// QgsServerOgcApiHandler interface
|
||||
QRegularExpression path() const override { return QRegularExpression( R"re(^/map/([a-f0-9]{32}).*$)re" ); }
|
||||
std::string operationId() const override { return "getMap"; }
|
||||
QStringList tags() const override { return { QStringLiteral( "Catalog" ), QStringLiteral( "Map Viewer" ) }; }
|
||||
std::string summary() const override
|
||||
{
|
||||
return "Server Map Viewer";
|
||||
}
|
||||
std::string description() const override
|
||||
{
|
||||
return "Shows a map";
|
||||
}
|
||||
std::string linkTitle() const override { return "Map Viewer"; }
|
||||
QgsServerOgcApi::Rel linkType() const override { return QgsServerOgcApi::Rel::self; }
|
||||
};
|
||||
|
||||
#endif // QGS_LANDINGPAGE_HANDLERS_H
|
486
src/server/services/landingpage/qgslandingpageutils.cpp
Normal file
486
src/server/services/landingpage/qgslandingpageutils.cpp
Normal file
@ -0,0 +1,486 @@
|
||||
/***************************************************************************
|
||||
qgslandingpageutils.cpp - QgsLandingPageUtils
|
||||
|
||||
---------------------
|
||||
begin : 3.8.2020
|
||||
copyright : (C) 2020 by Alessandro Pasotti
|
||||
email : elpaso at itopen dot it
|
||||
***************************************************************************
|
||||
* *
|
||||
* 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 "qgslandingpageutils.h"
|
||||
#include "qgsserverprojectutils.h"
|
||||
#include "qgsmessagelog.h"
|
||||
#include "qgslayertree.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
const QRegularExpression QgsLandingPageUtils::PROJECT_HASH_RE { QStringLiteral( "/(?<projectHash>[a-f0-9]{32})" ) };
|
||||
|
||||
QMap<QString, QString> QgsLandingPageUtils::projects( )
|
||||
{
|
||||
// TODO: cache this information and use a dir-watcher to invalidate
|
||||
QMap<QString, QString> availableProjects;
|
||||
for ( const auto &path : QString( qgetenv( "QGIS_SERVER_PROJECTS_DIRECTORIES" ) ).split( QStringLiteral( "||" ) ) )
|
||||
{
|
||||
const QDir dir { path };
|
||||
if ( dir.exists() )
|
||||
{
|
||||
const auto constFiles { dir.entryList( ) };
|
||||
for ( const auto &f : constFiles )
|
||||
{
|
||||
if ( f.endsWith( QStringLiteral( ".qgs" ), Qt::CaseSensitivity::CaseInsensitive ) ||
|
||||
f.endsWith( QStringLiteral( ".qgz" ), Qt::CaseSensitivity::CaseInsensitive ) )
|
||||
{
|
||||
const QString fullPath { path + '/' + f };
|
||||
availableProjects[ QCryptographicHash::hash( fullPath.toUtf8(), QCryptographicHash::Md5 ).toHex() ] = fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_DIRECTORIES entry '%1' was not found: skipping." ).arg( path ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: PG projects
|
||||
|
||||
return availableProjects;
|
||||
}
|
||||
|
||||
json QgsLandingPageUtils::projectInfo( const QString &projectPath )
|
||||
{
|
||||
// Helper for QStringList
|
||||
auto jList = [ ]( const QStringList & l ) -> json
|
||||
{
|
||||
json a = json::array( );
|
||||
for ( const auto &e : qgis::as_const( l ) )
|
||||
{
|
||||
a.push_back( e.toStdString() );
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
auto jHash = [ ]( const QHash<QString, QString> &l ) -> json
|
||||
{
|
||||
json a;
|
||||
const auto &constKeys { l.keys() };
|
||||
for ( const auto &k : constKeys )
|
||||
{
|
||||
a[ k.toStdString() ] = l[ k ].toStdString();
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
auto jContactList = [ ]( const QgsAbstractMetadataBase::ContactList & contacts ) -> json
|
||||
{
|
||||
json jContacts = json::array();
|
||||
for ( const auto &c : contacts )
|
||||
{
|
||||
json jContact
|
||||
{
|
||||
{ "name", c.name.toStdString() },
|
||||
{ "role", c.role.toStdString() },
|
||||
{ "voice", c.voice.toStdString() },
|
||||
{ "fax", c.fax.toStdString() },
|
||||
{ "email", c.email.toStdString() },
|
||||
{ "position", c.position.toStdString() },
|
||||
{ "organization", c.organization.toStdString() },
|
||||
{ "addresses", json::array() }
|
||||
};
|
||||
// Addresses
|
||||
const auto &addrs { c.addresses };
|
||||
for ( const auto &a : addrs )
|
||||
{
|
||||
jContact[ "addresses" ].push_back(
|
||||
{
|
||||
{ "address", a.address.toStdString()},
|
||||
{ "type", a.type.toStdString() },
|
||||
{ "city", a.city.toStdString() },
|
||||
{ "country", a.country.toStdString() },
|
||||
{ "postalCode", a.postalCode.toStdString() },
|
||||
{ "administrativeArea", a.administrativeArea.toStdString() }
|
||||
} );
|
||||
}
|
||||
jContacts.push_back( jContacts );
|
||||
}
|
||||
return jContacts;
|
||||
};
|
||||
|
||||
auto jLinksList = [ ]( const QgsAbstractMetadataBase::LinkList & links ) -> json
|
||||
{
|
||||
json jLinks = json::array();
|
||||
for ( const auto &l : links )
|
||||
{
|
||||
jLinks.push_back(
|
||||
{
|
||||
{ "name", l.name.toStdString() },
|
||||
{ "url", l.url.toStdString()},
|
||||
{ "description", l.description.toStdString()},
|
||||
{ "type", l.type.toStdString()},
|
||||
{ "mimeType", l.mimeType.toStdString()},
|
||||
{ "format", l.format.toStdString()}
|
||||
} );
|
||||
}
|
||||
return jLinks;
|
||||
};
|
||||
|
||||
json info;
|
||||
QgsProject p;
|
||||
if ( p.read( projectPath ) )
|
||||
{
|
||||
// Title
|
||||
QString title { p.metadata().title() };
|
||||
if ( title.isEmpty() )
|
||||
title = QgsServerProjectUtils::owsServiceTitle( p );
|
||||
if ( title.isEmpty() )
|
||||
title = p.title();
|
||||
if ( title.isEmpty() )
|
||||
title = p.baseName();
|
||||
info["title"] = title.toStdString();
|
||||
// Description
|
||||
QString description { p.metadata().abstract() };
|
||||
if ( description.isEmpty() )
|
||||
description = QgsServerProjectUtils::owsServiceAbstract( p );
|
||||
info["description"] = description.toStdString();
|
||||
// CRS
|
||||
const QStringList wmsOutputCrsList { QgsServerProjectUtils::wmsOutputCrsList( p ) };
|
||||
const QString crs { wmsOutputCrsList.contains( QStringLiteral( "EPSG:4326" ) ) || wmsOutputCrsList.isEmpty() ?
|
||||
QStringLiteral( "EPSG:4326" ) : wmsOutputCrsList.first() };
|
||||
info["crs"] = crs.toStdString();
|
||||
// Typenames for WMS
|
||||
const bool useIds { QgsServerProjectUtils::wmsUseLayerIds( p ) };
|
||||
QStringList typenames;
|
||||
const QStringList restrictedWms { QgsServerProjectUtils::wmsRestrictedLayers( p ) };
|
||||
const auto constLayers { p.mapLayers().values( ) };
|
||||
for ( const auto &l : constLayers )
|
||||
{
|
||||
if ( ! restrictedWms.contains( l->name() ) )
|
||||
{
|
||||
typenames.push_back( useIds ? l->id() : l->name() );
|
||||
}
|
||||
}
|
||||
// Extent
|
||||
QgsRectangle extent { QgsServerProjectUtils::wmsExtent( p ) };
|
||||
QgsCoordinateReferenceSystem targetCrs;
|
||||
if ( crs.split( ':' ).count() == 2 )
|
||||
targetCrs = QgsCoordinateReferenceSystem::fromEpsgId( crs.split( ':' ).last().toLong() );
|
||||
if ( extent.isNull() && crs.split( ':' ).count() == 2 )
|
||||
{
|
||||
for ( const auto &l : constLayers )
|
||||
{
|
||||
if ( ! restrictedWms.contains( l->name() ) )
|
||||
{
|
||||
QgsRectangle layerExtent { l->extent() };
|
||||
if ( l->crs() != targetCrs && targetCrs.isValid() )
|
||||
{
|
||||
QgsCoordinateTransform ct { l->crs(), targetCrs, p.transformContext() };
|
||||
layerExtent = ct.transform( layerExtent );
|
||||
}
|
||||
if ( extent.isNull() )
|
||||
{
|
||||
extent = layerExtent;
|
||||
}
|
||||
else
|
||||
{
|
||||
extent.combineExtentWith( layerExtent );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( ! extent.isNull() )
|
||||
{
|
||||
if ( targetCrs.isValid() && targetCrs != p.crs() )
|
||||
{
|
||||
QgsCoordinateTransform ct { p.crs(), targetCrs, p.transformContext() };
|
||||
extent = ct.transform( extent );
|
||||
}
|
||||
}
|
||||
info["extent"] = json::array( { extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum() } );
|
||||
QgsRectangle geographicExtent { extent };
|
||||
if ( targetCrs.authid() != 4326 )
|
||||
{
|
||||
QgsCoordinateTransform ct { targetCrs, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), p.transformContext() };
|
||||
geographicExtent = ct.transform( geographicExtent );
|
||||
}
|
||||
info["geographic_extent"] = json::array( { geographicExtent.xMinimum(), geographicExtent.yMinimum(), geographicExtent.xMaximum(), geographicExtent.yMaximum() } );
|
||||
// Metadata
|
||||
json metadata;
|
||||
const QgsProjectMetadata &md { p.metadata() };
|
||||
metadata["tile"] = md.title().toStdString();
|
||||
metadata["identifier"] = md.identifier().toStdString();
|
||||
metadata["parentIdentifier"] = md.parentIdentifier().toStdString();
|
||||
metadata["abstract"] = md.abstract().toStdString();
|
||||
metadata["author"] = md.author().toStdString();
|
||||
metadata["language"] = md.language().toStdString();
|
||||
metadata["categories"] = jList( md.categories() );
|
||||
metadata["history"] = jList( md.history() );
|
||||
metadata["type"] = md.type().toStdString();
|
||||
// Links
|
||||
metadata["links"] = jLinksList( md.links() );
|
||||
// Contacts
|
||||
metadata["contacts"] = jContactList( md.contacts() );
|
||||
info[ "metadata" ] = metadata;
|
||||
// Capabilities
|
||||
json capabilities = json::object();
|
||||
capabilities["owsServiceCapabilities"] = QgsServerProjectUtils::owsServiceCapabilities( p );
|
||||
capabilities["owsServiceAbstract"] = QgsServerProjectUtils::owsServiceAbstract( p ).toStdString();
|
||||
capabilities["owsServiceAccessConstraints"] = QgsServerProjectUtils::owsServiceAccessConstraints( p ).toStdString();
|
||||
capabilities["owsServiceContactMail"] = QgsServerProjectUtils::owsServiceContactMail( p ).toStdString();
|
||||
capabilities["owsServiceContactOrganization"] = QgsServerProjectUtils::owsServiceContactOrganization( p ).toStdString();
|
||||
capabilities["owsServiceContactPerson"] = QgsServerProjectUtils::owsServiceContactPerson( p ).toStdString();
|
||||
capabilities["owsServiceContactPhone"] = QgsServerProjectUtils::owsServiceContactPhone( p ).toStdString();
|
||||
capabilities["owsServiceContactPosition"] = QgsServerProjectUtils::owsServiceContactPosition( p ).toStdString();
|
||||
capabilities["owsServiceFees"] = QgsServerProjectUtils::owsServiceFees( p ).toStdString();
|
||||
capabilities["owsServiceKeywords"] = jList( QgsServerProjectUtils::owsServiceKeywords( p ) );
|
||||
capabilities["owsServiceOnlineResource"] = QgsServerProjectUtils::owsServiceOnlineResource( p ).toStdString();
|
||||
capabilities["owsServiceTitle"] = QgsServerProjectUtils::owsServiceTitle( p ).toStdString();
|
||||
capabilities["wcsLayerIds"] = jList( QgsServerProjectUtils::wcsLayerIds( p ) );
|
||||
capabilities["wcsServiceUrl"] = QgsServerProjectUtils::wcsServiceUrl( p ).toStdString();
|
||||
capabilities["wfsLayerIds"] = jList( QgsServerProjectUtils::wfsLayerIds( p ) );
|
||||
capabilities["wfsServiceUrl"] = QgsServerProjectUtils::wfsServiceUrl( p ).toStdString();
|
||||
capabilities["wfstDeleteLayerIds"] = jList( QgsServerProjectUtils::wfstDeleteLayerIds( p ) );
|
||||
capabilities["wfstInsertLayerIds"] = jList( QgsServerProjectUtils::wfstInsertLayerIds( p ) );
|
||||
capabilities["wfstUpdateLayerIds"] = jList( QgsServerProjectUtils::wfstUpdateLayerIds( p ) );
|
||||
capabilities["wmsDefaultMapUnitsPerMm"] = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( p );
|
||||
// Skip wmsExtent because it's already in "extent"
|
||||
capabilities["wmsFeatureInfoAddWktGeometry"] = QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( p );
|
||||
capabilities["wmsFeatureInfoDocumentElement"] = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( p ).toStdString();
|
||||
capabilities["wmsFeatureInfoDocumentElementNs"] = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( p ).toStdString();
|
||||
capabilities["wmsFeatureInfoLayerAliasMap"] = jHash( QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( p ) );
|
||||
capabilities["wmsFeatureInfoPrecision"] = QgsServerProjectUtils::wmsFeatureInfoPrecision( p );
|
||||
capabilities["wmsFeatureInfoSchema"] = QgsServerProjectUtils::wmsFeatureInfoSchema( p ).toStdString();
|
||||
capabilities["wmsFeatureInfoSegmentizeWktGeometry"] = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( p );
|
||||
capabilities["wmsImageQuality"] = QgsServerProjectUtils::wmsImageQuality( p );
|
||||
capabilities["wmsInfoFormatSia2045"] = QgsServerProjectUtils::wmsInfoFormatSia2045( p );
|
||||
capabilities["wmsInspireActivate"] = QgsServerProjectUtils::wmsInspireActivate( p );
|
||||
capabilities["wmsInspireLanguage"] = QgsServerProjectUtils::wmsInspireLanguage( p ).toStdString();
|
||||
capabilities["wmsInspireMetadataDate"] = QgsServerProjectUtils::wmsInspireMetadataDate( p ).toStdString();
|
||||
capabilities["wmsInspireMetadataUrl"] = QgsServerProjectUtils::wmsInspireMetadataUrl( p ).toStdString();
|
||||
capabilities["wmsInspireMetadataUrlType"] = QgsServerProjectUtils::wmsInspireMetadataUrlType( p ).toStdString();
|
||||
capabilities["wmsInspireTemporalReference"] = QgsServerProjectUtils::wmsInspireTemporalReference( p ).toStdString();
|
||||
capabilities["wmsMaxAtlasFeatures"] = QgsServerProjectUtils::wmsMaxAtlasFeatures( p );
|
||||
capabilities["wmsMaxHeight"] = QgsServerProjectUtils::wmsMaxHeight( p );
|
||||
capabilities["wmsMaxWidth"] = QgsServerProjectUtils::wmsMaxWidth( p );
|
||||
capabilities["wmsOutputCrsList"] = jList( QgsServerProjectUtils::wmsOutputCrsList( p ) );
|
||||
capabilities["wmsRestrictedComposers"] = jList( QgsServerProjectUtils::wmsRestrictedComposers( p ) );
|
||||
capabilities["wmsRestrictedLayers"] = jList( QgsServerProjectUtils::wmsRestrictedLayers( p ) );
|
||||
capabilities["wmsRootName"] = QgsServerProjectUtils::wmsRootName( p ).toStdString();
|
||||
capabilities["wmsServiceUrl"] = QgsServerProjectUtils::wmsServiceUrl( p ).toStdString();
|
||||
capabilities["wmsTileBuffer"] = QgsServerProjectUtils::wmsTileBuffer( p );
|
||||
capabilities["wmsUseLayerIds"] = QgsServerProjectUtils::wmsUseLayerIds( p );
|
||||
capabilities["wmtsServiceUrl" ] = QgsServerProjectUtils::wmtsServiceUrl( p ).toStdString();
|
||||
info["capabilities"] = capabilities;
|
||||
// WMS layers
|
||||
info[ "wms_root_name" ] = QgsServerProjectUtils::wmsRootName( p ).toStdString();
|
||||
if ( QgsServerProjectUtils::wmsRootName( p ).isEmpty() )
|
||||
{
|
||||
info[ "wms_root_name" ] = title.toStdString();
|
||||
}
|
||||
json wmsLayers;
|
||||
// For convenience:
|
||||
// Map layer (short) name to layer id
|
||||
json wmsLayersTypenameIdMap;
|
||||
// Map layer title to layer (short) name (or id if use_ids)
|
||||
json wmsLayersMap;
|
||||
QStringList wmsLayersSearchable;
|
||||
QStringList wmsLayersQueryable;
|
||||
for ( const auto &l : constLayers )
|
||||
{
|
||||
if ( ! restrictedWms.contains( l->name() ) )
|
||||
{
|
||||
json wmsLayer
|
||||
{
|
||||
{ "name", l->name().toStdString() },
|
||||
{ "id", l->id().toStdString() },
|
||||
{ "crs", l->crs().authid().toStdString() },
|
||||
{ "type", l->type() == QgsMapLayerType::VectorLayer ? "vector" : "raster" },
|
||||
};
|
||||
if ( l->type() == QgsMapLayerType::VectorLayer )
|
||||
{
|
||||
const QgsVectorLayer *vl = static_cast<const QgsVectorLayer *>( l );
|
||||
wmsLayer[ "pk" ] = vl->primaryKeyAttributes();
|
||||
int fieldIdx { 0 };
|
||||
json fieldsData;
|
||||
const auto &cFields { vl->fields() };
|
||||
for ( const auto &f : cFields )
|
||||
{
|
||||
if ( vl->excludeAttributesWfs().contains( vl->name() ) )
|
||||
{
|
||||
++fieldIdx;
|
||||
continue;
|
||||
}
|
||||
const auto &constraints { f.constraints().constraints() };
|
||||
const bool notNull { constraints &QgsFieldConstraints::Constraint::ConstraintNotNull &&
|
||||
f.constraints().constraintStrength( QgsFieldConstraints::Constraint::ConstraintNotNull ) == QgsFieldConstraints::ConstraintStrength::ConstraintStrengthHard };
|
||||
const bool unique { constraints &QgsFieldConstraints::Constraint::ConstraintUnique &&
|
||||
f.constraints().constraintStrength( QgsFieldConstraints::Constraint::ConstraintUnique ) == QgsFieldConstraints::ConstraintStrength::ConstraintStrengthHard };
|
||||
const bool hasExpression { constraints &QgsFieldConstraints::Constraint::ConstraintExpression &&
|
||||
f.constraints().constraintStrength( QgsFieldConstraints::Constraint::ConstraintExpression ) == QgsFieldConstraints::ConstraintStrength::ConstraintStrengthHard };
|
||||
const QString &defaultValue { vl->dataProvider()->defaultValueClause( fieldIdx ) };
|
||||
fieldsData[ f.name().toStdString() ] =
|
||||
{
|
||||
{ "type", f.typeName().toStdString() },
|
||||
{ "label", f.alias().isEmpty() ? f.name().toStdString() : f.alias().toStdString() },
|
||||
{ "precision", f.precision() },
|
||||
{ "length", f.length() },
|
||||
{ "unique", unique },
|
||||
{ "not_null", notNull },
|
||||
{ "has_expression", hasExpression },
|
||||
{ "default", defaultValue.toStdString() },
|
||||
{ "expression", f.constraints().constraintExpression().toStdString() },
|
||||
{ "editable", !( notNull &&unique && ! defaultValue.isEmpty() ) }
|
||||
};
|
||||
|
||||
++fieldIdx;
|
||||
}
|
||||
wmsLayer[ "fields" ] = fieldsData;
|
||||
}
|
||||
wmsLayer[ "extent" ] = {{ l->extent().xMinimum(), l->extent().yMinimum(), l->extent().xMaximum(), l->extent().yMaximum() }};
|
||||
wmsLayers[ l->id().toStdString() ] = wmsLayer;
|
||||
|
||||
// Fill maps
|
||||
const QString name { l->title().isEmpty() ? l->name() : l->title() };
|
||||
const QString shortName { l->shortName().isEmpty() ? l->shortName() : l->name() };
|
||||
wmsLayersTypenameIdMap[ shortName.toStdString()] = l->id().toStdString();
|
||||
wmsLayersMap[ name.toStdString() ] = useIds ? l->id().toStdString() : shortName.toStdString();
|
||||
|
||||
if ( l->flags() & QgsMapLayer::Searchable )
|
||||
{
|
||||
wmsLayersSearchable.push_back( l->id() );
|
||||
}
|
||||
if ( l->flags() & QgsMapLayer::Identifiable )
|
||||
{
|
||||
wmsLayersQueryable.push_back( l->id() );
|
||||
}
|
||||
|
||||
// Layer metadata
|
||||
json layerMetadata;
|
||||
const auto &md { l->metadata() };
|
||||
layerMetadata[ "abstract" ] = md.abstract( ).toStdString();
|
||||
layerMetadata[ "categories" ] = jList( md.categories( ) );
|
||||
layerMetadata[ "contacts" ] = jContactList( md.contacts() );
|
||||
layerMetadata[ "encoding" ] = md.encoding( ).toStdString();
|
||||
layerMetadata[ "fees" ] = md.fees( ).toStdString();
|
||||
layerMetadata[ "history" ] = jList( md.history( ) );
|
||||
layerMetadata[ "identifier" ] = md.identifier( ).toStdString();
|
||||
layerMetadata[ "keywordVocabularies" ] = jList( md.keywordVocabularies( ) );
|
||||
// Keywords
|
||||
const auto &cKw { md.keywords().keys() };
|
||||
json jKeywords;
|
||||
for ( const auto &k : cKw )
|
||||
{
|
||||
jKeywords[ k.toStdString() ] = jList( md.keywords()[ k ] );
|
||||
}
|
||||
layerMetadata[ "keywords" ] = jKeywords;
|
||||
layerMetadata[ "language" ] = md.language( ).toStdString();
|
||||
layerMetadata[ "licenses" ] = jList( md.licenses( ) );
|
||||
layerMetadata[ "links" ] = jLinksList( md.links( ) );
|
||||
layerMetadata[ "parentIdentifier" ] = md.parentIdentifier( ).toStdString();
|
||||
layerMetadata[ "rights" ] = jList( md.rights( ) );
|
||||
layerMetadata[ "title" ] = md.title( ).toStdString();
|
||||
layerMetadata[ "type" ] = md.type( ).toStdString();
|
||||
}
|
||||
}
|
||||
info[ "wms_layers" ] = wmsLayers;
|
||||
info[ "wms_layers_map" ] = wmsLayersMap;
|
||||
info[ "wms_layers_queryable" ] = jList( wmsLayersQueryable );
|
||||
info[ "wms_layers_searchable" ] = jList( wmsLayersSearchable );
|
||||
info[ "wms_layers_typename_id_map" ] = wmsLayersTypenameIdMap;
|
||||
info[ "toc " ] = layerTree( p, wmsLayersQueryable, wmsLayersSearchable );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Could not read project '%1': skipping." ).arg( projectPath ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
json QgsLandingPageUtils::layerTree( const QgsProject &project, const QStringList &wmsLayersQueryable, const QStringList &wmsLayersSearchable )
|
||||
{
|
||||
const bool useIds { QgsServerProjectUtils::wmsUseLayerIds( project ) };
|
||||
const QStringList wmsRestrictedLayers { QgsServerProjectUtils::wmsRestrictedLayers( project ) };
|
||||
const QStringList wfsLayerIds { QgsServerProjectUtils::wfsLayerIds( project ) };
|
||||
|
||||
std::function<json( const QgsLayerTreeNode *, const QString & )> harvest = [ & ]( const QgsLayerTreeNode * node, const QString & parentId ) -> json
|
||||
{
|
||||
const std::string nodeName { parentId.isEmpty() ? "root" : node->name().toStdString() };
|
||||
QString title { QString::fromStdString( nodeName ) };
|
||||
json rec {
|
||||
{ "title", nodeName },
|
||||
{ "name", nodeName },
|
||||
{ "expanded", node->isExpanded() },
|
||||
{ "visible", node->isVisible() },
|
||||
};
|
||||
if ( QgsLayerTree::isLayer( node ) )
|
||||
{
|
||||
const QgsLayerTreeLayer *l { static_cast<const QgsLayerTreeLayer *>( node ) };
|
||||
if ( l->layer()->type() == QgsMapLayerType::VectorLayer || l->layer()->type() == QgsMapLayerType::RasterLayer )
|
||||
{
|
||||
rec[ "id" ] = l->layerId().toStdString();
|
||||
rec[ "queryable" ] = wmsLayersQueryable.contains( l->layerId() );
|
||||
rec[ "searchable" ] = wmsLayersSearchable.contains( l->layerId() );
|
||||
rec[ "wfs_enabled" ] = wfsLayerIds.contains( l->layerId() );
|
||||
const QString layerName { l->layer()->shortName().isEmpty() ? l->layer()->name() : l->layer()->shortName()};
|
||||
rec[ "typename" ] = useIds ? l->layer()->id().toStdString() : layerName.toStdString();
|
||||
// Override title
|
||||
if ( ! l->layer()->title().isEmpty() )
|
||||
{
|
||||
title = l->layer()->title();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported layer
|
||||
return nullptr;
|
||||
}
|
||||
rec[ "is_layer" ] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rec[ "is_layer" ] = false;
|
||||
}
|
||||
rec[ "title"] = title.toStdString();
|
||||
const QString treeId = parentId == QStringLiteral( "root" ) ? QStringLiteral( "root" ) : parentId + "." + title;
|
||||
rec[ "tree_id" ] = treeId.toStdString();
|
||||
rec[ "tree_id_hash" ] = QCryptographicHash::hash( treeId.toUtf8(), QCryptographicHash::Md5 ).toHex().toStdString();
|
||||
|
||||
// Collect children
|
||||
json children = json::array();
|
||||
const auto cChildren { node->children() };
|
||||
for ( const auto &c : cChildren )
|
||||
{
|
||||
const json harvested { harvest( c, treeId ) };
|
||||
if ( ! harvested.is_null() )
|
||||
{
|
||||
children.push_back( harvested );
|
||||
}
|
||||
}
|
||||
rec [ "children" ] = children;
|
||||
|
||||
return rec;
|
||||
};
|
||||
return harvest( project.layerTreeRoot(), QString() );
|
||||
}
|
||||
|
||||
QString QgsLandingPageUtils::projectPathFromUrl( const QString &url )
|
||||
{
|
||||
const auto match { QgsLandingPageUtils::PROJECT_HASH_RE.match( url ) };
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
const auto availableProjects { QgsLandingPageUtils::projects() };
|
||||
return availableProjects.value( match.captured( QStringLiteral( "projectHash" ) ), QString() );
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
|
72
src/server/services/landingpage/qgslandingpageutils.h
Normal file
72
src/server/services/landingpage/qgslandingpageutils.h
Normal file
@ -0,0 +1,72 @@
|
||||
/***************************************************************************
|
||||
qgslandingpageutils.h - QgsLandingPageUtils
|
||||
|
||||
---------------------
|
||||
begin : 3.8.2020
|
||||
copyright : (C) 2020 by Alessandro Pasotti
|
||||
email : elpaso at itopen dot it
|
||||
***************************************************************************
|
||||
* *
|
||||
* 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 QGSLANDINGPAGEUTILS_H
|
||||
#define QGSLANDINGPAGEUTILS_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "nlohmann/json_fwd.hpp"
|
||||
|
||||
#ifndef SIP_RUN
|
||||
using namespace nlohmann;
|
||||
#endif
|
||||
|
||||
class QgsProject;
|
||||
|
||||
/**
|
||||
* The QgsLandingPageUtils struct contains static utilities for the
|
||||
* landing page plugin
|
||||
*/
|
||||
struct QgsLandingPageUtils
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a list of available projects from various sources:
|
||||
*
|
||||
* - QGIS_SERVER_PROJECTS_DIRECTORIES directories
|
||||
* - QGIS_SERVER_PROJECTS_PG_CONNECTIONS postgres connections
|
||||
*
|
||||
* Multiple paths and connections may be separated by two pipe chars: '||'
|
||||
*
|
||||
* \returns hash of project paths (or other storage identifiers) with a digest key
|
||||
*/
|
||||
static QMap<QString, QString> projects();
|
||||
|
||||
/**
|
||||
* Returns project information for a given \a projectPath
|
||||
*/
|
||||
static json projectInfo( const QString &projectPath );
|
||||
|
||||
/**
|
||||
* Returns the layer tree information for the given \a project
|
||||
*/
|
||||
static json layerTree( const QgsProject &project, const QStringList &wmsLayersQueryable, const QStringList &wmsLayersSearchable );
|
||||
|
||||
/**
|
||||
* Extracts the project hash from the URL and returns the (possibly empty) project path.
|
||||
*/
|
||||
static QString projectPathFromUrl( const QString &url );
|
||||
|
||||
/**
|
||||
* PROJECTS_RE regex to extract project hash from URL
|
||||
*/
|
||||
static const QRegularExpression PROJECT_HASH_RE;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSLANDINGPAGEUTILS_H
|
@ -137,6 +137,15 @@ class QgsServerTestBase(unittest.TestCase):
|
||||
pass
|
||||
self.server = QgsServer()
|
||||
|
||||
# Disable landing page API to test standard legacy XML responses in case of errors
|
||||
os.environ["QGIS_SERVER_DISABLED_APIS"] = "Landing Page"
|
||||
|
||||
def tearDown(self):
|
||||
""""Cleanup env"""
|
||||
|
||||
super().tearDown()
|
||||
del os.environ["QGIS_SERVER_DISABLED_APIS"]
|
||||
|
||||
def strip_version_xmlns(self, text):
|
||||
"""Order of attributes is random, strip version and xmlns"""
|
||||
return text.replace(b'version="1.3.0"', b'').replace(b'xmlns="http://www.opengis.net/ogc"', b'')
|
||||
|
@ -182,14 +182,14 @@ class QgsServerAPITestBase(QgsServerTestBase):
|
||||
result.append(bytes(response.body()).decode('utf8'))
|
||||
return '\n'.join(result)
|
||||
|
||||
def compareApi(self, request, project, reference_file):
|
||||
def compareApi(self, request, project, reference_file, subdir='api'):
|
||||
response = QgsBufferServerResponse()
|
||||
# Add json to accept it reference_file is JSON
|
||||
if reference_file.endswith('.json'):
|
||||
request.setHeader('Accept', 'application/json')
|
||||
self.server.handleRequest(request, response, project)
|
||||
result = bytes(response.body()).decode('utf8') if reference_file.endswith('html') else self.dump(response)
|
||||
path = unitTestDataPath('qgis_server') + '/api/' + reference_file
|
||||
path = os.path.join(unitTestDataPath('qgis_server'), subdir, reference_file)
|
||||
if self.regeregenerate_api_reference:
|
||||
# Try to change timestamp
|
||||
try:
|
||||
|
Loading…
x
Reference in New Issue
Block a user