Skip to content

Commit

Permalink
Move logic for maintaining exact extent when CRS changes from
Browse files Browse the repository at this point in the history
raster layer save as dialog to QgsExtentGroupBox, add tests
  • Loading branch information
nyalldawson committed May 31, 2017
1 parent af029c5 commit 9e14741
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 51 deletions.
12 changes: 7 additions & 5 deletions python/gui/qgsextentgroupbox.sip
Expand Up @@ -67,32 +67,34 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox
void setCurrentExtent( const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &currentCrs ); void setCurrentExtent( const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &currentCrs );
%Docstring %Docstring
Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes). Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes).
The current extent is usually set to match the current map canvas extent.
.. seealso:: currentExtent() .. seealso:: currentExtent()
.. seealso:: currentCrs() .. seealso:: currentCrs()
%End %End


QgsRectangle currentExtent() const; QgsRectangle currentExtent() const;
%Docstring %Docstring
Returns the current extent set for the widget. Returns the current extent set for the widget. The current extent is usually set to match the
current map canvas extent.
.. seealso:: setCurrentExtent() .. seealso:: setCurrentExtent()
.. seealso:: currentCrs() .. seealso:: currentCrs()
:rtype: QgsRectangle :rtype: QgsRectangle
%End %End


QgsCoordinateReferenceSystem currentCrs() const; QgsCoordinateReferenceSystem currentCrs() const;
%Docstring %Docstring
Returns the coordinate reference system for the current extent set for the widget. Returns the coordinate reference system for the current extent set for the widget. The current
extent and CRS usually reflects the map canvas extent and CRS.
.. seealso:: setCurrentExtent() .. seealso:: setCurrentExtent()
.. seealso:: currentExtent() .. seealso:: currentExtent()
:rtype: QgsCoordinateReferenceSystem :rtype: QgsCoordinateReferenceSystem
%End %End


void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs, bool reprojectCurrentExtent = true ); void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs );
%Docstring %Docstring
Sets the output CRS - may need to be used for transformation from original/current extent. Sets the output CRS - may need to be used for transformation from original/current extent.
Should be called as part of initialization and whenever the the output CRS is changed. Should be called as part of initialization and whenever the the output CRS is changed.
If ``reprojectCurrentExtent`` is true then the current extent will be reproject into the The current extent will be reprojected into the new output CRS.
new output CRS.
%End %End


QgsRectangle outputExtent() const; QgsRectangle outputExtent() const;
Expand Down
52 changes: 36 additions & 16 deletions src/gui/qgsextentgroupbox.cpp
Expand Up @@ -62,25 +62,45 @@ void QgsExtentGroupBox::setCurrentExtent( const QgsRectangle &currentExtent, con
mCurrentCrs = currentCrs; mCurrentCrs = currentCrs;
} }


void QgsExtentGroupBox::setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs, bool reprojectCurrentExtent ) void QgsExtentGroupBox::setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs )
{ {
if ( reprojectCurrentExtent && mOutputCrs != outputCrs ) if ( mOutputCrs != outputCrs )
{ {
try switch ( mExtentState )
{ {
QgsCoordinateTransform ct( mOutputCrs, outputCrs ); case CurrentExtent:
QgsRectangle extent = ct.transformBoundingBox( outputExtent() ); mOutputCrs = outputCrs;
mXMinLineEdit->setText( QgsRasterBlock::printValue( extent.xMinimum() ) ); setOutputExtentFromCurrent();
mXMaxLineEdit->setText( QgsRasterBlock::printValue( extent.xMaximum() ) ); break;
mYMinLineEdit->setText( QgsRasterBlock::printValue( extent.yMinimum() ) );
mYMaxLineEdit->setText( QgsRasterBlock::printValue( extent.yMaximum() ) ); case OriginalExtent:
} mOutputCrs = outputCrs;
catch ( QgsCsException & ) setOutputExtentFromOriginal();
{ break;
// can't reproject
case ProjectLayerExtent:
mOutputCrs = outputCrs;
setOutputExtentFromLayer( mExtentLayer.data() );
break;

case UserExtent:
try
{
QgsCoordinateTransform ct( mOutputCrs, outputCrs );
QgsRectangle extent = ct.transformBoundingBox( outputExtent() );
mOutputCrs = outputCrs;
setOutputExtentFromUser( extent, outputCrs );
}
catch ( QgsCsException & )
{
// can't reproject
mOutputCrs = outputCrs;
}
break;
} }

} }
mOutputCrs = outputCrs;
} }


