Skip to content

Commit

Permalink
Ensure symbols using map unit sizing work correctly in elevation
Browse files Browse the repository at this point in the history
profile plots

Fixes qgis#52833
  • Loading branch information
nyalldawson committed May 22, 2023
1 parent 4c6018d commit 1f48b5f
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 16 deletions.
7 changes: 5 additions & 2 deletions python/testing/__init__.py
Expand Up @@ -156,9 +156,12 @@ def render_map_settings_check(cls,
return result

@classmethod
def render_layout_check(cls, name: str, layout: QgsLayout, size: QSize):
def render_layout_check(cls, name: str,
layout: QgsLayout,
size: Optional[QSize] = None):
checker = QgsLayoutChecker(name, layout)
checker.setSize(size)
if size is not None:
checker.setSize(size)
if cls.control_path_prefix():
checker.setControlPathPrefix(cls.control_path_prefix())
result, message = checker.testLayout()
Expand Down
3 changes: 3 additions & 0 deletions src/core/elevation/qgsprofilerenderer.cpp
Expand Up @@ -305,6 +305,9 @@ QImage QgsProfilePlotRenderer::renderToImage( int width, int height, double dist
QgsRenderContext context = QgsRenderContext::fromQPainter( &p );
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
context.setPainterFlagsUsingContext( &p );
const double mapUnitsPerPixel = ( distanceMax - distanceMin ) / width;
context.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );

render( context, width, height, distanceMin, distanceMax, zMin, zMax, sourceId );
p.end();

Expand Down
7 changes: 7 additions & 0 deletions src/core/layout/qgslayoutitemelevationprofile.cpp
Expand Up @@ -641,6 +641,9 @@ void QgsLayoutItemElevationProfile::paint( QPainter *painter, const QStyleOption
layoutSize *= dotsPerMM; // output size will be in dots (pixels)
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots

const double mapUnitsPerPixel = static_cast<double>( mPlot->xMaximum() - mPlot->xMinimum() ) / layoutSize.width();
rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );

QList< QgsAbstractProfileSource * > sources;
for ( const QgsMapLayerRef &layer : std::as_const( mLayers ) )
{
Expand Down Expand Up @@ -889,6 +892,10 @@ void QgsLayoutItemElevationProfile::profileGenerationFinished()

QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( mLayout, mPainter.get() );

const double mapUnitsPerPixel = static_cast< double >( mPlot->xMaximum() - mPlot->xMinimum() ) /
mCacheRenderingImage->size().width();
rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );

// size must be in pixels, not layout units
mPlot->setSize( mCacheRenderingImage->size() );

Expand Down
3 changes: 3 additions & 0 deletions src/gui/elevation/qgselevationprofilecanvas.cpp
Expand Up @@ -172,6 +172,9 @@ class QgsElevationProfilePlotItem : public Qgs2DPlot, public QgsPlotCanvasItem
imagePainter.setRenderHint( QPainter::Antialiasing, true );
QgsRenderContext rc = QgsRenderContext::fromQPainter( &imagePainter );

const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) / plotArea().width();
rc.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );

rc.expressionContext().appendScope( QgsExpressionContextUtils::globalScope() );
rc.expressionContext().appendScope( QgsExpressionContextUtils::projectScope( mProject ) );

Expand Down
4 changes: 4 additions & 0 deletions src/quickgui/qgsquickelevationprofilecanvas.cpp
Expand Up @@ -117,6 +117,10 @@ class QgsElevationProfilePlotItem : public Qgs2DPlot
plotPainter.setRenderHint( QPainter::Antialiasing, true );
QgsRenderContext plotRc = QgsRenderContext::fromQPainter( &plotPainter );
plotRc.setDevicePixelRatio( devicePixelRatio );

const double mapUnitsPerPixel = ( xMaximum() - xMinimum() ) / plotArea.width();
context.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );

mRenderer->render( plotRc, plotArea.width(), plotArea.height(), xMinimum(), xMaximum(), yMinimum(), yMaximum(), source );
plotPainter.end();

Expand Down
99 changes: 85 additions & 14 deletions tests/src/python/test_qgslayoutelevationprofile.py
Expand Up @@ -32,7 +32,8 @@
QgsRasterLayer,
QgsTextFormat,
QgsVectorLayer,
QgsLayoutChecker
QgsLayoutChecker,
QgsMarkerSymbol
)
from qgis.testing import start_app, unittest

Expand All @@ -45,18 +46,14 @@

class TestQgsLayoutItemElevationProfile(unittest.TestCase, LayoutItemTestCase):

@classmethod
def control_path_prefix(cls):
return "layout_profile"

@classmethod
def setUpClass(cls):
super(TestQgsLayoutItemElevationProfile, cls).setUpClass()
cls.item_class = QgsLayoutItemElevationProfile
cls.report = "<h1>Python QgsLayoutItemElevationProfile Tests</h1>\n"

