Merge pull request #62180 from pathmapper/only_maptip_project_setting

[server] Add project setting to enable HTML GetFeatureInfo maptip-only mode
This commit is contained in:
Alessandro Pasotti 2025-07-22 09:38:34 +02:00 committed by GitHub
commit ae2f54a5ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 5047 additions and 15 deletions

View File

@ -4,6 +4,7 @@ QgsProjectServerValidator.LayerShortName = QgsProjectServerValidator.ValidationE
QgsProjectServerValidator.LayerEncoding = QgsProjectServerValidator.ValidationError.LayerEncoding
QgsProjectServerValidator.ProjectShortName = QgsProjectServerValidator.ValidationError.ProjectShortName
QgsProjectServerValidator.ProjectRootNameConflict = QgsProjectServerValidator.ValidationError.ProjectRootNameConflict
QgsProjectServerValidator.OnlyMaptipTrueButEmptyMaptip = QgsProjectServerValidator.ValidationError.OnlyMaptipTrueButEmptyMaptip
try:
QgsProjectServerValidator.ValidationResult.__attribute_docs__ = {'error': 'Error which occurred during the validation process.', 'identifier': 'Identifier related to the error. It can be a layer/group name.'}
QgsProjectServerValidator.ValidationResult.__annotations__ = {'error': 'QgsProjectServerValidator.ValidationError', 'identifier': 'object'}

View File

@ -33,6 +33,7 @@ project.
LayerEncoding,
ProjectShortName,
ProjectRootNameConflict,
OnlyMaptipTrueButEmptyMaptip,
};
static QString displayValidationError( QgsProjectServerValidator::ValidationError error );

View File

@ -23,6 +23,7 @@ try:
QgsServerProjectUtils.wmsInfoFormatSia2045 = staticmethod(QgsServerProjectUtils.wmsInfoFormatSia2045)
QgsServerProjectUtils.wmsFeatureInfoAddWktGeometry = staticmethod(QgsServerProjectUtils.wmsFeatureInfoAddWktGeometry)
QgsServerProjectUtils.wmsFeatureInfoUseAttributeFormSettings = staticmethod(QgsServerProjectUtils.wmsFeatureInfoUseAttributeFormSettings)
QgsServerProjectUtils.wmsHTMLFeatureInfoUseOnlyMaptip = staticmethod(QgsServerProjectUtils.wmsHTMLFeatureInfoUseOnlyMaptip)
QgsServerProjectUtils.wmsFeatureInfoSegmentizeWktGeometry = staticmethod(QgsServerProjectUtils.wmsFeatureInfoSegmentizeWktGeometry)
QgsServerProjectUtils.wmsAddLegendGroupsLegendGraphic = staticmethod(QgsServerProjectUtils.wmsAddLegendGroupsLegendGraphic)
QgsServerProjectUtils.wmsSkipNameForGroup = staticmethod(QgsServerProjectUtils.wmsSkipNameForGroup)

View File

@ -252,6 +252,20 @@ the feature info response
feature info response
%End
static bool wmsHTMLFeatureInfoUseOnlyMaptip( const QgsProject &project );
%Docstring
Returns if only the maptip should be used for HTML feature info response
so that the HTML response to the feature info request only contains the
maptip. If no maptip is set, the HTML response is empty.
:param project: the QGIS project
:return: true if only the maptip should be used for the feature info
response only
.. versionadded:: 4.0
%End
static bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project );
%Docstring
Returns if the geometry has to be segmentize in GetFeatureInfo request.

View File

@ -33,6 +33,7 @@ project.
LayerEncoding,
ProjectShortName,
ProjectRootNameConflict,
OnlyMaptipTrueButEmptyMaptip,
};
static QString displayValidationError( QgsProjectServerValidator::ValidationError error );

View File

