Skip to content
Permalink
Browse files

Merge pull request #2888 from pvalsecc/scaleDenom

Scale based layer visibility cosmetics
  • Loading branch information
nyalldawson committed Mar 9, 2016
2 parents c55ef38 + 556b621 commit 01f615d0d089ece02ece42ea4abc078b88e43f5d
@@ -230,6 +230,7 @@ class QgsLayerTreeModel : QAbstractItemModel

//! emit dataChanged() for layer tree node items
void recursivelyEmitDataChanged( const QModelIndex& index = QModelIndex() );
void refreshScaleBasedLayers( const QModelIndex& index = QModelIndex() );

static const QIcon& iconGroup();

@@ -235,6 +235,12 @@ class QGis
/** Default highlight line/outline minimum width in mm.
* @note added in 2.3 */
static double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM;

/** 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 >=.
* @note added in 2.15*/
static double SCALE_PRECISION;
};


@@ -106,6 +106,9 @@ class QgsLabel
void setScaleBasedVisibility( bool theVisibilityFlag );
bool scaleBasedVisibility() const;

/** Return true if the label is visible at the given scale */
bool isInScaleRange( double scale ) const;

private:
QgsLabel (); // pretend that constructor is private for now
QgsLabel( const QgsLabel& rh );
@@ -492,6 +492,11 @@ class QgsMapLayer : QObject
*/
QgsMapLayerStyleManager* styleManager() const;

/**
* @returns true if the layer is visible at the given scale.
*/
bool isInScaleRange( double scale ) const;

/** Returns the minimum scale denominator at which the layer is visible.
* Scale based visibility is only used if hasScaleBasedVisibility is true.
* @returns minimum scale denominator at which the layer will render
@@ -7938,6 +7938,31 @@ void QgisApp::setLayerScaleVisibility()
delete dlg;
}

void QgisApp::zoomToLayerScale()
{
if ( !mLayerTreeView )
return;

QList<QgsMapLayer*> layers = mLayerTreeView->selectedLayers();

if ( layers.length() < 1 )
return;

QgsMapLayer* layer = mLayerTreeView->currentLayer();
if ( layer && layer->hasScaleBasedVisibility() )
{
const double scale = mMapCanvas->scale();
if ( scale > layer->maximumScale() )
{
mMapCanvas->zoomScale( layer->maximumScale() * QGis::SCALE_PRECISION );
}
else if ( scale <= layer->minimumScale() )
{
mMapCanvas->zoomScale( layer->minimumScale() );
}
}
}

void QgisApp::setLayerCRS()
{
if ( !( mLayerTreeView && mLayerTreeView->currentLayer() ) )
@@ -744,6 +744,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void duplicateLayers( const QList<QgsMapLayer *>& lyrList = QList<QgsMapLayer *>() );
//! Set Scale visibility of selected layers
void setLayerScaleVisibility();
void zoomToLayerScale();
//! Set CRS of a layer
void setLayerCRS();
//! Assign layer CRS to project
@@ -11,6 +11,7 @@
#include "qgslayertreemodel.h"
#include "qgslayertreemodellegendnode.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerstyleguiutils.h"
#include "qgsmaplayerregistry.h"
#include "qgsproject.h"
@@ -105,6 +106,9 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
// set layer scale visibility
menu->addAction( tr( "&Set Layer Scale Visibility" ), QgisApp::instance(), SLOT( setLayerScaleVisibility() ) );

if ( !vlayer->isInScaleRange( mCanvas->scale() ) )
menu->addAction( tr( "&Zoom to Layer Scale" ), QgisApp::instance(), SLOT( zoomToLayerScale() ) );

// set layer crs
menu->addAction( QgsApplication::getThemeIcon( "/mActionSetCRS.png" ), tr( "&Set Layer CRS" ), QgisApp::instance(), SLOT( setLayerCRS() ) );

@@ -4109,11 +4109,10 @@ bool QgsDxfExport::layerIsScaleBasedVisible( const QgsMapLayer* layer ) const
if ( !layer )
return false;

if ( mSymbologyExport == QgsDxfExport::NoSymbology || !layer->hasScaleBasedVisibility() )
if ( mSymbologyExport == QgsDxfExport::NoSymbology )
return true;

return layer->minimumScale() < mSymbologyScaleDenominator &&
layer->maximumScale() > mSymbologyScaleDenominator;
return layer->isInScaleRange( mSymbologyScaleDenominator );
}

QString QgsDxfExport::layerName( const QString &id, const QgsFeature &f ) const
@@ -271,6 +271,19 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const
f.setUnderline( true );
return f;
}
else if ( role == Qt::ForegroundRole )
{
QBrush brush( Qt::black, Qt::SolidPattern );
if ( QgsLayerTree::isLayer( node ) )
{
const QgsMapLayer* layer = QgsLayerTree::toLayer( node )->layer();
if ( layer && !layer->isInScaleRange( mLegendMapViewScale ) )
{
brush.setColor( Qt::lightGray );
}
}
return brush;
}
else if ( role == Qt::ToolTipRole )
{
if ( QgsLayerTree::isLayer( node ) )
@@ -633,6 +646,8 @@ void QgsLayerTreeModel::setLegendMapViewData( double mapUnitsPerPixel, int dpi,

// now invalidate legend nodes!
legendInvalidateMapBasedData();

refreshScaleBasedLayers();
}

void QgsLayerTreeModel::legendMapViewData( double* mapUnitsPerPixel, int* dpi, double* scale )
@@ -919,6 +934,24 @@ void QgsLayerTreeModel::recursivelyEmitDataChanged( const QModelIndex& idx )
recursivelyEmitDataChanged( index( i, 0, idx ) );
}

void QgsLayerTreeModel::refreshScaleBasedLayers( const QModelIndex& idx )
{
QgsLayerTreeNode* node = index2node( idx );
if ( !node )
return;

if ( node->nodeType() == QgsLayerTreeNode::NodeLayer )
{
const QgsMapLayer* layer = QgsLayerTree::toLayer( node )->layer();
if ( layer->hasScaleBasedVisibility() )
{
emit dataChanged( idx, idx );
}
}
int count = node->children().count();
for ( int i = 0; i < count; ++i )
refreshScaleBasedLayers( index( i, 0, idx ) );
}

Qt::DropActions QgsLayerTreeModel::supportedDropActions() const
{
@@ -254,6 +254,8 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel

//! emit dataChanged() for layer tree node items
void recursivelyEmitDataChanged( const QModelIndex& index = QModelIndex() );
//! emit dataChanged() for scale dependent layers
void refreshScaleBasedLayers( const QModelIndex& index = QModelIndex() );

static const QIcon& iconGroup();

@@ -87,6 +87,8 @@ double QGis::DEFAULT_HIGHLIGHT_BUFFER_MM = 0.5;

double QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM = 1.0;

double QGis::SCALE_PRECISION = 0.9999999999;

// description strings for units
// Order must match enum indices
const char* QGis::qgisUnitTypes[] =
@@ -246,6 +246,12 @@ class CORE_EXPORT QGis
* @note added in 2.3 */
static double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM;

