From fa2735bf7e4201a77f3e7d82271edfa2ca1f973d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Feb 2026 19:30:43 +0000 Subject: [PATCH 1/6] Add contour line label provider for raster layers Introduces QgsRasterContourLabelProvider and QgsRasterLayerContourLabeling, enabling labels placed along contour lines generated on-the-fly from raster DEM data via GDAL's contour generation API. Key changes: - New QgsRasterContourLabelProvider generates contour geometries in map CRS and registers them as line features with the PAL labeling engine - New QgsRasterLayerContourLabeling configuration class with contour interval, index interval, downscale factor, and label-index-only settings - QgsRasterLayerLabelProvider changed from final to extensible (protected members, virtual generateLabels, override instead of final) - Factory dispatch in createFromElement handles "contour" type https://claude.ai/code/session_01TaS7A3X8CXBYqJzq2EMLak --- src/core/CMakeLists.txt | 2 + src/core/raster/qgsrastercontourlabeling.cpp | 477 +++++++++++++++++++ src/core/raster/qgsrastercontourlabeling.h | 158 ++++++ src/core/raster/qgsrasterlabeling.cpp | 5 + src/core/raster/qgsrasterlabeling.h | 16 +- 5 files changed, 651 insertions(+), 7 deletions(-) create mode 100644 src/core/raster/qgsrastercontourlabeling.cpp create mode 100644 src/core/raster/qgsrastercontourlabeling.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c7fdd1ed13fdf..7b75d845769ad 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -802,6 +802,7 @@ set(QGIS_CORE_SRCS raster/qgsraster.cpp raster/qgsrasterblock.cpp raster/qgsrasterchecker.cpp + raster/qgsrastercontourlabeling.cpp raster/qgsrastercontourrenderer.cpp raster/qgsrasterdataprovider.cpp raster/qgsrasterdataproviderelevationproperties.cpp @@ -1964,6 +1965,7 @@ set(QGIS_CORE_HDRS raster/qgsrasterbandstats.h raster/qgsrasterblock.h raster/qgsrasterchecker.h + raster/qgsrastercontourlabeling.h raster/qgsrastercontourrenderer.h raster/qgsrasterdataprovider.h raster/qgsrasterdataproviderelevationproperties.h diff --git a/src/core/raster/qgsrastercontourlabeling.cpp b/src/core/raster/qgsrastercontourlabeling.cpp new file mode 100644 index 0000000000000..14110a26ad60f --- /dev/null +++ b/src/core/raster/qgsrastercontourlabeling.cpp @@ -0,0 +1,477 @@ +/*************************************************************************** + qgsrastercontourlabeling.cpp + --------------- + begin : February 2026 + copyright : (C) 2026 by the QGIS project + email : info at qgis dot org + ***************************************************************************/ +/*************************************************************************** + * * + * 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 "qgsrastercontourlabeling.h" + +#include + +#include "qgsapplication.h" +#include "qgsbasicnumericformat.h" +#include "qgscoordinatetransform.h" +#include "qgsgeos.h" +#include "qgslinestring.h" +#include "qgsmessagelog.h" +#include "qgsnumericformat.h" +#include "qgsnumericformatregistry.h" +#include "qgsrasterdataprovider.h" +#include "qgsrasteriterator.h" +#include "qgsrasterlayer.h" +#include "qgsrasterlayerrenderer.h" +#include "qgsrasterpipe.h" +#include "qgsrasterviewport.h" +#include "qgsscaleutils.h" +#include "qgsstyle.h" +#include "qgsstyleentityvisitor.h" +#include "qgstextlabelfeature.h" + +#include + +using namespace Qt::StringLiterals; + +// +// QgsRasterContourLabelProvider +// + +struct ContourLabelData +{ + QgsRasterContourLabelProvider *provider; + QgsRenderContext *context; + QgsNumericFormat *numericFormat; + QgsNumericFormatContext *numericContext; + QgsRectangle extent; + int inputWidth; + int inputHeight; + QgsCoordinateTransform transform; + double contourIndexInterval; + bool labelIndexOnly; +}; + +static CPLErr _contourLabelWriter( double dfLevel, int nPoints, double *padfX, double *padfY, void *ptr ) +{ + ContourLabelData *data = static_cast( ptr ); + + if ( data->labelIndexOnly && data->contourIndexInterval > 0 ) + { + if ( !qgsDoubleNear( fmod( dfLevel, data->contourIndexInterval ), 0 ) ) + return CE_None; + } + + if ( nPoints < 2 ) + return CE_None; + + QVector xCoords( nPoints ); + QVector yCoords( nPoints ); + for ( int i = 0; i < nPoints; ++i ) + { + // pixel coords → layer CRS + xCoords[i] = data->extent.xMinimum() + ( padfX[i] / data->inputWidth ) * data->extent.width(); + yCoords[i] = data->extent.yMaximum() - ( padfY[i] / data->inputHeight ) * data->extent.height(); + } + + QgsLineString lineString( xCoords, yCoords ); + + if ( !data->transform.isShortCircuited() ) + { + try + { + lineString.transform( data->transform ); + } + catch ( QgsCsException & ) + { + return CE_None; + } + } + + const QString labelText = data->numericFormat->formatDouble( dfLevel, *data->numericContext ); + data->provider->addContourLabel( lineString, labelText, *data->context ); + + return CE_None; +} + + +QgsRasterContourLabelProvider::QgsRasterContourLabelProvider( QgsRasterLayer *layer ) + : QgsRasterLayerLabelProvider( layer ) +{ + mPlacement = Qgis::LabelPlacement::Line; + mFlags |= MergeConnectedLines; +} + +QgsRasterContourLabelProvider::~QgsRasterContourLabelProvider() = default; + +void QgsRasterContourLabelProvider::addContourLabel( const QgsLineString &line, const QString &text, QgsRenderContext &context ) +{ + const QgsMapToPixel &m2p = context.mapToPixel(); + const double uPP = m2p.mapUnitsPerPixel(); + + QgsLineString geom( line ); + + if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) ) + { + QgsPointXY center = context.mapExtent().center(); + QTransform t = QTransform::fromTranslate( center.x(), center.y() ); + t.rotate( -m2p.mapRotation() ); + t.translate( -center.x(), -center.y() ); + geom.transform( t ); + } + + const QgsTextDocument doc = QgsTextDocument::fromTextAndFormat( { text }, mFormat ); + QgsTextDocumentMetrics documentMetrics = QgsTextDocumentMetrics::calculateMetrics( doc, mFormat, context ); + const QSizeF size = documentMetrics.documentSize( Qgis::TextLayoutMode::Labeling, Qgis::TextOrientation::Horizontal ); + + auto feature = std::make_unique( mLabels.size(), + QgsGeos::asGeos( &geom ), + QSizeF( size.width() * uPP, + size.height() * uPP ) ); + + feature->setDocument( doc, documentMetrics ); + feature->setZIndex( mZIndex ); + feature->setOverlapHandling( mPlacementSettings.overlapHandling() ); + + mLabels.append( feature.release() ); +} + +void QgsRasterContourLabelProvider::generateLabels( QgsRenderContext &context, QgsRasterPipe *pipe, QgsRasterViewPort *rasterViewPort, QgsRasterLayerRendererFeedback *feedback ) +{ + if ( !pipe ) + return; + + QgsRasterDataProvider *provider = pipe->provider(); + if ( !provider ) + return; + + if ( provider->xSize() == 0 || provider->ySize() == 0 ) + return; + + if ( !rasterViewPort ) + return; + + QgsCoordinateTransform layerToMapTransform = context.coordinateTransform(); + layerToMapTransform.setBallparkTransformsAreAppropriate( true ); + QgsRectangle layerVisibleExtent; + try + { + layerVisibleExtent = layerToMapTransform.transformBoundingBox( rasterViewPort->mDrawnExtent, Qgis::TransformDirection::Reverse ); + } + catch ( QgsCsException &cs ) + { + QgsMessageLog::logMessage( QObject::tr( "Could not reproject view extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) ); + return; + } + + int subRegionWidth = 0; + int subRegionHeight = 0; + int subRegionLeft = 0; + int subRegionTop = 0; + QgsRectangle rasterSubRegion = QgsRasterIterator::subRegion( + provider->extent(), + provider->xSize(), + provider->ySize(), + layerVisibleExtent, + subRegionWidth, + subRegionHeight, + subRegionLeft, + subRegionTop ); + + const int inputWidth = std::max( 1, static_cast( std::round( subRegionWidth / mDownscale ) ) ); + const int inputHeight = std::max( 1, static_cast( std::round( subRegionHeight / mDownscale ) ) ); + + std::unique_ptr inputBlock( provider->block( mInputBand, rasterSubRegion, inputWidth, inputHeight, feedback ) ); + if ( !inputBlock || inputBlock->isEmpty() ) + return; + + if ( !inputBlock->convert( Qgis::DataType::Float64 ) ) + return; + + double *scanline = reinterpret_cast( inputBlock->bits() ); + + QgsNumericFormatContext numericContext; + numericContext.setExpressionContext( context.expressionContext() ); + + ContourLabelData clData; + clData.provider = this; + clData.context = &context; + clData.numericFormat = numericFormat(); + clData.numericContext = &numericContext; + clData.extent = rasterSubRegion; + clData.inputWidth = inputWidth; + clData.inputHeight = inputHeight; + clData.transform = layerToMapTransform; + clData.contourIndexInterval = mContourIndexInterval; + clData.labelIndexOnly = mLabelIndexOnly; + + const double contourBase = 0.; + GDALContourGeneratorH cg = GDAL_CG_Create( inputWidth, inputHeight, + inputBlock->hasNoDataValue(), inputBlock->noDataValue(), + mContourInterval, contourBase, + _contourLabelWriter, static_cast( &clData ) ); + + for ( int i = 0; i < inputHeight; ++i ) + { + if ( feedback && feedback->isCanceled() ) + break; + GDAL_CG_FeedLine( cg, scanline ); + scanline += inputWidth; + } + + GDAL_CG_Destroy( cg ); +} + + +// +// QgsRasterLayerContourLabeling +// + +QgsRasterLayerContourLabeling::QgsRasterLayerContourLabeling() + : mNumericFormat( std::make_unique() ) +{ + mThinningSettings.setMaximumNumberLabels( 2000 ); + mThinningSettings.setLimitNumberLabelsEnabled( true ); +} + +QgsRasterLayerContourLabeling::~QgsRasterLayerContourLabeling() = default; + +QString QgsRasterLayerContourLabeling::type() const +{ + return u"contour"_s; +} + +QgsRasterLayerContourLabeling *QgsRasterLayerContourLabeling::clone() const +{ + auto res = std::make_unique(); + res->setTextFormat( mTextFormat ); + + if ( mNumericFormat ) + res->mNumericFormat.reset( mNumericFormat->clone() ); + + res->setBand( mBandNumber ); + res->setContourInterval( mContourInterval ); + res->setContourIndexInterval( mContourIndexInterval ); + res->setDownscale( mDownscale ); + res->setLabelIndexOnly( mLabelIndexOnly ); + res->setPriority( mPriority ); + res->setPlacementSettings( mPlacementSettings ); + res->setThinningSettings( mThinningSettings ); + res->setZIndex( mZIndex ); + res->setScaleBasedVisibility( mScaleVisibility ); + res->setMaximumScale( mMaximumScale ); + res->setMinimumScale( mMinimumScale ); + + return res.release(); +} + +std::unique_ptr QgsRasterLayerContourLabeling::provider( QgsRasterLayer *layer ) const +{ + auto res = std::make_unique( layer ); + res->setTextFormat( mTextFormat ); + res->setInputBand( mBandNumber ); + res->setContourInterval( mContourInterval ); + res->setContourIndexInterval( mContourIndexInterval ); + res->setDownscale( mDownscale ); + res->setLabelIndexOnly( mLabelIndexOnly ); + res->setPriority( mPriority ); + res->setPlacementSettings( mPlacementSettings ); + res->setZIndex( mZIndex ); + res->setThinningSettings( mThinningSettings ); + if ( mNumericFormat ) + { + res->setNumericFormat( std::unique_ptr( mNumericFormat->clone() ) ); + } + return res; +} + +QDomElement QgsRasterLayerContourLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const +{ + QDomElement elem = doc.createElement( u"labeling"_s ); + elem.setAttribute( u"type"_s, u"contour"_s ); + elem.setAttribute( u"band"_s, mBandNumber ); + elem.setAttribute( u"contourInterval"_s, mContourInterval ); + elem.setAttribute( u"contourIndexInterval"_s, mContourIndexInterval ); + elem.setAttribute( u"downscale"_s, mDownscale ); + elem.setAttribute( u"labelIndexOnly"_s, mLabelIndexOnly ? 1 : 0 ); + elem.setAttribute( u"priority"_s, mPriority ); + elem.setAttribute( u"zIndex"_s, mZIndex ); + + { + QDomElement textFormatElem = doc.createElement( u"textFormat"_s ); + textFormatElem.appendChild( mTextFormat.writeXml( doc, context ) ); + elem.appendChild( textFormatElem ); + } + + { + QDomElement numericFormatElem = doc.createElement( u"numericFormat"_s ); + mNumericFormat->writeXml( numericFormatElem, doc, context ); + elem.appendChild( numericFormatElem ); + } + + { + QDomElement placementElem = doc.createElement( u"placement"_s ); + placementElem.setAttribute( u"overlapHandling"_s, qgsEnumValueToKey( mPlacementSettings.overlapHandling() ) ); + elem.appendChild( placementElem ); + } + + { + QDomElement thinningElem = doc.createElement( u"thinning"_s ); + thinningElem.setAttribute( u"maxNumLabels"_s, mThinningSettings.maximumNumberLabels() ); + thinningElem.setAttribute( u"limitNumLabels"_s, mThinningSettings.limitNumberOfLabelsEnabled() ); + elem.appendChild( thinningElem ); + } + + { + QDomElement renderingElem = doc.createElement( u"rendering"_s ); + renderingElem.setAttribute( u"scaleVisibility"_s, mScaleVisibility ); + // element names flipped vs member — matches vector labeling convention + renderingElem.setAttribute( u"scaleMin"_s, mMaximumScale ); + renderingElem.setAttribute( u"scaleMax"_s, mMinimumScale ); + elem.appendChild( renderingElem ); + } + + return elem; +} + +bool QgsRasterLayerContourLabeling::accept( QgsStyleEntityVisitorInterface *visitor ) const +{ + QgsStyleTextFormatEntity entity( mTextFormat ); + if ( !visitor->visit( &entity ) ) + return false; + + return true; +} + +bool QgsRasterLayerContourLabeling::requiresAdvancedEffects() const +{ + return mTextFormat.containsAdvancedEffects(); +} + +bool QgsRasterLayerContourLabeling::hasNonDefaultCompositionMode() const +{ + return mTextFormat.hasNonDefaultCompositionMode(); +} + +void QgsRasterLayerContourLabeling::multiplyOpacity( double opacityFactor ) +{ + mTextFormat.multiplyOpacity( opacityFactor ); +} + +bool QgsRasterLayerContourLabeling::isInScaleRange( double scale ) const +{ + return !mScaleVisibility + || ( ( mMinimumScale == 0 || !QgsScaleUtils::lessThanMaximumScale( scale, mMinimumScale ) ) + && ( mMaximumScale == 0 || !QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMaximumScale ) ) ); +} + +QgsRasterLayerContourLabeling *QgsRasterLayerContourLabeling::create( const QDomElement &element, const QgsReadWriteContext &context ) +{ + auto res = std::make_unique(); + res->setBand( element.attribute( u"band"_s, u"1"_s ).toInt() ); + res->setContourInterval( element.attribute( u"contourInterval"_s, u"100"_s ).toDouble() ); + res->setContourIndexInterval( element.attribute( u"contourIndexInterval"_s, u"0"_s ).toDouble() ); + res->setDownscale( element.attribute( u"downscale"_s, u"4"_s ).toDouble() ); + res->setLabelIndexOnly( element.attribute( u"labelIndexOnly"_s, u"0"_s ).toInt() ); + res->setPriority( element.attribute( u"priority"_s, u"0.5"_s ).toDouble() ); + res->setZIndex( element.attribute( u"zIndex"_s, u"0"_s ).toDouble() ); + + const QDomElement textFormatElem = element.firstChildElement( u"textFormat"_s ); + if ( !textFormatElem.isNull() ) + { + const QDomNodeList textFormatNodeList = textFormatElem.elementsByTagName( u"text-style"_s ); + const QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement(); + QgsTextFormat format; + format.readXml( textFormatElem, context ); + res->setTextFormat( format ); + } + + const QDomNodeList numericFormatNodeList = element.elementsByTagName( u"numericFormat"_s ); + if ( !numericFormatNodeList.isEmpty() ) + { + const QDomElement numericFormatElem = numericFormatNodeList.at( 0 ).toElement(); + res->mNumericFormat.reset( QgsApplication::numericFormatRegistry()->createFromXml( numericFormatElem, context ) ); + } + + QDomElement placementElem = element.firstChildElement( u"placement"_s ); + res->mPlacementSettings.setOverlapHandling( qgsEnumKeyToValue( placementElem.attribute( u"overlapHandling"_s ), Qgis::LabelOverlapHandling::PreventOverlap ) ); + + QDomElement thinningElem = element.firstChildElement( u"thinning"_s ); + res->mThinningSettings.setMaximumNumberLabels( thinningElem.attribute( u"maxNumLabels"_s, u"2000"_s ).toInt() ); + res->mThinningSettings.setLimitNumberLabelsEnabled( thinningElem.attribute( u"limitNumLabels"_s, u"1"_s ).toInt() ); + + QDomElement renderingElem = element.firstChildElement( u"rendering"_s ); + res->mMaximumScale = renderingElem.attribute( u"scaleMin"_s, u"0"_s ).toDouble(); + res->mMinimumScale = renderingElem.attribute( u"scaleMax"_s, u"0"_s ).toDouble(); + res->mScaleVisibility = renderingElem.attribute( u"scaleVisibility"_s ).toInt(); + + return res.release(); +} + +QgsTextFormat QgsRasterLayerContourLabeling::textFormat() const +{ + return mTextFormat; +} + +void QgsRasterLayerContourLabeling::setTextFormat( const QgsTextFormat &format ) +{ + mTextFormat = format; +} + +const QgsNumericFormat *QgsRasterLayerContourLabeling::numericFormat() const +{ + return mNumericFormat.get(); +} + +void QgsRasterLayerContourLabeling::setNumericFormat( QgsNumericFormat *format ) +{ + if ( format != mNumericFormat.get() ) + mNumericFormat.reset( format ); +} + +double QgsRasterLayerContourLabeling::zIndex() const +{ + return mZIndex; +} + +void QgsRasterLayerContourLabeling::setZIndex( double index ) +{ + mZIndex = index; +} + +double QgsRasterLayerContourLabeling::maximumScale() const +{ + return mMaximumScale; +} + +void QgsRasterLayerContourLabeling::setMaximumScale( double scale ) +{ + mMaximumScale = scale; +} + +double QgsRasterLayerContourLabeling::minimumScale() const +{ + return mMinimumScale; +} + +void QgsRasterLayerContourLabeling::setMinimumScale( double scale ) +{ + mMinimumScale = scale; +} + +bool QgsRasterLayerContourLabeling::hasScaleBasedVisibility() const +{ + return mScaleVisibility; +} + +void QgsRasterLayerContourLabeling::setScaleBasedVisibility( bool enabled ) +{ + mScaleVisibility = enabled; +} diff --git a/src/core/raster/qgsrastercontourlabeling.h b/src/core/raster/qgsrastercontourlabeling.h new file mode 100644 index 0000000000000..0c594b3da6764 --- /dev/null +++ b/src/core/raster/qgsrastercontourlabeling.h @@ -0,0 +1,158 @@ +/*************************************************************************** + qgsrastercontourlabeling.h + --------------- + begin : February 2026 + copyright : (C) 2026 by the QGIS project + email : info at qgis dot org + ***************************************************************************/ +/*************************************************************************** + * * + * 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 QGSRASTERCONTOURLABELING_H +#define QGSRASTERCONTOURLABELING_H + +#include "qgis_core.h" +#include "qgis_sip.h" +#include "qgsrasterlabeling.h" + +class QgsLineString; +class QgsNumericFormat; + +#ifndef SIP_RUN + +/** + * \ingroup core + * \brief Implements labeling for raster contour lines. + * + * Generates contour line geometries from a raster band using GDAL and registers + * them with the labeling engine for placement along the contour lines. + * + * \note Not available in Python bindings + * + * \since QGIS 3.44 + */ +class CORE_EXPORT QgsRasterContourLabelProvider : public QgsRasterLayerLabelProvider +{ + public: + explicit QgsRasterContourLabelProvider( QgsRasterLayer *layer ); + ~QgsRasterContourLabelProvider() override; + + void generateLabels( QgsRenderContext &context, QgsRasterPipe *pipe, QgsRasterViewPort *rasterViewPort, QgsRasterLayerRendererFeedback *feedback ) override; + + void setContourInterval( double interval ) { mContourInterval = interval; } + void setContourIndexInterval( double interval ) { mContourIndexInterval = interval; } + void setInputBand( int band ) { mInputBand = band; } + void setDownscale( double downscale ) { mDownscale = downscale; } + void setLabelIndexOnly( bool indexOnly ) { mLabelIndexOnly = indexOnly; } + + void addContourLabel( const QgsLineString &line, const QString &text, QgsRenderContext &context ); + + private: + double mContourInterval = 100.0; + double mContourIndexInterval = 0.0; + int mInputBand = 1; + double mDownscale = 4.0; + bool mLabelIndexOnly = false; +}; + +#endif + +/** + * \ingroup core + * \brief Labeling configuration for raster contour lines. + * + * Produces labels placed along contour lines generated on-the-fly from raster data. + * + * \since QGIS 3.44 + */ +class CORE_EXPORT QgsRasterLayerContourLabeling : public QgsAbstractRasterLayerLabeling +{ + public: + + QgsRasterLayerContourLabeling(); + ~QgsRasterLayerContourLabeling() override; + + QString type() const override; + QgsRasterLayerContourLabeling *clone() const override SIP_FACTORY; + std::unique_ptr< QgsRasterLayerLabelProvider > provider( QgsRasterLayer *layer ) const override SIP_SKIP; + QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const override; + bool accept( QgsStyleEntityVisitorInterface *visitor ) const override; + bool requiresAdvancedEffects() const override; + bool hasNonDefaultCompositionMode() const override; + void multiplyOpacity( double opacityFactor ) override; + bool isInScaleRange( double scale ) const override; + + //! Creates a QgsRasterLayerContourLabeling from a DOM element with saved configuration + static QgsRasterLayerContourLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY; + + QgsTextFormat textFormat() const; + void setTextFormat( const QgsTextFormat &format ); + + const QgsNumericFormat *numericFormat() const; + void setNumericFormat( QgsNumericFormat *format SIP_TRANSFER ); + + int band() const { return mBandNumber; } + void setBand( int band ) { mBandNumber = band; } + + double contourInterval() const { return mContourInterval; } + void setContourInterval( double interval ) { mContourInterval = interval; } + + double contourIndexInterval() const { return mContourIndexInterval; } + void setContourIndexInterval( double interval ) { mContourIndexInterval = interval; } + + double downscale() const { return mDownscale; } + void setDownscale( double downscale ) { mDownscale = downscale; } + + bool labelIndexOnly() const { return mLabelIndexOnly; } + void setLabelIndexOnly( bool indexOnly ) { mLabelIndexOnly = indexOnly; } + + double priority() const { return mPriority; } + void setPriority( double priority ) { mPriority = priority; } + + const QgsLabelPlacementSettings &placementSettings() const { return mPlacementSettings; } SIP_SKIP + QgsLabelPlacementSettings &placementSettings() { return mPlacementSettings; } + void setPlacementSettings( const QgsLabelPlacementSettings &settings ) { mPlacementSettings = settings; } + + const QgsLabelThinningSettings &thinningSettings() const { return mThinningSettings; } SIP_SKIP + QgsLabelThinningSettings &thinningSettings() { return mThinningSettings; } + void setThinningSettings( const QgsLabelThinningSettings &settings ) { mThinningSettings = settings; } + + double zIndex() const; + void setZIndex( double index ); + + double maximumScale() const; + void setMaximumScale( double scale ); + + double minimumScale() const; + void setMinimumScale( double scale ); + + void setScaleBasedVisibility( bool enabled ); + bool hasScaleBasedVisibility() const; + + private: + int mBandNumber = 1; + double mContourInterval = 100.0; + double mContourIndexInterval = 0.0; + double mDownscale = 4.0; + bool mLabelIndexOnly = false; + + QgsTextFormat mTextFormat; + std::unique_ptr< QgsNumericFormat > mNumericFormat; + + double mPriority = 0.5; + QgsLabelPlacementSettings mPlacementSettings; + QgsLabelThinningSettings mThinningSettings; + double mZIndex = 0; + + bool mScaleVisibility = false; + double mMaximumScale = 0; + double mMinimumScale = 0; +}; + +#endif // QGSRASTERCONTOURLABELING_H diff --git a/src/core/raster/qgsrasterlabeling.cpp b/src/core/raster/qgsrasterlabeling.cpp index 23f416bad3594..06d69d4f84bca 100644 --- a/src/core/raster/qgsrasterlabeling.cpp +++ b/src/core/raster/qgsrasterlabeling.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "qgsrasterlabeling.h" +#include "qgsrastercontourlabeling.h" #include "feature.h" #include "labelposition.h" @@ -333,6 +334,10 @@ QgsAbstractRasterLayerLabeling *QgsAbstractRasterLayerLabeling::createFromElemen { return QgsRasterLayerSimpleLabeling::create( element, context ); } + else if ( type == "contour"_L1 ) + { + return QgsRasterLayerContourLabeling::create( element, context ); + } else { return nullptr; diff --git a/src/core/raster/qgsrasterlabeling.h b/src/core/raster/qgsrasterlabeling.h index b7d969d9b8c62..e6949f9b88ff7 100644 --- a/src/core/raster/qgsrasterlabeling.h +++ b/src/core/raster/qgsrasterlabeling.h @@ -42,7 +42,7 @@ class QgsRasterLayerRendererFeedback; * * \since QGIS 3.42 */ -class CORE_EXPORT QgsRasterLayerLabelProvider final : public QgsAbstractLabelProvider +class CORE_EXPORT QgsRasterLayerLabelProvider : public QgsAbstractLabelProvider { public: @@ -51,15 +51,15 @@ class CORE_EXPORT QgsRasterLayerLabelProvider final : public QgsAbstractLabelPro */ explicit QgsRasterLayerLabelProvider( QgsRasterLayer *layer ); - ~QgsRasterLayerLabelProvider() final; - QList labelFeatures( QgsRenderContext & ) final; - void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const final; - void startRender( QgsRenderContext &context ) final; + ~QgsRasterLayerLabelProvider() override; + QList labelFeatures( QgsRenderContext & ) override; + void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const override; + void startRender( QgsRenderContext &context ) override; /** * Generates the labels, given a render context and input pipe. */ - void generateLabels( QgsRenderContext &context, QgsRasterPipe *pipe, QgsRasterViewPort *rasterViewPort, QgsRasterLayerRendererFeedback *feedback ); + virtual void generateLabels( QgsRenderContext &context, QgsRasterPipe *pipe, QgsRasterViewPort *rasterViewPort, QgsRasterLayerRendererFeedback *feedback ); /** * Adds a label at the specified point in map coordinates. @@ -139,7 +139,7 @@ class CORE_EXPORT QgsRasterLayerLabelProvider final : public QgsAbstractLabelPro */ void setResampleOver( int pixels ); - private: + protected: QgsTextFormat mFormat; int mBandNumber = 1; std::unique_ptr< QgsNumericFormat > mNumericFormat; @@ -173,6 +173,8 @@ class CORE_EXPORT QgsAbstractRasterLayerLabeling SIP_ABSTRACT SIP_CONVERT_TO_SUBCLASS_CODE if ( sipCpp->type() == "simple" ) sipType = sipType_QgsRasterLayerSimpleLabeling; + else if ( sipCpp->type() == "contour" ) + sipType = sipType_QgsRasterLayerContourLabeling; else sipType = 0; SIP_END From 649d422fab81af32819d79a88a06b9416cbd3f75 Mon Sep 17 00:00:00 2001 From: McGuiness Date: Sat, 28 Feb 2026 19:23:47 +1000 Subject: [PATCH 2/6] Add SIP bindings and tests for raster contour labeling Generate SIP bindings for QgsRasterLayerContourLabeling and update the base class ConvertToSubClassCode for the "contour" type. Add Python tests covering properties, clone, XML round-trip, scale visibility, and layer serialization. Co-Authored-By: Claude Opus 4.6 --- .../raster/qgsrastercontourlabeling.sip.in | 105 ++++++++++++ .../raster/qgsrasterlabeling.sip.in | 2 + tests/src/python/test_qgsrasterlabeling.py | 161 ++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in diff --git a/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in b/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in new file mode 100644 index 0000000000000..f5528feab7ea0 --- /dev/null +++ b/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in @@ -0,0 +1,105 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrastercontourlabeling.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + + + +class QgsRasterLayerContourLabeling : QgsAbstractRasterLayerLabeling +{ +%Docstring(signature="appended") +Labeling configuration for raster contour lines. + +Produces labels placed along contour lines generated on-the-fly from +raster data. + +.. versionadded:: 3.44 +%End + +%TypeHeaderCode +#include "qgsrastercontourlabeling.h" +%End + public: + + QgsRasterLayerContourLabeling(); + ~QgsRasterLayerContourLabeling(); + + virtual QString type() const; + + virtual QgsRasterLayerContourLabeling *clone() const /Factory/; + + virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const; + + virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const; + + virtual bool requiresAdvancedEffects() const; + + virtual bool hasNonDefaultCompositionMode() const; + + virtual void multiplyOpacity( double opacityFactor ); + + virtual bool isInScaleRange( double scale ) const; + + + static QgsRasterLayerContourLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) /Factory/; +%Docstring +Creates a QgsRasterLayerContourLabeling from a DOM element with saved +configuration +%End + + QgsTextFormat textFormat() const; + void setTextFormat( const QgsTextFormat &format ); + + const QgsNumericFormat *numericFormat() const; + void setNumericFormat( QgsNumericFormat *format /Transfer/ ); + + int band() const; + void setBand( int band ); + + double contourInterval() const; + void setContourInterval( double interval ); + + double contourIndexInterval() const; + void setContourIndexInterval( double interval ); + + double downscale() const; + void setDownscale( double downscale ); + + bool labelIndexOnly() const; + void setLabelIndexOnly( bool indexOnly ); + + double priority() const; + void setPriority( double priority ); + + QgsLabelPlacementSettings &placementSettings(); + void setPlacementSettings( const QgsLabelPlacementSettings &settings ); + + QgsLabelThinningSettings &thinningSettings(); + void setThinningSettings( const QgsLabelThinningSettings &settings ); + + double zIndex() const; + void setZIndex( double index ); + + double maximumScale() const; + void setMaximumScale( double scale ); + + double minimumScale() const; + void setMinimumScale( double scale ); + + void setScaleBasedVisibility( bool enabled ); + bool hasScaleBasedVisibility() const; + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrastercontourlabeling.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/PyQt6/core/auto_generated/raster/qgsrasterlabeling.sip.in b/python/PyQt6/core/auto_generated/raster/qgsrasterlabeling.sip.in index dcdbfdde775fe..469c6b0410d52 100644 --- a/python/PyQt6/core/auto_generated/raster/qgsrasterlabeling.sip.in +++ b/python/PyQt6/core/auto_generated/raster/qgsrasterlabeling.sip.in @@ -30,6 +30,8 @@ Abstract base class for labeling settings for raster layers. %ConvertToSubClassCode if ( sipCpp->type() == "simple" ) sipType = sipType_QgsRasterLayerSimpleLabeling; + else if ( sipCpp->type() == "contour" ) + sipType = sipType_QgsRasterLayerContourLabeling; else sipType = 0; %End diff --git a/tests/src/python/test_qgsrasterlabeling.py b/tests/src/python/test_qgsrasterlabeling.py index ca3af06fba2a6..53404268bdc8e 100644 --- a/tests/src/python/test_qgsrasterlabeling.py +++ b/tests/src/python/test_qgsrasterlabeling.py @@ -20,6 +20,7 @@ QgsPercentageNumericFormat, QgsProperty, QgsRasterLayer, + QgsRasterLayerContourLabeling, QgsRasterLayerSimpleLabeling, QgsReadWriteContext, QgsRectangle, @@ -418,5 +419,165 @@ def test_render_resampled(self): ) + def test_contour_labeling(self): + labeling = QgsRasterLayerContourLabeling() + self.assertEqual(labeling.type(), "contour") + + labeling.setBand(2) + text_format = QgsTextFormat() + text_format.setSize(14) + labeling.setTextFormat(text_format) + labeling.setNumericFormat(QgsCurrencyNumericFormat()) + labeling.setContourInterval(25) + labeling.setContourIndexInterval(100) + labeling.setDownscale(8) + labeling.setLabelIndexOnly(True) + labeling.setPriority(0.3) + labeling.placementSettings().setOverlapHandling( + Qgis.LabelOverlapHandling.AllowOverlapIfRequired + ) + labeling.thinningSettings().setLimitNumberLabelsEnabled(True) + labeling.thinningSettings().setMaximumNumberLabels(500) + labeling.setZIndex(5) + labeling.setMaximumScale(5000) + labeling.setMinimumScale(50000) + labeling.setScaleBasedVisibility(True) + + self.assertEqual(labeling.band(), 2) + self.assertEqual(labeling.textFormat().size(), 14) + self.assertIsInstance(labeling.numericFormat(), QgsCurrencyNumericFormat) + self.assertEqual(labeling.contourInterval(), 25) + self.assertEqual(labeling.contourIndexInterval(), 100) + self.assertEqual(labeling.downscale(), 8) + self.assertTrue(labeling.labelIndexOnly()) + self.assertEqual(labeling.priority(), 0.3) + self.assertEqual( + labeling.placementSettings().overlapHandling(), + Qgis.LabelOverlapHandling.AllowOverlapIfRequired, + ) + self.assertTrue(labeling.thinningSettings().limitNumberOfLabelsEnabled()) + self.assertEqual(labeling.thinningSettings().maximumNumberLabels(), 500) + self.assertEqual(labeling.zIndex(), 5) + self.assertEqual(labeling.maximumScale(), 5000) + self.assertEqual(labeling.minimumScale(), 50000) + self.assertTrue(labeling.hasScaleBasedVisibility()) + + labeling_clone = labeling.clone() + self.assertIsInstance(labeling_clone, QgsRasterLayerContourLabeling) + self.assertEqual(labeling_clone.band(), 2) + self.assertEqual(labeling_clone.textFormat().size(), 14) + self.assertIsInstance(labeling_clone.numericFormat(), QgsCurrencyNumericFormat) + self.assertEqual(labeling_clone.contourInterval(), 25) + self.assertEqual(labeling_clone.contourIndexInterval(), 100) + self.assertEqual(labeling_clone.downscale(), 8) + self.assertTrue(labeling_clone.labelIndexOnly()) + self.assertEqual(labeling_clone.priority(), 0.3) + self.assertEqual( + labeling_clone.placementSettings().overlapHandling(), + Qgis.LabelOverlapHandling.AllowOverlapIfRequired, + ) + self.assertTrue(labeling_clone.thinningSettings().limitNumberOfLabelsEnabled()) + self.assertEqual(labeling_clone.thinningSettings().maximumNumberLabels(), 500) + self.assertEqual(labeling_clone.zIndex(), 5) + self.assertEqual(labeling_clone.maximumScale(), 5000) + self.assertEqual(labeling_clone.minimumScale(), 50000) + self.assertTrue(labeling_clone.hasScaleBasedVisibility()) + + doc = QDomDocument() + context = QgsReadWriteContext() + element = labeling.save(doc, context) + + labeling_from_xml = QgsAbstractRasterLayerLabeling.createFromElement( + element, context + ) + self.assertIsInstance(labeling_from_xml, QgsRasterLayerContourLabeling) + self.assertEqual(labeling_from_xml.band(), 2) + self.assertEqual(labeling_from_xml.textFormat().size(), 14) + self.assertIsInstance( + labeling_from_xml.numericFormat(), QgsCurrencyNumericFormat + ) + self.assertEqual(labeling_from_xml.contourInterval(), 25) + self.assertEqual(labeling_from_xml.contourIndexInterval(), 100) + self.assertEqual(labeling_from_xml.downscale(), 8) + self.assertTrue(labeling_from_xml.labelIndexOnly()) + self.assertEqual(labeling_from_xml.priority(), 0.3) + self.assertEqual( + labeling_from_xml.placementSettings().overlapHandling(), + Qgis.LabelOverlapHandling.AllowOverlapIfRequired, + ) + self.assertTrue( + labeling_from_xml.thinningSettings().limitNumberOfLabelsEnabled() + ) + self.assertEqual( + labeling_from_xml.thinningSettings().maximumNumberLabels(), 500 + ) + self.assertEqual(labeling_from_xml.zIndex(), 5) + self.assertEqual(labeling_from_xml.maximumScale(), 5000) + self.assertEqual(labeling_from_xml.minimumScale(), 50000) + self.assertTrue(labeling_from_xml.hasScaleBasedVisibility()) + + def test_contour_labeling_defaults(self): + labeling = QgsRasterLayerContourLabeling() + self.assertEqual(labeling.band(), 1) + self.assertEqual(labeling.contourInterval(), 100) + self.assertEqual(labeling.contourIndexInterval(), 0) + self.assertEqual(labeling.downscale(), 4) + self.assertFalse(labeling.labelIndexOnly()) + self.assertEqual(labeling.priority(), 0.5) + self.assertEqual(labeling.zIndex(), 0) + self.assertFalse(labeling.hasScaleBasedVisibility()) + self.assertIsNotNone(labeling.numericFormat()) + self.assertIsInstance(labeling.numericFormat(), QgsBasicNumericFormat) + self.assertTrue(labeling.thinningSettings().limitNumberOfLabelsEnabled()) + self.assertEqual(labeling.thinningSettings().maximumNumberLabels(), 2000) + + def test_contour_labeling_scale_range(self): + labeling = QgsRasterLayerContourLabeling() + self.assertTrue(labeling.isInScaleRange(1000)) + self.assertTrue(labeling.isInScaleRange(100000)) + + labeling.setScaleBasedVisibility(True) + labeling.setMaximumScale(10000) + labeling.setMinimumScale(50000) + self.assertFalse(labeling.isInScaleRange(5000)) + self.assertTrue(labeling.isInScaleRange(25000)) + self.assertFalse(labeling.isInScaleRange(100000)) + + def test_contour_labeling_layer_roundtrip(self): + raster_layer = QgsRasterLayer( + self.get_test_data_path("raster/dem.tif").as_posix() + ) + self.assertTrue(raster_layer.isValid()) + + labeling = QgsRasterLayerContourLabeling() + labeling.setContourInterval(50) + labeling.setBand(1) + raster_layer.setLabeling(labeling) + raster_layer.setLabelsEnabled(True) + + doc = QDomDocument() + context = QgsReadWriteContext() + element = doc.createElement("maplayer") + raster_layer.writeLayerXml(element, doc, context) + + raster_layer2 = QgsRasterLayer( + self.get_test_data_path("raster/dem.tif").as_posix() + ) + raster_layer2.readLayerXml(element, context) + + self.assertIsInstance(raster_layer2.labeling(), QgsRasterLayerContourLabeling) + self.assertTrue(raster_layer2.labelsEnabled()) + self.assertEqual(raster_layer2.labeling().contourInterval(), 50) + + def testHasNonDefaultCompositionModeContour(self): + labeling = QgsRasterLayerContourLabeling() + self.assertFalse(labeling.hasNonDefaultCompositionMode()) + + t = QgsTextFormat() + t.setBlendMode(QPainter.CompositionMode.CompositionMode_DestinationAtop) + labeling.setTextFormat(t) + self.assertTrue(labeling.hasNonDefaultCompositionMode()) + + if __name__ == "__main__": unittest.main() From 663028e7a94cab4cf0f8f37228d4d5c6f927c3bc Mon Sep 17 00:00:00 2001 From: McGuiness Date: Sat, 28 Feb 2026 19:52:52 +1000 Subject: [PATCH 3/6] Add GUI widget for raster contour line labeling Adds QgsRasterContourLabelSettingsWidget with controls for band, contour interval, index interval, downscale, number format, and index-only labeling. Integrates as a third option ("Label with Contour Lines") in the raster layer Labels tab. Co-Authored-By: Claude Opus 4.6 --- src/gui/CMakeLists.txt | 2 + .../qgsrastercontourlabelsettingswidget.cpp | 283 ++++++++++++++++++ .../qgsrastercontourlabelsettingswidget.h | 74 +++++ src/gui/raster/qgsrasterlabelingwidget.cpp | 50 ++++ 4 files changed, 409 insertions(+) create mode 100644 src/gui/raster/qgsrastercontourlabelsettingswidget.cpp create mode 100644 src/gui/raster/qgsrastercontourlabelsettingswidget.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b2631681c03e4..7c6e0932f3e90 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -19,6 +19,7 @@ set(QGIS_GUI_SRCS raster/qgsrastercontourrendererwidget.cpp raster/qgsrasterhistogramwidget.cpp raster/qgsrasterlabelingwidget.cpp + raster/qgsrastercontourlabelsettingswidget.cpp raster/qgsrasterlabelsettingswidget.cpp raster/qgsrasterlayerproperties.cpp raster/qgsrasterlayertemporalpropertieswidget.cpp @@ -1564,6 +1565,7 @@ set(QGIS_GUI_HDRS raster/qgsrastercontourrendererwidget.h raster/qgsrasterhistogramwidget.h raster/qgsrasterlabelingwidget.h + raster/qgsrastercontourlabelsettingswidget.h raster/qgsrasterlabelsettingswidget.h raster/qgsrasterminmaxwidget.h raster/qgsrasterrendererwidget.h diff --git a/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp b/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp new file mode 100644 index 0000000000000..971aae579e48b --- /dev/null +++ b/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp @@ -0,0 +1,283 @@ +/*************************************************************************** + qgsrastercontourlabelsettingswidget.cpp + --------------------------- + begin : February 2026 + copyright : (C) 2026 by the QGIS project + email : info at qgis dot org + *************************************************************************** + * * + * 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 "qgsrastercontourlabelsettingswidget.h" + +#include "qgsbasicnumericformat.h" +#include "qgsdoublespinbox.h" +#include "qgsnumericformatselectorwidget.h" +#include "qgsrasterbandcombobox.h" +#include "qgsrastercontourlabeling.h" + +#include +#include +#include + +#include "moc_qgsrastercontourlabelsettingswidget.cpp" + +QgsRasterContourLabelSettingsWidget::QgsRasterContourLabelSettingsWidget( QgsRasterLayer *layer, QgsMapCanvas *mapCanvas, QWidget *parent ) + : QgsLabelingGui( mapCanvas, parent, layer ) + , mNumberFormat( std::make_unique() ) +{ + mGeomType = Qgis::GeometryType::Line; + mMode = Labels; + + init(); + + QWidget *labelWithWidget = new QWidget(); + QGridLayout *gLayout = new QGridLayout(); + gLayout->setContentsMargins( 0, 0, 0, 0 ); + + // Row 0: Band + gLayout->addWidget( new QLabel( tr( "Band" ) ), 0, 0 ); + mBandCombo = new QgsRasterBandComboBox(); + mBandCombo->setLayer( layer ); + gLayout->addWidget( mBandCombo, 0, 1 ); + + // Row 1: Contour interval + gLayout->addWidget( new QLabel( tr( "Contour interval" ) ), 1, 0 ); + mContourIntervalSpin = new QgsDoubleSpinBox(); + mContourIntervalSpin->setMinimum( 0.001 ); + mContourIntervalSpin->setMaximum( 999999999.0 ); + mContourIntervalSpin->setDecimals( 3 ); + mContourIntervalSpin->setValue( 100.0 ); + mContourIntervalSpin->setClearValue( 100.0 ); + connect( mContourIntervalSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); + gLayout->addWidget( mContourIntervalSpin, 1, 1 ); + + // Row 2: Index interval + gLayout->addWidget( new QLabel( tr( "Index interval" ) ), 2, 0 ); + mContourIndexIntervalSpin = new QgsDoubleSpinBox(); + mContourIndexIntervalSpin->setMinimum( 0.0 ); + mContourIndexIntervalSpin->setMaximum( 999999999.0 ); + mContourIndexIntervalSpin->setDecimals( 3 ); + mContourIndexIntervalSpin->setValue( 0.0 ); + mContourIndexIntervalSpin->setClearValue( 0.0 ); + mContourIndexIntervalSpin->setSpecialValueText( tr( "None" ) ); + connect( mContourIndexIntervalSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); + gLayout->addWidget( mContourIndexIntervalSpin, 2, 1 ); + + // Row 3: Downscale + gLayout->addWidget( new QLabel( tr( "Downscale" ) ), 3, 0 ); + mDownscaleSpin = new QgsDoubleSpinBox(); + mDownscaleSpin->setMinimum( 1.0 ); + mDownscaleSpin->setMaximum( 16.0 ); + mDownscaleSpin->setDecimals( 1 ); + mDownscaleSpin->setValue( 4.0 ); + mDownscaleSpin->setClearValue( 4.0 ); + connect( mDownscaleSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); + gLayout->addWidget( mDownscaleSpin, 3, 1 ); + + // Row 4: Number format + gLayout->addWidget( new QLabel( tr( "Number format" ) ), 4, 0 ); + QPushButton *numberFormatButton = new QPushButton( tr( "Customize" ) ); + connect( numberFormatButton, &QPushButton::clicked, this, &QgsRasterContourLabelSettingsWidget::changeNumberFormat ); + gLayout->addWidget( numberFormatButton, 4, 1 ); + + // Row 5: Label index contours only + mLabelIndexOnlyCheck = new QCheckBox( tr( "Label index contours only" ) ); + connect( mLabelIndexOnlyCheck, &QCheckBox::toggled, this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); + gLayout->addWidget( mLabelIndexOnlyCheck, 5, 0, 1, 2 ); + + gLayout->setColumnStretch( 0, 1 ); + gLayout->setColumnStretch( 1, 2 ); + + labelWithWidget->setLayout( gLayout ); + + mStackedWidgetLabelWith->addWidget( labelWithWidget ); + mStackedWidgetLabelWith->setCurrentWidget( labelWithWidget ); + + setLayer( layer ); + + const int prevIndex = mOptionsTab->currentIndex(); + + setPropertyOverrideButtonsVisible( true ); + mTextFormatsListWidget->setEntityTypes( QList() << QgsStyle::TextFormatEntity ); + + delete mCalloutItem; + mCalloutItem = nullptr; + delete mMaskItem; + mMaskItem = nullptr; + + mOptionsTab->removeTab( mOptionsTab->indexOf( calloutsTab ) ); + mOptionsTab->removeTab( mOptionsTab->indexOf( maskTab ) ); + + mLabelStackedWidget->removeWidget( mLabelPage_Callouts ); + mLabelStackedWidget->removeWidget( mLabelPage_Mask ); + + switch ( prevIndex ) + { + case 0: + case 1: + case 2: + break; + + case 4: // background - account for removed mask tab + case 5: // shadow + mLabelStackedWidget->setCurrentIndex( prevIndex - 1 ); + mOptionsTab->setCurrentIndex( prevIndex - 1 ); + break; + + case 7: // background - account for removed mask & callouts tab + case 8: // shadow- account for removed mask & callouts tab + mLabelStackedWidget->setCurrentIndex( prevIndex - 2 ); + mOptionsTab->setCurrentIndex( prevIndex - 2 ); + break; + + case 3: // mask + case 6: // callouts + mLabelStackedWidget->setCurrentIndex( 0 ); + mOptionsTab->setCurrentIndex( 0 ); + break; + + default: + break; + } + + // hide settings which have no relevance to raster contour labeling + mDirectSymbolsFrame->hide(); + mFormatNumFrame->hide(); + mFormatNumChkBx->hide(); + mFormatNumDDBtn->hide(); + mCheckBoxSubstituteText->hide(); + mToolButtonConfigureSubstitutes->hide(); + mLabelWrapOnCharacter->hide(); + wrapCharacterEdit->hide(); + mWrapCharDDBtn->hide(); + mLabelWrapLinesTo->hide(); + mAutoWrapLengthSpinBox->hide(); + mAutoWrapLengthDDBtn->hide(); + mAutoWrapTypeComboBox->hide(); + mFontMultiLineLabel->hide(); + mFontMultiLineAlignComboBox->hide(); + mFontMultiLineAlignDDBtn->hide(); + mGeometryGeneratorGroupBox->hide(); + mObstaclesGroupBox->hide(); + mPlacementDDGroupBox->hide(); + mPlacementGroupBox->hide(); + mInferiorPlacementWidget->hide(); + mLabelRenderingDDFrame->hide(); + mUpsidedownFrame->hide(); + mMultipartBehaviorWidget->hide(); + mFramePixelSizeVisibility->hide(); + line->hide(); + + mMinSizeFrame->show(); + mMinSizeLabel->setText( tr( "Suppress labeling of contours shorter than" ) ); + + mLimitLabelChkBox->setText( tr( "Limit number of contours to be labeled to" ) ); + + // fix precision for priority slider + mPrioritySlider->setRange( 0, 100 ); + mPrioritySlider->setTickInterval( 10 ); + + connect( mBandCombo, &QgsRasterBandComboBox::bandChanged, this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); +} + +QgsRasterContourLabelSettingsWidget::~QgsRasterContourLabelSettingsWidget() = default; + +void QgsRasterContourLabelSettingsWidget::setLabeling( QgsAbstractRasterLayerLabeling *labeling ) +{ + if ( QgsRasterLayerContourLabeling *contourLabeling = dynamic_cast( labeling ) ) + { + setFormat( contourLabeling->textFormat() ); + mBandCombo->setBand( contourLabeling->band() ); + mContourIntervalSpin->setValue( contourLabeling->contourInterval() ); + mContourIndexIntervalSpin->setValue( contourLabeling->contourIndexInterval() ); + mDownscaleSpin->setValue( contourLabeling->downscale() ); + mLabelIndexOnlyCheck->setChecked( contourLabeling->labelIndexOnly() ); + mPrioritySlider->setValue( static_cast( 100 - contourLabeling->priority() * 100 ) ); + + mComboOverlapHandling->setCurrentIndex( mComboOverlapHandling->findData( static_cast( contourLabeling->placementSettings().overlapHandling() ) ) ); + mZIndexSpinBox->setValue( contourLabeling->zIndex() ); + if ( const QgsNumericFormat *format = contourLabeling->numericFormat() ) + mNumberFormat.reset( format->clone() ); + + mLimitLabelChkBox->setChecked( contourLabeling->thinningSettings().limitNumberOfLabelsEnabled() ); + mLimitLabelSpinBox->setValue( contourLabeling->thinningSettings().maximumNumberLabels() ); + mMinSizeSpinBox->setValue( contourLabeling->thinningSettings().minimumFeatureSize() ); + + mScaleBasedVisibilityChkBx->setChecked( contourLabeling->hasScaleBasedVisibility() ); + mMinScaleWidget->setScale( contourLabeling->minimumScale() ); + mMaxScaleWidget->setScale( contourLabeling->maximumScale() ); + + updateUi(); + } +} + +void QgsRasterContourLabelSettingsWidget::updateLabeling( QgsAbstractRasterLayerLabeling *labeling ) +{ + if ( QgsRasterLayerContourLabeling *contourLabeling = dynamic_cast( labeling ) ) + { + contourLabeling->setTextFormat( format() ); + contourLabeling->setBand( mBandCombo->currentBand() ); + contourLabeling->setContourInterval( mContourIntervalSpin->value() ); + contourLabeling->setContourIndexInterval( mContourIndexIntervalSpin->value() ); + contourLabeling->setDownscale( mDownscaleSpin->value() ); + contourLabeling->setLabelIndexOnly( mLabelIndexOnlyCheck->isChecked() ); + contourLabeling->setPriority( 1.0 - mPrioritySlider->value() / 100.0 ); + contourLabeling->placementSettings().setOverlapHandling( static_cast( mComboOverlapHandling->currentData().toInt() ) ); + contourLabeling->setZIndex( mZIndexSpinBox->value() ); + contourLabeling->setNumericFormat( mNumberFormat->clone() ); + + contourLabeling->thinningSettings().setLimitNumberLabelsEnabled( mLimitLabelChkBox->isChecked() ); + contourLabeling->thinningSettings().setMaximumNumberLabels( mLimitLabelSpinBox->value() ); + contourLabeling->thinningSettings().setMinimumFeatureSize( mMinSizeSpinBox->value() ); + + contourLabeling->setScaleBasedVisibility( mScaleBasedVisibilityChkBx->isChecked() ); + contourLabeling->setMinimumScale( mMinScaleWidget->scale() ); + contourLabeling->setMaximumScale( mMaxScaleWidget->scale() ); + } +} + +void QgsRasterContourLabelSettingsWidget::setLayer( QgsMapLayer *layer ) +{ + if ( mBandCombo ) + mBandCombo->setLayer( layer ); + + QgsLabelingGui::setLayer( layer ); + mMinSizeFrame->show(); +} + +void QgsRasterContourLabelSettingsWidget::changeNumberFormat() +{ + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsNumericFormatSelectorWidget *widget = new QgsNumericFormatSelectorWidget( this ); + widget->setPanelTitle( tr( "Number Format" ) ); + widget->setFormat( mNumberFormat.get() ); + widget->registerExpressionContextGenerator( this ); + connect( widget, &QgsNumericFormatSelectorWidget::changed, this, [this, widget] { + if ( !mBlockChangesSignal ) + { + mNumberFormat.reset( widget->format() ); + emit widgetChanged(); + } + } ); + panel->openPanel( widget ); + } + else + { + QgsNumericFormatSelectorDialog dialog( this ); + dialog.setFormat( mNumberFormat.get() ); + dialog.registerExpressionContextGenerator( this ); + if ( dialog.exec() ) + { + mNumberFormat.reset( dialog.format() ); + emit widgetChanged(); + } + } +} diff --git a/src/gui/raster/qgsrastercontourlabelsettingswidget.h b/src/gui/raster/qgsrastercontourlabelsettingswidget.h new file mode 100644 index 0000000000000..6cfd618433d3b --- /dev/null +++ b/src/gui/raster/qgsrastercontourlabelsettingswidget.h @@ -0,0 +1,74 @@ +/*************************************************************************** + qgsrastercontourlabelsettingswidget.h + ------------------------- + begin : February 2026 + copyright : (C) 2026 by the QGIS project + email : info at qgis dot org + *************************************************************************** + * * + * 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 QGSRASTERCONTOURLABELSETTINGSWIDGET_H +#define QGSRASTERCONTOURLABELSETTINGSWIDGET_H + +#include "qgis_gui.h" +#include "qgslabelinggui.h" + +#define SIP_NO_FILE + +class QgsRasterLayer; +class QgsAbstractRasterLayerLabeling; +class QgsRasterBandComboBox; +class QgsNumericFormat; +class QgsDoubleSpinBox; +class QCheckBox; + +// We don't want to expose this in the public API + +/** + * \class QgsRasterContourLabelSettingsWidget + * \ingroup gui + * \brief A widget for customizing settings for raster contour line labeling. + * + * \since QGIS 3.44 + */ +class GUI_EXPORT QgsRasterContourLabelSettingsWidget : public QgsLabelingGui +{ + Q_OBJECT + public: + /** + * Constructor for QgsRasterContourLabelSettingsWidget, for configuring a raster \a layer contour labeling. + */ + QgsRasterContourLabelSettingsWidget( QgsRasterLayer *layer, QgsMapCanvas *mapCanvas, QWidget *parent = nullptr ); + ~QgsRasterContourLabelSettingsWidget() override; + + /** + * Sets the \a labeling settings to show in the widget. + */ + void setLabeling( QgsAbstractRasterLayerLabeling *labeling ); + + /** + * Updates \a labeling by setting properties to match the current state of the widget. + */ + void updateLabeling( QgsAbstractRasterLayerLabeling *labeling ); + + void setLayer( QgsMapLayer *layer ) final; + + private slots: + void changeNumberFormat(); + + private: + QgsRasterBandComboBox *mBandCombo = nullptr; + QgsDoubleSpinBox *mContourIntervalSpin = nullptr; + QgsDoubleSpinBox *mContourIndexIntervalSpin = nullptr; + QgsDoubleSpinBox *mDownscaleSpin = nullptr; + QCheckBox *mLabelIndexOnlyCheck = nullptr; + std::unique_ptr mNumberFormat; + int mBlockChangesSignal = 0; +}; + +#endif // QGSRASTERCONTOURLABELSETTINGSWIDGET_H diff --git a/src/gui/raster/qgsrasterlabelingwidget.cpp b/src/gui/raster/qgsrasterlabelingwidget.cpp index f3276b14acb64..ab3f2156afd07 100644 --- a/src/gui/raster/qgsrasterlabelingwidget.cpp +++ b/src/gui/raster/qgsrasterlabelingwidget.cpp @@ -18,6 +18,8 @@ #include "qgsapplication.h" #include "qgslabelingwidget.h" #include "qgsproject.h" +#include "qgsrastercontourlabeling.h" +#include "qgsrastercontourlabelsettingswidget.h" #include "qgsrasterlabelsettingswidget.h" #include "qgsrasterlayer.h" @@ -37,6 +39,7 @@ QgsRasterLabelingWidget::QgsRasterLabelingWidget( QgsRasterLayer *layer, QgsMapC mLabelModeComboBox->addItem( QgsApplication::getThemeIcon( u"labelingNone.svg"_s ), tr( "No Labels" ), u"none"_s ); mLabelModeComboBox->addItem( QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ), tr( "Label with Pixel Values" ), u"simple"_s ); + mLabelModeComboBox->addItem( QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ), tr( "Label with Contour Lines" ), u"contour"_s ); connect( mLabelModeComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsRasterLabelingWidget::labelModeChanged ); setLayer( layer ); @@ -57,6 +60,10 @@ void QgsRasterLabelingWidget::setDockMode( bool dockMode ) { l->setDockMode( dockMode ); } + else if ( QgsRasterContourLabelSettingsWidget *l = qobject_cast( mWidget ) ) + { + l->setDockMode( dockMode ); + } } void QgsRasterLabelingWidget::setLayer( QgsMapLayer *mapLayer ) @@ -92,6 +99,11 @@ void QgsRasterLabelingWidget::adaptToLayer() settingsWidget->setLayer( mLayer ); settingsWidget->setLabeling( labeling ); } + else if ( QgsRasterContourLabelSettingsWidget *settingsWidget = qobject_cast( mWidget ) ) + { + settingsWidget->setLayer( mLayer ); + settingsWidget->setLabeling( labeling ); + } } else { @@ -117,6 +129,16 @@ void QgsRasterLabelingWidget::writeSettingsToLayer() mLayer->setLabeling( labeling.release() ); mLayer->setLabelsEnabled( true ); } + else if ( mode == "contour"_L1 ) + { + auto labeling = std::make_unique(); + if ( QgsRasterContourLabelSettingsWidget *settingsWidget = qobject_cast( mWidget ) ) + { + settingsWidget->updateLabeling( labeling.get() ); + } + mLayer->setLabeling( labeling.release() ); + mLayer->setLabelsEnabled( true ); + } else { mLayer->setLabelsEnabled( false ); @@ -171,6 +193,34 @@ void QgsRasterLabelingWidget::labelModeChanged( int index ) mStackedWidget->addWidget( mWidget ); mStackedWidget->setCurrentWidget( mWidget ); } + else if ( mode == "contour"_L1 ) + { + QgsSymbolWidgetContext context; + context.setMapCanvas( mMapCanvas ); + context.setMessageBar( mMessageBar ); + + QgsRasterContourLabelSettingsWidget *settingsWidget = new QgsRasterContourLabelSettingsWidget( mLayer, mCanvas, this ); + settingsWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); + settingsWidget->setContext( context ); + + settingsWidget->setDockMode( dockMode() ); + connect( settingsWidget, &QgsLabelingGui::widgetChanged, this, &QgsRasterLabelingWidget::widgetChanged ); + + mWidget = settingsWidget; + if ( !dynamic_cast( mLayer->labeling() ) ) + { + auto labeling = std::make_unique(); + settingsWidget->setLabeling( labeling.get() ); + mLayer->setLabeling( labeling.release() ); + } + else + { + settingsWidget->setLabeling( mLayer->labeling() ); + } + + mStackedWidget->addWidget( mWidget ); + mStackedWidget->setCurrentWidget( mWidget ); + } emit widgetChanged(); } From 9a25acc29a1a34c64c4e6a2ff258fd31c746568d Mon Sep 17 00:00:00 2001 From: McGuiness Date: Sat, 28 Feb 2026 19:54:48 +1000 Subject: [PATCH 4/6] Re-add workflow_dispatch trigger to Windows Qt6 workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/windows-qt6.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows-qt6.yml b/.github/workflows/windows-qt6.yml index 2f1d7db1c194d..07cee57d45f57 100644 --- a/.github/workflows/windows-qt6.yml +++ b/.github/workflows/windows-qt6.yml @@ -8,6 +8,7 @@ on: pull_request: release: types: ['published'] + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 84bc4abeff927e1920fee6396193e759c6930b0b Mon Sep 17 00:00:00 2001 From: McGuiness Date: Sat, 28 Feb 2026 22:14:26 +1000 Subject: [PATCH 5/6] Fix missing SIP registration for QgsRasterLayerContourLabeling Add %Include in core_auto.sip and create auto_additions file so the Python bindings compile and the prepare_sip pre-commit hook passes. Co-Authored-By: Claude Opus 4.6 --- .../PyQt6/core/auto_additions/qgsrastercontourlabeling.py | 7 +++++++ python/PyQt6/core/core_auto.sip | 1 + 2 files changed, 8 insertions(+) create mode 100644 python/PyQt6/core/auto_additions/qgsrastercontourlabeling.py diff --git a/python/PyQt6/core/auto_additions/qgsrastercontourlabeling.py b/python/PyQt6/core/auto_additions/qgsrastercontourlabeling.py new file mode 100644 index 0000000000000..d94c2f846a971 --- /dev/null +++ b/python/PyQt6/core/auto_additions/qgsrastercontourlabeling.py @@ -0,0 +1,7 @@ +# The following has been generated automatically from src/core/raster/qgsrastercontourlabeling.h +try: + QgsRasterLayerContourLabeling.create = staticmethod(QgsRasterLayerContourLabeling.create) + QgsRasterLayerContourLabeling.__overridden_methods__ = ['type', 'clone', 'save', 'accept', 'requiresAdvancedEffects', 'hasNonDefaultCompositionMode', 'multiplyOpacity', 'isInScaleRange'] + QgsRasterLayerContourLabeling.__group__ = ['raster'] +except (NameError, AttributeError): + pass diff --git a/python/PyQt6/core/core_auto.sip b/python/PyQt6/core/core_auto.sip index 9ebe607256bbe..d0a56acba19cf 100644 --- a/python/PyQt6/core/core_auto.sip +++ b/python/PyQt6/core/core_auto.sip @@ -680,6 +680,7 @@ %Include auto_generated/raster/qgsrasteridentifyresult.sip %Include auto_generated/raster/qgsrasterinterface.sip %Include auto_generated/raster/qgsrasteriterator.sip +%Include auto_generated/raster/qgsrastercontourlabeling.sip %Include auto_generated/raster/qgsrasterlabeling.sip %Include auto_generated/raster/qgsrasterlayer.sip %Include auto_generated/raster/qgsrasterlayerelevationproperties.sip From c2f28c04caa4308cb4dfe5da1e2b0c934c764873 Mon Sep 17 00:00:00 2001 From: McGuiness Date: Sat, 28 Feb 2026 22:50:18 +1000 Subject: [PATCH 6/6] Remove duplicate contour settings from Labels widget Band, contour interval, index interval, and downscale are already configured in the Symbology tab via QgsRasterContourRenderer. The label provider now reads these values directly from the renderer at render time, keeping the two tabs in sync and avoiding confusing duplication. Co-Authored-By: Claude Opus 4.6 --- .../raster/qgsrastercontourlabeling.sip.in | 12 ---- src/core/raster/qgsrastercontourlabeling.cpp | 27 ++++---- src/core/raster/qgsrastercontourlabeling.h | 16 ----- .../qgsrastercontourlabelsettingswidget.cpp | 65 ++----------------- .../qgsrastercontourlabelsettingswidget.h | 6 -- 5 files changed, 17 insertions(+), 109 deletions(-) diff --git a/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in b/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in index f5528feab7ea0..f78efacffe6d5 100644 --- a/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in +++ b/python/PyQt6/core/auto_generated/raster/qgsrastercontourlabeling.sip.in @@ -58,18 +58,6 @@ configuration const QgsNumericFormat *numericFormat() const; void setNumericFormat( QgsNumericFormat *format /Transfer/ ); - int band() const; - void setBand( int band ); - - double contourInterval() const; - void setContourInterval( double interval ); - - double contourIndexInterval() const; - void setContourIndexInterval( double interval ); - - double downscale() const; - void setDownscale( double downscale ); - bool labelIndexOnly() const; void setLabelIndexOnly( bool indexOnly ); diff --git a/src/core/raster/qgsrastercontourlabeling.cpp b/src/core/raster/qgsrastercontourlabeling.cpp index 14110a26ad60f..de848c72bca97 100644 --- a/src/core/raster/qgsrastercontourlabeling.cpp +++ b/src/core/raster/qgsrastercontourlabeling.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "qgsrastercontourlabeling.h" +#include "qgsrastercontourrenderer.h" #include @@ -256,10 +257,6 @@ QgsRasterLayerContourLabeling *QgsRasterLayerContourLabeling::clone() const if ( mNumericFormat ) res->mNumericFormat.reset( mNumericFormat->clone() ); - res->setBand( mBandNumber ); - res->setContourInterval( mContourInterval ); - res->setContourIndexInterval( mContourIndexInterval ); - res->setDownscale( mDownscale ); res->setLabelIndexOnly( mLabelIndexOnly ); res->setPriority( mPriority ); res->setPlacementSettings( mPlacementSettings ); @@ -276,10 +273,6 @@ std::unique_ptr QgsRasterLayerContourLabeling::prov { auto res = std::make_unique( layer ); res->setTextFormat( mTextFormat ); - res->setInputBand( mBandNumber ); - res->setContourInterval( mContourInterval ); - res->setContourIndexInterval( mContourIndexInterval ); - res->setDownscale( mDownscale ); res->setLabelIndexOnly( mLabelIndexOnly ); res->setPriority( mPriority ); res->setPlacementSettings( mPlacementSettings ); @@ -289,6 +282,16 @@ std::unique_ptr QgsRasterLayerContourLabeling::prov { res->setNumericFormat( std::unique_ptr( mNumericFormat->clone() ) ); } + + // Read contour parameters from the renderer so they stay in sync with the Symbology tab + if ( const QgsRasterContourRenderer *renderer = dynamic_cast( layer->renderer() ) ) + { + res->setInputBand( renderer->inputBand() ); + res->setContourInterval( renderer->contourInterval() ); + res->setContourIndexInterval( renderer->contourIndexInterval() ); + res->setDownscale( renderer->downscale() ); + } + return res; } @@ -296,10 +299,6 @@ QDomElement QgsRasterLayerContourLabeling::save( QDomDocument &doc, const QgsRea { QDomElement elem = doc.createElement( u"labeling"_s ); elem.setAttribute( u"type"_s, u"contour"_s ); - elem.setAttribute( u"band"_s, mBandNumber ); - elem.setAttribute( u"contourInterval"_s, mContourInterval ); - elem.setAttribute( u"contourIndexInterval"_s, mContourIndexInterval ); - elem.setAttribute( u"downscale"_s, mDownscale ); elem.setAttribute( u"labelIndexOnly"_s, mLabelIndexOnly ? 1 : 0 ); elem.setAttribute( u"priority"_s, mPriority ); elem.setAttribute( u"zIndex"_s, mZIndex ); @@ -375,10 +374,6 @@ bool QgsRasterLayerContourLabeling::isInScaleRange( double scale ) const QgsRasterLayerContourLabeling *QgsRasterLayerContourLabeling::create( const QDomElement &element, const QgsReadWriteContext &context ) { auto res = std::make_unique(); - res->setBand( element.attribute( u"band"_s, u"1"_s ).toInt() ); - res->setContourInterval( element.attribute( u"contourInterval"_s, u"100"_s ).toDouble() ); - res->setContourIndexInterval( element.attribute( u"contourIndexInterval"_s, u"0"_s ).toDouble() ); - res->setDownscale( element.attribute( u"downscale"_s, u"4"_s ).toDouble() ); res->setLabelIndexOnly( element.attribute( u"labelIndexOnly"_s, u"0"_s ).toInt() ); res->setPriority( element.attribute( u"priority"_s, u"0.5"_s ).toDouble() ); res->setZIndex( element.attribute( u"zIndex"_s, u"0"_s ).toDouble() ); diff --git a/src/core/raster/qgsrastercontourlabeling.h b/src/core/raster/qgsrastercontourlabeling.h index 0c594b3da6764..8c1729b016517 100644 --- a/src/core/raster/qgsrastercontourlabeling.h +++ b/src/core/raster/qgsrastercontourlabeling.h @@ -97,18 +97,6 @@ class CORE_EXPORT QgsRasterLayerContourLabeling : public QgsAbstractRasterLayerL const QgsNumericFormat *numericFormat() const; void setNumericFormat( QgsNumericFormat *format SIP_TRANSFER ); - int band() const { return mBandNumber; } - void setBand( int band ) { mBandNumber = band; } - - double contourInterval() const { return mContourInterval; } - void setContourInterval( double interval ) { mContourInterval = interval; } - - double contourIndexInterval() const { return mContourIndexInterval; } - void setContourIndexInterval( double interval ) { mContourIndexInterval = interval; } - - double downscale() const { return mDownscale; } - void setDownscale( double downscale ) { mDownscale = downscale; } - bool labelIndexOnly() const { return mLabelIndexOnly; } void setLabelIndexOnly( bool indexOnly ) { mLabelIndexOnly = indexOnly; } @@ -136,10 +124,6 @@ class CORE_EXPORT QgsRasterLayerContourLabeling : public QgsAbstractRasterLayerL bool hasScaleBasedVisibility() const; private: - int mBandNumber = 1; - double mContourInterval = 100.0; - double mContourIndexInterval = 0.0; - double mDownscale = 4.0; bool mLabelIndexOnly = false; QgsTextFormat mTextFormat; diff --git a/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp b/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp index 971aae579e48b..23ad3466439f1 100644 --- a/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp +++ b/src/gui/raster/qgsrastercontourlabelsettingswidget.cpp @@ -16,10 +16,9 @@ #include "qgsrastercontourlabelsettingswidget.h" #include "qgsbasicnumericformat.h" -#include "qgsdoublespinbox.h" #include "qgsnumericformatselectorwidget.h" -#include "qgsrasterbandcombobox.h" #include "qgsrastercontourlabeling.h" +#include "qgsrasterlayer.h" #include #include @@ -40,56 +39,16 @@ QgsRasterContourLabelSettingsWidget::QgsRasterContourLabelSettingsWidget( QgsRas QGridLayout *gLayout = new QGridLayout(); gLayout->setContentsMargins( 0, 0, 0, 0 ); - // Row 0: Band - gLayout->addWidget( new QLabel( tr( "Band" ) ), 0, 0 ); - mBandCombo = new QgsRasterBandComboBox(); - mBandCombo->setLayer( layer ); - gLayout->addWidget( mBandCombo, 0, 1 ); - - // Row 1: Contour interval - gLayout->addWidget( new QLabel( tr( "Contour interval" ) ), 1, 0 ); - mContourIntervalSpin = new QgsDoubleSpinBox(); - mContourIntervalSpin->setMinimum( 0.001 ); - mContourIntervalSpin->setMaximum( 999999999.0 ); - mContourIntervalSpin->setDecimals( 3 ); - mContourIntervalSpin->setValue( 100.0 ); - mContourIntervalSpin->setClearValue( 100.0 ); - connect( mContourIntervalSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); - gLayout->addWidget( mContourIntervalSpin, 1, 1 ); - - // Row 2: Index interval - gLayout->addWidget( new QLabel( tr( "Index interval" ) ), 2, 0 ); - mContourIndexIntervalSpin = new QgsDoubleSpinBox(); - mContourIndexIntervalSpin->setMinimum( 0.0 ); - mContourIndexIntervalSpin->setMaximum( 999999999.0 ); - mContourIndexIntervalSpin->setDecimals( 3 ); - mContourIndexIntervalSpin->setValue( 0.0 ); - mContourIndexIntervalSpin->setClearValue( 0.0 ); - mContourIndexIntervalSpin->setSpecialValueText( tr( "None" ) ); - connect( mContourIndexIntervalSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); - gLayout->addWidget( mContourIndexIntervalSpin, 2, 1 ); - - // Row 3: Downscale - gLayout->addWidget( new QLabel( tr( "Downscale" ) ), 3, 0 ); - mDownscaleSpin = new QgsDoubleSpinBox(); - mDownscaleSpin->setMinimum( 1.0 ); - mDownscaleSpin->setMaximum( 16.0 ); - mDownscaleSpin->setDecimals( 1 ); - mDownscaleSpin->setValue( 4.0 ); - mDownscaleSpin->setClearValue( 4.0 ); - connect( mDownscaleSpin, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); - gLayout->addWidget( mDownscaleSpin, 3, 1 ); - - // Row 4: Number format - gLayout->addWidget( new QLabel( tr( "Number format" ) ), 4, 0 ); + // Row 0: Number format + gLayout->addWidget( new QLabel( tr( "Number format" ) ), 0, 0 ); QPushButton *numberFormatButton = new QPushButton( tr( "Customize" ) ); connect( numberFormatButton, &QPushButton::clicked, this, &QgsRasterContourLabelSettingsWidget::changeNumberFormat ); - gLayout->addWidget( numberFormatButton, 4, 1 ); + gLayout->addWidget( numberFormatButton, 0, 1 ); - // Row 5: Label index contours only + // Row 1: Label index contours only mLabelIndexOnlyCheck = new QCheckBox( tr( "Label index contours only" ) ); connect( mLabelIndexOnlyCheck, &QCheckBox::toggled, this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); - gLayout->addWidget( mLabelIndexOnlyCheck, 5, 0, 1, 2 ); + gLayout->addWidget( mLabelIndexOnlyCheck, 1, 0, 1, 2 ); gLayout->setColumnStretch( 0, 1 ); gLayout->setColumnStretch( 1, 2 ); @@ -183,7 +142,6 @@ QgsRasterContourLabelSettingsWidget::QgsRasterContourLabelSettingsWidget( QgsRas mPrioritySlider->setRange( 0, 100 ); mPrioritySlider->setTickInterval( 10 ); - connect( mBandCombo, &QgsRasterBandComboBox::bandChanged, this, &QgsRasterContourLabelSettingsWidget::widgetChanged ); } QgsRasterContourLabelSettingsWidget::~QgsRasterContourLabelSettingsWidget() = default; @@ -193,10 +151,6 @@ void QgsRasterContourLabelSettingsWidget::setLabeling( QgsAbstractRasterLayerLab if ( QgsRasterLayerContourLabeling *contourLabeling = dynamic_cast( labeling ) ) { setFormat( contourLabeling->textFormat() ); - mBandCombo->setBand( contourLabeling->band() ); - mContourIntervalSpin->setValue( contourLabeling->contourInterval() ); - mContourIndexIntervalSpin->setValue( contourLabeling->contourIndexInterval() ); - mDownscaleSpin->setValue( contourLabeling->downscale() ); mLabelIndexOnlyCheck->setChecked( contourLabeling->labelIndexOnly() ); mPrioritySlider->setValue( static_cast( 100 - contourLabeling->priority() * 100 ) ); @@ -222,10 +176,6 @@ void QgsRasterContourLabelSettingsWidget::updateLabeling( QgsAbstractRasterLayer if ( QgsRasterLayerContourLabeling *contourLabeling = dynamic_cast( labeling ) ) { contourLabeling->setTextFormat( format() ); - contourLabeling->setBand( mBandCombo->currentBand() ); - contourLabeling->setContourInterval( mContourIntervalSpin->value() ); - contourLabeling->setContourIndexInterval( mContourIndexIntervalSpin->value() ); - contourLabeling->setDownscale( mDownscaleSpin->value() ); contourLabeling->setLabelIndexOnly( mLabelIndexOnlyCheck->isChecked() ); contourLabeling->setPriority( 1.0 - mPrioritySlider->value() / 100.0 ); contourLabeling->placementSettings().setOverlapHandling( static_cast( mComboOverlapHandling->currentData().toInt() ) ); @@ -244,9 +194,6 @@ void QgsRasterContourLabelSettingsWidget::updateLabeling( QgsAbstractRasterLayer void QgsRasterContourLabelSettingsWidget::setLayer( QgsMapLayer *layer ) { - if ( mBandCombo ) - mBandCombo->setLayer( layer ); - QgsLabelingGui::setLayer( layer ); mMinSizeFrame->show(); } diff --git a/src/gui/raster/qgsrastercontourlabelsettingswidget.h b/src/gui/raster/qgsrastercontourlabelsettingswidget.h index 6cfd618433d3b..1b390383f5658 100644 --- a/src/gui/raster/qgsrastercontourlabelsettingswidget.h +++ b/src/gui/raster/qgsrastercontourlabelsettingswidget.h @@ -22,9 +22,7 @@ class QgsRasterLayer; class QgsAbstractRasterLayerLabeling; -class QgsRasterBandComboBox; class QgsNumericFormat; -class QgsDoubleSpinBox; class QCheckBox; // We don't want to expose this in the public API @@ -62,10 +60,6 @@ class GUI_EXPORT QgsRasterContourLabelSettingsWidget : public QgsLabelingGui void changeNumberFormat(); private: - QgsRasterBandComboBox *mBandCombo = nullptr; - QgsDoubleSpinBox *mContourIntervalSpin = nullptr; - QgsDoubleSpinBox *mContourIndexIntervalSpin = nullptr; - QgsDoubleSpinBox *mDownscaleSpin = nullptr; QCheckBox *mLabelIndexOnlyCheck = nullptr; std::unique_ptr mNumberFormat; int mBlockChangesSignal = 0;