@ -23,6 +23,7 @@ try:
QgsServerProjectUtils.wmsInfoFormatSia2045 = staticmethod(QgsServerProjectUtils.wmsInfoFormatSia2045)
QgsServerProjectUtils.wmsFeatureInfoAddWktGeometry = staticmethod(QgsServerProjectUtils.wmsFeatureInfoAddWktGeometry)
QgsServerProjectUtils.wmsFeatureInfoUseAttributeFormSettings = staticmethod(QgsServerProjectUtils.wmsFeatureInfoUseAttributeFormSettings)
QgsServerProjectUtils.wmsHTMLFeatureInfoUseOnlyMaptip = staticmethod(QgsServerProjectUtils.wmsHTMLFeatureInfoUseOnlyMaptip)
QgsServerProjectUtils.wmsFeatureInfoSegmentizeWktGeometry = staticmethod(QgsServerProjectUtils.wmsFeatureInfoSegmentizeWktGeometry)
QgsServerProjectUtils.wmsAddLegendGroupsLegendGraphic = staticmethod(QgsServerProjectUtils.wmsAddLegendGroupsLegendGraphic)
QgsServerProjectUtils.wmsSkipNameForGroup = staticmethod(QgsServerProjectUtils.wmsSkipNameForGroup)

View File

@ -252,6 +252,20 @@ the feature info response
feature info response
%End
static bool wmsHTMLFeatureInfoUseOnlyMaptip( const QgsProject &project );
%Docstring
Returns if only the maptip should be used for HTML feature info response
so that the HTML response to the feature info request only contains the
maptip. If no maptip is set, the HTML response is empty.
:param project: the QGIS project
:return: true if only the maptip should be used for the feature info
response only
.. versionadded:: 4.0
%End
static bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project );
%Docstring
Returns if the geometry has to be segmentize in GetFeatureInfo request.

View File

@ -736,6 +736,9 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
bool useAttributeFormSettings = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSFeatureInfoUseAttributeFormSettings" ), QStringLiteral( "/" ) );
mUseAttributeFormSettingsCheckBox->setChecked( useAttributeFormSettings );
bool useOnlyMaptip = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSHTMLFeatureInfoUseOnlyMaptip" ), QStringLiteral( "/" ) );
mHTMLFiOnlyMaptip->setChecked( useOnlyMaptip );
bool addWktGeometry = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSAddWktGeometry" ), QStringLiteral( "/" ) );
mAddWktGeometryCheckBox->setChecked( addWktGeometry );
@ -1566,6 +1569,7 @@ void QgsProjectProperties::apply()
}
QgsProject::instance()->writeEntry( QStringLiteral( "WMSFeatureInfoUseAttributeFormSettings" ), QStringLiteral( "/" ), mUseAttributeFormSettingsCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSHTMLFeatureInfoUseOnlyMaptip" ), QStringLiteral( "/" ), mHTMLFiOnlyMaptip->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSAddWktGeometry" ), QStringLiteral( "/" ), mAddWktGeometryCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSSegmentizeFeatureInfoGeometry" ), QStringLiteral( "/" ), mSegmentizeFeatureInfoGeometryCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSAddLayerGroupsLegendGraphic" ), QStringLiteral( "/" ), mAddLayerGroupsLegendGraphicCheckBox->isChecked() );

View File

