Skip to content

Commit

Permalink
[annotations] Implement scale dependent bounding box calculation for …
Browse files Browse the repository at this point in the history
…marker items
  • Loading branch information
nyalldawson committed Sep 2, 2021
1 parent 9e38e43 commit f5bd9cc
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 23 deletions.
Expand Up @@ -75,7 +75,7 @@ Returns a unique (untranslated) string identifying the type of item.
Returns the bounding box of the item's geographic location, in the parent layer's coordinate reference system.
%End

virtual QgsRectangle boundingBox( const QgsRenderContext &context ) const;
virtual QgsRectangle boundingBox( QgsRenderContext &context ) const;
%Docstring
Returns the bounding box of the item's geographic location, in the parent layer's coordinate reference system.
%End
Expand Down
Expand Up @@ -100,7 +100,7 @@ Returns the item with the specified ``id``, or ``None`` if no matching item was
.. versionadded:: 3.22
%End

QStringList itemsInBounds( const QgsRectangle &bounds, const QgsRenderContext &context, QgsFeedback *feedback = 0 ) const;
QStringList itemsInBounds( const QgsRectangle &bounds, QgsRenderContext &context, QgsFeedback *feedback = 0 ) const;
%Docstring
Returns a list of the IDs of all annotation items within the specified ``bounds`` (in layer CRS), when
rendered using the given render ``context``.
Expand Down
Expand Up @@ -35,6 +35,8 @@ Constructor for QgsAnnotationMarkerItem, at the specified ``point``.

virtual bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;

virtual Qgis::AnnotationItemFlags flags() const;


static QgsAnnotationMarkerItem *create() /Factory/;
%Docstring
Expand All @@ -47,6 +49,8 @@ Creates a new marker annotation item.

virtual QgsRectangle boundingBox() const;

virtual QgsRectangle boundingBox( QgsRenderContext &context ) const;


QgsPointXY geometry() const;
%Docstring
Expand Down
Expand Up @@ -49,7 +49,7 @@ Creates a new text at point annotation item.

virtual QgsRectangle boundingBox() const;

virtual QgsRectangle boundingBox( const QgsRenderContext &context ) const;
virtual QgsRectangle boundingBox( QgsRenderContext &context ) const;


QgsPointXY point() const;
Expand Down
2 changes: 1 addition & 1 deletion src/core/annotations/qgsannotationitem.h
Expand Up @@ -104,7 +104,7 @@ class CORE_EXPORT QgsAnnotationItem
/**
* Returns the bounding box of the item's geographic location, in the parent layer's coordinate reference system.
*/
virtual QgsRectangle boundingBox( const QgsRenderContext &context ) const { Q_UNUSED( context ) return boundingBox();}
virtual QgsRectangle boundingBox( QgsRenderContext &context ) const { Q_UNUSED( context ) return boundingBox();}

/**
* Renders the item to the specified render \a context.
Expand Down
2 changes: 1 addition & 1 deletion src/core/annotations/qgsannotationlayer.cpp
Expand Up @@ -192,7 +192,7 @@ QStringList QgsAnnotationLayer::queryIndex( const QgsRectangle &bounds, QgsFeedb
return res;
}

QStringList QgsAnnotationLayer::itemsInBounds( const QgsRectangle &bounds, const QgsRenderContext &context, QgsFeedback *feedback ) const
QStringList QgsAnnotationLayer::itemsInBounds( const QgsRectangle &bounds, QgsRenderContext &context, QgsFeedback *feedback ) const
{
QStringList res = queryIndex( bounds, feedback );
// we also have to search through any non-indexed items
Expand Down
2 changes: 1 addition & 1 deletion src/core/annotations/qgsannotationlayer.h
Expand Up @@ -135,7 +135,7 @@ class CORE_EXPORT QgsAnnotationLayer : public QgsMapLayer
*
* \since QGIS 3.22
*/
QStringList itemsInBounds( const QgsRectangle &bounds, const QgsRenderContext &context, QgsFeedback *feedback = nullptr ) const;
QStringList itemsInBounds( const QgsRectangle &bounds, QgsRenderContext &context, QgsFeedback *feedback = nullptr ) const;

