Skip to content

Commit 69ddc32

Browse files
committed
Restore atlas map handling
1 parent 3ffdda3 commit 69ddc32

5 files changed

Lines changed: 183 additions & 47 deletions

File tree

python/core/layout/qgslayoutitemmap.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,9 @@ associated legend items know they should update
531531

532532
public slots:
533533

534+
virtual void refresh();
535+
536+
534537
virtual void invalidateCache();
535538

536539

src/app/layout/qgslayoutmapwidget.cpp

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "qgslayoutmapgridwidget.h"
3030
#include "qgsstyle.h"
3131
#include "qgslayoutundostack.h"
32+
#include "qgslayoutatlas.h"
3233
#include <QMenu>
3334
#include <QMessageBox>
3435

@@ -111,17 +112,14 @@ QgsLayoutMapWidget::QgsLayoutMapWidget( QgsLayoutItemMap *item )
111112

112113
connect( item, &QgsLayoutObject::changed, this, &QgsLayoutMapWidget::updateGuiElements );
113114

114-
#if 0 //TODO
115-
QgsAtlasComposition *atlas = atlasComposition();
116-
if ( atlas )
115+
connect( &item->layout()->context(), &QgsLayoutContext::layerChanged,
116+
this, &QgsLayoutMapWidget::atlasLayerChanged );
117+
if ( QgsLayoutAtlas *atlas = layoutAtlas() )
117118
{
118-
connect( atlas, &QgsAtlasComposition::coverageLayerChanged,
119-
this, &QgsLayoutMapWidget::atlasLayerChanged );
120-
connect( atlas, &QgsAtlasComposition::toggled, this, &QgsLayoutMapWidget::compositionAtlasToggled );
121-
119+
connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutMapWidget::compositionAtlasToggled );
122120
compositionAtlasToggled( atlas->enabled() );
123121
}
124-
#endif
122+
125123
mOverviewFrameMapComboBox->setCurrentLayout( item->layout() );
126124
mOverviewFrameMapComboBox->setItemType( QgsLayoutItemRegistry::LayoutMap );
127125
mOverviewFrameStyleButton->registerExpressionContextGenerator( item );
@@ -196,11 +194,9 @@ void QgsLayoutMapWidget::populateDataDefinedButtons()
196194

197195
void QgsLayoutMapWidget::compositionAtlasToggled( bool atlasEnabled )
198196
{
199-
Q_UNUSED( atlasEnabled );
200-
#if 0 //TODO
201197
if ( atlasEnabled &&
202-
mMapItem && mMapItem->composition() && mMapItem->composition()->atlasComposition().coverageLayer()
203-
&& mMapItem->composition()->atlasComposition().coverageLayer()->wkbType() != QgsWkbTypes::NoGeometry )
198+
mMapItem && mMapItem->layout() && mMapItem->layout()->context().layer()
199+
&& mMapItem->layout()->context().layer()->wkbType() != QgsWkbTypes::NoGeometry )
204200
{
205201
mAtlasCheckBox->setEnabled( true );
206202
}
@@ -209,7 +205,6 @@ void QgsLayoutMapWidget::compositionAtlasToggled( bool atlasEnabled )
209205
mAtlasCheckBox->setEnabled( false );
210206
mAtlasCheckBox->setChecked( false );
211207
}
212-
#endif
213208
}
214209

215210
void QgsLayoutMapWidget::aboutToShowKeepLayersVisibilityPresetsMenu()
@@ -387,31 +382,16 @@ void QgsLayoutMapWidget::mAtlasCheckBox_toggled( bool checked )
387382

388383
void QgsLayoutMapWidget::updateMapForAtlas()
389384
{
390-
#if 0 //TODO
391385
//update map if in atlas preview mode
392-
QgsComposition *composition = mMapItem->composition();
393-
if ( !composition )
394-
{
395-
return;
396-
}
397-
if ( composition->atlasMode() == QgsComposition::AtlasOff )
398-
{
399-
return;
400-
}
401-
402386
if ( mMapItem->atlasDriven() )
403387
{
404-
//update atlas based extent for map
405-
QgsAtlasComposition *atlas = &composition->atlasComposition();
406-
//prepareMap causes a redraw
407-
atlas->prepareMap( mMapItem );
388+
mMapItem->refresh();
408389
}
409390
else
410391
{
411392
//redraw map
412393
mMapItem->invalidateCache();
413394
}
414-
#endif
415395
}
416396

417397
void QgsLayoutMapWidget::mAtlasMarginRadio_toggled( bool checked )
@@ -708,15 +688,14 @@ void QgsLayoutMapWidget::toggleAtlasScalingOptionsByLayerType()
708688
return;
709689
}
710690

711-
#if 0 //TODO
712691
//get atlas coverage layer
713-
QgsVectorLayer *coverageLayer = atlasCoverageLayer();
714-
if ( !coverageLayer )
692+
QgsVectorLayer *layer = coverageLayer();
693+
if ( !layer )
715694
{
716695
return;
717696
}
718697

