Skip to content

Comments

g.region: Output CRS info in JSON#6407

Merged
wenzeslaus merged 12 commits intoOSGeo:mainfrom
wenzeslaus:add-crs-to-g_region-json
Sep 30, 2025
Merged

g.region: Output CRS info in JSON#6407
wenzeslaus merged 12 commits intoOSGeo:mainfrom
wenzeslaus:add-crs-to-g_region-json

Conversation

@wenzeslaus
Copy link
Member

@wenzeslaus wenzeslaus commented Sep 25, 2025

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 (crs or crs_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 code or type_code inside):

{
    "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.

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
petrasovaa previously approved these changes Sep 25, 2025
Copy link
Contributor

@petrasovaa petrasovaa left a comment

Choose a reason for hiding this comment

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

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.

@github-actions github-actions bot added Python Related code is in Python C Related code is in C module general tests Related to Test Suite labels Sep 25, 2025
…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).
@wenzeslaus
Copy link
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).

@github-actions github-actions bot added the raster Related to raster data processing label Sep 26, 2025
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
petrasovaa previously approved these changes Sep 30, 2025
@wenzeslaus wenzeslaus enabled auto-merge (squash) September 30, 2025 14:56
@wenzeslaus wenzeslaus merged commit d28238d into OSGeo:main Sep 30, 2025
27 of 28 checks passed
@wenzeslaus wenzeslaus deleted the add-crs-to-g_region-json branch September 30, 2025 16:51
@github-actions github-actions bot added this to the 8.5.0 milestone 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C Related code is in C general libraries module Python Related code is in Python raster Related to raster data processing tests Related to Test Suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants