Skip to content

Commit db4bf52

Browse files
author
Hugo Mercier
authoredFeb 19, 2019
Merge pull request #9192 from mhugo/backports
Backports
2 parents c4ef769 + 3e60181 commit db4bf52

File tree

16 files changed

+110
-19
lines changed

16 files changed

+110
-19
lines changed
 

‎python/core/auto_generated/expression/qgsexpression.sip.in‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,12 @@ Returns calculator used for distance and area calculations
293293
void setGeomCalculator( const QgsDistanceArea *calc );
294294
%Docstring
295295
Sets the geometry calculator used for distance and area calculations in expressions.
296-
(used by $length, $area and $perimeter functions only). By default, no geometry
297-
calculator is set and all distance and area calculations are performed using simple
296+
(used by $length, $area and $perimeter functions only).
297+
If the geometry calculator is set to None (default), prepare() will read variables
298+
from the expression context ("project_ellipsoid", "_project_transform_context" and
299+
"_layer_crs") to build a geometry calculator.
300+
If these variables does not exist and if setGeomCalculator() is not called,
301+
all distance and area calculations are performed using simple
298302
Cartesian methods (ie no ellipsoidal calculations).
299303

300304
:param calc: geometry calculator. Ownership is not transferred. Set to a None to force
@@ -321,6 +325,8 @@ Returns the desired distance units for calculations involving geomCalculator(),
321325
void setDistanceUnits( QgsUnitTypes::DistanceUnit unit );
322326
%Docstring
323327
Sets the desired distance units for calculations involving geomCalculator(), e.g., "$length" and "$perimeter".
328+
If distance units are set to QgsUnitTypes.DistanceUnknownUnit (default), prepare() will read
329+
variables from the expression context ("project_distance_units") to determine distance units.
324330

325331
.. note::
326332

@@ -351,6 +357,8 @@ Returns the desired areal units for calculations involving geomCalculator(), e.g
351357
void setAreaUnits( QgsUnitTypes::AreaUnit unit );
352358
%Docstring
353359
Sets the desired areal units for calculations involving geomCalculator(), e.g., "$area".
360+
If distance units are set to QgsUnitTypes.AreaUnknownUnit (default), prepare() will read
361+
variables from the expression context ("project_distance_units") to determine distance units.
354362

355363
.. note::
356364

‎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: 42 additions & 5 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,37 @@ 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;
319+
// Set the geometry calculator from the context if it has not been set by setGeomCalculator()
320+
if ( context && ! d->mCalc )
321+
{
322+
QString ellipsoid = context->variable( QStringLiteral( "project_ellipsoid" ) ).toString();
323+
QgsCoordinateReferenceSystem crs = context->variable( QStringLiteral( "_layer_crs" ) ).value<QgsCoordinateReferenceSystem>();
324+
QgsCoordinateTransformContext tContext = context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>();
325+
if ( crs.isValid() )
326+
{
327+
d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
328+
d->mCalc->setEllipsoid( ellipsoid.isEmpty() ? GEO_NONE : ellipsoid );
329+
d->mCalc->setSourceCrs( crs, tContext );
330+
}
331+
}
332+
333+
// Set the distance units from the context if it has not been set by setDistanceUnits()
334+
if ( context && distanceUnits() == QgsUnitTypes::DistanceUnknownUnit )
335+
{
336+
QString distanceUnitsStr = context->variable( QStringLiteral( "project_distance_units" ) ).toString();
337+
if ( ! distanceUnitsStr.isEmpty() )
338+
setDistanceUnits( QgsUnitTypes::stringToDistanceUnit( distanceUnitsStr ) );
339+
}
320340

321-
// Use planimetric as default
322-
d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
341+
// Set the area units from the context if it has not been set by setAreaUnits()
342+
if ( context && areaUnits() == QgsUnitTypes::AreaUnknownUnit )
343+
{
344+
QString areaUnitsStr = context->variable( QStringLiteral( "project_area_units" ) ).toString();
345+
if ( ! areaUnitsStr.isEmpty() )
346+
setAreaUnits( QgsUnitTypes::stringToAreaUnit( areaUnitsStr ) );
347+
}
323348
}
324349

