g.region: Output CRS info in JSON#6407
Merged
wenzeslaus merged 12 commits intoOSGeo:mainfrom Sep 30, 2025
Merged
Conversation
The original g.region output contains basic info about the current CRS (aka projection). While this is only basic, providing only distinction between XY, LL, and other (and UTM+zone to certain extent), it is used in the code base and expected to be correct, for example g.proj is using it before checking anything else. Although it is separate from the stored PROJ files, it serves as the primary way of checking what kind of CRS is one dealing with. While this is unrelated to computational region, and in fact, it cannot be modified by g.region (it is ready-only for g.region), you could say it encourages a correct interpretation of the computational region information provided by g.region. This adds pytest-based tests covering XY, LL, UTM N, UTM S, and other CRS cases. The key names, the structure, and the values are simply taken from the current output with format='shell', i.e., keys are at the top-level, called 'projection' and 'zone', and values are integers. This means that there is no obvious way how to interpret that information, but the information can be directly used to compare with another computational region even when accessed in a different way (like directly looking into the DEFAULT_WIND file).
petrasovaa
previously approved these changes
Sep 25, 2025
Contributor
petrasovaa
left a comment
There was a problem hiding this comment.
I would leave it as you have it, I still think this should go away, so I wouldn't try to come up with a better naming.
…rinted at the the end of the output. Use CRS (crs) instead of projection because it is more general. Include also string representation of CRS (projection) type (defined in g.region). Print also the name since other outputs are printing it. Test for a custom projection. Use the new API in r.pack and also modify the new region info part of the pack format to align with g.region API (rather than the already loose alignment with WIND file format).
Member
Author
|
I'm afraid we won't be removing it or redoing it in the next two releases or so, so I at the end decided to make it the best possible output with what we need and are doing now. Here it is: {
"north": 320000,
"south": 10000,
"west": 120000,
"east": 935000,
"nsres": 500,
"ewres": 500,
"rows": 620,
"cols": 1630,
"cells": 1010600,
"crs": {
"name": "Lambert Conformal Conic",
"type": "other",
"type_code": 99,
"zone": 0
}
}I also fixed the tests I missed the first time around. Additionally, I changed the recent addition to the r.pack output file format (#6408) to be aligned with the new API (and of course to use it as well). |
petrasovaa
reviewed
Sep 29, 2025
petrasovaa
reviewed
Sep 29, 2025
…st can be its own file separated from the CRS topic.
wenzeslaus
added a commit
to wenzeslaus/grass
that referenced
this pull request
Sep 30, 2025
The code for reading cell header (and computational region) used -1 as a default for zone (following example of the proj field). However, negative numbers are used to represent UTM south zones. On the other hand, 0 is used as a value for CRSs without zone (so everything else except UTM). So, this changes the default from -1 to 0. The code is reading internal GRASS data, and the writing code is setting the zone to 0, so -1 did not pop up anywhere based on the fact that zone is required by the code to be present (so -1 was either replaced or error was produced). This should be covered by g.region tests in OSGeo#6407, so let's merge that and update here.
petrasovaa
previously approved these changes
Sep 30, 2025
wenzeslaus
commented
Sep 30, 2025
petrasovaa
approved these changes
Sep 30, 2025
petrasovaa
approved these changes
Sep 30, 2025
wenzeslaus
added a commit
that referenced
this pull request
Sep 30, 2025
The code for reading cell header (and computational region) used -1 as a default for zone (following example of the proj field). However, negative numbers are used to represent UTM south zones. On the other hand, 0 is used as a value for CRSs without zone (so everything else except UTM). So, this changes the default from -1 to 0. The code is reading internal GRASS data, and the writing code is setting the zone to 0, so -1 did not pop up anywhere based on the fact that zone is required by the code to be present (so -1 was either replaced or error was produced). Core the context, the internal stograte is a custom key-value pair text file which uses 0. On the other hand, 'null' is available and used as a value in JSON outputs (#6407) and in r.pack computation region CRS JSON info which is implemented as a pass-through from the g.region JSON output (#6408 for original and #6407 for pass-through). This does not have a specific test because in the overall integration only 0 is occuring (see above). The general functionality should be covered by g.region tests from #6407.
wenzeslaus
added a commit
that referenced
this pull request
Oct 1, 2025
This adds another parameter to create_project called pack which allows for creating a project from raster pack files (requires the recent fix from #6408 for the complete CRS info including the computational region addition). Additionally, it refactors general CRS string handling from grass.py so that the pack code is available in the command line, but also that the general handling is available in the Python API. Because grass/script/core.py can't take any more code, the underlying code is in grass.grassdb.create. This includes both the new functions and refactored code (the code reuses pieces of the XY project creation). Tests are included for the create_project level and separate tests for the underlying grass.grassdb.create level. The new functions include private functions (some of them used in create_project) and public functions (but somewhat internal in grass.grassdb). The new add/create region function uses the general terms used now in g.region JSON API (#6407), so it is less dependent on the keys in the internal region format. As part of the refactoring, this moves handling of a general CRS string (aka geostring) from the main executable (grass.py) script to the library, so that library function has the one, universal parameter feature and the CLI has the pack without adding CLI specific code to support pack in CLI. A new translatable intro error message is now produced in the command line when project creation fails to. The text serves as an into to whatever comes afterwards from the exception (e.g. GDAL output). Similarly to the main grass.py CLI, this adds the CRS string parameter to the experimental subcommand run. Since the create_function now supports this universal, CLI-friendly way, the addition is just about passing the parameter from command line to the function, so it is easy to add with benefits mostly for testing at this point. This is adding a separate fixture for grass.app which is the same as the one added to grass.script, but having the fixture at the grass package level interfered with session setups in the grass.app tests. The grass.app fixture is using grass.tools while the grass.script one is written with run_command, so that also makes them different and aligns well with grass.script not using the grass.tools API. For tests of the subcommands, this uses the standard pytest output capturing fixture, but it also runs the same tests with the subcommand package to capture stdout on Windows in a more reliable way because otherwise srid is not in the JSON from g.proj in the test. We skip rather than xfail the capdf tests on Windows since the issues don't seem to be in our code since subprocess way works just fine, but we use subprocess on all platform for simplicity.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The original g.region output contains basic info about the current CRS (aka projection). While this is only basic, providing only distinction between XY, LL, and other (and UTM+zone to certain extent), it is used in the code base and expected to be correct, for example g.proj is using it before checking anything else. Although it is separate from the stored PROJ files, it serves as the primary way of checking what kind of CRS is one dealing with. While this is unrelated to computational region, and in fact, it cannot be modified by g.region (it is ready-only for g.region), you could say it encourages a correct interpretation of the computational region information provided by g.region. A criticism of that here is that while you see XY vs LL vs others, you don't see units which are also important context for computational region values.
This adds pytest-based tests covering XY, LL, UTM N, UTM S, and other CRS cases.
The key names, the structure, and the values are simply taken from the current output with format='shell', i.e., keys are at the top-level, called 'projection' and 'zone', and values are integers. This means that there is no obvious way how to interpret that information, but the information can be directly used to compare with another computational region even when accessed in a different way (like directly looking into the DEFAULT_WIND file).
Before
{ "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }After
{ "projection": 99, "zone": 0, "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }Alternatives
At some places, we call it projection code:
{ "projection_code": 99, "zone": 0, "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }Call it crs (
crsorcrs_code), not projection:{ "crs_code": 99, "zone": 0, "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }We could nest it to emphasize the auxiliary nature in relation to the computational region (and use
codeortype_codeinside):{ "crs" : { "code": 99, "zone": 0, } "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }Include a string representation as well (
"xy","ll","utm","other"):{ "crs" : { "type_code": 99, "type": "other" "zone": 0, } "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }Use null for zone when not applicable (it is only set for UTM):
{ "crs" : { "type_code": 99, "type": "other" "zone": null, } "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 }All the suggestions combined except the nesting (using prefix
crs_) and with the CRS info at the end, not at the top:{ "north": 1, "south": 0, "west": 0, "east": 1, "nsres": 1, "ewres": 1, "rows": 1, "cols": 1, "cells": 1 "crs_type": "other" "crs_type_code": 99, "crs_zone": null, }I like the last one the most. Do you have a favorite?
Going even further
We could also try to remove it from the region completely, but that's whole another issue, much more involved than this PR. There could be also a separate file which would contain this info (e.g., PROJ_TYPE) which would keep the logic, but move it to a different file (and similar thing would have to happen on the C API level).
Commit message
The original pre-JSON g.region output contains basic info about the current CRS (aka projection). While this is only basic, providing only distinction between XY, LL, and other (and UTM+zone to certain extent), it is used in the code base and expected to be correct, for example g.proj is using it before checking anything else. Although it is separate from the stored PROJ files, it serves as the primary way of checking what kind of CRS is one dealing with. While this is unrelated to computational region, and in fact, it cannot be modified by g.region (it is ready-only for g.region), you could say it encourages a correct interpretation of the computational region information provided by g.region. A criticism of that here is that while you see XY vs LL vs others, you don't see units which are also important context for computational region values, but nevertheless the basic CRS type check is the current usage.
Instead of simply taking the key names, the structure, and the values from the current output with format='shell' (i.e., keys are at the top-level, called 'projection' and 'zone', and values are always integers), this introduces a new key 'crs' with nested values and the key is placed at the end of the output, not at the beginning. This helps to interpret that information as CRS and makes it an sort of an additional info instead of the first thing user reads when examining the raw JSON output.
A new string value 'type' is added to complete the type code integer (original 'projection') which is ideal for writing readable checks of LL and YX in the code. The integer code is still good for direct comparison with another computational region even when accessed in a different way (like directly looking into the DEFAULT_WIND file).
A new 'name' value is also added. It appears in some of the outputs and it is stored in PROJ_INFO. There is couple different places in the library determining what this value can be and it is generally meant to be human-readable, so it is distinct from 'type' (although it is fairly deterministic for XY and LL and the library tries hard to always supply some value).
The zone works differently in the way that it may be set None, specifically original zone=0 for non-UTM is now considered as unset, so JSON represents that as null. The clarity of this is highlighted in tests where comparison with none needs no comment, but the comparison with zero would call for a comment about the value not being the ideal representation.
All JSON code now newly uses JSON wrapper functions, so a missing JSON wrapper for dotset_null was added.
This adds pytest-based tests covering XY, LL, UTM N, UTM S, and 'other' CRS cases. The test is focused on CRS, so reflect that in the name. Another pytest can be its own file separated from the CRS topic.