@ -37,6 +37,8 @@ QString QgsProjectServerValidator::displayValidationError( QgsProjectServerValid
return QObject::tr( "The project root name (either the project short name or project title) is not valid. It must start with an unaccented alphabetical letter, followed by any alphanumeric letters, dot, dash or underscore" );
case QgsProjectServerValidator::ProjectRootNameConflict:
return QObject::tr( "The project root name (either the project short name or project title) is already used by a layer or a group" );
case QgsProjectServerValidator::OnlyMaptipTrueButEmptyMaptip:
return QObject::tr( "Use only maptip for HTML GetFeatureInfo response is enabled but the HTML maptip is empty" );
}
return QString();
}
@ -49,7 +51,7 @@ QString getShortName( T *node )
return shortName.isEmpty() ? node->name() : shortName;
}
void QgsProjectServerValidator::browseLayerTree( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages )
void QgsProjectServerValidator::browseLayerTree( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages, QStringList &layerNames, QStringList &maptipTemplates )
{
const QList< QgsLayerTreeNode * > treeGroupChildren = treeGroup->children();
for ( int i = 0; i < treeGroupChildren.size(); ++i )
@ -59,7 +61,7 @@ void QgsProjectServerValidator::browseLayerTree( QgsLayerTreeGroup *treeGroup, Q
{
QgsLayerTreeGroup *treeGroupChild = static_cast<QgsLayerTreeGroup *>( treeNode );
owsNames << getShortName( treeGroupChild );
browseLayerTree( treeGroupChild, owsNames, encodingMessages );
browseLayerTree( treeGroupChild, owsNames, encodingMessages, layerNames, maptipTemplates );
}
else
{
@ -74,11 +76,22 @@ void QgsProjectServerValidator::browseLayerTree( QgsLayerTreeGroup *treeGroup, Q
if ( vl->dataProvider() && vl->dataProvider()->encoding() == QLatin1String( "System" ) )
encodingMessages << layer->name();
}
layerNames << treeLayer->name();
maptipTemplates << layer->mapTipTemplate();
}
}
}
}
bool QgsProjectServerValidator::isOnlyMaptipEnabled( QgsProject *project )
{
return project->readBoolEntry(
QStringLiteral( "WMSHTMLFeatureInfoUseOnlyMaptip" ),
QString(),
false
);
}
bool QgsProjectServerValidator::validate( QgsProject *project, QList<QgsProjectServerValidator::ValidationResult> &results )
{
results.clear();
@ -90,8 +103,8 @@ bool QgsProjectServerValidator::validate( QgsProject *project, QList<QgsProjectS
if ( !project->layerTreeRoot() )
return false;
QStringList owsNames, encodingMessages;
browseLayerTree( project->layerTreeRoot(), owsNames, encodingMessages );
QStringList owsNames, encodingMessages, layerNames, maptipTemplates;
browseLayerTree( project->layerTreeRoot(), owsNames, encodingMessages, layerNames, maptipTemplates );
QStringList duplicateNames, regExpMessages;
const thread_local QRegularExpression snRegExp = QgsApplication::shortNameRegularExpression();
@ -152,6 +165,24 @@ bool QgsProjectServerValidator::validate( QgsProject *project, QList<QgsProjectS
results << ValidationResult( QgsProjectServerValidator::ProjectShortName, rootLayerName );
}
}
if ( isOnlyMaptipEnabled( project ) )
{
QStringList emptyLayers;
for ( int i = 0; i < maptipTemplates.size(); ++i )
{
if ( maptipTemplates[i].trimmed().isEmpty() )
emptyLayers << layerNames[i];
}
if ( !emptyLayers.isEmpty() )
{
result = false;
QString details = emptyLayers.join( QLatin1String( ", " ) ).toHtmlEscaped();
results << ValidationResult(
QgsProjectServerValidator::OnlyMaptipTrueButEmptyMaptip,
details );
}
}
return result;
}

View File