@classmethod
def tearDownClass(cls):
report_file_path = f"{QDir.tempPath()}/qgistest.html"
with open(report_file_path, 'a') as report_file:
report_file.write(cls.report)
super(TestQgsLayoutItemElevationProfile, cls).tearDownClass()

def test_layers(self):
project = QgsProject()
Expand Down Expand Up @@ -239,12 +236,86 @@ def test_draw(self):

profile_item.setLayers([vl])

checker = QgsLayoutChecker('vector_layer', layout)
checker.setControlPathPrefix("layout_profile")
self.assertTrue(self.render_layout_check(
'vector_layer', layout
))

def test_draw_map_units(self):
"""
Test rendering the layout profile item using symbols with map unit sizes
"""
vl = QgsVectorLayer('LineStringZ?crs=EPSG:27700', 'lines', 'memory')
vl.setCrs(QgsCoordinateReferenceSystem())
self.assertTrue(vl.isValid())

for line in [
'LineStringZ (321829.48893365426920354 129991.38697145861806348 1, 321847.89668515208177269 129996.63588572069420479 1, 321848.97131609614007175 129979.22330882755341008 1, 321830.31725845142500475 129978.07136809575604275 1, 321829.48893365426920354 129991.38697145861806348 1)',
'LineStringZ (321920.00953056826256216 129924.58260190498549491 2, 321924.65299345907988027 129908.43546159457764588 2, 321904.78543491888558492 129903.99811821122420952 2, 321900.80605239619035274 129931.39860145389684476 2, 321904.84799937985371798 129931.71552911199978553 2, 321908.93646715773502365 129912.90030360443051904 2, 321914.20495146053144708 129913.67693978428724222 2, 321911.30165811872575432 129923.01272751353099011 2, 321920.00953056826256216 129924.58260190498549491 2)',
'LineStringZ (321923.10517279652412981 129919.61521573827485554 3, 321922.23537852568551898 129928.3598982143739704 3, 321928.60423935484141111 129934.22530528216157109 3, 321929.39881197665818036 129923.29054521876969375 3, 321930.55804549407912418 129916.53248518184409477 3, 321923.10517279652412981 129919.61521573827485554 3)',
'LineStringZ (321990.47451346553862095 129909.63588680300745182 4, 321995.04325810901354998 129891.84052284323843196 4, 321989.66826330573530868 129890.5092018858413212 4, 321990.78512359503656626 129886.49917887404444627 4, 321987.37291929306229576 129885.64982962771318853 4, 321985.2254804756375961 129893.81317058412241749 4, 321987.63158903241856024 129894.41078495365218259 4, 321984.34022761805681512 129907.57450046355370432 4, 321990.47451346553862095 129909.63588680300745182 4)',
'LineStringZ (322103.03910495212767273 129795.91051736124791205 5, 322108.25568856322206557 129804.76113295342656784 5, 322113.29666162584908307 129803.9285887333098799 5, 322117.78645010641776025 129794.48194090687320568 5, 322103.03910495212767273 129795.91051736124791205 5)']:
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt(line))
self.assertTrue(vl.dataProvider().addFeature(f))

vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute)
marker_symbol = QgsMarkerSymbol.createSimple(
{'name': 'square', 'size': 4, 'color': '#00ff00',
'outline_style': 'no'})
marker_symbol.setSizeUnit(Qgis.RenderUnit.MapUnits)
vl.elevationProperties().setRespectLayerSymbology(False)
vl.elevationProperties().setProfileMarkerSymbol(marker_symbol)

p = QgsProject()
p.addMapLayer(vl)
layout = QgsLayout(p)
layout.initializeDefaults()

profile_item = QgsLayoutItemElevationProfile(layout)
layout.addLayoutItem(profile_item)
profile_item.attemptSetSceneRect(QRectF(10, 10, 180, 180))

curve = QgsLineString()
curve.fromWkt(
'LineString (321897.18831187387695536 129916.86947759155009408, 321942.11597351566888392 129924.94403429214435164)')

profile_item.setProfileCurve(curve)
profile_item.setCrs(QgsCoordinateReferenceSystem())

profile_item.plot().setXMaximum(curve.length())
profile_item.plot().setYMaximum(14)

profile_item.plot().xAxis().setGridIntervalMajor(10)
profile_item.plot().xAxis().setGridIntervalMinor(5)
profile_item.plot().xAxis().setGridMajorSymbol(QgsLineSymbol.createSimple({'color': '#ffaaff', 'width': 2}))
profile_item.plot().xAxis().setGridMinorSymbol(
QgsLineSymbol.createSimple({'color': '#ffffaa', 'width': 2}))

format = QgsTextFormat()
format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
format.setSize(20)
format.setNamedStyle("Bold")
format.setColor(QColor(0, 0, 0))
profile_item.plot().xAxis().setTextFormat(format)
profile_item.plot().xAxis().setLabelInterval(20)

