Skip to content

Commit

Permalink
[Server] WMS GetCapabilities: Cleaning and simplifying the GetCapabil…
Browse files Browse the repository at this point in the history
…ities building

The objectives of these changes are:
* Improvement of the quality of the code for a better maintenance of it
* Simplification of layer group extent calculation

To do so, we introduced WMS Layer infos to collect layer informations for WMS GetCapabilities and
calculate layer extent in the different available CRSes for WMS.

With WMS Layer infos, we can combine extent to calculate layer group extent and removed the WMS
GetCapabilities method `combineExtentAndCrsOfGroupChildren`.

We probably can alo removed searching of preceding element in the methods to insert CRS and
BoundingBox.

Funded by Ifremer https://wwz.ifremer.fr/
  • Loading branch information
dmarteau authored and troopa81 committed Nov 17, 2022
1 parent 69e26fc commit 85d3065
Show file tree
Hide file tree
Showing 15 changed files with 735 additions and 526 deletions.
1 change: 1 addition & 0 deletions src/server/services/wms/CMakeLists.txt
Expand Up @@ -22,6 +22,7 @@ set (WMS_SRCS
qgswmsrestorer.cpp
qgswmsrendercontext.cpp
qgswmsrequest.cpp
qgswmslayerinfos.cpp
)

set (WMS_HDRS
Expand Down
720 changes: 252 additions & 468 deletions src/server/services/wms/qgswmsgetcapabilities.cpp

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion src/server/services/wms/qgswmsgetcapabilities.h
Expand Up @@ -27,6 +27,7 @@
#include "qgslayertree.h"

#include "qgswmsrequest.h"
#include "qgswmslayerinfos.h"

namespace QgsWms
{
Expand Down Expand Up @@ -89,7 +90,33 @@ namespace QgsWms
const QgsWmsRequest &request,
bool projectSettings );

bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers );
/**
* Returns true if at least one layer from the layers ids is queryable
* \param layerIds list of layer ids
* \param wmsLayerInfos WMS layers definition to build WMS capabilities
* \returns True if at least one layer form the layers ids is queryable
* \since QGIS 3.28.0
*/
bool hasQueryableLayers( const QStringList &layerIds, const QMap< QString, QgsWmsLayerInfos > &wmsLayerInfos );

/**
* Returns the combination of the WGS84 bounding rectangle of the layers from the list of layers ids
* \param layerIds list of layer ids
* \param wmsLayerInfos WMS layers definition to build WMS capabilities
* \returns the extent combination of the WGS84 bounding rectangle of the layers from the list of layers ids
* \since QGIS 3.28.0
*/
QgsRectangle combineWgs84BoundingRect( const QStringList &layerIds, const QMap< QString, QgsWmsLayerInfos > &wmsLayerInfos );

/**
* Returns the combinations of the extent CRSes of the layers from the list of layers ids
* \param layerIds list of layer ids
* \param wmsLayerInfos WMS layers definition to build WMS capabilities
* \returns the extent combination of the WGS84 bounding rectangle of the layers from the list of layers ids
* \since QGIS 3.28.0
*/
QMap<QString, QgsRectangle> combineCrsExtents( const QStringList &layerIds, const QMap< QString, QgsWmsLayerInfos > &wmsLayerInfos );

} // namespace QgsWms