325350
void QgsExpression::detach()
@@ -361,6 +386,8 @@ bool QgsExpression::prepare( const QgsExpressionContext *context )
361386
return false;
362387
}
363388

389+
initGeomCalculator( context );
390+
d->mIsPrepared = true;
364391
return d->mRootNode->prepare( this, context );
365392
}
366393

@@ -385,6 +412,11 @@ QVariant QgsExpression::evaluate( const QgsExpressionContext *context )
385412
return QVariant();
386413
}
387414

415+
if ( ! d->mIsPrepared )
416+
{
417+
qWarning( "QgsExpression::evaluate() called on an expression not yet prepared !" );
418+
prepare( context );
419+
}
388420
return d->mRootNode->eval( this, context );
389421
}
390422

@@ -684,8 +716,10 @@ void QgsExpression::initVariableHelp()
684716
sVariableHelpTexts.insert( QStringLiteral( "qgis_version" ), QCoreApplication::translate( "variable_help", "Current QGIS version string." ) );
685717
sVariableHelpTexts.insert( QStringLiteral( "qgis_version_no" ), QCoreApplication::translate( "variable_help", "Current QGIS version number." ) );
686718
sVariableHelpTexts.insert( QStringLiteral( "qgis_release_name" ), QCoreApplication::translate( "variable_help", "Current QGIS release name." ) );
719+
sVariableHelpTexts.insert( QStringLiteral( "qgis_short_version" ), QCoreApplication::translate( "variable_help", "Short QGIS version string." ) );
687720
sVariableHelpTexts.insert( QStringLiteral( "qgis_os_name" ), QCoreApplication::translate( "variable_help", "Operating system name, e.g., 'windows', 'linux' or 'osx'." ) );
688721
sVariableHelpTexts.insert( QStringLiteral( "qgis_platform" ), QCoreApplication::translate( "variable_help", "QGIS platform, e.g., 'desktop' or 'server'." ) );
722+
sVariableHelpTexts.insert( QStringLiteral( "qgis_locale" ), QCoreApplication::translate( "variable_help", "Two letter identifier for current QGIS locale." ) );
689723
sVariableHelpTexts.insert( QStringLiteral( "user_account_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system account name." ) );
690724
sVariableHelpTexts.insert( QStringLiteral( "user_full_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system user name (if available)." ) );
691725

@@ -703,6 +737,9 @@ void QgsExpression::initVariableHelp()
703737
sVariableHelpTexts.insert( QStringLiteral( "project_creation_date" ), QCoreApplication::translate( "variable_help", "Project creation date, taken from project metadata." ) );
704738
sVariableHelpTexts.insert( QStringLiteral( "project_identifier" ), QCoreApplication::translate( "variable_help", "Project identifier, taken from project metadata." ) );
705739
sVariableHelpTexts.insert( QStringLiteral( "project_keywords" ), QCoreApplication::translate( "variable_help", "Project keywords, taken from project metadata." ) );
740+
sVariableHelpTexts.insert( QStringLiteral( "project_area_units" ), QCoreApplication::translate( "variable_help", "Area unit for current project, used when calculating areas of geometries." ) );
741+
sVariableHelpTexts.insert( QStringLiteral( "project_distance_units" ), QCoreApplication::translate( "variable_help", "Distance unit for current project, used when calculating lengths of geometries." ) );
742+
sVariableHelpTexts.insert( QStringLiteral( "project_ellipsoid" ), QCoreApplication::translate( "variable_help", "Name of ellipsoid of current project, used when calculating geodetic areas and lengths of geometries." ) );
706743

707744
//layer variables
708745
sVariableHelpTexts.insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );

‎src/core/expression/qgsexpression.h‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,12 @@ class CORE_EXPORT QgsExpression
379379

