mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
[labeling] Highlight all characters in curved labels when hovering
using a map label tool, instead of just one character
This commit is contained in:
parent
52ff5f3941
commit
955cc8bf8c
@ -40,6 +40,13 @@ Returns the details of any labels placed at the specified point (in map coordina
|
||||
QList<QgsLabelPosition> labelsWithinRect( const QgsRectangle &r ) const;
|
||||
%Docstring
|
||||
Returns the details of any labels placed within the specified rectangle (in map coordinates).
|
||||
%End
|
||||
|
||||
QList<QgsLabelPosition> groupedLabelPositions( long long groupId ) const;
|
||||
%Docstring
|
||||
Returns a list of all label positions sharing the same group ID (i.e. positions for individual characters in a curved label).
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
QList<QgsCalloutPosition> calloutsWithinRectangle( const QgsRectangle &rectangle ) const;
|
||||
|
||||
@ -83,6 +83,8 @@ Constructor for QgsLabelPosition
|
||||
QString providerID;
|
||||
|
||||
bool isUnplaced;
|
||||
|
||||
long long groupedLabelId;
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
||||
@ -62,6 +62,7 @@ QgsLabelSearchTree keeps ownership, don't delete the returned objects.
|
||||
.. versionadded:: 3.20
|
||||
%End
|
||||
|
||||
|
||||
void setMapSettings( const QgsMapSettings &settings );
|
||||
%Docstring
|
||||
Sets the map ``settings`` associated with the labeling run.
|
||||
|
||||
@ -1194,11 +1194,26 @@ void QgsMapToolLabel::updateHoveredLabel( QgsMapMouseEvent *e )
|
||||
|
||||
mHoverRubberBand->show();
|
||||
mHoverRubberBand->reset( QgsWkbTypes::LineGeometry );
|
||||
mHoverRubberBand->addPoint( labelPos.cornerPoints.at( 0 ) );
|
||||
mHoverRubberBand->addPoint( labelPos.cornerPoints.at( 1 ) );
|
||||
mHoverRubberBand->addPoint( labelPos.cornerPoints.at( 2 ) );
|
||||
mHoverRubberBand->addPoint( labelPos.cornerPoints.at( 3 ) );
|
||||
mHoverRubberBand->addPoint( labelPos.cornerPoints.at( 0 ) );
|
||||
if ( const QgsLabelingResults *labelingResults = mCanvas->labelingResults( false ) )
|
||||
{
|
||||
if ( labelPos.groupedLabelId != 0 )
|
||||
{
|
||||
// if it's a curved label, we need to highlight ALL characters
|
||||
const QList< QgsLabelPosition > allPositions = labelingResults->groupedLabelPositions( labelPos.groupedLabelId );
|
||||
for ( const QgsLabelPosition &position : allPositions )
|
||||
{
|
||||
mHoverRubberBand->addGeometry( position.labelGeometry );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mHoverRubberBand->addGeometry( labelPos.labelGeometry );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mHoverRubberBand->addGeometry( labelPos.labelGeometry );
|
||||
}
|
||||
QgisApp::instance()->statusBarIface()->showMessage( tr( "Label “%1” in %2" ).arg( labelPos.labelText, mCurrentHoverLabel.layer->name() ), 2000 );
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,19 @@ QList<QgsLabelPosition> QgsLabelingResults::labelsWithinRect( const QgsRectangle
|
||||
return positions;
|
||||
}
|
||||
|
||||
QList<QgsLabelPosition> QgsLabelingResults::groupedLabelPositions( long long groupId ) const
|
||||
{
|
||||
QList<QgsLabelPosition> positions;
|
||||
if ( mLabelSearchTree )
|
||||
{
|
||||
const QList<QgsLabelPosition *> positionPointers = mLabelSearchTree->groupedLabelPositions( groupId );
|
||||
positions.reserve( positionPointers.size() );
|
||||
for ( const QgsLabelPosition *pos : positionPointers )
|
||||
positions.push_back( QgsLabelPosition( *pos ) );
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
QList<QgsCalloutPosition> QgsLabelingResults::calloutsWithinRectangle( const QgsRectangle &rectangle ) const
|
||||
{
|
||||
QList<QgsCalloutPosition> positions;
|
||||
|
||||
@ -57,6 +57,13 @@ class CORE_EXPORT QgsLabelingResults
|
||||
*/
|
||||
QList<QgsLabelPosition> labelsWithinRect( const QgsRectangle &r ) const;
|
||||
|
||||
/**
|
||||
* Returns a list of all label positions sharing the same group ID (i.e. positions for individual characters in a curved label).
|
||||
*
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
QList<QgsLabelPosition> groupedLabelPositions( long long groupId ) const;
|
||||
|
||||
/**
|
||||
* Returns a list of callouts with origins or destinations inside the given \a rectangle.
|
||||
*
|
||||
|
||||
@ -153,6 +153,13 @@ class CORE_EXPORT QgsLabelPosition
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
bool isUnplaced = false;
|
||||
|
||||
/**
|
||||
* If non zero, indicates that the label position is part of a group of label positions (i.e. a character in a curved label).
|
||||
*
|
||||
* All other linked positions will share the same groupedLabelId.
|
||||
*/
|
||||
long long groupedLabelId = 0;
|
||||
};
|
||||
|
||||
#endif // QGSLABELPOSITION_H
|
||||
|
||||
@ -73,7 +73,7 @@ void QgsLabelSearchTree::labelsInRect( const QgsRectangle &r, QList<QgsLabelPosi
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsLabelSearchTree::insertLabel( pal::LabelPosition *labelPos, QgsFeatureId featureId, const QString &layerName, const QString &labeltext, const QFont &labelfont, bool diagram, bool pinned, const QString &providerId, bool isUnplaced )
|
||||
bool QgsLabelSearchTree::insertLabel( pal::LabelPosition *labelPos, QgsFeatureId featureId, const QString &layerName, const QString &labeltext, const QFont &labelfont, bool diagram, bool pinned, const QString &providerId, bool isUnplaced, long long linkedId )
|
||||
{
|
||||
if ( !labelPos )
|
||||
{
|
||||
@ -97,16 +97,30 @@ bool QgsLabelSearchTree::insertLabel( pal::LabelPosition *labelPos, QgsFeatureId
|
||||
yMax = std::max( yMax, res.y() );
|
||||
}
|
||||
|
||||
pal::LabelPosition *next = labelPos->nextPart();
|
||||
long long uniqueLinkedId = 0;
|
||||
if ( linkedId != 0 )
|
||||
uniqueLinkedId = linkedId;
|
||||
else if ( next )
|
||||
uniqueLinkedId = mNextFeatureId++;
|
||||
|
||||
const QgsRectangle bounds( xMin, yMin, xMax, yMax );
|
||||
const QgsGeometry labelGeometry( QgsGeometry::fromPolygonXY( QVector<QgsPolylineXY>() << cornerPoints ) );
|
||||
std::unique_ptr< QgsLabelPosition > newEntry = std::make_unique< QgsLabelPosition >( featureId, labelPos->getAlpha() + mMapSettings.rotation(), cornerPoints, bounds,
|
||||
labelPos->getWidth(), labelPos->getHeight(), layerName, labeltext, labelfont, labelPos->getUpsideDown(), diagram, pinned, providerId, labelGeometry, isUnplaced );
|
||||
newEntry->groupedLabelId = uniqueLinkedId;
|
||||
mSpatialIndex.insert( newEntry.get(), bounds );
|
||||
|
||||
if ( uniqueLinkedId != 0 )
|
||||
{
|
||||
mLinkedLabelHash[ uniqueLinkedId ].append( newEntry.get() );
|
||||
}
|
||||
|
||||
mOwnedPositions.emplace_back( std::move( newEntry ) );
|
||||
|
||||
if ( pal::LabelPosition *next = labelPos->nextPart() )
|
||||
if ( next )
|
||||
{
|
||||
return insertLabel( next, featureId, layerName, labeltext, labelfont, diagram, pinned, providerId, isUnplaced );
|
||||
return insertLabel( next, featureId, layerName, labeltext, labelfont, diagram, pinned, providerId, isUnplaced, uniqueLinkedId );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -141,6 +155,11 @@ QList<const QgsCalloutPosition *> QgsLabelSearchTree::calloutsInRectangle( const
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
QList<QgsLabelPosition *> QgsLabelSearchTree::groupedLabelPositions( long long groupId ) const
|
||||
{
|
||||
return mLinkedLabelHash.value( groupId );
|
||||
}
|
||||
|
||||
void QgsLabelSearchTree::setMapSettings( const QgsMapSettings &settings )
|
||||
{
|
||||
mMapSettings = settings;
|
||||
|
||||
@ -92,7 +92,7 @@ class CORE_EXPORT QgsLabelSearchTree
|
||||
* \returns TRUE in case of success
|
||||
* \note not available in Python bindings
|
||||
*/
|
||||
bool insertLabel( pal::LabelPosition *labelPos, QgsFeatureId featureId, const QString &layerName, const QString &labeltext, const QFont &labelfont, bool diagram = false, bool pinned = false, const QString &providerId = QString(), bool isUnplaced = false ) SIP_SKIP;
|
||||
bool insertLabel( pal::LabelPosition *labelPos, QgsFeatureId featureId, const QString &layerName, const QString &labeltext, const QFont &labelfont, bool diagram = false, bool pinned = false, const QString &providerId = QString(), bool isUnplaced = false, long long linkedId = 0 ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Inserts a rendered callout position.
|
||||
@ -114,6 +114,16 @@ class CORE_EXPORT QgsLabelSearchTree
|
||||
*/
|
||||
QList<const QgsCalloutPosition *> calloutsInRectangle( const QgsRectangle &rectangle ) const;
|
||||
|
||||
/**
|
||||
* Returns a list of all label positions sharing the same group ID (i.e. positions for individual characters in a curved label).
|
||||
*
|
||||
* QgsLabelSearchTree keeps ownership, don't delete the LabelPositions
|
||||
*
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
QList<QgsLabelPosition *> groupedLabelPositions( long long groupId ) const SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Sets the map \a settings associated with the labeling run.
|
||||
* \since QGIS 3.4.8
|
||||
@ -122,6 +132,8 @@ class CORE_EXPORT QgsLabelSearchTree
|
||||
|
||||
private:
|
||||
QgsGenericSpatialIndex< QgsLabelPosition > mSpatialIndex;
|
||||
long long mNextFeatureId = 1;
|
||||
QHash< long long, QList< QgsLabelPosition * > > mLinkedLabelHash;
|
||||
std::vector< std::unique_ptr< QgsLabelPosition > > mOwnedPositions;
|
||||
QgsGenericSpatialIndex< QgsCalloutPosition > mCalloutIndex;
|
||||
std::vector< std::unique_ptr< QgsCalloutPosition > > mOwnedCalloutPositions;
|
||||
|
||||
@ -84,6 +84,7 @@ class TestQgsLabelingEngine : public QObject
|
||||
void testLabelRotationUnit();
|
||||
void drawUnplaced();
|
||||
void labelingResults();
|
||||
void labelingResultsCurved();
|
||||
void labelingResultsWithCallouts();
|
||||
void pointsetExtend();
|
||||
void curvedOverrun();
|
||||
@ -2458,6 +2459,109 @@ void TestQgsLabelingEngine::labelingResults()
|
||||
QCOMPARE( labels.count(), 0 );
|
||||
}
|
||||
|
||||
void TestQgsLabelingEngine::labelingResultsCurved()
|
||||
{
|
||||
// test retrieval of labeling results for curved placement
|
||||
QgsPalLayerSettings settings;
|
||||
setDefaultLabelParams( settings );
|
||||
|
||||
QgsTextFormat format = settings.format();
|
||||
format.setSize( 20 );
|
||||
format.setColor( QColor( 0, 0, 0 ) );
|
||||
settings.setFormat( format );
|
||||
|
||||
settings.fieldName = QStringLiteral( "\"id\"" );
|
||||
settings.isExpression = true;
|
||||
settings.placement = QgsPalLayerSettings::Curved;
|
||||
settings.priority = 10;
|
||||
|
||||
std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
|
||||
vl2->setRenderer( new QgsNullSymbolRenderer() );
|
||||
|
||||
QgsFeature f;
|
||||
f.setAttributes( QgsAttributes() << 1 );
|
||||
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (-23.48732038587919746 58.94708170839115979, -11.017713405345674 60.99534928128858979)" ) ) );
|
||||
QVERIFY( vl2->dataProvider()->addFeature( f ) );
|
||||
f.setAttributes( QgsAttributes() << 8888 );
|
||||
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (-16.22889244198655234 57.45670302419525655, -5.39708458725444817 57.53670918909697463)" ) ) );
|
||||
QVERIFY( vl2->dataProvider()->addFeature( f ) );
|
||||
f.setAttributes( QgsAttributes() << 33333 );
|
||||
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (-23.52454309328377136 55.7985199122237816, -12.24606274969673869 53.62741130396216249)" ) ) );
|
||||
QVERIFY( vl2->dataProvider()->addFeature( f ) );
|
||||
vl2->updateExtents();
|
||||
|
||||
vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
|
||||
vl2->setLabelsEnabled( true );
|
||||
|
||||
// make a fake render context
|
||||
const QSize size( 640, 480 );
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
|
||||
const QgsCoordinateReferenceSystem tgtCrs( QStringLiteral( "EPSG:3857" ) );
|
||||
mapSettings.setDestinationCrs( tgtCrs );
|
||||
|
||||
mapSettings.setOutputSize( size );
|
||||
mapSettings.setExtent( QgsRectangle( -4137976.6, 6557092.6, 1585557.4, 9656515.0 ) );
|
||||
// mapSettings.setRotation( 60 );
|
||||
mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
|
||||
mapSettings.setOutputDpi( 96 );
|
||||
|
||||
QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
|
||||
engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
|
||||
engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
|
||||
//engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
|
||||
mapSettings.setLabelingEngineSettings( engineSettings );
|
||||
|
||||
QgsMapRendererSequentialJob job( mapSettings );
|
||||
job.start();
|
||||
job.waitForFinished();
|
||||
|
||||
std::unique_ptr< QgsLabelingResults > results( job.takeLabelingResults() );
|
||||
QVERIFY( results );
|
||||
|
||||
// retrieve some labels
|
||||
QList<QgsLabelPosition> labels = results->allLabels();
|
||||
QCOMPARE( labels.count(), 10 );
|
||||
std::sort( labels.begin(), labels.end(), []( const QgsLabelPosition & a, const QgsLabelPosition & b )
|
||||
{
|
||||
return a.labelText.compare( b.labelText ) < 0;
|
||||
} );
|
||||
QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "1" ) );
|
||||
QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "33333" ) );
|
||||
long long group2 = labels.at( 1 ).groupedLabelId;
|
||||
QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 2 ).groupedLabelId, group2 );
|
||||
QCOMPARE( labels.at( 3 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 3 ).groupedLabelId, group2 );
|
||||
QCOMPARE( labels.at( 4 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 4 ).groupedLabelId, group2 );
|
||||
QCOMPARE( labels.at( 5 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 5 ).groupedLabelId, group2 );
|
||||
long long group3 = labels.at( 6 ).groupedLabelId;
|
||||
QCOMPARE( labels.at( 6 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 7 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 7 ).groupedLabelId, group3 );
|
||||
QCOMPARE( labels.at( 8 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 8 ).groupedLabelId, group3 );
|
||||
QCOMPARE( labels.at( 9 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 9 ).groupedLabelId, group3 );
|
||||
|
||||
labels = results->groupedLabelPositions( group2 );
|
||||
QCOMPARE( labels.size(), 5 );
|
||||
QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 3 ).labelText, QStringLiteral( "33333" ) );
|
||||
QCOMPARE( labels.at( 4 ).labelText, QStringLiteral( "33333" ) );
|
||||
|
||||
labels = results->groupedLabelPositions( group3 );
|
||||
QCOMPARE( labels.size(), 4 );
|
||||
QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "8888" ) );
|
||||
QCOMPARE( labels.at( 3 ).labelText, QStringLiteral( "8888" ) );
|
||||
}
|
||||
|
||||
void TestQgsLabelingEngine::labelingResultsWithCallouts()
|
||||
{
|
||||
// test retrieval of rendered callout properties from labeling results
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user