#endif
240 changes: 240 additions & 0 deletions src/server/services/wms/qgswmslayerinfos.cpp
@@ -0,0 +1,240 @@
/***************************************************************************
qgswmslayerinfos.cpp
Layer's information
------------------------------------
begin : September 26 , 2022
copyright : (C) 2022 by René-Luc D'Hont and David Marteau
email : rldhont at 3liz doc com
dmarteau at 3liz dot 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 "qgsserverprojectutils.h"
#include "qgscoordinatetransform.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgswmslayerinfos.h"
#include "qgsmessagelog.h"
#include "qgsserverinterface.h"
#include "qgsmaplayerstylemanager.h"

#include <algorithm>

QgsRectangle QgsWmsLayerInfos::transformExtent(
const QgsRectangle &extent,
const QgsCoordinateReferenceSystem &source,
const QgsCoordinateReferenceSystem &destination,
const QgsCoordinateTransformContext &context,
const bool &ballparkTransformsAreAppropriate )
{
QgsCoordinateTransform transformer { source, destination, context };
transformer.setBallparkTransformsAreAppropriate( ballparkTransformsAreAppropriate );
// Transform extent and do not catch exception
return transformer.transformBoundingBox( extent );
}

QMap< QString, QgsRectangle > QgsWmsLayerInfos::transformExtentToCrsList(
const QgsRectangle &extent,
const QgsCoordinateReferenceSystem &source,
const QList<QgsCoordinateReferenceSystem> &destinations,
const QgsCoordinateTransformContext &context )
{
QMap< QString, QgsRectangle > crsExtents;
if ( extent.isEmpty() )
{
return crsExtents;
}
for ( const QgsCoordinateReferenceSystem &destination : std::as_const( destinations ) )
{
// Transform extent and do not catch exception
QgsCoordinateTransform crsTransform { source, destination, context };
crsExtents[ destination.authid() ] = crsTransform.transformBoundingBox( extent );
}
return crsExtents;
}


bool setBoundingRect(
const QgsProject *project,
QgsWmsLayerInfos &pLayer,
QgsMapLayer *ml,
const QgsRectangle &wmsExtent,
const QgsCoordinateReferenceSystem &wgs84,
const QList<QgsCoordinateReferenceSystem> &outputCrsList )
{
QgsRectangle layerExtent = ml->extent();
if ( layerExtent.isEmpty() )
{
// if the extent is empty (not only Null), use the wms extent
// defined in the project...
if ( wmsExtent.isNull() )
{
// or the CRS extent otherwise
layerExtent = ml->crs().bounds();
}
else
{
layerExtent = QgsRectangle( wmsExtent );
if ( ml->crs() != project->crs() )
{
// If CRS is different transform it to layer's CRS
try
{
layerExtent = QgsWmsLayerInfos::transformExtent( wmsExtent, project->crs(), ml->crs(), project->transformContext() );
}
catch ( QgsCsException &cse )
{
QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent for layer %1: %2" ).arg( ml->name() ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
return false;
}
}
}

// Now we have a layer Extent we need the WGS84 bounding rectangle
try
{
pLayer.wgs84BoundingRect = QgsWmsLayerInfos::transformExtent( layerExtent, ml->crs(), wgs84, project->transformContext(), true );
}
catch ( const QgsCsException &cse )
{
QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent for layer %1: %2" ).arg( ml->name() ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
return false;
}
}
else
{
pLayer.wgs84BoundingRect = ml->wgs84Extent();
}

try
{
pLayer.crsExtents = QgsWmsLayerInfos::transformExtentToCrsList(
layerExtent, ml->crs(), outputCrsList, project->transformContext()
);
}
catch ( QgsCsException &cse )
{
QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent for layer %1: %2" ).arg( ml->name() ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
return false;
}

return true;
}

// ===================================
// Get wms layer infos
// ===================================
QMap< QString, QgsWmsLayerInfos > QgsWmsLayerInfos::buildWmsLayerInfos(
QgsServerInterface *serverIface,
const QgsProject *project,
const QList<QgsCoordinateReferenceSystem> &outputCrsList )
{
QMap< QString, QgsWmsLayerInfos > wmsLayers;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsAccessControl *accessControl = serverIface->accessControls();
#else
( void )serverIface;
#endif

bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
const QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
const QgsRectangle wmsExtent = QgsServerProjectUtils::wmsExtent( *project );
const QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( geoEpsgCrsAuthId() );

for ( QgsMapLayer *ml : project->mapLayers() )
{
if ( !ml || restrictedLayers.contains( ml->name() ) ) //unpublished layer
{
continue;
}

#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( accessControl && !accessControl->layerReadPermission( ml ) )
{
continue;
}
#endif

QgsWmsLayerInfos pLayer;
pLayer.id = ml->id();

// Calculate layer extents for the WMS output CRSes list
// First define if the layer has an extent
// Vector layer with No Geometry has no extent the other has one
bool hasExtent = true;
if ( ml->type() == QgsMapLayerType::VectorLayer )
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( ml );
if ( !vLayer || vLayer->wkbType() == QgsWkbTypes::NoGeometry )
{
hasExtent = false;
}
}

// If the layer has an extent and we cannot get CRS bounding boxes, do not keep the layer
if ( hasExtent && !setBoundingRect( project, pLayer, ml, wmsExtent, wgs84, outputCrsList ) )
continue;

// layer type
pLayer.type = ml->type();
// layer wms name
pLayer.name = ml->name();
if ( useLayerIds )
{
pLayer.name = ml->id();
}
else if ( !ml->shortName().isEmpty() )
{
pLayer.name = ml->shortName();
}
// layer title
pLayer.title = ml->title();
if ( pLayer.title.isEmpty() )
{
pLayer.title = ml->name();
}
// layer abstract
pLayer.abstract = ml->abstract();
// layer is queryable
pLayer.queryable = ml->flags().testFlag( QgsMapLayer::Identifiable );
// layer keywords
if ( !ml->keywordList().isEmpty() )
{
pLayer.keywords = ml->keywordList().split( ',' );
}
// layer styles
pLayer.styles = ml->styleManager()->styles();
// layer legend URL
pLayer.legendUrl = ml->legendUrl();
// layer legend URL format
pLayer.legendUrlFormat = ml->legendUrlFormat();
// layer min/max scales
if ( ml->hasScaleBasedVisibility() )
{
pLayer.hasScaleBasedVisibility = ml->hasScaleBasedVisibility();
pLayer.maxScale = ml->maximumScale();
pLayer.minScale = ml->minimumScale();
}
// layer data URL
pLayer.dataUrl = ml->dataUrl();
// layer attribution
pLayer.attribution = ml->attribution();
pLayer.attributionUrl = ml->attributionUrl();
// layer metadata URLs
pLayer.metadataUrls = ml->serverProperties()->metadataUrls();

wmsLayers[pLayer.id] = pLayer;
}

return wmsLayers;
}

0 comments on commit 85d3065

Please sign in to comment.