380380
/**
381381
* Sets the geometry calculator used for distance and area calculations in expressions.
382-
* (used by $length, $area and $perimeter functions only). By default, no geometry
383-
* calculator is set and all distance and area calculations are performed using simple
382+
* (used by $length, $area and $perimeter functions only).
383+
* If the geometry calculator is set to nullptr (default), prepare() will read variables
384+
* from the expression context ("project_ellipsoid", "_project_transform_context" and
385+
* "_layer_crs") to build a geometry calculator.
386+
* If these variables does not exist and if setGeomCalculator() is not called,
387+
* all distance and area calculations are performed using simple
384388
* Cartesian methods (ie no ellipsoidal calculations).
385389
* \param calc geometry calculator. Ownership is not transferred. Set to a nullptr to force
386390
* Cartesian calculations.
@@ -399,6 +403,8 @@ class CORE_EXPORT QgsExpression
399403

400404
/**
401405
* Sets the desired distance units for calculations involving geomCalculator(), e.g., "$length" and "$perimeter".
406+
* If distance units are set to QgsUnitTypes::DistanceUnknownUnit (default), prepare() will read
407+
* variables from the expression context ("project_distance_units") to determine distance units.
402408
* \note distances are only converted when a geomCalculator() has been set
403409
* \see distanceUnits()
404410
* \see setAreaUnits()
@@ -417,6 +423,8 @@ class CORE_EXPORT QgsExpression
417423

418424
/**
419425
* Sets the desired areal units for calculations involving geomCalculator(), e.g., "$area".
426+
* If distance units are set to QgsUnitTypes::AreaUnknownUnit (default), prepare() will read
427+
* variables from the expression context ("project_distance_units") to determine distance units.
420428
* \note areas are only converted when a geomCalculator() has been set
421429
* \see areaUnits()
422430
* \see setDistanceUnits()
@@ -619,7 +627,7 @@ class CORE_EXPORT QgsExpression
619627
#endif
620628

621629
private:
622-
void initGeomCalculator();
630+
void initGeomCalculator( const QgsExpressionContext *context );
623631

624632
struct HelpArg SIP_SKIP
625633
{

‎src/core/expression/qgsexpressionnodeimpl.cpp‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,16 +1261,18 @@ bool QgsExpressionNodeColumnRef::prepareNode( QgsExpression *parent, const QgsEx
12611261
QgsFields fields = qvariant_cast<QgsFields>( context->variable( QgsExpressionContext::EXPR_FIELDS ) );
12621262

12631263
mIndex = fields.lookupField( mName );
1264-
if ( mIndex >= 0 )
1264+
1265+
if ( mIndex == -1 && context->hasFeature() )
12651266
{
1266-
return true;
1267+
mIndex = context->feature().fieldNameIndex( mName );
12671268
}
1268-
else
1269+
1270+
if ( mIndex == -1 )
12691271
{
12701272
parent->setEvalErrorString( tr( "Column '%1' not found" ).arg( mName ) );
1271-
mIndex = -1;
12721273
return false;
12731274
}
1275+
return true;
12741276
}
12751277

12761278
QString QgsExpressionNodeColumnRef::dump() const

‎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

‎src/gui/editorwidgets/qgsrangewidgetwrapper.cpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,12 @@ void QgsRangeWidgetWrapper::initWidget( QWidget *editor )
144144
int minval = min.toInt();
145145
if ( allowNull )
146146
{
147-
int stepval = step.isValid() ? step.toInt() : 1;
148-
int newMinval = minval - stepval;
147+
uint stepval = step.isValid() ? step.toUInt() : 1;
149148
// make sure there is room for a new value (i.e. signed integer does not overflow)
150-
if ( newMinval < minval )
149+
int minvalOverflow = uint( minval ) - stepval;
150+
if ( minvalOverflow < minval )
151151
{
152-
minval = newMinval;
152+
minval = minvalOverflow;
153153
}
154154
mIntSpinBox->setValue( minval );
155155
QgsSpinBox *intSpinBox( qobject_cast<QgsSpinBox *>( mIntSpinBox ) );

‎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

14.2 KB
Loading
14.2 KB
Loading
11.5 KB

Error rendering embedded code

Invalid image source.

14.2 KB
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.