Qgis::MapLayerProperties properties() const override;
QgsAnnotationLayer *clone() const override SIP_FACTORY;
Expand Down
35 changes: 35 additions & 0 deletions src/core/annotations/qgsannotationmarkeritem.cpp
Expand Up @@ -67,6 +67,12 @@ bool QgsAnnotationMarkerItem::writeXml( QDomElement &element, QDomDocument &docu
return true;
}

Qgis::AnnotationItemFlags QgsAnnotationMarkerItem::flags() const
{
// in truth this should depend on whether the marker symbol is scale dependent or not!
return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox;
}

QgsAnnotationMarkerItem *QgsAnnotationMarkerItem::create()
{
return new QgsAnnotationMarkerItem( QgsPoint() );
Expand Down Expand Up @@ -100,6 +106,35 @@ QgsRectangle QgsAnnotationMarkerItem::boundingBox() const
return QgsRectangle( mPoint.x(), mPoint.y(), mPoint.x(), mPoint.y() );
}

QgsRectangle QgsAnnotationMarkerItem::boundingBox( QgsRenderContext &context ) const
{
QPointF pt;
if ( context.coordinateTransform().isValid() )
{
double x = mPoint.x();
double y = mPoint.y();
double z = 0.0;
context.coordinateTransform().transformInPlace( x, y, z );
pt = QPointF( x, y );
}
else
pt = mPoint.toQPointF();

context.mapToPixel().transformInPlace( pt.rx(), pt.ry() );

mSymbol->startRender( context );
const QRectF boundsInPixels = mSymbol->bounds( pt, context );
mSymbol->stopRender( context );

const QgsPointXY topLeft = context.mapToPixel().toMapCoordinates( boundsInPixels.left(), boundsInPixels.top() );
const QgsPointXY topRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.top() );
const QgsPointXY bottomLeft = context.mapToPixel().toMapCoordinates( boundsInPixels.left(), boundsInPixels.bottom() );
const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() );

const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() );
return context.coordinateTransform().transformBoundingBox( boundsMapUnits, QgsCoordinateTransform::ReverseTransform );
}

