Navigation Menu

Skip to content

Commit

Permalink
Add api to set the maximal extent for a project
Browse files Browse the repository at this point in the history
If set, this extent will be used when zooming to full extent (or for the
full extent for a map overview frame) instead of the extent calculated
from all map layers.

The intention is to eventually allow users a way to manually set their
desired "area of interest" for a project, so that zooming to full extent
won't zoom all the way out when the project contains global or national
datasets...
  • Loading branch information
nyalldawson committed Nov 23, 2020
1 parent 5bd0058 commit 483cb8d
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 17 deletions.
49 changes: 47 additions & 2 deletions python/core/auto_generated/qgsprojectviewsettings.sip.in
Expand Up @@ -22,9 +22,11 @@ map canvas, e.g. map scales and default view extent for the project.
%End
public:

QgsProjectViewSettings( QObject *parent = 0 );
QgsProjectViewSettings( QgsProject *project = 0 );
%Docstring
Constructor for QgsProjectViewSettings with the specified ``parent`` object.
Constructor for QgsProjectViewSettings for the specified ``project``.

Ownership is transferred to the ``project``.
%End

void reset();
Expand Down Expand Up @@ -62,6 +64,49 @@ when this project is opened.
view used when the project is opened for the very first time.

.. seealso:: :py:func:`defaultViewExtent`
%End

QgsReferencedRectangle presetFullExtent() const;
%Docstring
Returns the project's preset full extent.

This extent represents the maximal limits of the project. The full extent defaults to a null rectangle,
which indicates that the maximal extent should be calculated based on the layers in the project.

.. seealso:: :py:func:`setPresetFullExtent`

.. seealso:: :py:func:`fullExtent`

.. versionadded:: 3.18
%End

void setPresetFullExtent( const QgsReferencedRectangle &extent );
%Docstring
Sets the project's preset full ``extent``.

This extent represents the maximal limits of the project. Setting the full ``extent`` to a null rectangle
indicates that the maximal extent should be calculated based on the layers in the project.

.. seealso:: :py:func:`setPresetFullExtent`

.. seealso:: :py:func:`fullExtent`

.. versionadded:: 3.18
%End

QgsReferencedRectangle fullExtent() const;
%Docstring
Returns the full extent of the project, which represents the maximal limits of the project.

The returned extent will be in the project's CRS.

If the :py:func:`~QgsProjectViewSettings.presetFullExtent` is non null, this will be returned as the full extent.
Otherwise the full extent will be calculated based on the extent of all layers
in the project.

.. seealso:: :py:func:`presetFullExtent`

.. seealso:: :py:func:`setPresetFullExtent`
%End

void setMapScales( const QVector<double> &scales );
Expand Down
114 changes: 112 additions & 2 deletions src/core/qgsprojectviewsettings.cpp
Expand Up @@ -15,17 +15,21 @@

#include "qgsprojectviewsettings.h"
#include "qgis.h"
#include "qgsproject.h"
#include "qgslogger.h"
#include <QDomElement>

QgsProjectViewSettings::QgsProjectViewSettings( QObject *parent )
: QObject( parent )
QgsProjectViewSettings::QgsProjectViewSettings( QgsProject *project )
: QObject( project )
, mProject( project )
{

}