profile_item.plot().yAxis().setGridIntervalMajor(10)
profile_item.plot().yAxis().setGridIntervalMinor(5)
profile_item.plot().yAxis().setGridMajorSymbol(QgsLineSymbol.createSimple({'color': '#ffffaa', 'width': 2}))
profile_item.plot().yAxis().setGridMinorSymbol(
QgsLineSymbol.createSimple({'color': '#aaffaa', 'width': 2}))

profile_item.plot().yAxis().setTextFormat(format)
profile_item.plot().yAxis().setLabelInterval(10)

profile_item.plot().setChartBorderSymbol(
QgsFillSymbol.createSimple({'style': 'no', 'color': '#aaffaa', 'width_border': 2}))

profile_item.setLayers([vl])

result, message = checker.testLayout()
TestQgsLayoutItemElevationProfile.report += checker.report()
self.assertTrue(result, message)
self.assertTrue(self.render_layout_check(
'vector_layer_map_units', layout
))


if __name__ == '__main__':
Expand Down
36 changes: 36 additions & 0 deletions tests/src/python/test_qgsvectorlayerprofilegenerator.py
Expand Up @@ -1615,6 +1615,42 @@ def testRenderProfileAsSurfaceFillAboveLimit(self):
res = plot_renderer.renderToImage(400, 400, 0, curve.length(), 0, 14)
self.assertTrue(self.image_check('vector_lines_as_fill_above_surface_limit', 'vector_lines_as_fill_above_surface_limit', res))

def testRenderProfileSymbolWithMapUnits(self):
vl = QgsVectorLayer('LineStringZ?crs=EPSG:27700', 'lines', 'memory')
vl.setCrs(QgsCoordinateReferenceSystem())
self.assertTrue(vl.isValid())

for line in [
'LineStringZ (321829.48893365426920354 129991.38697145861806348 1, 321847.89668515208177269 129996.63588572069420479 1, 321848.97131609614007175 129979.22330882755341008 1, 321830.31725845142500475 129978.07136809575604275 1, 321829.48893365426920354 129991.38697145861806348 1)',
'LineStringZ (321920.00953056826256216 129924.58260190498549491 2, 321924.65299345907988027 129908.43546159457764588 2, 321904.78543491888558492 129903.99811821122420952 2, 321900.80605239619035274 129931.39860145389684476 2, 321904.84799937985371798 129931.71552911199978553 2, 321908.93646715773502365 129912.90030360443051904 2, 321914.20495146053144708 129913.67693978428724222 2, 321911.30165811872575432 129923.01272751353099011 2, 321920.00953056826256216 129924.58260190498549491 2)',
'LineStringZ (321923.10517279652412981 129919.61521573827485554 3, 321922.23537852568551898 129928.3598982143739704 3, 321928.60423935484141111 129934.22530528216157109 3, 321929.39881197665818036 129923.29054521876969375 3, 321930.55804549407912418 129916.53248518184409477 3, 321923.10517279652412981 129919.61521573827485554 3)',
'LineStringZ (321990.47451346553862095 129909.63588680300745182 4, 321995.04325810901354998 129891.84052284323843196 4, 321989.66826330573530868 129890.5092018858413212 4, 321990.78512359503656626 129886.49917887404444627 4, 321987.37291929306229576 129885.64982962771318853 4, 321985.2254804756375961 129893.81317058412241749 4, 321987.63158903241856024 129894.41078495365218259 4, 321984.34022761805681512 129907.57450046355370432 4, 321990.47451346553862095 129909.63588680300745182 4)',
'LineStringZ (322103.03910495212767273 129795.91051736124791205 5, 322108.25568856322206557 129804.76113295342656784 5, 322113.29666162584908307 129803.9285887333098799 5, 322117.78645010641776025 129794.48194090687320568 5, 322103.03910495212767273 129795.91051736124791205 5)']:
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt(line))
self.assertTrue(vl.dataProvider().addFeature(f))

vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute)
marker_symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'size': 4, 'color': '#00ff00', 'outline_style': 'no'})
marker_symbol.setSizeUnit(Qgis.RenderUnit.MapUnits)
vl.elevationProperties().setRespectLayerSymbology(False)
vl.elevationProperties().setProfileMarkerSymbol(marker_symbol)

curve = QgsLineString()
curve.fromWkt(
'LineString (321897.18831187387695536 129916.86947759155009408, 321942.11597351566888392 129924.94403429214435164)')
req = QgsProfileRequest(curve)
req.setTransformContext(self.create_transform_context())

req.setCrs(QgsCoordinateReferenceSystem())

plot_renderer = QgsProfilePlotRenderer([vl], req)
plot_renderer.startGeneration()
plot_renderer.waitForFinished()

res = plot_renderer.renderToImage(400, 400, 0, curve.length(), 0, 14)
self.assertTrue(self.image_check('vector_profile_map_units', 'vector_profile_map_units', res))

def testRenderLayerSymbology(self):
vl = QgsVectorLayer('PolygonZ?crs=EPSG:27700', 'lines', 'memory')
vl.setCrs(QgsCoordinateReferenceSystem())
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1f48b5f

Please sign in to comment.