diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index b8201f90ad7d..a026e4a730dc 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -10824,7 +10824,7 @@ # -- Qgis.ColorModel.baseClass = Qgis try: - Qgis.__attribute_docs__ = {'QGIS_DEV_VERSION': 'The development version', 'DEFAULT_SEARCH_RADIUS_MM': 'Identify search radius in mm', 'DEFAULT_MAPTOPIXEL_THRESHOLD': 'Default threshold between map coordinates and device coordinates for map2pixel simplification', 'DEFAULT_HIGHLIGHT_COLOR': 'Default highlight color. The transparency is expected to only be applied to polygon\nfill. Lines and outlines are rendered opaque.', 'DEFAULT_HIGHLIGHT_BUFFER_MM': 'Default highlight buffer in mm.', 'DEFAULT_HIGHLIGHT_MIN_WIDTH_MM': 'Default highlight line/stroke minimum width in mm.', 'SCALE_PRECISION': 'Fudge factor used to compare two scales. The code is often going from scale to scale\ndenominator. So it looses precision and, when a limit is inclusive, can lead to errors.\nTo avoid that, use this factor instead of using <= or >=.', 'DEFAULT_Z_COORDINATE': 'Default Z coordinate value.\nThis value have to be assigned to the Z coordinate for the vertex.', 'DEFAULT_M_COORDINATE': 'Default M coordinate value.\nThis value have to be assigned to the M coordinate for the vertex.\n\n.. versionadded:: 3.20', 'UI_SCALE_FACTOR': 'UI scaling factor. This should be applied to all widget sizes obtained from font metrics,\nto account for differences in the default font sizes across different platforms.', 'DEFAULT_SNAP_TOLERANCE': 'Default snapping distance tolerance.', 'DEFAULT_SNAP_UNITS': 'Default snapping distance units.'} + Qgis.__attribute_docs__ = {'QGIS_DEV_VERSION': 'The development version', 'DEFAULT_SEARCH_RADIUS_MM': 'Identify search radius in mm', 'DEFAULT_MAPTOPIXEL_THRESHOLD': 'Default threshold between map coordinates and device coordinates for map2pixel simplification', 'DEFAULT_HIGHLIGHT_COLOR': 'Default highlight color. The transparency is expected to only be applied to polygon\nfill. Lines and outlines are rendered opaque.', 'DEFAULT_HIGHLIGHT_BUFFER_MM': 'Default highlight buffer in mm.', 'DEFAULT_HIGHLIGHT_MIN_WIDTH_MM': 'Default highlight line/stroke minimum width in mm.', 'SCALE_PRECISION': 'Fudge factor used to compare two scales. The code is often going from scale to scale\ndenominator. So it looses precision and, when a limit is inclusive, can lead to errors.\nTo avoid that, use this factor instead of using <= or >=.\n\n.. deprecated:: 3.40\n\n No longer used by QGIS and will be removed in QGIS 4.0.', 'DEFAULT_Z_COORDINATE': 'Default Z coordinate value.\nThis value have to be assigned to the Z coordinate for the vertex.', 'DEFAULT_M_COORDINATE': 'Default M coordinate value.\nThis value have to be assigned to the M coordinate for the vertex.\n\n.. versionadded:: 3.20', 'UI_SCALE_FACTOR': 'UI scaling factor. This should be applied to all widget sizes obtained from font metrics,\nto account for differences in the default font sizes across different platforms.', 'DEFAULT_SNAP_TOLERANCE': 'Default snapping distance tolerance.', 'DEFAULT_SNAP_UNITS': 'Default snapping distance units.'} Qgis.version = staticmethod(Qgis.version) Qgis.versionInt = staticmethod(Qgis.versionInt) Qgis.releaseName = staticmethod(Qgis.releaseName) diff --git a/python/PyQt6/core/auto_additions/qgsscaleutils.py b/python/PyQt6/core/auto_additions/qgsscaleutils.py index 16e1f921bc36..498b6b854fee 100644 --- a/python/PyQt6/core/auto_additions/qgsscaleutils.py +++ b/python/PyQt6/core/auto_additions/qgsscaleutils.py @@ -2,5 +2,7 @@ try: QgsScaleUtils.saveScaleList = staticmethod(QgsScaleUtils.saveScaleList) QgsScaleUtils.loadScaleList = staticmethod(QgsScaleUtils.loadScaleList) + QgsScaleUtils.equalToOrGreaterThanMinimumScale = staticmethod(QgsScaleUtils.equalToOrGreaterThanMinimumScale) + QgsScaleUtils.lessThanMaximumScale = staticmethod(QgsScaleUtils.lessThanMaximumScale) except NameError: pass diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 7f7611f34e52..d39314ce0a58 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -3162,7 +3162,7 @@ The development version static const double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM; - static const double SCALE_PRECISION; + static const double SCALE_PRECISION; static const double DEFAULT_Z_COORDINATE; diff --git a/python/PyQt6/core/auto_generated/qgsscaleutils.sip.in b/python/PyQt6/core/auto_generated/qgsscaleutils.sip.in index 23b5d395e799..8b4fddb232ca 100644 --- a/python/PyQt6/core/auto_generated/qgsscaleutils.sip.in +++ b/python/PyQt6/core/auto_generated/qgsscaleutils.sip.in @@ -42,6 +42,36 @@ Load scales from the given file went wrong :return: ``True`` on success and ``False`` if failed +%End + + static bool equalToOrGreaterThanMinimumScale( const double scale, const double minScale ); +%Docstring +Returns whether the ``scale`` is equal to or greater than the ``minScale``, +taking non-round numbers into account. + +:param scale: The current scale to be compared. +:param minScale: The minimum map scale (i.e. most "zoomed out" scale) at + which features, labels or diagrams will be visible. The scale value + indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. + +.. seealso:: :py:func:`lessThanMaximumScale` + +.. versionadded:: 3.40 +%End + + static bool lessThanMaximumScale( const double scale, const double maxScale ); +%Docstring +Returns whether the ``scale`` is less than the ``maxScale``, taking non-round +numbers into account. + +:param scale: The current scale to be compared. +:param maxScale: The maximum map scale (i.e. most "zoomed in" scale) at which + features, labels or diagrams will be visible. The scale value indicates the + scale denominator, e.g. 1000.0 for a 1:1000 map. + +.. seealso:: :py:func:`equalToOrGreaterThanMinimumScale` + +.. versionadded:: 3.40 %End }; diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 095bf396602b..dfd0f509b091 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -10769,7 +10769,7 @@ def _force_int(v): return int(v.value) if isinstance(v, Enum) else v Qgis.DataProviderReadFlag.__or__ = lambda flag1, flag2: Qgis.DataProviderReadFlags(_force_int(flag1) | _force_int(flag2)) Qgis.VectorProviderCapability.__or__ = lambda flag1, flag2: Qgis.VectorProviderCapabilities(_force_int(flag1) | _force_int(flag2)) try: - Qgis.__attribute_docs__ = {'QGIS_DEV_VERSION': 'The development version', 'DEFAULT_SEARCH_RADIUS_MM': 'Identify search radius in mm', 'DEFAULT_MAPTOPIXEL_THRESHOLD': 'Default threshold between map coordinates and device coordinates for map2pixel simplification', 'DEFAULT_HIGHLIGHT_COLOR': 'Default highlight color. The transparency is expected to only be applied to polygon\nfill. Lines and outlines are rendered opaque.', 'DEFAULT_HIGHLIGHT_BUFFER_MM': 'Default highlight buffer in mm.', 'DEFAULT_HIGHLIGHT_MIN_WIDTH_MM': 'Default highlight line/stroke minimum width in mm.', 'SCALE_PRECISION': 'Fudge factor used to compare two scales. The code is often going from scale to scale\ndenominator. So it looses precision and, when a limit is inclusive, can lead to errors.\nTo avoid that, use this factor instead of using <= or >=.', 'DEFAULT_Z_COORDINATE': 'Default Z coordinate value.\nThis value have to be assigned to the Z coordinate for the vertex.', 'DEFAULT_M_COORDINATE': 'Default M coordinate value.\nThis value have to be assigned to the M coordinate for the vertex.\n\n.. versionadded:: 3.20', 'UI_SCALE_FACTOR': 'UI scaling factor. This should be applied to all widget sizes obtained from font metrics,\nto account for differences in the default font sizes across different platforms.', 'DEFAULT_SNAP_TOLERANCE': 'Default snapping distance tolerance.', 'DEFAULT_SNAP_UNITS': 'Default snapping distance units.'} + Qgis.__attribute_docs__ = {'QGIS_DEV_VERSION': 'The development version', 'DEFAULT_SEARCH_RADIUS_MM': 'Identify search radius in mm', 'DEFAULT_MAPTOPIXEL_THRESHOLD': 'Default threshold between map coordinates and device coordinates for map2pixel simplification', 'DEFAULT_HIGHLIGHT_COLOR': 'Default highlight color. The transparency is expected to only be applied to polygon\nfill. Lines and outlines are rendered opaque.', 'DEFAULT_HIGHLIGHT_BUFFER_MM': 'Default highlight buffer in mm.', 'DEFAULT_HIGHLIGHT_MIN_WIDTH_MM': 'Default highlight line/stroke minimum width in mm.', 'SCALE_PRECISION': 'Fudge factor used to compare two scales. The code is often going from scale to scale\ndenominator. So it looses precision and, when a limit is inclusive, can lead to errors.\nTo avoid that, use this factor instead of using <= or >=.\n\n.. deprecated:: 3.40\n\n No longer used by QGIS and will be removed in QGIS 4.0.', 'DEFAULT_Z_COORDINATE': 'Default Z coordinate value.\nThis value have to be assigned to the Z coordinate for the vertex.', 'DEFAULT_M_COORDINATE': 'Default M coordinate value.\nThis value have to be assigned to the M coordinate for the vertex.\n\n.. versionadded:: 3.20', 'UI_SCALE_FACTOR': 'UI scaling factor. This should be applied to all widget sizes obtained from font metrics,\nto account for differences in the default font sizes across different platforms.', 'DEFAULT_SNAP_TOLERANCE': 'Default snapping distance tolerance.', 'DEFAULT_SNAP_UNITS': 'Default snapping distance units.'} Qgis.version = staticmethod(Qgis.version) Qgis.versionInt = staticmethod(Qgis.versionInt) Qgis.releaseName = staticmethod(Qgis.releaseName) diff --git a/python/core/auto_additions/qgsscaleutils.py b/python/core/auto_additions/qgsscaleutils.py index 16e1f921bc36..498b6b854fee 100644 --- a/python/core/auto_additions/qgsscaleutils.py +++ b/python/core/auto_additions/qgsscaleutils.py @@ -2,5 +2,7 @@ try: QgsScaleUtils.saveScaleList = staticmethod(QgsScaleUtils.saveScaleList) QgsScaleUtils.loadScaleList = staticmethod(QgsScaleUtils.loadScaleList) + QgsScaleUtils.equalToOrGreaterThanMinimumScale = staticmethod(QgsScaleUtils.equalToOrGreaterThanMinimumScale) + QgsScaleUtils.lessThanMaximumScale = staticmethod(QgsScaleUtils.lessThanMaximumScale) except NameError: pass diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index fdbaad9f57a2..d8de59fa7ec6 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -3162,7 +3162,7 @@ The development version static const double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM; - static const double SCALE_PRECISION; + static const double SCALE_PRECISION; static const double DEFAULT_Z_COORDINATE; diff --git a/python/core/auto_generated/qgsscaleutils.sip.in b/python/core/auto_generated/qgsscaleutils.sip.in index 23b5d395e799..8b4fddb232ca 100644 --- a/python/core/auto_generated/qgsscaleutils.sip.in +++ b/python/core/auto_generated/qgsscaleutils.sip.in @@ -42,6 +42,36 @@ Load scales from the given file went wrong :return: ``True`` on success and ``False`` if failed +%End + + static bool equalToOrGreaterThanMinimumScale( const double scale, const double minScale ); +%Docstring +Returns whether the ``scale`` is equal to or greater than the ``minScale``, +taking non-round numbers into account. + +:param scale: The current scale to be compared. +:param minScale: The minimum map scale (i.e. most "zoomed out" scale) at + which features, labels or diagrams will be visible. The scale value + indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. + +.. seealso:: :py:func:`lessThanMaximumScale` + +.. versionadded:: 3.40 +%End + + static bool lessThanMaximumScale( const double scale, const double maxScale ); +%Docstring +Returns whether the ``scale`` is less than the ``maxScale``, taking non-round +numbers into account. + +:param scale: The current scale to be compared. +:param maxScale: The maximum map scale (i.e. most "zoomed in" scale) at which + features, labels or diagrams will be visible. The scale value indicates the + scale denominator, e.g. 1000.0 for a 1:1000 map. + +.. seealso:: :py:func:`equalToOrGreaterThanMinimumScale` + +.. versionadded:: 3.40 %End }; diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index e584b303f47e..99ab5b93fc3e 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -94,6 +94,7 @@ #include "qgsexpressioncontextutils.h" #include "qgsauxiliarystorage.h" #include "qgsvectortileutils.h" +#include "qgsscaleutils.h" #include "qgsbrowserwidget.h" #include "annotations/qgsannotationitempropertieswidget.h" @@ -12120,12 +12121,15 @@ void QgisApp::zoomToLayerScale() if ( layer && layer->hasScaleBasedVisibility() ) { const double scale = mMapCanvas->scale(); - if ( scale > layer->minimumScale() && layer->minimumScale() > 0 ) + + if ( layer->minimumScale() > 0 && QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, layer->minimumScale() ) ) { - mMapCanvas->zoomScale( layer->minimumScale() * Qgis::SCALE_PRECISION ); + // minimum is exclusive ( >= --> out of range ), decrease by 1 to be sure + mMapCanvas->zoomScale( layer->minimumScale() - 1 ); } - else if ( scale <= layer->maximumScale() && layer->maximumScale() > 0 ) + else if ( layer->maximumScale() > 0 && QgsScaleUtils::lessThanMaximumScale( scale, layer->maximumScale() ) ) { + // maximum is inclusive ( < --> out of range ), pass maximum mMapCanvas->zoomScale( layer->maximumScale() ); } } diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp index 338be82b2d1e..cd4d0a933c9a 100644 --- a/src/app/qgsapplayertreeviewmenuprovider.cpp +++ b/src/app/qgsapplayertreeviewmenuprovider.cpp @@ -465,7 +465,8 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu() // set layer scale visibility menu->addAction( tr( "Set Layer Scale &Visibility…" ), QgisApp::instance(), &QgisApp::setLayerScaleVisibility ); - if ( !layer->isInScaleRange( mCanvas->scale() ) ) + if ( !layer->isInScaleRange( mCanvas->scale() ) && ( layer->minimumScale() - layer->maximumScale() >= 1 ) ) + // Only show if we can make sure there's a scale where layer is visible menu->addAction( tr( "Zoom to &Visible Scale" ), QgisApp::instance(), &QgisApp::zoomToLayerScale ); QMenu *menuSetCRS = new QMenu( tr( "Layer CRS" ), menu ); diff --git a/src/core/labeling/qgsrulebasedlabeling.cpp b/src/core/labeling/qgsrulebasedlabeling.cpp index fba20134f596..f757a934822f 100644 --- a/src/core/labeling/qgsrulebasedlabeling.cpp +++ b/src/core/labeling/qgsrulebasedlabeling.cpp @@ -13,6 +13,7 @@ * * ***************************************************************************/ #include "qgsrulebasedlabeling.h" +#include "qgsscaleutils.h" #include "qgssymbollayerutils.h" #include "qgsstyleentityvisitor.h" @@ -427,9 +428,13 @@ bool QgsRuleBasedLabeling::Rule::isScaleOK( double scale ) const return true; if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) ) return true; - if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale ) + + // maxScale is inclusive ( < --> no label ) + if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && QgsScaleUtils::lessThanMaximumScale( scale, mMaximumScale ) ) return false; - if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale ) + + // minScale is exclusive ( >= --> no label ) + if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMinimumScale ) ) return false; return true; } diff --git a/src/core/qgis.h b/src/core/qgis.h index 397a5bf681fa..aa2aaa8eef80 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -5568,8 +5568,10 @@ class CORE_EXPORT Qgis * Fudge factor used to compare two scales. The code is often going from scale to scale * denominator. So it looses precision and, when a limit is inclusive, can lead to errors. * To avoid that, use this factor instead of using <= or >=. + * + * \deprecated QGIS 3.40. No longer used by QGIS and will be removed in QGIS 4.0. */ - static const double SCALE_PRECISION; + Q_DECL_DEPRECATED static const double SCALE_PRECISION; /** * Default Z coordinate value. diff --git a/src/core/qgsdiagramrenderer.cpp b/src/core/qgsdiagramrenderer.cpp index 0468516f0f04..040016fdb40f 100644 --- a/src/core/qgsdiagramrenderer.cpp +++ b/src/core/qgsdiagramrenderer.cpp @@ -31,6 +31,7 @@ #include "qgslinesymbol.h" #include "qgsmarkersymbol.h" #include "qgsunittypes.h" +#include "qgsscaleutils.h" #include #include @@ -538,16 +539,16 @@ QSizeF QgsDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const QgsRen // Note: scale might be a non-round number, so compare with qgsDoubleNear const double rendererScale = c.rendererScale(); - // maxScale is inclusive ( (< && !=) --> < --> no size ) + // maxScale is inclusive ( < --> no size ) double maxScale = s.maximumScale; - if ( maxScale > 0 && ( rendererScale < maxScale && !qgsDoubleNear( rendererScale, maxScale, 1E-8 ) ) ) + if ( maxScale > 0 && QgsScaleUtils::lessThanMaximumScale( rendererScale, maxScale ) ) { return QSizeF(); } // minScale is exclusive ( >= --> no size) double minScale = s.minimumScale; - if ( minScale > 0 && ( rendererScale > minScale || qgsDoubleNear( rendererScale, minScale, 1E-8 ) ) ) + if ( minScale > 0 && QgsScaleUtils::equalToOrGreaterThanMinimumScale( rendererScale, minScale ) ) { return QSizeF(); } @@ -947,16 +948,16 @@ void QgsStackedDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRen // Note: scale might be a non-round number, so compare with qgsDoubleNear const double rendererScale = c.rendererScale(); - // maxScale is inclusive ( (< && !=) --> < --> no diagram ) + // maxScale is inclusive ( < --> no diagram ) double maxScale = s.maximumScale; - if ( maxScale > 0 && ( rendererScale < maxScale && !qgsDoubleNear( rendererScale, maxScale, 1E-8 ) ) ) + if ( maxScale > 0 && QgsScaleUtils::lessThanMaximumScale( rendererScale, maxScale ) ) { continue; } // minScale is exclusive ( >= --> no diagram) double minScale = s.minimumScale; - if ( minScale > 0 && ( rendererScale > minScale || qgsDoubleNear( rendererScale, minScale, 1E-8 ) ) ) + if ( minScale > 0 && QgsScaleUtils::equalToOrGreaterThanMinimumScale( rendererScale, minScale ) ) { continue; } diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 48f32e7d1379..2c8036eb6a14 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -37,6 +37,7 @@ #include "qgsrasterlayer.h" #include "qgsreadwritecontext.h" #include "qgsrectangle.h" +#include "qgsscaleutils.h" #include "qgssldexportcontext.h" #include "qgsvectorlayer.h" #include "qgsxmlutils.h" @@ -1153,9 +1154,11 @@ bool QgsMapLayer::isInScaleRange( double scale ) const // non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL - return !mScaleBasedVisibility || - ( ( mMinScale == 0 || mMinScale * Qgis::SCALE_PRECISION < scale ) - && ( mMaxScale == 0 || scale < mMaxScale ) ); + // mMinScale (denominator!) is inclusive ( >= --> In range ) + // mMaxScale (denominator!) is exclusive ( < --> In range ) + return !mScaleBasedVisibility + || ( ( mMinScale == 0 || !QgsScaleUtils::lessThanMaximumScale( scale, mMinScale ) ) + && ( mMaxScale == 0 || !QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMaxScale ) ) ); } bool QgsMapLayer::hasScaleBasedVisibility() const diff --git a/src/core/qgsscaleutils.cpp b/src/core/qgsscaleutils.cpp index d2f13ddb4a61..78c269c5945a 100644 --- a/src/core/qgsscaleutils.cpp +++ b/src/core/qgsscaleutils.cpp @@ -13,12 +13,13 @@ * * ***************************************************************************/ +#include "qgsscaleutils.h" +#include "qgis.h" + #include #include #include -#include "qgsscaleutils.h" - bool QgsScaleUtils::saveScaleList( const QString &fileName, const QStringList &scales, QString &errorMessage ) { QDomDocument doc; @@ -84,3 +85,13 @@ bool QgsScaleUtils::loadScaleList( const QString &fileName, QStringList &scales, return true; } + +bool QgsScaleUtils::equalToOrGreaterThanMinimumScale( const double scale, const double minScale ) +{ + return scale > minScale || qgsDoubleNear( scale, minScale, 1E-8 ); +} + +bool QgsScaleUtils::lessThanMaximumScale( const double scale, const double maxScale ) +{ + return scale < maxScale && !qgsDoubleNear( scale, maxScale, 1E-8 ); +} diff --git a/src/core/qgsscaleutils.h b/src/core/qgsscaleutils.h index c7e6cea4ac99..63fc770e05f1 100644 --- a/src/core/qgsscaleutils.h +++ b/src/core/qgsscaleutils.h @@ -48,6 +48,34 @@ class CORE_EXPORT QgsScaleUtils * \returns TRUE on success and FALSE if failed */ static bool loadScaleList( const QString &fileName, QStringList &scales, QString &errorMessage ); + + /** + * Returns whether the \a scale is equal to or greater than the \a minScale, + * taking non-round numbers into account. + * + * \param scale The current scale to be compared. + * \param minScale The minimum map scale (i.e. most "zoomed out" scale) at + * which features, labels or diagrams will be visible. The scale value + * indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. + * \see lessThanMaximumScale() + * + * \since QGIS 3.40 + */ + static bool equalToOrGreaterThanMinimumScale( const double scale, const double minScale ); + + /** + * Returns whether the \a scale is less than the \a maxScale, taking non-round + * numbers into account. + * + * \param scale The current scale to be compared. + * \param maxScale The maximum map scale (i.e. most "zoomed in" scale) at which + * features, labels or diagrams will be visible. The scale value indicates the + * scale denominator, e.g. 1000.0 for a 1:1000 map. + * \see equalToOrGreaterThanMinimumScale() + * + * \since QGIS 3.40 + */ + static bool lessThanMaximumScale( const double scale, const double maxScale ); }; #endif diff --git a/src/core/symbology/qgsrulebasedrenderer.cpp b/src/core/symbology/qgsrulebasedrenderer.cpp index 6b8af097d611..4ed4f107a4e4 100644 --- a/src/core/symbology/qgsrulebasedrenderer.cpp +++ b/src/core/symbology/qgsrulebasedrenderer.cpp @@ -30,6 +30,7 @@ #include "qgsfillsymbol.h" #include "qgsmarkersymbol.h" #include "qgspointdistancerenderer.h" +#include "qgsscaleutils.h" #include @@ -297,10 +298,15 @@ bool QgsRuleBasedRenderer::Rule::isScaleOK( double scale ) const return true; if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) ) return true; - if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale ) + + // maxScale is inclusive ( < --> no render ) + if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && QgsScaleUtils::lessThanMaximumScale( scale, mMaximumScale ) ) return false; - if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale ) + + // minScale is exclusive ( >= --> no render ) + if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMinimumScale ) ) return false; + return true; } diff --git a/src/core/vector/qgsvectorlayerdiagramprovider.cpp b/src/core/vector/qgsvectorlayerdiagramprovider.cpp index a23d2c11ff58..92107bf6ac3e 100644 --- a/src/core/vector/qgsvectorlayerdiagramprovider.cpp +++ b/src/core/vector/qgsvectorlayerdiagramprovider.cpp @@ -24,6 +24,7 @@ #include "qgslabelingresults.h" #include "qgsrendercontext.h" #include "qgsexpressioncontextutils.h" +#include "qgsscaleutils.h" #include "feature.h" #include "labelposition.h" @@ -197,16 +198,16 @@ QgsLabelFeature *QgsVectorLayerDiagramProvider::registerDiagram( const QgsFeatur // Note: scale might be a non-round number, so compare with qgsDoubleNear const double rendererScale = context.rendererScale(); - // maxScale is inclusive ( (< && !=) --> < --> no diagram ) + // maxScale is inclusive ( < --> no diagram ) double maxScale = settingList.at( 0 ).maximumScale; - if ( maxScale > 0 && ( rendererScale < maxScale && !qgsDoubleNear( rendererScale, maxScale, 1E-8 ) ) ) + if ( maxScale > 0 && QgsScaleUtils::lessThanMaximumScale( rendererScale, maxScale ) ) { return nullptr; } // minScale is exclusive ( >= --> no diagram) double minScale = settingList.at( 0 ).minimumScale; - if ( minScale > 0 && ( rendererScale > minScale || qgsDoubleNear( rendererScale, minScale, 1E-8 ) ) ) + if ( minScale > 0 && QgsScaleUtils::equalToOrGreaterThanMinimumScale( rendererScale, minScale ) ) { return nullptr; } diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 81b8326760a7..01dc8881ea7f 100644 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -169,6 +169,7 @@ set(TESTS testqgsrenderers.cpp testqgsrulebasedrenderer.cpp testqgsruntimeprofiler.cpp + testqgsscaleutils.cpp testqgssensorthingsconnection.cpp testqgssettings.cpp testqgssettingsentry.cpp diff --git a/tests/src/core/testqgsmaplayer.cpp b/tests/src/core/testqgsmaplayer.cpp index a448585ec00a..ce6100bf266a 100644 --- a/tests/src/core/testqgsmaplayer.cpp +++ b/tests/src/core/testqgsmaplayer.cpp @@ -188,9 +188,13 @@ void TestQgsMapLayer::isInScaleRange_data() QTest::newRow( "in the middle" ) << 3000.0 << true; QTest::newRow( "too low" ) << 1000.0 << false; QTest::newRow( "too high" ) << 6000.0 << false; - QTest::newRow( "max is not inclusive" ) << 5000.0 << false; - QTest::newRow( "min is inclusive" ) << 2500.0 << true; - QTest::newRow( "min is inclusive even with conversion errors" ) << static_cast< double >( 1.0f / ( ( float )1.0 / 2500.0 ) ) << true; + QTest::newRow( "min is not inclusive" ) << 5000.0 << false; + QTest::newRow( "max is inclusive" ) << 2500.0 << true; + QTest::newRow( "max is inclusive even with conversion errors" ) << static_cast< double >( 1.0f / ( ( float )1.0 / 2500.0 ) ) << true; + QTest::newRow( "max is inclusive even with non-round scales (below)" ) << 2499.9999999966526 << true; + QTest::newRow( "max is inclusive even with non-round scales (above)" ) << 2500.0000000027226 << true; + QTest::newRow( "min is exclusive even with non-round scales (below)" ) << 4999.999999997278 << false; + QTest::newRow( "min is exclusive even with non-round scales (above)" ) << 5000.000000003348 << false; } void TestQgsMapLayer::isInScaleRange() diff --git a/tests/src/core/testqgsscaleutils.cpp b/tests/src/core/testqgsscaleutils.cpp new file mode 100644 index 000000000000..457fb9d26790 --- /dev/null +++ b/tests/src/core/testqgsscaleutils.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** + testqgsscaleutils.cpp + -------------------------------------- + Date : October 2024 + Copyright : (C) 2024 by Germán Carrillo + Email : gcarrillo at linuxmail 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 "qgstest.h" +#include +#include + +#include "qgsscaleutils.h" + +class TestQgsScaleUtils: public QObject +{ + Q_OBJECT + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void testMaximumScaleComparisons_data(); + void testMaximumScaleComparisons(); + void testMinimumScaleComparisons_data(); + void testMinimumScaleComparisons(); +}; + +void TestQgsScaleUtils::initTestCase() +{ + // Runs once before any tests are run + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + QgsApplication::init(); + QgsApplication::initQgis(); +} + +void TestQgsScaleUtils::cleanupTestCase() +{ + // Runs once after all tests are run + QgsApplication::exitQgis(); +} + +void TestQgsScaleUtils::testMaximumScaleComparisons_data() +{ + QTest::addColumn( "scale" ); + QTest::addColumn( "maxScale" ); + QTest::addColumn( "expected" ); + + QTest::newRow( "same scales" ) << 2500.0 << 2500.0 << false; + QTest::newRow( "same scales, non-round below" ) << 2499.9999999966526 << 2500.0 << false; + QTest::newRow( "same scales, non-round above" ) << 2500.0000000027226 << 2500.0 << false; + QTest::newRow( "scale greater than max scale" ) << 3000.0 << 2500.0 << false; + QTest::newRow( "scale less than max scale" ) << 2000.0 << 2500.0 << true; +} + +void TestQgsScaleUtils::testMaximumScaleComparisons() +{ + QFETCH( double, scale ); + QFETCH( double, maxScale ); + QFETCH( bool, expected ); + + QCOMPARE( QgsScaleUtils::lessThanMaximumScale( scale, maxScale ), expected ); +} + +void TestQgsScaleUtils::testMinimumScaleComparisons_data() +{ + QTest::addColumn( "scale" ); + QTest::addColumn( "minScale" ); + QTest::addColumn( "expected" ); + + QTest::newRow( "same scales" ) << 5000.0 << 5000.0 << true; + QTest::newRow( "same scales, non-round below" ) << 4999.999999997278 << 5000.0 << true; + QTest::newRow( "same scales, non-round above" ) << 5000.000000003348 << 5000.0 << true; + QTest::newRow( "scale greater than min scale" ) << 10000.0 << 5000.0 << true; + QTest::newRow( "scale less than min scale" ) << 3000.0 << 5000.0 << false; +} + +void TestQgsScaleUtils::testMinimumScaleComparisons() +{ + QFETCH( double, scale ); + QFETCH( double, minScale ); + QFETCH( bool, expected ); + + QCOMPARE( QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, minScale ), expected ); +} + +QGSTEST_MAIN( TestQgsScaleUtils ) +#include "testqgsscaleutils.moc" + + + +