void QgsProjectViewSettings::reset()
{
mDefaultViewExtent = QgsReferencedRectangle();
mPresetFullExtent = QgsReferencedRectangle();
if ( mUseProjectScales || !mMapScales.empty() )
{
mUseProjectScales = false;
Expand All @@ -44,6 +48,85 @@ void QgsProjectViewSettings::setDefaultViewExtent( const QgsReferencedRectangle
mDefaultViewExtent = extent;
}

QgsReferencedRectangle QgsProjectViewSettings::presetFullExtent() const
{
return mPresetFullExtent;
}

void QgsProjectViewSettings::setPresetFullExtent( const QgsReferencedRectangle &extent )
{
mPresetFullExtent = extent;
}

QgsReferencedRectangle QgsProjectViewSettings::fullExtent() const
{
if ( !mProject )
return mPresetFullExtent;

if ( !mPresetFullExtent.isNull() )
{
QgsCoordinateTransform ct( mPresetFullExtent.crs(), mProject->crs(), mProject->transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
return QgsReferencedRectangle( ct.transformBoundingBox( mPresetFullExtent ), mProject->crs() );
}
else
{
// reset the map canvas extent since the extent may now be smaller
// We can't use a constructor since QgsRectangle normalizes the rectangle upon construction
QgsRectangle fullExtent;
fullExtent.setMinimal();

// iterate through the map layers and test each layers extent
// against the current min and max values
const QMap<QString, QgsMapLayer *> layers = mProject->mapLayers( true );
QgsDebugMsgLevel( QStringLiteral( "Layer count: %1" ).arg( layers.count() ), 5 );
for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
{
QgsDebugMsgLevel( "Updating extent using " + it.value()->name(), 5 );
QgsDebugMsgLevel( "Input extent: " + it.value()->extent().toString(), 5 );

if ( it.value()->extent().isNull() )
continue;

// Layer extents are stored in the coordinate system (CS) of the
// layer. The extent must be projected to the canvas CS
QgsCoordinateTransform ct( it.value()->crs(), mProject->crs(), mProject->transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
const QgsRectangle extent = ct.transformBoundingBox( it.value()->extent() );

QgsDebugMsgLevel( "Output extent: " + extent.toString(), 5 );
fullExtent.combineExtentWith( extent );
}

if ( fullExtent.width() == 0.0 || fullExtent.height() == 0.0 )
{
// If all of the features are at the one point, buffer the
// rectangle a bit. If they are all at zero, do something a bit
// more crude.

if ( fullExtent.xMinimum() == 0.0 && fullExtent.xMaximum() == 0.0 &&
fullExtent.yMinimum() == 0.0 && fullExtent.yMaximum() == 0.0 )
{
fullExtent.set( -1.0, -1.0, 1.0, 1.0 );
}
else
{
const double padFactor = 1e-8;
double widthPad = fullExtent.xMinimum() * padFactor;
double heightPad = fullExtent.yMinimum() * padFactor;
double xmin = fullExtent.xMinimum() - widthPad;
double xmax = fullExtent.xMaximum() + widthPad;
double ymin = fullExtent.yMinimum() - heightPad;
double ymax = fullExtent.yMaximum() + heightPad;
fullExtent.set( xmin, ymin, xmax, ymax );
}
}

QgsDebugMsgLevel( "Full extent: " + fullExtent.toString(), 5 );
return QgsReferencedRectangle( fullExtent, mProject->crs() );
}
}

void QgsProjectViewSettings::setMapScales( const QVector<double> &scales )
{
// sort scales in descending order
Expand Down Expand Up @@ -116,6 +199,22 @@ bool QgsProjectViewSettings::readXml( const QDomElement &element, const QgsReadW
mDefaultViewExtent = QgsReferencedRectangle();
}

QDomElement presetViewElement = element.firstChildElement( QStringLiteral( "PresetFullExtent" ) );
if ( !presetViewElement.isNull() )
{
double xMin = presetViewElement.attribute( QStringLiteral( "xmin" ) ).toDouble();
double yMin = presetViewElement.attribute( QStringLiteral( "ymin" ) ).toDouble();
double xMax = presetViewElement.attribute( QStringLiteral( "xmax" ) ).toDouble();
double yMax = presetViewElement.attribute( QStringLiteral( "ymax" ) ).toDouble();
QgsCoordinateReferenceSystem crs;
crs.readXml( presetViewElement );
mPresetFullExtent = QgsReferencedRectangle( QgsRectangle( xMin, yMin, xMax, yMax ), crs );
}
else
{
mPresetFullExtent = QgsReferencedRectangle();
}

return true;
}

Expand Down Expand Up @@ -144,5 +243,16 @@ QDomElement QgsProjectViewSettings::writeXml( QDomDocument &doc, const QgsReadWr
element.appendChild( defaultViewElement );
}

if ( !mPresetFullExtent.isNull() )
{
QDomElement presetViewElement = doc.createElement( QStringLiteral( "PresetFullExtent" ) );
presetViewElement.setAttribute( QStringLiteral( "xmin" ), qgsDoubleToString( mPresetFullExtent.xMinimum() ) );
presetViewElement.setAttribute( QStringLiteral( "ymin" ), qgsDoubleToString( mPresetFullExtent.yMinimum() ) );
presetViewElement.setAttribute( QStringLiteral( "xmax" ), qgsDoubleToString( mPresetFullExtent.xMaximum() ) );
presetViewElement.setAttribute( QStringLiteral( "ymax" ), qgsDoubleToString( mPresetFullExtent.yMaximum() ) );
mPresetFullExtent.crs().writeXml( presetViewElement, doc );
element.appendChild( presetViewElement );
}

return element;
}
49 changes: 47 additions & 2 deletions src/core/qgsprojectviewsettings.h
Expand Up @@ -23,6 +23,7 @@
class QDomElement;
class QgsReadWriteContext;
class QDomDocument;
class QgsProject;

/**
* Contains settings and properties relating to how a QgsProject should be displayed inside
Expand All @@ -38,9 +39,11 @@ class CORE_EXPORT QgsProjectViewSettings : public QObject
public:

/**
* Constructor for QgsProjectViewSettings with the specified \a parent object.
* Constructor for QgsProjectViewSettings for the specified \a project.
*
* Ownership is transferred to the \a project.
*/
QgsProjectViewSettings( QObject *parent = nullptr );
QgsProjectViewSettings( QgsProject *project = nullptr );

/**
* Resets the settings to a default state.
Expand Down Expand Up @@ -75,6 +78,46 @@ class CORE_EXPORT QgsProjectViewSettings : public QObject
*/
void setDefaultViewExtent( const QgsReferencedRectangle &extent );

/**
* Returns the project's preset full extent.
*
* This extent represents the maximal limits of the project. The full extent defaults to a null rectangle,
* which indicates that the maximal extent should be calculated based on the layers in the project.
*
* \see setPresetFullExtent()
* \see fullExtent()
*
* \since QGIS 3.18
*/
QgsReferencedRectangle presetFullExtent() const;

/**
* Sets the project's preset full \a extent.
*
* This extent represents the maximal limits of the project. Setting the full \a extent to a null rectangle
* indicates that the maximal extent should be calculated based on the layers in the project.
*
* \see setPresetFullExtent()
* \see fullExtent()
*
* \since QGIS 3.18
*/
void setPresetFullExtent( const QgsReferencedRectangle &extent );

/**
* Returns the full extent of the project, which represents the maximal limits of the project.
*
* The returned extent will be in the project's CRS.
*
* If the presetFullExtent() is non null, this will be returned as the full extent.
* Otherwise the full extent will be calculated based on the extent of all layers
* in the project.
*
* \see presetFullExtent()
* \see setPresetFullExtent()
*/
QgsReferencedRectangle fullExtent() const;

/**
* Sets the list of custom project map \a scales.
*
Expand Down Expand Up @@ -143,9 +186,11 @@ class CORE_EXPORT QgsProjectViewSettings : public QObject

private:

QgsProject *mProject = nullptr;
QVector<double> mMapScales;
bool mUseProjectScales = false;
QgsReferencedRectangle mDefaultViewExtent;
QgsReferencedRectangle mPresetFullExtent;
};

#endif // QGSPROJECTVIEWSETTINGS_H
22 changes: 16 additions & 6 deletions src/gui/qgsmapcanvas.cpp
Expand Up @@ -1049,20 +1049,30 @@ void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, co
}
QTextStream myStream( &myWorldFile );
myStream << QgsMapSettingsUtils::worldFileContent( mapSettings() );
} // saveAsImage


}

QgsRectangle QgsMapCanvas::extent() const
{
return mapSettings().visibleExtent();
} // extent
}

QgsRectangle QgsMapCanvas::fullExtent() const
{
return mapSettings().fullExtent();
} // extent
const QgsReferencedRectangle extent = QgsProject::instance()->viewSettings()->fullExtent();
QgsCoordinateTransform ct( extent.crs(), QgsProject::instance()->crs(), QgsProject::instance()->transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
QgsRectangle rect;
try
{
rect = ct.transformBoundingBox( extent );
}
catch ( QgsCsException & )
{
rect = mapSettings().fullExtent();
}

return rect;
}

void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
{
Expand Down
19 changes: 15 additions & 4 deletions src/gui/qgsmapoverviewcanvas.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsmapoverviewcanvas.h"
#include "qgsmaprenderersequentialjob.h"
#include "qgsmaptopixel.h"
#include "qgsprojectviewsettings.h"

#include <QPainter>
#include <QPainterPath>
Expand Down Expand Up @@ -249,11 +250,21 @@ void QgsMapOverviewCanvas::setLayers( const QList<QgsMapLayer *> &layers )

void QgsMapOverviewCanvas::updateFullExtent()
{
QgsReferencedRectangle extent = QgsProject::instance()->viewSettings()->fullExtent();
QgsCoordinateTransform ct( extent.crs(), mSettings.destinationCrs(), QgsProject::instance()->transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
QgsRectangle rect;
if ( mSettings.hasValidSettings() )
rect = mSettings.fullExtent();
else
rect = mMapCanvas->fullExtent();
try
{
rect = ct.transformBoundingBox( extent );
}
catch ( QgsCsException & )
{
if ( mSettings.hasValidSettings() )
rect = mSettings.fullExtent();
else
rect = mMapCanvas->fullExtent();
}

// expand a bit to keep features on margin
rect.scale( 1.1 );
Expand Down

0 comments on commit 483cb8d

Please sign in to comment.