Skip to content

Conversation

elpaso
Copy link
Contributor

@elpaso elpaso commented Sep 29, 2025

When map units is not set in the project properties, they are supposed to be taken from the map canvas, but map canvas is not available inside the calculator (or the feature iterator), the fix is to pass the map settings down into the rabbit hole by attaching them to the expression context (in a similar way to what already happens with the project scope variables, with the notable exception that the project is actually available as a global instance while the map canvas is not).

Remaining issue: the settings are not available in the project properties field editor.

Fix #62883

Note: this was tricky, if anyone has a better idea on how to fix this in a better way please let me know and I will be happy to refactor this PR.

When map units is not set in the project properties, they are supposed
to be taken from the map canvas, but map canvas is not available inside
the calculator, the fix is to pass the map settings down into the rabbit
hole by attaching them to the expression context (in a similar way to
what already happens with the project scope variables, with the notable
exception that the project is actually available as a global instance
while the map canvas is not).

Remaining issue: the settings are not available in the project
properties field editor.

Fix qgis#62883
@elpaso elpaso added Bug Either a bug report, or a bug fix. Let's hope for the latter! Field Calculator labels Sep 29, 2025
@github-actions github-actions bot added this to the 4.0.0 milestone Sep 29, 2025
Copy link
Contributor

github-actions bot commented Sep 29, 2025

🪟 Windows Qt6 builds

Download Windows Qt6 builds of this PR for testing.
(Built from commit e322e28)

🍎 MacOS Qt6 builds

Download MacOS Qt6 builds of this PR for testing.
This installer is not signed, control+click > open the app to avoid the warning
(Built from commit e322e28)

@nyalldawson
Copy link
Collaborator

I'm a little confused why the map settings scope is required for distance units resolution -- this should be entirely accessible from the project scope alone, as it's a project level setting. My reservation with adding the map settings scope to field calculator is that there's not a 1:1 relationship with canvases there -- a single project may have many map canvases, and each of these canvases can have different settings (eg scale, extent). That's why I originally limited the use of the map settings scope to ONLY rendering in a particular canvas, and also why it's not available for use with virtual fields.

mExpressionContext->appendScope( new QgsExpressionContextScope( mSource->mLayerScope ) );
mExpressionContext->setFeedback( mRequest.feedback() );
// We need to keep the map settings scope if set in the original context
const int settingsScopeIndex = mRequest.expressionContext()->indexOfScope( QStringLiteral( "Map Settings" ) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the reason for this change -- could you break this pr up into separate atomic commits so that I can understand better?

@elpaso
Copy link
Contributor Author

elpaso commented Sep 30, 2025

I'm a little confused why the map settings scope is required for distance units resolution -- this should be entirely accessible from the project scope alone, as it's a project level setting.

@nyalldawson Sorry if that wasn't clear:

When map units is not set in the project properties, they are supposed to be taken from the map canvas, but map canvas is not available inside the calculator (or the feature iterator)

I'll try to rephrase it: the problem is when:

  1. there are virtual fields with an expression that need the map units information (e.g. $length)
  2. the project settings map units is set to Unknown (which means: take the units from the canvas current CRS)

The canvas is not accessible in the context of the expression from inside the iterator (which is used by the expression preview dialog and from the attribute dialog).

So, I thought about the possible solutions:

  1. change the API of the field calculator and all the intermediate objects to pass a (possibly null) canvas pointer down the (long) chain of function calls
  2. add an hidden property to the QgsProject to store the current canvas CRS (or the CRS distance units), and keep it in sync when the canvas CRS changes
  3. the one I implemented: store the map settings in the expression context and retrieve the CRS map units in case they are Unknown in the project settings.

@elpaso
Copy link
Contributor Author

elpaso commented Oct 1, 2025

That's why I originally limited the use of the map settings scope to ONLY rendering in a particular canvas, and also why it's not available for use with virtual fields.

I understand and agree, but if that's the case how can the expression get the map units from the canvas if the units are set to Unknown in the project properties?

So either we change the meaning of Unknown to get the units of the CRS of the project instead of get the units from the canvas or we store the current (main) canvas CRS in the project and keep it in sync or we implement my solution.

Any other idea?

@nyalldawson
Copy link
Collaborator

So either we change the meaning of Unknown to get the units of the CRS of the project

I think this is the correct solution -- I believe the "get from the canvas" definition is a relic from when we ONLY had a canvas crs and had no concept of a "project crs".

@elpaso
Copy link
Contributor Author

elpaso commented Oct 1, 2025

So either we change the meaning of Unknown to get the units of the CRS of the project

I think this is the correct solution -- I believe the "get from the canvas" definition is a relic from when we ONLY had a canvas crs and had no concept of a "project crs".

Ok, I'll proceed in that direction.

@elpaso
Copy link
Contributor Author

elpaso commented Oct 1, 2025

@nyalldawson I'm having second thoughts: if we change Unknown to be the project CRS instead of the map units there are many consequences: see for instance QgsProjectProperties::updateGuiForMapUnits().

Or are you suggesting to change the concept of "Map Units" from "Canvas units" to "Project units" ? this would have an even more deeper impact.

The current behavior is that when a user changes the map canvas units, the distance units (if set to Unknown) are changed to match the current canvas units.

This is not going to work anymore. I assume the same apply for the area units.

There is also the possibility to change the documentation of the $length to state that when used in a context where the map canvas is not accessible (hard to explain exactly what they are though) the distance units set to Unknown won't be retrieved from the current map (canvas) units.

I must say I don't really like any of the above solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Either a bug report, or a bug fix. Let's hope for the latter! Field Calculator
Projects
None yet
Development

Successfully merging this pull request may close these issues.

$length returns meters when distance units set to map units
2 participants