const QgsMarkerSymbol *QgsAnnotationMarkerItem::symbol() const
{
return mSymbol.get();
Expand Down
2 changes: 2 additions & 0 deletions src/core/annotations/qgsannotationmarkeritem.h
Expand Up @@ -42,6 +42,7 @@ class CORE_EXPORT QgsAnnotationMarkerItem : public QgsAnnotationItem
QString type() const override;
void render( QgsRenderContext &context, QgsFeedback *feedback ) override;
bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
Qgis::AnnotationItemFlags flags() const override;

/**
* Creates a new marker annotation item.
Expand All @@ -51,6 +52,7 @@ class CORE_EXPORT QgsAnnotationMarkerItem : public QgsAnnotationItem
bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
QgsAnnotationMarkerItem *clone() override SIP_FACTORY;
QgsRectangle boundingBox() const override;
QgsRectangle boundingBox( QgsRenderContext &context ) const override;

/**
* Returns the point geometry of the marker.
Expand Down
2 changes: 1 addition & 1 deletion src/core/annotations/qgsannotationpointtextitem.cpp
Expand Up @@ -117,7 +117,7 @@ QgsRectangle QgsAnnotationPointTextItem::boundingBox() const
return QgsRectangle( mPoint.x(), mPoint.y(), mPoint.x(), mPoint.y() );
}

QgsRectangle QgsAnnotationPointTextItem::boundingBox( const QgsRenderContext &context ) const
QgsRectangle QgsAnnotationPointTextItem::boundingBox( QgsRenderContext &context ) const
{
const double widthInPixels = QgsTextRenderer::textWidth( context, mTextFormat, mText.split( '\n' ) );
const double heightInPixels = QgsTextRenderer::textHeight( context, mTextFormat, mText.split( '\n' ) );
Expand Down
2 changes: 1 addition & 1 deletion src/core/annotations/qgsannotationpointtextitem.h
Expand Up @@ -53,7 +53,7 @@ class CORE_EXPORT QgsAnnotationPointTextItem : public QgsAnnotationItem
bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
QgsAnnotationPointTextItem *clone() override SIP_FACTORY;
QgsRectangle boundingBox() const override;
QgsRectangle boundingBox( const QgsRenderContext &context ) const override;
QgsRectangle boundingBox( QgsRenderContext &context ) const override;

/**
* Returns the point location of the text.
Expand Down
36 changes: 21 additions & 15 deletions tests/src/python/test_qgsannotationlayer.py
Expand Up @@ -44,7 +44,7 @@
)
from qgis.testing import start_app, unittest

from utilities import unitTestDataPath
from utilities import unitTestDataPath, compareWkt

start_app()
TEST_DATA_DIR = unitTestDataPath()
Expand Down Expand Up @@ -293,8 +293,8 @@ def testRenderLayer(self):
QgsRectangle(12, 13, 14, 15))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
QgsRectangle(11, 13, 12, 15))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
QgsRectangle(12, 13, 12, 13))
self.assertEqual([i.boundingBox().toString(1) for i in item_details if i.itemId() == i3_id][0],
'11.7,12.7 : 12.3,13.3')

def testRenderWithTransform(self):
layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext()))
Expand Down Expand Up @@ -353,8 +353,8 @@ def testRenderWithTransform(self):
QgsRectangle(11.5, 13, 12, 13.5))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
QgsRectangle(11, 13, 12, 15))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
QgsRectangle(12, 13, 12, 13))
self.assertEqual([i.boundingBox().toString(2) for i in item_details if i.itemId() == i3_id][0],
'11.94,12.94 : 12.06,13.06')

def test_render_via_job(self):
"""
Expand Down Expand Up @@ -398,19 +398,21 @@ def test_render_via_job(self):
self.assertEqual(len(item_details), 3)
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
self.assertCountEqual([i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(0, 0, 1, 1))], [])
self.assertCountEqual(
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(0, 0, 1, 1))], [])
self.assertCountEqual(
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 11, 18))], [i2_id])
self.assertCountEqual(
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 12, 18))], [i1_id, i2_id, i3_id])
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 12, 18))],
[i1_id, i2_id, i3_id])

# bounds should be in map crs
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i1_id][0],
QgsRectangle(11.5, 13, 12, 13.5))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
QgsRectangle(11, 13, 12, 15))
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
QgsRectangle(12, 13, 12, 13))
self.assertEqual([i.boundingBox().toString(1) for i in item_details if i.itemId() == i3_id][0],
'11.5,12.5 : 12.5,13.5')

def test_render_via_job_with_transform(self):
"""
Expand Down Expand Up @@ -455,12 +457,16 @@ def test_render_via_job_with_transform(self):
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
# bounds should be in map crs
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i1_id][0],
'Polygon ((1280174 1459732, 1335834 1459732, 1335834 1516914, 1280174 1516914, 1280174 1459732))')
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i2_id][0],
'Polygon ((1224514 1459732, 1335834 1459732, 1335834 1689200, 1224514 1689200, 1224514 1459732))')
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i3_id][0],
'Polygon ((1335834 1459732, 1335834 1459732, 1335834 1459732, 1335834 1459732, 1335834 1459732))')
self.assertEqual(
[QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i1_id][0],
'Polygon ((1280174 1459732, 1335834 1459732, 1335834 1516914, 1280174 1516914, 1280174 1459732))')
self.assertEqual(
[QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i2_id][0],
'Polygon ((1224514 1459732, 1335834 1459732, 1335834 1689200, 1224514 1689200, 1224514 1459732))')
expected = 'Polygon ((1325786 1449684, 1345882 1449684, 1345882 1469780, 1325786 1469780, 1325786 1449684))'
result = [QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i3_id][0]
self.assertTrue(compareWkt(result, expected, tol=1000), "mismatch Expected:\n{}\nGot:\n{}\n".format(expected,
result))

def imageCheck(self, name, reference_image, image):
TestQgsAnnotationLayer.report += "<h2>Render {}</h2>\n".format(name)
Expand Down

0 comments on commit f5bd9cc

Please sign in to comment.