719-
switch ( coverageLayer->wkbType() )
698+
switch ( layer->wkbType() )
720699
{
721700
case QgsWkbTypes::Point:
722701
case QgsWkbTypes::Point25D:
@@ -732,7 +711,6 @@ void QgsLayoutMapWidget::toggleAtlasScalingOptionsByLayerType()
732711
mAtlasMarginRadio->setEnabled( true );
733712
mAtlasPredefinedScaleRadio->setEnabled( true );
734713
}
735-
#endif
736714
}
737715

738716
void QgsLayoutMapWidget::updateComposerExtentFromGui()
@@ -1066,8 +1044,6 @@ void QgsLayoutMapWidget::initAnnotationDirectionBox( QComboBox *c, QgsLayoutItem
10661044

10671045
void QgsLayoutMapWidget::atlasLayerChanged( QgsVectorLayer *layer )
10681046
{
1069-
Q_UNUSED( layer );
1070-
#if 0 //TODO
10711047
if ( !layer || layer->wkbType() == QgsWkbTypes::NoGeometry )
10721048
{
10731049
//geometryless layer, disable atlas control
@@ -1083,7 +1059,6 @@ void QgsLayoutMapWidget::atlasLayerChanged( QgsVectorLayer *layer )
10831059
// enable or disable fixed scale control based on layer type
10841060
if ( mAtlasCheckBox->isChecked() )
10851061
toggleAtlasScalingOptionsByLayerType();
1086-
#endif
10871062
}
10881063

10891064
bool QgsLayoutMapWidget::hasPredefinedScales() const

src/core/layout/qgslayoutitemmap.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ QgsLayoutItemMap *QgsLayoutItemMap::create( QgsLayout *layout )
123123
return new QgsLayoutItemMap( layout );
124124
}
125125

126+
void QgsLayoutItemMap::refresh()
127+
{
128+
QgsLayoutItem::refresh();
129+
invalidateCache();
130+
131+
updateAtlasFeature();
132+
}
133+
126134
double QgsLayoutItemMap::scale() const
127135
{
128136
QgsScaleCalculator calculator;
@@ -1799,3 +1807,157 @@ void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
17991807
emit mapRotationChanged( mapRotation );
18001808
}
18011809
}
1810+
1811+
void QgsLayoutItemMap::updateAtlasFeature()
1812+
{
1813+
if ( !atlasDriven() || !mLayout->context().layer() )
1814+
return; // nothing to do
1815+
1816+
QgsRectangle bounds = computeAtlasRectangle();
1817+
if ( bounds.isNull() )
1818+
return;
1819+
1820+
double xa1 = bounds.xMinimum();
1821+
double xa2 = bounds.xMaximum();
1822+
double ya1 = bounds.yMinimum();
1823+
double ya2 = bounds.yMaximum();
1824+
QgsRectangle newExtent = bounds;
1825+
QgsRectangle originalExtent = mExtent;
1826+
1827+
//sanity check - only allow fixed scale mode for point layers
1828+
bool isPointLayer = false;
1829+
switch ( mLayout->context().layer()->wkbType() )
1830+
{
1831+
case QgsWkbTypes::Point:
1832+
case QgsWkbTypes::Point25D:
1833+
case QgsWkbTypes::MultiPoint:
1834+
case QgsWkbTypes::MultiPoint25D:
1835+
isPointLayer = true;
1836+
break;
1837+
default:
1838+
isPointLayer = false;
1839+
break;
1840+
}
1841+
1842+
if ( mAtlasScalingMode == Fixed || mAtlasScalingMode == Predefined || isPointLayer )
1843+
{
1844+
QgsScaleCalculator calc;
1845+
calc.setMapUnits( crs().mapUnits() );
1846+
calc.setDpi( 25.4 );
1847+
double originalScale = calc.calculate( originalExtent, rect().width() );
1848+
double geomCenterX = ( xa1 + xa2 ) / 2.0;
1849+
double geomCenterY = ( ya1 + ya2 ) / 2.0;
1850+
1851+
if ( mAtlasScalingMode == Fixed || isPointLayer )
1852+
{
1853+
// only translate, keep the original scale (i.e. width x height)
1854+
double xMin = geomCenterX - originalExtent.width() / 2.0;
1855+
double yMin = geomCenterY - originalExtent.height() / 2.0;
1856+
newExtent = QgsRectangle( xMin,
1857+
yMin,
1858+
xMin + originalExtent.width(),
1859+
yMin + originalExtent.height() );
1860+
1861+
//scale newExtent to match original scale of map
1862+
//this is required for geographic coordinate systems, where the scale varies by extent
1863+
double newScale = calc.calculate( newExtent, rect().width() );
1864+
newExtent.scale( originalScale / newScale );
1865+
}
1866+
else if ( mAtlasScalingMode == Predefined )
1867+
{
1868+
// choose one of the predefined scales
1869+
double newWidth = originalExtent.width();
1870+
double newHeight = originalExtent.height();
1871+
QVector<qreal> scales = mLayout->context().predefinedScales();
1872+
for ( int i = 0; i < scales.size(); i++ )
1873+
{
1874+
double ratio = scales[i] / originalScale;
1875+
newWidth = originalExtent.width() * ratio;
1876+
newHeight = originalExtent.height() * ratio;
1877+
1878+
// compute new extent, centered on feature
1879+
double xMin = geomCenterX - newWidth / 2.0;
1880+
double yMin = geomCenterY - newHeight / 2.0;
1881+
newExtent = QgsRectangle( xMin,
1882+
yMin,
1883+
xMin + newWidth,
1884+
yMin + newHeight );
1885+
1886+
//scale newExtent to match desired map scale
1887+
//this is required for geographic coordinate systems, where the scale varies by extent
1888+
double newScale = calc.calculate( newExtent, rect().width() );
1889+
newExtent.scale( scales[i] / newScale );
1890+
1891+
if ( ( newExtent.width() >= bounds.width() ) && ( newExtent.height() >= bounds.height() ) )
1892+
{
1893+
// this is the smallest extent that embeds the feature, stop here
1894+
break;
1895+
}
1896+
}
1897+
}
1898+
}
1899+
else if ( mAtlasScalingMode == Auto )
1900+
{
1901+
// auto scale
1902+
1903+
double geomRatio = bounds.width() / bounds.height();
1904+
double mapRatio = originalExtent.width() / originalExtent.height();
1905+
1906+
// geometry height is too big
1907+
if ( geomRatio < mapRatio )
1908+
{
1909+
// extent the bbox's width
1910+
double adjWidth = ( mapRatio * bounds.height() - bounds.width() ) / 2.0;
1911+
xa1 -= adjWidth;
1912+
xa2 += adjWidth;
1913+
}
1914+
// geometry width is too big
1915+
else if ( geomRatio > mapRatio )
1916+
{
1917+
// extent the bbox's height
1918+
double adjHeight = ( bounds.width() / mapRatio - bounds.height() ) / 2.0;
1919+
ya1 -= adjHeight;
1920+
ya2 += adjHeight;
1921+
}
1922+
newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
1923+
1924+
if ( mAtlasMargin > 0.0 )
1925+
{
1926+
newExtent.scale( 1 + mAtlasMargin );
1927+
}
1928+
}
1929+
1930+
// set the new extent (and render)
1931+
setExtent( newExtent );
1932+
}
1933+
1934+
QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
1935+
{
1936+
// QgsGeometry::boundingBox is expressed in the geometry"s native CRS
1937+
// We have to transform the geometry to the destination CRS and ask for the bounding box
1938+
// Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
1939+
QgsGeometry g = mLayout->context().currentGeometry( crs() );
1940+
// Rotating the geometry, so the bounding box is correct wrt map rotation
1941+
if ( mEvaluatedMapRotation != 0.0 )
1942+
{
1943+
QgsPointXY prevCenter = g.boundingBox().center();
1944+
g.rotate( mEvaluatedMapRotation, g.boundingBox().center() );
1945+
// Rotation center will be still the bounding box center of an unrotated geometry.
1946+
// Which means, if the center of bbox moves after rotation, the viewport will
1947+
// also be offset, and part of the geometry will fall out of bounds.
1948+
// Here we compensate for that roughly: by extending the rotated bounds
1949+
// so that its center is the same as the original.
1950+
QgsRectangle bounds = g.boundingBox();
1951+
double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
1952+
std::abs( prevCenter.x() - bounds.xMaximum() ) );
1953+
double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
1954+
std::abs( prevCenter.y() - bounds.yMaximum() ) );
1955+
QgsPointXY center = g.boundingBox().center();
1956+
return QgsRectangle( center.x() - dx, center.y() - dy,
1957+
center.x() + dx, center.y() + dy );
1958+
}
1959+
else
1960+
{
1961+
return g.boundingBox();
1962+
}
1963+
}