/** 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 >=.
* @note added in 2.15*/
static double SCALE_PRECISION;

private:
// String representation of unit types (set in qgis.cpp)
static const char *qgisUnitTypes[];
@@ -1414,3 +1414,9 @@ float QgsLabel::maxScale() const
{
return mMaxScale;
}

bool QgsLabel::isInScaleRange( double scale ) const
{
return !mScaleBasedVisibility ||
( mMinScale * QGis::SCALE_PRECISION < scale && scale * QGis::SCALE_PRECISION < mMaxScale );
}
@@ -146,6 +146,9 @@ class CORE_EXPORT QgsLabel
void setScaleBasedVisibility( bool theVisibilityFlag );
bool scaleBasedVisibility() const;

/** Return true if the label is visible at the given scale */
bool isInScaleRange( double scale ) const;

private:
/** Does the actual rendering of a label at the given point */
void renderLabel( QgsRenderContext &renderContext, QgsPoint point,
@@ -59,7 +59,7 @@ void QgsMapHitTest::run()

if ( !mOnlyExpressions )
{
if ( vl->hasScaleBasedVisibility() && ( mSettings.scale() < vl->minimumScale() || mSettings.scale() > vl->maximumScale() ) )
if ( !vl->isInScaleRange( mSettings.scale() ) )
{
mHitTest[vl] = SymbolV2Set(); // no symbols -> will not be shown
mHitTestRuleKey[vl] = SymbolV2Set();
@@ -925,6 +925,10 @@ void QgsMapLayer::connectNotify( const char * signal )
} // QgsMapLayer::connectNotify
#endif

bool QgsMapLayer::isInScaleRange( double scale ) const
{
return !mScaleBasedVisibility || ( mMinScale * QGis::SCALE_PRECISION < scale && scale < mMaxScale );
}

void QgsMapLayer::toggleScaleBasedVisibility( bool theVisibilityFlag )
{
@@ -511,6 +511,11 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
QgsMapLayerStyleManager* styleManager() const;

/**
* @returns true if the layer is visible at the given scale.
*/
bool isInScaleRange( double scale ) const;

/** Returns the minimum scale denominator at which the layer is visible.
* Scale based visibility is only used if hasScaleBasedVisibility is true.
* @returns minimum scale denominator at which the layer will render
@@ -394,7 +394,7 @@ void QgsMapRenderer::render( QPainter* painter, double* forceWidthScale )
mypContextPainter->setCompositionMode( ml->blendMode() );
}

if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() <= mScale && mScale < ml->maximumScale() ) || mOverview )
if ( ml->isInScaleRange( mScale ) || mOverview )
{
connect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) );

@@ -584,7 +584,7 @@ void QgsMapRenderer::render( QPainter* painter, double* forceWidthScale )
{
// only make labels if the layer is visible
// after scale dep viewing settings are checked
if ( !ml->hasScaleBasedVisibility() || ( ml->minimumScale() < mScale && mScale < ml->maximumScale() ) )
if ( ml->isInScaleRange( mScale ) )
{
bool split = false;

@@ -332,7 +332,7 @@ void QgsMapRendererJob::drawOldLabeling( const QgsMapSettings& settings, QgsRend

// only make labels if the layer is visible
// after scale dep viewing settings are checked
if ( ml->hasScaleBasedVisibility() && ( settings.scale() < ml->minimumScale() || settings.scale() > ml->maximumScale() ) )
if ( !ml->isInScaleRange( settings.scale() ) )
continue;

const QgsCoordinateTransform* ct = nullptr;
@@ -207,7 +207,7 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabelin
.arg( ml->blendMode() )
);

if ( ml->hasScaleBasedVisibility() && ( mSettings.scale() < ml->minimumScale() || mSettings.scale() > ml->maximumScale() ) ) //|| mOverview )
if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
{
QgsDebugMsg( "Layer not rendered because it is not within the defined visibility scale range" );
continue;
@@ -320,9 +320,7 @@ void QgsVectorLayer::drawLabels( QgsRenderContext& rendererContext )
QgsDebugMsg( "Starting draw of labels: " + id() );

if ( mRendererV2 && mLabelOn && mLabel &&
( !mLabel->scaleBasedVisibility() ||
( mLabel->minScale() <= rendererContext.rendererScale() &&
rendererContext.rendererScale() <= mLabel->maxScale() ) ) )
mLabel->isInScaleRange( rendererContext.rendererScale() ) )
{
QgsAttributeList attributes;
Q_FOREACH ( const QString& attrName, mRendererV2->usedAttributes() )
@@ -245,9 +245,7 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
}

const QgsMapSettings& ms = mCanvas->mapSettings();
if ( layer()->hasScaleBasedVisibility() &&
( layer()->minimumScale() > ms.scale() ||
layer()->maximumScale() <= ms.scale() ) )
if ( !layer()->isInScaleRange( ms.scale() ) )
{
QgsDebugMsg( "Out of scale limits" );
}
@@ -200,9 +200,7 @@ bool QgsMapToolIdentify::identifyVectorLayer( QList<IdentifyResult> *results, Qg
if ( !layer || !layer->hasGeometryType() )
return false;

if ( layer->hasScaleBasedVisibility() &&
( layer->minimumScale() > mCanvas->mapSettings().scale() ||
layer->maximumScale() <= mCanvas->mapSettings().scale() ) )
if ( !layer->isInScaleRange( mCanvas->mapSettings().scale() ) )
{
QgsDebugMsg( "Out of scale limits" );
return false;
@@ -55,12 +55,15 @@ class TestQgsMapLayer : public QObject
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 init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void isValid();

void setBlendMode();

void isInScaleRange();

private:
QgsMapLayer * mpLayer;
};
@@ -75,6 +78,10 @@ void TestQgsMapLayer::initTestCase()
QgsApplication::initQgis();
QgsApplication::showSettings();

}

void TestQgsMapLayer::init()
{
//create some objects that will be used in all tests...
//create a map layer that will be used in all tests...
QString myFileName( TEST_DATA_DIR ); //defined in CmakeLists.txt
@@ -84,6 +91,11 @@ void TestQgsMapLayer::initTestCase()
myMapFileInfo.completeBaseName(), "ogr" );
}

void TestQgsMapLayer::cleanup()
{
delete mpLayer;
}

void TestQgsMapLayer::cleanupTestCase()
{
QgsApplication::exitQgis();
@@ -107,5 +119,18 @@ void TestQgsMapLayer::setBlendMode()
QCOMPARE( mpLayer->blendMode(), QPainter::CompositionMode_Screen );
}

void TestQgsMapLayer::isInScaleRange()
{
mpLayer->setMinimumScale( 2500.0f );
mpLayer->setMaximumScale( 5000.0f );
mpLayer->setScaleBasedVisibility( true );
QCOMPARE( mpLayer->isInScaleRange( 3000.0f ), true ); // In the middle
QCOMPARE( mpLayer->isInScaleRange( 1000.0f ), false ); // Too low
QCOMPARE( mpLayer->isInScaleRange( 6000.0f ), false ); // Too high
QCOMPARE( mpLayer->isInScaleRange( 5000.0f ), false ); // Max is not inclusive
QCOMPARE( mpLayer->isInScaleRange( 2500.0f ), true ); // Min is inclusive
QCOMPARE( mpLayer->isInScaleRange( 1.0f / (( float )1.0 / 2500.0 ) ), true ); // Min is inclusive even with conversion errors
}

QTEST_MAIN( TestQgsMapLayer )
#include "testqgsmaplayer.moc"

0 comments on commit 01f615d

Please sign in to comment.
You can’t perform that action at this time.