@@ -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+
126134double 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+ }
0 commit comments