void QgsExtentGroupBox::setOutputExtent( const QgsRectangle &r, const QgsCoordinateReferenceSystem &srcCrs, ExtentState state ) void QgsExtentGroupBox::setOutputExtent( const QgsRectangle &r, const QgsCoordinateReferenceSystem &srcCrs, ExtentState state )
Expand Down Expand Up @@ -168,7 +188,7 @@ void QgsExtentGroupBox::layerMenuAboutToShow()
QAction *act = new QAction( icon, text, mLayerMenu ); QAction *act = new QAction( icon, text, mLayerMenu );
act->setToolTip( mMapLayerModel->data( index, Qt::ToolTipRole ).toString() ); act->setToolTip( mMapLayerModel->data( index, Qt::ToolTipRole ).toString() );
QString layerId = mMapLayerModel->data( index, QgsMapLayerModel::LayerIdRole ).toString(); QString layerId = mMapLayerModel->data( index, QgsMapLayerModel::LayerIdRole ).toString();
if ( mExtentState == ProjectLayerExtent && mExtentLayerId == layerId ) if ( mExtentState == ProjectLayerExtent && mExtentLayer && mExtentLayer->id() == layerId )
{ {
act->setCheckable( true ); act->setCheckable( true );
act->setChecked( true ); act->setChecked( true );
Expand Down Expand Up @@ -212,7 +232,7 @@ void QgsExtentGroupBox::setOutputExtentFromLayer( const QgsMapLayer *layer )
if ( !layer ) if ( !layer )
return; return;


mExtentLayerId = layer->id(); mExtentLayer = layer;
mExtentLayerName = layer->name(); mExtentLayerName = layer->name();


setOutputExtent( layer->extent(), layer->crs(), ProjectLayerExtent ); setOutputExtent( layer->extent(), layer->crs(), ProjectLayerExtent );
Expand Down
14 changes: 8 additions & 6 deletions src/gui/qgsextentgroupbox.h
Expand Up @@ -82,20 +82,23 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::


/** /**
* Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes). * Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes).
* The current extent is usually set to match the current map canvas extent.
* \see currentExtent() * \see currentExtent()
* \see currentCrs() * \see currentCrs()
*/ */
void setCurrentExtent( const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &currentCrs ); void setCurrentExtent( const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &currentCrs );


/** /**
* Returns the current extent set for the widget. * Returns the current extent set for the widget. The current extent is usually set to match the
* current map canvas extent.
* \see setCurrentExtent() * \see setCurrentExtent()
* \see currentCrs() * \see currentCrs()
*/ */
QgsRectangle currentExtent() const { return mCurrentExtent; } QgsRectangle currentExtent() const { return mCurrentExtent; }


/** /**
* Returns the coordinate reference system for the current extent set for the widget. * Returns the coordinate reference system for the current extent set for the widget. The current
* extent and CRS usually reflects the map canvas extent and CRS.
* \see setCurrentExtent() * \see setCurrentExtent()
* \see currentExtent() * \see currentExtent()
*/ */
Expand All @@ -104,10 +107,9 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
/** /**
* Sets the output CRS - may need to be used for transformation from original/current extent. * Sets the output CRS - may need to be used for transformation from original/current extent.
* Should be called as part of initialization and whenever the the output CRS is changed. * Should be called as part of initialization and whenever the the output CRS is changed.
* If \a reprojectCurrentExtent is true then the current extent will be reproject into the * The current extent will be reprojected into the new output CRS.
* new output CRS.
*/ */
void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs, bool reprojectCurrentExtent = true ); void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs );


