Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #4730 from Zverik/atlas_rotate
Rotate geometry before calculating bounding box in atlas
  • Loading branch information
nyalldawson committed Jul 4, 2017
2 parents 1aad689 + 391f76b commit 70d2ae2
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 4 deletions.
26 changes: 25 additions & 1 deletion src/core/composer/qgsatlascomposition.cpp
Expand Up @@ -14,6 +14,7 @@
* (at your option) any later version. *
* *
***************************************************************************/
#include <algorithm>
#include <stdexcept>
#include <QtAlgorithms>

Expand Down Expand Up @@ -439,7 +440,30 @@ void QgsAtlasComposition::computeExtent( QgsComposerMap *map )
// QgsGeometry::boundingBox is expressed in the geometry"s native CRS
// We have to transform the geometry to the destination CRS and ask for the bounding box
// Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
mTransformedFeatureBounds = currentGeometry( map->crs() ).boundingBox();
QgsGeometry g = currentGeometry( map->crs() );
// Rotating the geometry, so the bounding box is correct wrt map rotation
if ( map->mapRotation() != 0.0 )
{
QgsPointXY prevCenter = g.boundingBox().center();
g.rotate( map->mapRotation(), g.boundingBox().center() );
// Rotation center will be still the bounding box center of an unrotated geometry.
// Which means, if the center of bbox moves after rotation, the viewport will
// also be offset, and part of the geometry will fall out of bounds.
// Here we compensate for that roughly: by extending the rotated bounds
// so that its center is the same as the original.
QgsRectangle bounds = g.boundingBox();
double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
std::abs( prevCenter.x() - bounds.xMaximum() ) );
double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
std::abs( prevCenter.y() - bounds.yMaximum() ) );
QgsPointXY center = g.boundingBox().center();
mTransformedFeatureBounds = QgsRectangle( center.x() - dx, center.y() - dy,
center.x() + dx, center.y() + dy );
}
else
{
mTransformedFeatureBounds = g.boundingBox();
}
}

void QgsAtlasComposition::prepareMap( QgsComposerMap *map )
Expand Down
70 changes: 67 additions & 3 deletions tests/src/python/test_qgsatlascomposition.py
Expand Up @@ -22,9 +22,25 @@
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
from qgis.PyQt.QtCore import QFileInfo, QRectF, qWarning
from qgis.core import QgsVectorLayer, QgsProject, QgsCoordinateReferenceSystem, \
QgsComposition, QgsFillSymbol, QgsSingleSymbolRenderer, QgsComposerLabel, QgsComposerMap, QgsFontUtils, \
QgsRectangle, QgsComposerLegend, QgsFeature, QgsGeometry, QgsPointXY, QgsRendererCategory, QgsCategorizedSymbolRenderer, QgsMarkerSymbol
from qgis.core import (
QgsCategorizedSymbolRenderer,
QgsComposerLabel,
QgsComposerLegend,
QgsComposerMap,
QgsComposition,
QgsCoordinateReferenceSystem,
QgsFeature,
QgsFillSymbol,
QgsFontUtils,
QgsGeometry,
QgsMarkerSymbol,
QgsPointXY,
QgsProject,
QgsRectangle,
QgsRendererCategory,
QgsSingleSymbolRenderer,
QgsVectorLayer,
)
from qgscompositionchecker import QgsCompositionChecker

start_app()
Expand Down Expand Up @@ -113,6 +129,7 @@ def testCase(self):
self.predefinedscales_render_test()
self.hidden_render_test()
self.legend_test()
self.rotation_test()

shutil.rmtree(tmppath, True)

Expand Down Expand Up @@ -311,6 +328,53 @@ def legend_test(self):
self.mComposition.removeComposerItem(legend)
QgsProject.instance().removeMapLayer(ptLayer.id())

def rotation_test(self):
# We will create a polygon layer with a rotated rectangle.
# Then we will make it the object layer for the atlas,
# rotate the map and test that the bounding rectangle
# is smaller than the bounds without rotation.
polygonLayer = QgsVectorLayer('Polygon', 'test_polygon', 'memory')
poly = QgsFeature(polygonLayer.pendingFields())
points = [(10, 15), (15, 10), (45, 40), (40, 45)]
poly.setGeometry(QgsGeometry.fromPolygon([[QgsPointXY(x[0], x[1]) for x in points]]))
polygonLayer.dataProvider().addFeatures([poly])
QgsProject.instance().addMapLayer(polygonLayer)

# Recreating the composer locally
composition = QgsComposition(QgsProject.instance())
composition.setPaperSize(297, 210)

# the atlas map
atlasMap = QgsComposerMap(composition, 20, 20, 130, 130)
atlasMap.setFrameEnabled(True)
atlasMap.setLayers([polygonLayer])
atlasMap.setNewExtent(QgsRectangle(0, 0, 100, 50))
composition.addComposerMap(atlasMap)

# the atlas
atlas = composition.atlasComposition()
atlas.setCoverageLayer(polygonLayer)
atlas.setEnabled(True)
composition.setAtlasMode(QgsComposition.ExportAtlas)

atlasMap.setAtlasDriven(True)
atlasMap.setAtlasScalingMode(QgsComposerMap.Auto)
atlasMap.setAtlasMargin(0.0)

# Testing
atlasMap.setMapRotation(0.0)
atlas.firstFeature()
nonRotatedExtent = QgsRectangle(atlasMap.currentMapExtent())

atlasMap.setMapRotation(45.0)
atlas.firstFeature()
rotatedExtent = QgsRectangle(atlasMap.currentMapExtent())

assert rotatedExtent.width() < nonRotatedExtent.width() * 0.9
assert rotatedExtent.height() < nonRotatedExtent.height() * 0.9

QgsProject.instance().removeMapLayer(polygonLayer)


if __name__ == '__main__':
unittest.main()

0 comments on commit 70d2ae2

Please sign in to comment.