Skip to content

Commit aaed9ff

Browse files
author
Hugo Mercier
committedFeb 18, 2019
Fix $length in labels (fixes #19355)
Use the project expression scope to access project parameters (ellipsoid and distance/area units)
1 parent 3ef1605 commit aaed9ff

File tree

13 files changed

+65
-8
lines changed

13 files changed

+65
-8
lines changed
 

‎python/core/auto_generated/qgscoordinatetransformcontext.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ Write the context's state to application settings.
185185

186186

187187

188+
188189
/************************************************************************
189190
* This file has been generated automatically from *
190191
* *

‎src/core/expression/qgsexpression.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void QgsExpression::setExpression( const QString &expression )
102102
d->mRootNode = ::parseExpression( expression, d->mParserErrorString, d->mParserErrors );
103103
d->mEvalErrorString = QString();
104104
d->mExp = expression;
105+
d->mIsPrepared = false;
105106
}
106107

107108
QString QgsExpression::expression() const
@@ -313,13 +314,25 @@ bool QgsExpression::needsGeometry() const
313314
return d->mRootNode->needsGeometry();
314315
}
315316

316-
void QgsExpression::initGeomCalculator()
317+
void QgsExpression::initGeomCalculator( const QgsExpressionContext *context )
317318
{
318-
if ( d->mCalc )
319-
return;
320-
321-
// Use planimetric as default
322-
d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
319+
if ( ! d->mCalc )
320+
d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
321+
QString ellipsoid = context->variable( "project_ellipsoid" ).toString();
322+
QString distanceUnitsStr = context->variable( "project_distance_units" ).toString();
323+
QString areaUnitsStr = context->variable( "project_area_units" ).toString();
324+
QgsCoordinateReferenceSystem crs = context->variable( "_layer_crs" ).value<QgsCoordinateReferenceSystem>();
325+
QgsCoordinateTransformContext tContext = context->variable( "_project_transform_context" ).value<QgsCoordinateTransformContext>();
326+
327+
d->mCalc->setEllipsoid( ellipsoid.isEmpty() ? GEO_NONE : ellipsoid );
328+
if ( ! distanceUnitsStr.isEmpty() )
329+
setDistanceUnits( QgsUnitTypes::stringToDistanceUnit( distanceUnitsStr ) );
330+
if ( ! areaUnitsStr.isEmpty() )
331+
setAreaUnits( QgsUnitTypes::stringToAreaUnit( areaUnitsStr ) );
332+
if ( crs.isValid() )
333+
{
334+
d->mCalc->setSourceCrs( crs, tContext );
335+
}
323336
}
324337

325338
void QgsExpression::detach()
@@ -361,6 +374,8 @@ bool QgsExpression::prepare( const QgsExpressionContext *context )
361374
return false;
362375
}
363376

377+
initGeomCalculator( context );
378+
d->mIsPrepared = true;
364379
return d->mRootNode->prepare( this, context );
365380
}
366381

@@ -385,6 +400,12 @@ QVariant QgsExpression::evaluate( const QgsExpressionContext *context )
385400
return QVariant();
386401
}
387402

403+
if ( ! d->mIsPrepared )
404+
{
405+
qWarning( "QgsExpression::evaluate() called on an expression not yet prepared !" );
406+
if ( ! prepare( context ) )
407+
return QVariant();
408+
}
388409
return d->mRootNode->eval( this, context );
389410
}
390411

‎src/core/expression/qgsexpression.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ class CORE_EXPORT QgsExpression
619619
#endif
620620

621621
private:
622-
void initGeomCalculator();
622+
void initGeomCalculator( const QgsExpressionContext *context );
623623

624624
struct HelpArg SIP_SKIP
625625
{

‎src/core/qgscoordinatetransformcontext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include "qgis.h"
2323
#include "qgsdatumtransform.h"
2424

25+
#include <QMetaType>
26+
#include <QExplicitlySharedDataPointer>
2527
class QgsCoordinateReferenceSystem;
2628
class QgsReadWriteContext;
2729
class QgsCoordinateTransformContextPrivate;
@@ -274,6 +276,8 @@ class CORE_EXPORT QgsCoordinateTransformContext
274276

275277
};
276278

279+
Q_DECLARE_METATYPE( QgsCoordinateTransformContext )
280+
277281
#endif // QGSCOORDINATETRANSFORMCONTEXT_H
278282

279283

‎src/core/qgsexpressioncontext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,10 @@ QgsExpressionContextScope *QgsExpressionContextUtils::projectScope( const QgsPro
807807
QgsCoordinateReferenceSystem projectCrs = project->crs();
808808
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
809809
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj4(), true, true ) );
810+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), project->ellipsoid(), true, true ) );
811+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( project->distanceUnits() ), true, true ) );
812+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( project->areaUnits() ), true, true ) );
813+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( project->transformContext() ), true, true ) );
810814

811815
// metadata
812816
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), project->metadata().author(), true, true ) );
@@ -885,6 +889,7 @@ QgsExpressionContextScope *QgsExpressionContextUtils::layerScope( const QgsMapLa
885889

886890
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_name" ), layer->name(), true, true ) );
887891
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_id" ), layer->id(), true, true ) );
892+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_layer_crs" ), QVariant::fromValue<QgsCoordinateReferenceSystem>( layer->crs() ), true, true ) );
888893
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer" ), QVariant::fromValue<QgsWeakMapLayerPointer >( QgsWeakMapLayerPointer( const_cast<QgsMapLayer *>( layer ) ) ), true, true ) );
889894

890895
const QgsVectorLayer *vLayer = qobject_cast< const QgsVectorLayer * >( layer );

‎src/core/qgsexpressionprivate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ class QgsExpressionPrivate
7070
std::shared_ptr<QgsDistanceArea> mCalc;
7171
QgsUnitTypes::DistanceUnit mDistanceUnit = QgsUnitTypes::DistanceUnknownUnit;
7272
QgsUnitTypes::AreaUnit mAreaUnit = QgsUnitTypes::AreaUnknownUnit;
73+
74+
//! Whether prepare() has been called before evaluate()
75+
bool mIsPrepared = false;
7376
};
7477
///@endcond
7578

‎tests/src/core/testqgslayoutitem.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,7 @@ void TestQgsLayoutItem::itemVariablesFunction()
14201420
map->setId( QStringLiteral( "Map_id" ) );
14211421

14221422
c = l.createExpressionContext();
1423+
e.prepare( &c );
14231424
r = e.evaluate( &c );
14241425
QGSCOMPARENEAR( r.toDouble(), 184764103, 100 );
14251426

‎tests/src/core/testqgsmapsettings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,15 @@ void TestQgsMapSettings::testIsLayerVisible()
258258
QCOMPARE( r.toBool(), false );
259259

260260
QgsProject::instance()->removeMapLayer( vlA );
261+
e.prepare( &context );
261262
r = e.evaluate( &context );
262263
QCOMPARE( r.toBool(), false ); // layer is deleted
264+
e2.prepare( &context );
263265
r = e2.evaluate( &context );
264266
QCOMPARE( r.toBool(), true ); // layer still exists
265267

266268
QgsProject::instance()->removeMapLayer( vlB );
269+
e2.prepare( &context );
267270
r = e2.evaluate( &context );
268271
QCOMPARE( r.toBool(), false ); // layer is deleted
269272

‎tests/src/python/test_qgspallabeling_tests.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from qgis.PyQt.QtCore import Qt, QPointF, QSizeF
2424
from qgis.PyQt.QtGui import QFont
2525

26-
from qgis.core import QgsLabelingEngineSettings, QgsPalLayerSettings, QgsUnitTypes, QgsTextBackgroundSettings
26+
from qgis.core import QgsLabelingEngineSettings, QgsPalLayerSettings, QgsUnitTypes, QgsTextBackgroundSettings, QgsProject, QgsExpressionContextUtils, QgsExpressionContext
27+
from qgis.core import QgsCoordinateReferenceSystem
2728

2829
from utilities import svgSymbolsPath
2930

@@ -308,6 +309,24 @@ def test_curved_placement_below(self):
308309
self.lyr.placementFlags = QgsPalLayerSettings.BelowLine | QgsPalLayerSettings.MapOrientation
309310
self.checkTest()
310311

312+
def test_length_expression(self):
313+
# compare length using the ellipsoid in kms and the planimetric distance in meters
314+
self.lyr.fieldName = "round($length,5) || ' - ' || round(length($geometry),2)"
315+
self.lyr.isExpression = True
316+
317+
QgsProject.instance().setCrs(QgsCoordinateReferenceSystem("EPSG:32613"))
318+
QgsProject.instance().setEllipsoid("WGS84")
319+
QgsProject.instance().setDistanceUnits(QgsUnitTypes.DistanceKilometers)
320+
321+
ctxt = QgsExpressionContext()
322+
ctxt.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
323+
ctxt.appendScope(QgsExpressionContextUtils.layerScope(self.layer))
324+
self._TestMapSettings.setExpressionContext(ctxt)
325+
326+
self.lyr.placement = QgsPalLayerSettings.Curved
327+
self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.MapOrientation
328+
self.checkTest()
329+
311330
# noinspection PyPep8Naming
312331

313332

0 commit comments

Comments
 (0)
Please sign in to comment.