/** /**
* Returns the extent shown in the widget - in output CRS coordinates. * Returns the extent shown in the widget - in output CRS coordinates.
Expand Down Expand Up @@ -194,7 +196,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
QMenu *mLayerMenu = nullptr; QMenu *mLayerMenu = nullptr;
QgsMapLayerModel *mMapLayerModel = nullptr; QgsMapLayerModel *mMapLayerModel = nullptr;
QList< QAction * > mMenuActions; QList< QAction * > mMenuActions;
QString mExtentLayerId; QPointer< const QgsMapLayer > mExtentLayer;
QString mExtentLayerName; QString mExtentLayerName;


void setExtentToLayerExtent( const QString &layerId ); void setExtentToLayerExtent( const QString &layerId );
Expand Down
19 changes: 1 addition & 18 deletions src/gui/qgsrasterlayersaveasdialog.cpp
Expand Up @@ -435,24 +435,7 @@ void QgsRasterLayerSaveAsDialog::crsChanged()
{ {
if ( outputCrs() != mPreviousCrs ) if ( outputCrs() != mPreviousCrs )
{ {
mExtentGroupBox->setOutputCrs( outputCrs(), false ); mExtentGroupBox->setOutputCrs( outputCrs() );
QgsExtentGroupBox::ExtentState state = mExtentGroupBox->extentState();

// Reset extent
// We could reproject previous but that would add additional space also if
// it is was not necessary or at leas it could decrease accuracy
if ( state == QgsExtentGroupBox::OriginalExtent )
{
mExtentGroupBox->setOutputExtentFromOriginal();
}
else if ( state == QgsExtentGroupBox::CurrentExtent )
{
mExtentGroupBox->setOutputExtentFromCurrent();
}
else
{
mExtentGroupBox->setOutputExtentFromUser( mExtentGroupBox->outputExtent(), mPreviousCrs );
}


// Reset resolution // Reset resolution
if ( mResolutionRadioButton->isChecked() ) if ( mResolutionRadioButton->isChecked() )
Expand Down
66 changes: 60 additions & 6 deletions tests/src/python/test_qgsextentgroupbox.py
Expand Up @@ -15,7 +15,7 @@
import qgis # NOQA import qgis # NOQA
import os import os


from qgis.core import QgsRectangle, QgsCoordinateReferenceSystem, QgsVectorLayer, QgsProject from qgis.core import QgsRectangle, QgsCoordinateReferenceSystem, QgsVectorLayer, QgsProject, QgsFeature, QgsGeometry
from qgis.gui import QgsExtentGroupBox from qgis.gui import QgsExtentGroupBox


from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtTest import QSignalSpy
Expand Down Expand Up @@ -82,6 +82,8 @@ def test_SettingExtent(self):
self.assertEqual(w.extentState(), QgsExtentGroupBox.ProjectLayerExtent) self.assertEqual(w.extentState(), QgsExtentGroupBox.ProjectLayerExtent)
self.assertEqual(len(spy), 4) self.assertEqual(len(spy), 4)


QgsProject.instance().removeAllMapLayers()

def testSetOutputCrs(self): def testSetOutputCrs(self):
w = qgis.gui.QgsExtentGroupBox() w = qgis.gui.QgsExtentGroupBox()


Expand All @@ -90,14 +92,66 @@ def testSetOutputCrs(self):
w.setOutputExtentFromCurrent() w.setOutputExtentFromCurrent()
self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4)) self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4))


# no reprojection # with reprojection
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'), False) w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'))
self.assertEqual(w.outputExtent().toString(4), QgsRectangle(111319.4908, 222684.2085, 333958.4724, 445640.1097).toString(4))
# change CRS back
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
# extent should be back to current - not a reprojection of the reprojected bounds
self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20))

# repeat, this time using original extents
w = qgis.gui.QgsExtentGroupBox()

w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
w.setOriginalExtent(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326'))
w.setOutputExtentFromOriginal()
self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4)) self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4))
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'), False)


# with reprojection # with reprojection
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'), True) w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'))
self.assertEqual(w.outputExtent().toString(4), QgsRectangle(111319.4908, 222684.2085, 333958.4724, 445640.1097).toString(4)) self.assertEqual(w.outputExtent().toString(4),
QgsRectangle(111319.4908, 222684.2085, 333958.4724, 445640.1097).toString(4))
# change CRS back
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
# extent should be back to original - not a reprojection of the reprojected bounds
self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20))

# repeat, this time using layer extent
layer = QgsVectorLayer("Polygon?crs=4326", 'memory', 'memory')
self.assertTrue(layer.isValid())
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt('Polygon((1 2, 3 2, 3 4, 1 4, 1 2))'))
layer.dataProvider().addFeatures([f])
QgsProject.instance().addMapLayer(layer)
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
w.setOutputExtentFromLayer(layer)
self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4))

w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'))
self.assertEqual(w.outputExtent().toString(4),
QgsRectangle(111319.4908, 222684.2085, 333958.4724, 445640.1097).toString(4))
# change CRS back
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
# extent should be back to original - not a reprojection of the reprojected bounds
self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20))

# custom extent
w = qgis.gui.QgsExtentGroupBox()

w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
w.setOutputExtentFromUser(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326'))
self.assertEqual(w.outputExtent(), QgsRectangle(1, 2, 3, 4))

# with reprojection
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:3785'))
self.assertEqual(w.outputExtent().toString(4),
QgsRectangle(111319.4908, 222684.2085, 333958.4724, 445640.1097).toString(4))
# change CRS back
w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326'))
# in this case we can't retrieve the original user extent in 4326, so we have a reprojection of the reprojected bounds
# just test this by restricting the test to 4 decimals
self.assertEqual(w.outputExtent().toString(4), QgsRectangle(1, 2, 3, 4).toString(4))




if __name__ == '__main__': if __name__ == '__main__':
Expand Down

0 comments on commit 9e14741

Please sign in to comment.