@ -47,6 +47,7 @@ class CORE_EXPORT QgsProjectServerValidator
LayerEncoding = 2, //!< Encoding is not correctly set on a vector layer.
ProjectShortName = 3, //!< The project short name is not valid.
ProjectRootNameConflict = 4, //!< The project root name is already used by a layer or a group.
OnlyMaptipTrueButEmptyMaptip = 5, //!< Use only maptip for HTML GetFeatureInfo response is enabled but HTML maptip is empty
};
/**
@ -92,7 +93,8 @@ class CORE_EXPORT QgsProjectServerValidator
static bool validate( QgsProject *project, QList< QgsProjectServerValidator::ValidationResult > &results SIP_OUT );
private:
static void browseLayerTree( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages );
static void browseLayerTree( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages, QStringList &layerNames, QStringList &maptipTemplates );
static bool isOnlyMaptipEnabled( QgsProject *project );
};

View File

@ -180,6 +180,12 @@ bool QgsServerProjectUtils::wmsFeatureInfoUseAttributeFormSettings( const QgsPro
|| useFormSettings.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
}
bool QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( const QgsProject &project )
{
const QString useFormSettings = project.readEntry( QStringLiteral( "WMSHTMLFeatureInfoUseOnlyMaptip" ), QStringLiteral( "/" ), "" );
return useFormSettings.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
}
bool QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project )
{
const QString segmGeom = project.readEntry( QStringLiteral( "WMSSegmentizeFeatureInfoGeometry" ), QStringLiteral( "/" ), "" );

View File

@ -222,6 +222,16 @@ class SERVER_EXPORT QgsServerProjectUtils
*/
static bool wmsFeatureInfoUseAttributeFormSettings( const QgsProject &project );
/**
* Returns if only the maptip should be used for HTML feature info response so
* that the HTML response to the feature info request only contains the maptip.
* If no maptip is set, the HTML response is empty.
* \param project the QGIS project
* \returns true if only the maptip should be used for the feature info response only
* \since QGIS 4.0
*/
static bool wmsHTMLFeatureInfoUseOnlyMaptip( const QgsProject &project );
/**
* Returns if the geometry has to be segmentize in GetFeatureInfo request.
* \param project the QGIS project

View File

@ -2009,7 +2009,7 @@ namespace QgsWms
//add maptip attribute based on html/expression (in case there is no maptip attribute)
QString mapTip = layer->mapTipTemplate();
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() ) )
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
{
QDomElement maptipElem = infoDocument.createElement( QStringLiteral( "Attribute" ) );
maptipElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maptip" ) );
@ -2325,7 +2325,7 @@ namespace QgsWms
}
//add maptip attribute based on html/expression
QString mapTip = layer->mapTipTemplate();
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() ) )
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
{
QDomElement maptipElem = infoDocument.createElement( QStringLiteral( "Attribute" ) );
maptipElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maptip" ) );
@ -2586,7 +2586,7 @@ namespace QgsWms
QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const
{
const bool onlyMapTip = mWmsParameters.htmlInfoOnlyMapTip();
const bool onlyMapTip = mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject );
QString featureInfoString = QStringLiteral( " <!DOCTYPE html>" );
if ( !onlyMapTip )
{
@ -3203,7 +3203,7 @@ namespace QgsWms
{
QString mapTip = layer->mapTipTemplate();
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() ) )
if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
{
QString fieldTextString = QgsExpression::replaceExpressionText( mapTip, &expressionContext );
QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:maptip" ) );

View File

@ -2730,6 +2730,13 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="mHTMLFiOnlyMaptip">
<property name="text">
<string>Use only maptip for HTML GetFeatureInfo (empty response when maptip template is missing)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="mWmsUseLayerIDs">
<property name="text">
@ -2737,7 +2744,7 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<layout class="QHBoxLayout" name="grpWMSPrecision">
<item>
<widget class="QLabel" name="label_5">
@ -2761,21 +2768,21 @@
</item>
</layout>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QCheckBox" name="mSegmentizeFeatureInfoGeometryCheckBox">
<property name="text">
<string>Segmentize feature info geometry</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QCheckBox" name="mAddWktGeometryCheckBox">
<property name="text">
<string>Add geometry to feature response</string>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="mWMSUrlLabel">
@ -3765,6 +3772,7 @@
<tabstop>mWMSInspireMetadataDate</tabstop>
<tabstop>mWmsUseLayerIDs</tabstop>
<tabstop>mUseAttributeFormSettingsCheckBox</tabstop>
<tabstop>mHTMLFiOnlyMaptip</tabstop>
<tabstop>mAddWktGeometryCheckBox</tabstop>
<tabstop>mSegmentizeFeatureInfoGeometryCheckBox</tabstop>
<tabstop>mWMSPrecisionSpinBox</tabstop>

View File

@ -124,6 +124,60 @@ class TestQgsprojectServerValidator(QgisTestCase):
results[0].error,
)
def test_empty_maptip_enabled(self):
"""Empty maptip must fail when onlymaptip is enabled."""
project = QgsProject()
layer = QgsVectorLayer("Point?field=fldtxt:string", "testlayer", "memory")
project.addMapLayers([layer])
layer.setMapTipTemplate("")
project.writeEntry("WMSHTMLFeatureInfoUseOnlyMaptip", "", True)
valid, results = QgsProjectServerValidator.validate(project)
self.assertFalse(valid)
self.assertEqual(1, len(results))
self.assertEqual(
QgsProjectServerValidator.ValidationError.OnlyMaptipTrueButEmptyMaptip,
results[0].error,
)
# Explicitly enable MapTips — should still fail
layer.setMapTipsEnabled(True)
valid2, results2 = QgsProjectServerValidator.validate(project)
self.assertFalse(valid2)
self.assertEqual(1, len(results2))
self.assertEqual(
QgsProjectServerValidator.ValidationError.OnlyMaptipTrueButEmptyMaptip,
results2[0].error,
)
# Explicitly disable MapTips — should still fail
layer.setMapTipsEnabled(False)
valid3, results3 = QgsProjectServerValidator.validate(project)
self.assertFalse(valid3)
self.assertEqual(1, len(results3))
self.assertEqual(
QgsProjectServerValidator.ValidationError.OnlyMaptipTrueButEmptyMaptip,
results3[0].error,
)
def test_empty_maptip_disabled(self):
"""Empty maptip must pass when onlymaptip is disabled."""
project = QgsProject()
layer = QgsVectorLayer("Point?field=fldtxt:string", "testlayer", "memory")
project.addMapLayers([layer])
layer.setMapTipTemplate("")
project.writeEntry("WMSHTMLFeatureInfoUseOnlyMaptip", "", False)
valid, results = QgsProjectServerValidator.validate(project)
self.assertTrue(valid)
self.assertEqual(0, len(results))
# Explicitly enable MapTips — should still pass
layer.setMapTipsEnabled(True)
valid2, results2 = QgsProjectServerValidator.validate(project)
self.assertTrue(valid2)
self.assertEqual(0, len(results2))
# Explicitly disable MapTips — should still pass
layer.setMapTipsEnabled(False)
valid3, results3 = QgsProjectServerValidator.validate(project)
self.assertTrue(valid3)
self.assertEqual(0, len(results3))
if __name__ == "__main__":
unittest.main()

View File

@ -174,7 +174,7 @@ class TestQgsServerWMSGetFeatureInfo(TestQgsServerWMSTestBase):
"wms_getfeatureinfo-text-html-maptip",
)
# Test getfeatureinfo response html only with maptip for vector layer
# Test getfeatureinfo response html only with maptip for vector layer (URL parameter)
self.wms_request_compare(
"GetFeatureInfo",
"&layers=testlayer%20%C3%A8%C3%A9&styles=&"
@ -186,6 +186,18 @@ class TestQgsServerWMSGetFeatureInfo(TestQgsServerWMSTestBase):
"wms_getfeatureinfo-html-only-with-maptip-vector",
)
# Test getfeatureinfo response html only with maptip for vector layer (project settings)
self.wms_request_compare(
"GetFeatureInfo",
"&layers=testlayer%20%C3%A8%C3%A9&styles=&"
+ "info_format=text%2Fhtml&transparent=true&"
+ "width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C"
+ "5606005.488876367%2C913235.426296057%2C5606035.347090538&"
+ "query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320",
"wms_getfeatureinfo-html-only-with-maptip-vector",
"test_project_html_gfi_maptip_only.qgs",
)
# Test getfeatureinfo response html with maptip and display name in text mode for vector layer
self.wms_request_compare(
"GetFeatureInfo",
@ -352,7 +364,7 @@ class TestQgsServerWMSGetFeatureInfo(TestQgsServerWMSTestBase):
"wms_getfeatureinfo-raster-text-xml-maptip",
)
# Test GetFeatureInfo on raster layer HTML only with maptip
# Test GetFeatureInfo on raster layer HTML only with maptip (URL parameter)
self.wms_request_compare(
"GetFeatureInfo",
"&layers=landsat&styles=&"
@ -364,6 +376,18 @@ class TestQgsServerWMSGetFeatureInfo(TestQgsServerWMSTestBase):
"wms_getfeatureinfo-html-only-with-maptip-raster",
)
# Test GetFeatureInfo on raster layer HTML only with maptip (project settings)
self.wms_request_compare(
"GetFeatureInfo",
"&layers=landsat&styles=&"
+ "info_format=text%2Fhtml&transparent=true&"
+ "width=500&height=500&srs=EPSG%3A3857&"
+ "bbox=1989139.6,3522745.0,2015014.9,3537004.5&"
+ "query_layers=landsat&X=250&Y=250",
"wms_getfeatureinfo-html-only-with-maptip-raster",
"test_project_html_gfi_maptip_only.qgs",
)
def testGetFeatureInfoValueRelation(self):
"""Test GetFeatureInfo resolves "value relation" widget values. regression 18518"""
mypath = self.testdata_path + "test_project_values.qgz"

File diff suppressed because it is too large Load Diff