src/core/layout/qgslayoutitemmap.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -422,15 +422,6 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
422422
//! True if a draw is already in progress
423423
bool isDrawing() const {return mDrawing;}
424424

425-
#if 0 //TODO
426-
427-
/**
428-
* Sets new Extent for the current atlas preview and changes width, height (and implicitly also scale).
429-
Atlas preview extents are only temporary, and are regenerated whenever the atlas feature changes
430-
*/
431-
void setNewAtlasFeatureExtent( const QgsRectangle &extent );
432-
#endif
433-
434425
// In case of annotations, the bounding rectangle can be larger than the map item rectangle
435426
QRectF boundingRect() const override;
436427

@@ -472,6 +463,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
472463

473464
public slots:
474465

466+
void refresh() override;
467+
475468
void invalidateCache() override;
476469

477470
//! Updates the bounding rect of this item. Call this function before doing any changes related to annotation out of the map rectangle
@@ -656,6 +649,10 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
656649
*/
657650
void refreshMapExtents( const QgsExpressionContext *context = nullptr );
658651

652+
void updateAtlasFeature();
653+
654+
QgsRectangle computeAtlasRectangle();
655+
659656
friend class QgsLayoutItemMapGrid;
660657
friend class QgsLayoutItemMapOverview;
661658
friend class QgsLayoutItemLegend;

tests/src/core/testqgslayoutcontext.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/***************************************************************************
32
testqgslayoutcontext.cpp
43
------------------------

0 commit comments

Comments
 (0)