Skip to content

temporal: t.rast.aggregate: add -e flag to extend existing STRDS - #7223

Open
Sourish-spc wants to merge 26 commits into
OSGeo:mainfrom
Sourish-spc:temporal/t.rast.aggregate-extend-mode
Open

Sourish-spc wants to merge 26 commits into
OSGeo:mainfrom
Sourish-spc:temporal/t.rast.aggregate-extend-mode

Conversation

@Sourish-spc

Copy link
Copy Markdown
Contributor

Description

This PR adds support for extending an existing Space Time Raster Dataset (STRDS) in t.rast.aggregate by introducing a new -e flag, following the same pattern implemented in #3798 for t.rast.neighbors.

Changes

  • Added -e flag (Extend existing space time raster dataset) to module interface
  • Modified STRDS creation logic to open existing STRDS when -e flag is set instead of always creating a new one
  • Added history update via update_command_string() when extending existing STRDS
  • Added test test_extend_existing_strds to existing test suite

Usage

First run — creates new STRDS:
t.rast.aggregate input=daily_rain output=monthly_rain granularity="1 month" method=average basename=result

Second run — extends existing STRDS:
t.rast.aggregate input=daily_rain output=monthly_rain granularity="1 month" method=average basename=result_new -e

Related

Closes part of #3427
See also #3798

In stds_import.py, the finally block used the deprecated 'gisdbase'
option when calling g.mapset to switch back to the original location.
This caused a deprecation warning on every t.rast.import run with
the project parameter.

Replaced 'gisdbase' with 'dbase' to match the current g.mapset API.

Fixes OSGeo#4231
@github-actions github-actions Bot added temporal Related to temporal data processing Python Related code is in Python libraries module tests Related to Test Suite labels Mar 27, 2026
@Sourish-spc
Sourish-spc force-pushed the temporal/t.rast.aggregate-extend-mode branch from 7ee4adc to 9b2110e Compare March 27, 2026 07:45

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please also install pre-commit in your local working directory to avoid CI runs for formalities...

Comment on lines +162 to +168
gs.fatal(
_(
"Space time raster dataset <%s> does not exist. "
"Cannot extend a non-existing dataset."
)
% output
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Personally, I do not think a fatal error should be thrown here. I would say, a warning at best. In any case, this part should be consistent across all temporal tools that receive the e-flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated: changed fatal error to warning and fall back to creating new STRDS when dataset doesn't exist.

# Verify STRDS was extended
lister = SimpleModule("t.rast.list", input="B", columns="name", flags="u")
self.runModule(lister)
self.assertIn("b_ext", lister.outputs.stdout)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This only checks that some map with "b_ext" in the name exists. It doesn't verify the original maps are still present, that the STRDS wasn't just overwritten, and/or the total map count.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review. The test has been strengthened to also verify that original maps are still present and the total map count increased after extending.

Comment on lines +258 to +259
if output_exists:
output_strds.update_command_string(dbif=dbif)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this make sense when overwriting?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point, iI did not really think about it. It may make sense in any case. I did not check the behavior. Updates of the STDS should be written to the history I`ld say, which is what update_command_string does. The command should not be duplicated in the history though. That should be checked...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the condition to if not output_exists or extend to avoid duplicating the command string in history when overwriting, while still updating it correctly when extending.


if __name__ == "__main__":
options, flags = gs.parser()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please, review your code, including the PR changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review. The extra blank line has been removed in the latest commit.

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please keep in mind that the code in open_stds pre-dates Python 3 and has not been re-factored after since.
We should not do re-factoring as part of this PR, but don`t repeat outdated pattern in stuff you add....

Comment thread python/grass/temporal/open_stds.py Resolved
Comment on lines +243 to +262
dbif, connection_state_changed = init_dbif(dbif)
msgr = get_tgis_message_interface()

mapset = get_current_mapset()
id = name + "@" + mapset if name.find("@") < 0 else name

if type in {"strds", "rast", "raster"}:
sp = dataset_factory("strds", id)
elif type in {"str3ds", "raster3d", "rast3d", "raster_3d"}:
sp = dataset_factory("str3ds", id)
elif type in {"stvds", "vect", "vector"}:
sp = dataset_factory("stvds", id)
else:
msgr.fatal(_("Unknown type: %s") % (type))
if extend and sp.is_in_db(dbif):
# extend-if-exists: load and return existing dataset
sp.select(dbif)
if connection_state_changed:
dbif.close()
return sp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You duplicate quite a bit of code... You may consider adding a helper function similar to e.g. (untested, so not suitable for copy and paste!):

def get_stds(stds_id: str, stds_type: str) -> SpaceTimeDataset:
    """Return an initialized SpaceTimeDataset (STDS)."""
    msgr = get_tgis_message_interface()
    supported_stds_types = {
        "strds": "strds",
        "rast": "strds",
        "raster": "strds",
        "str3ds": "str3ds",
        "raster3d": "str3ds",
        "rast3d": "str3ds",
        "raster_3d": "str3ds",
        "stvds": "stvds",
        "vect": "stvds",
        "vector": "stvds",
    }
    if stds_type not in supported_stds_types:
        msgr.fatal(_("Unknown type: %s") % (type))
    return dataset_factory(supported_stds_types[stds_type], stds_id)]

@Sourish-spc Sourish-spc Apr 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added _get_stds helper function to avoid repeating the type mapping block, and ensure_id helper to avoid repeating the id construction pattern. Kept them private ( prefix) since they're only used internally in open_stds.py happy to make them public if needed.

Comment thread python/grass/temporal/open_stds.py Resolved
Comment thread python/grass/temporal/open_stds.py Resolved
Comment thread python/grass/temporal/open_stds.py Resolved
Comment thread python/grass/temporal/open_stds.py Resolved
Comment thread python/grass/temporal/open_stds.py Resolved

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now we are getting somewhere. Almost done. I just had some hopefully final, minor comments.
Once those are addressed, this can be approved as far as I am concerned. I would appreciate a second opinion, especially on having the new helper function public or private...

Comment thread python/grass/temporal/open_stds.py Resolved
Comment thread python/grass/temporal/open_stds.py Outdated
dbif, connection_state_changed = init_dbif(dbif)
output_exists = sp.is_in_db(dbif)

if output_exists:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See the comment above about collapsible-if...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed: collapsed nested if statements into single conditions.

Comment thread python/grass/temporal/open_stds.py Outdated
"""
msgr = get_tgis_message_interface()

if "@" in name:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can skip this check if you _ensure-id before.
If ensure_id is a public function and used in t.rast.aggregate, the new functions you add here could expect an id as input and those checks are not needed at all. Yet, they are insignificant and may be safer to double-check. No strong opinion from my side on this...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the redundant mapset check

)

# Check original maps are still present (STRDS was not overwritten)
for original_map in original_maps.strip().split(os.linesep):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe use all() or a set() based check and only one assertion...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replaced the loop with a single set.issubset() assertion.

self.assertIn(original_map, extended_maps)

# Check total map count increased (original 3 + extended 3 = 6)
info = SimpleModule("t.info", flags="g", input="B")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You get the number of maps from t.rast.list. No need to run t.info in addition for this check. If you want to check that the STRDS history is updated properly (command added to comments) you need to.info...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the t.info call, map count is now derived directly from t.rast.list output using len().

@Sourish-spc

Copy link
Copy Markdown
Contributor Author

Note: test_aggregation_relative.py was already failing before this PR's changes, verified by running the tests on the original code via git stash. This appears to be a pre-existing issue unrelated to the -e flag changes."

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two minor and hopefully final comments from my side. Looks otherwise good. I would appreciate a second opinion though, esp. regarding the library parts...

Co-authored-by: Stefan Blumentrath <stefan.blumentrath@gmx.de>

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing my comments. @wenzeslaus What do you think? Ready to merge?

@Sourish-spc

Copy link
Copy Markdown
Contributor Author

Any updates on this? Happy to address any further feedback.

ninsbl
ninsbl previously approved these changes May 17, 2026

@ninsbl ninsbl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me. @petrasovaa could you double-check the test and library code? Would be good to get this merged, so the functions can be reused...

id = _ensure_id(name)
sp = _get_stds(id, type)

if extend and sp.is_in_db(dbif):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be using check_new_stds somewhere?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Otherwise check you are writing/modifying data in current mapset is missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed your review comments:

Added mapset ownership check when extending
Added temporal type compatibility check early in check_open_output_stds() before processing starts
Added fatal error when -e is used but output STRDS does not exist
Added two new test cases covering both edge cases

@petrasovaa

Copy link
Copy Markdown
Contributor

I think the tests don't quite cover, e.g. -e and stds doesn't exist, extending with stds with incompatible temporal type...

- Add mapset ownership check in open_output_stds() when extending
- Add temporal type compatibility check in check_open_output_stds()
  before processing starts to avoid unnecessary computation
- Add temporaltype parameter to check_open_output_stds()
- Fatal error when -e flag used but output STRDS does not exist
- Add tests for nonexistent STRDS and incompatible temporal type cases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libraries module Python Related code is in Python temporal Related to temporal data processing tests Related to Test Suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants