Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] Make scale dependent visibility more robust when handling non-round scales (denominators) #58968

2 changes: 1 addition & 1 deletion python/PyQt6/core/auto_additions/qgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 31 additions & 1 deletion python/PyQt6/core/auto_generated/qgis.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -3511,6 +3511,36 @@ QVariant data types (such as strings, numeric values, dates and times)
.. seealso:: :py:func:`qgsVariantLessThan`
%End

bool qgsEqualToOrGreaterThanMinimumScale( 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:`qgsLessThanMaximumScale`

.. versionadded:: 3.40
%End

bool qgsLessThanMaximumScale( 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:`qgsEqualToOrGreaterThanMinimumScale`

.. versionadded:: 3.40
%End


bool operator> ( const QVariant &v1, const QVariant &v2 );

Expand Down
2 changes: 1 addition & 1 deletion python/core/auto_additions/qgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 31 additions & 1 deletion python/core/auto_generated/qgis.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -3511,6 +3511,36 @@ QVariant data types (such as strings, numeric values, dates and times)
.. seealso:: :py:func:`qgsVariantLessThan`
%End

bool qgsEqualToOrGreaterThanMinimumScale( 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:`qgsLessThanMaximumScale`

.. versionadded:: 3.40
%End

bool qgsLessThanMaximumScale( 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:`qgsEqualToOrGreaterThanMinimumScale`

.. versionadded:: 3.40
%End


bool operator> ( const QVariant &v1, const QVariant &v2 );

Expand Down
9 changes: 6 additions & 3 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12120,12 +12120,15 @@ void QgisApp::zoomToLayerScale()
if ( layer && layer->hasScaleBasedVisibility() )
{
const double scale = mMapCanvas->scale();
if ( scale > layer->minimumScale() && layer->minimumScale() > 0 )

if ( layer->minimumScale() > 0 && qgsEqualToOrGreaterThanMinimumScale( 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 && qgsLessThanMaximumScale( scale, layer->maximumScale() ) )
{
// maximum is inclusive ( < --> out of range ), pass maximum
mMapCanvas->zoomScale( layer->maximumScale() );
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/qgsapplayertreeviewmenuprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
8 changes: 6 additions & 2 deletions src/core/labeling/qgsrulebasedlabeling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,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 ) && qgsLessThanMaximumScale( scale, mMaximumScale ) )
return false;
if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )

// minScale is exclusive ( >= --> no label )
if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && qgsEqualToOrGreaterThanMinimumScale( scale, mMinimumScale ) )
return false;
return true;
}
Expand Down
11 changes: 11 additions & 0 deletions src/core/qgis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ bool qgsVariantGreaterThan( const QVariant &lhs, const QVariant &rhs )
return ! qgsVariantLessThan( lhs, rhs );
}

bool qgsEqualToOrGreaterThanMinimumScale( const double scale, const double minScale )
{
return scale > minScale || qgsDoubleNear( scale, minScale, 1E-8 );
}

bool qgsLessThanMaximumScale( const double scale, const double maxScale )
{
return scale < maxScale && !qgsDoubleNear( scale, maxScale, 1E-8 );
}


QString qgsVsiPrefix( const QString &path )
{
return QgsGdalUtils::vsiPrefixForPath( path );
Expand Down
32 changes: 31 additions & 1 deletion src/core/qgis.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -6256,6 +6258,34 @@ CORE_EXPORT bool qgsVariantEqual( const QVariant &lhs, const QVariant &rhs );
*/
CORE_EXPORT bool qgsVariantGreaterThan( const QVariant &lhs, const QVariant &rhs );

/**
* 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 qgsLessThanMaximumScale()
*
* \since QGIS 3.40
*/
CORE_EXPORT bool qgsEqualToOrGreaterThanMinimumScale( const double scale, const double minScale );
nyalldawson marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 qgsEqualToOrGreaterThanMinimumScale()
*
* \since QGIS 3.40
*/
CORE_EXPORT bool qgsLessThanMaximumScale( const double scale, const double maxScale );

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)

/**
Expand Down
12 changes: 6 additions & 6 deletions src/core/qgsdiagramrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,16 +538,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 && qgsLessThanMaximumScale( 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 && qgsEqualToOrGreaterThanMinimumScale( rendererScale, minScale ) )
{
return QSizeF();
}
Expand Down Expand Up @@ -947,16 +947,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 && qgsLessThanMaximumScale( 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 && qgsEqualToOrGreaterThanMinimumScale( rendererScale, minScale ) )
{
continue;
}
Expand Down
8 changes: 5 additions & 3 deletions src/core/qgsmaplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,9 +1153,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 || !qgsLessThanMaximumScale( scale, mMinScale ) )
&& ( mMaxScale == 0 || !qgsEqualToOrGreaterThanMinimumScale( scale, mMaxScale ) ) );
}

bool QgsMapLayer::hasScaleBasedVisibility() const
Expand Down
9 changes: 7 additions & 2 deletions src/core/symbology/qgsrulebasedrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,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 ) && qgsLessThanMaximumScale( scale, mMaximumScale ) )
return false;
if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )

// minScale is exclusive ( >= --> no render )
if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && qgsEqualToOrGreaterThanMinimumScale( scale, mMinimumScale ) )
return false;

return true;
}

Expand Down
6 changes: 3 additions & 3 deletions src/core/vector/qgsvectorlayerdiagramprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,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 && qgsLessThanMaximumScale( 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 && qgsEqualToOrGreaterThanMinimumScale( rendererScale, minScale ) )
{
return nullptr;
}
Expand Down
Loading
Loading