From f099c6f02cfff46f1a9657dd2bec7d89e23b3046 Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Tue, 14 Apr 2026 15:24:07 +0200 Subject: [PATCH 1/7] Updated plotting maps The plotting function now allows plotting from both 2D and 3D datasets. The user can specify the dimension on which the aggregation hould be performed as well as the aggregation function including mean, min and max. --- src/medunda/plots/maps.py | 84 +++++++++++++++++++++++++++++++-------- src/medunda/plotter.py | 24 ++++++----- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/medunda/plots/maps.py b/src/medunda/plots/maps.py index 3330365..51ddf66 100644 --- a/src/medunda/plots/maps.py +++ b/src/medunda/plots/maps.py @@ -20,21 +20,30 @@ def configure_parser(subparsers): required=True, help="Date to plot the map for (format: YYYY-MM-DD)", ) + plotting_maps.add_argument( - "--latitude", - type=float, - required=False, - help="Latitude of the area to plot", + "--aggregation-dimension", + type=str, + help="Dimension along which to aggregate the data" + "By default, no aggregation is performed and the data is plotted as is." + "If the data has more than 2 dimensions, an aggregation method must be specified using '--aggregation-method'", ) plotting_maps.add_argument( - "--longitude", - type=float, - required=False, - help="Longitude of the area to plot", + "--aggregation-method", + type=str, + choices=["mean", "max", "min"], + default="mean", + help="Method to use for aggregating data along the specified dimension", ) -def plotting_maps(data: "xr.DataArray", metadata: dict, time): +def plotting_maps( + data: "xr.DataArray", + metadata: dict, + time, + aggregation_dimension, + aggregation_method, +): selected_time = pd.to_datetime(time) data["time"] = pd.to_datetime(data["time"].values) @@ -47,13 +56,56 @@ def plotting_maps(data: "xr.DataArray", metadata: dict, time): except Exception as e: raise ValueError(f"Could not select time {selected_time}: {e}") - non_spatial_dims = [ - dim - for dim in data_slice.dims - if dim not in ["lat", "latitude", "lon", "longitude"] - ] - if non_spatial_dims: - data_slice = data_slice.mean(dim=non_spatial_dims) + n_dims = len(data_slice.dims) + + if n_dims == 2: + if ( + "latitude" not in data_slice.dims + or "longitude" not in data_slice.dims + ): + raise ValueError( + "Data must have 'latitude' and 'longitude' dimensions for 2D plotting." + ) + else: + LOGGER.debug("Data has 2 dimensions, proceeding with plotting.") + + if aggregation_dimension: + if aggregation_dimension in data_slice.dims: + raise ValueError( + f"Aggregation dimension '{aggregation_dimension}' cannot be used for 2D data with dimensions {data_slice.dims}" + ) + LOGGER.debug( + f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" + ) + + data_slice = getattr(data_slice, aggregation_method)( + dim=aggregation_dimension + ) + + elif n_dims > 2: + if not aggregation_method: + raise ValueError( + "Data has more than 2 dimensions, an aggregation method must be specified." + " Please provide an aggregation method using the '--aggregation-method'" + ) + + if aggregation_dimension not in data_slice.dims: + raise ValueError( + f"Aggregation dimension '{aggregation_dimension}' not found in data dimensions: {data_slice.dims}" + ) + + LOGGER.info( + f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" + ) + + data_slice = getattr(data_slice, aggregation_method)( + dim=aggregation_dimension + ) + + else: + raise ValueError( + f"Data has {n_dims} dimensions, expected at least 2 dimensions for plotting." + ) actual_time = pd.to_datetime(data_slice["time"].values) diff --git a/src/medunda/plotter.py b/src/medunda/plotter.py index 0225ed9..adb96d7 100644 --- a/src/medunda/plotter.py +++ b/src/medunda/plotter.py @@ -67,7 +67,7 @@ def configure_parser( if parser is None: parser = argparse.ArgumentParser( description="plots timeseries and maps" - ) ###### + ) parser.add_argument( "--input-file", type=Path, required=True, help="Path of the input file" @@ -79,12 +79,12 @@ def configure_parser( required=True, help="Name of the variable to plot", ) - parser.add_argument( - "--output-dir", - type=Path, - default=Path("."), - help="Directory where the downloaded files are saved", - ) + # parser.add_argument( + # "--output-dir", + # type=Path, + # default=Path("."), + # help="Directory where the downloaded files are saved", + # ) subparsers = parser.add_subparsers( title="mode", @@ -146,13 +146,17 @@ def plotter(filepath: Path, variable: str, mode: str, args): timeseries.plotting_timeseries( data=data_var, metadata=metadata, - start_time=args.start_time, - end_time=args.end_time, + start_date=args.start_date, + end_date=args.end_date, ) elif mode == "plotting_maps": maps.plotting_maps( - data=data_var, metadata=metadata, time=args.time + data=data_var, + metadata=metadata, + time=args.time, + aggregation_dimension=args.aggregation_dimension, + aggregation_method=args.aggregation_method, ) else: From 342871e5952acd085c3e9995877dfe41e66e20f9 Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Tue, 14 Apr 2026 15:35:42 +0200 Subject: [PATCH 2/7] Plotting automatically timeseries using the dataset's time range in case start-date and end-date are not specified. --- src/medunda/plots/timeseries.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/medunda/plots/timeseries.py b/src/medunda/plots/timeseries.py index a626f6a..02ec6f9 100644 --- a/src/medunda/plots/timeseries.py +++ b/src/medunda/plots/timeseries.py @@ -14,39 +14,45 @@ def configure_parser(subparsers): help="Plots timeseries for a specific variable over a defined period of time", ) plotting_timeseries.add_argument( - "--start-time", + "--start-date", type=date_from_str, - required=True, + required=False, help="Start date of the period to plot (format: YYYY-MM-DD)", ) plotting_timeseries.add_argument( - "--end-time", + "--end-date", type=date_from_str, - required=True, + required=False, help="End date of the period to plot (format: YYYY-MM-DD)", ) def plotting_timeseries( - data: "xr.DataArray", metadata: dict, start_time, end_time + data: "xr.DataArray", metadata: dict, start_date, end_date ): - start = start_time - end = end_time - if "time" not in data.dims: raise ValueError( "This dataset has no 'time' dimension therefore cannot plot time series." ) - # Aggregate spatial dims by mean over lat and lon if they exist + if start_date is None and end_date is None: + start_date = data["time"].values[0] + end_date = data["time"].values[-1] + else: + if start_date is not None: + start_date = start_date + if end_date is not None: + end_date = end_date + spatial_dims = [ dim for dim in ["lat", "latitude", "lon", "longitude"] if dim in data.dims ] + data_mean = data.mean(dim=spatial_dims) - ts = data_mean.sel(time=slice(start, end)) + ts = data_mean.sel(time=slice(start_date, end_date)) fig, ax = plt.subplots(figsize=(12, 6)) ax.plot(ts.time, ts) From 3e9af906e39c7b77c0f4d16fb4d5f3ad5095601a Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Thu, 30 Apr 2026 17:53:53 +0200 Subject: [PATCH 3/7] Extended the plotter to handle csv and geotiff inputs and added rioxarray to the dependencies Co-authored-by: Copilot --- poetry.lock | 399 ++++++++++++++++---- pyproject.toml | 1 + src/medunda/plots/maps.py | 113 +++--- src/medunda/plots/timeseries.py | 3 +- src/medunda/plotter.py | 167 ++++++-- src/medunda/tools/lazy_imports/cmocean.py | 8 +- src/medunda/tools/lazy_imports/rioxarray.py | 10 + 7 files changed, 535 insertions(+), 166 deletions(-) create mode 100644 src/medunda/tools/lazy_imports/rioxarray.py diff --git a/poetry.lock b/poetry.lock index 82962ae..4334ec9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +[[package]] +name = "affine" +version = "2.4.0" +description = "Matrices describing affine transformation of the plane" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92"}, + {file = "affine-2.4.0.tar.gz", hash = "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea"}, +] + +[package.extras] +dev = ["coveralls", "flake8", "pydocstyle"] +test = ["pytest (>=4.6)", "pytest-cov"] + [[package]] name = "alabaster" version = "1.0.0" @@ -59,6 +75,18 @@ files = [ astroid = ["astroid (>=2,<5)"] test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + [[package]] name = "babel" version = "2.18.0" @@ -129,18 +157,18 @@ xyzservices = ">=2021.9.1" [[package]] name = "boto3" -version = "1.43.32" +version = "1.43.33" description = "The AWS SDK for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "boto3-1.43.32-py3-none-any.whl", hash = "sha256:a3d7d7a9489c18cc9c806aca9be689677779d17ddbf919fe300146576c1bdee2"}, - {file = "boto3-1.43.32.tar.gz", hash = "sha256:15544d42af8aa8f775ea636f77c9c97fbc90ff28c0e5a0d1d47c8acd9f5b1982"}, + {file = "boto3-1.43.33-py3-none-any.whl", hash = "sha256:97b563127f7678ad20249f9bf99877b7a3a78f6fcdcf222c6e25d27d4406ce0d"}, + {file = "boto3-1.43.33.tar.gz", hash = "sha256:da6e400b2d11eb041fbbb2743ef3ffe6540889676ffdff0e628628e9b4f04cde"}, ] [package.dependencies] -botocore = ">=1.43.32,<1.44.0" +botocore = ">=1.43.33,<1.44.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.19.0,<0.20.0" @@ -149,14 +177,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.43.32" +version = "1.43.33" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "botocore-1.43.32-py3-none-any.whl", hash = "sha256:429796537fde1301df90d394808985d88cd51b36a6e769e5c12f6a6877605428"}, - {file = "botocore-1.43.32.tar.gz", hash = "sha256:88ed52268b8f7e8ff8f9df5adbbf61e5bbb5dc618ee50de4346cabe93a482792"}, + {file = "botocore-1.43.33-py3-none-any.whl", hash = "sha256:4292bb2cc645e5cb404515c54b5b164563f2b4218c52aeee3f1ce851724e177a"}, + {file = "botocore-1.43.33.tar.gz", hash = "sha256:74b3d5626ebeb2677fac1ca12ac975faf328e54349ceb4dcda17f50674d5b9b4"}, ] [package.dependencies] @@ -399,6 +427,43 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-plugins" +version = "1.1.1.2" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6"}, + {file = "click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "cligj" +version = "0.7.2" +description = "Click params for commmand line interfaces to GeoJSON" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" +groups = ["main"] +files = [ + {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, + {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +test = ["pytest-cov"] + [[package]] name = "cloudpickle" version = "3.1.2" @@ -1034,14 +1099,14 @@ numpy = ">=1.19.3" [[package]] name = "hypothesis" -version = "6.155.3" +version = "6.155.5" description = "The property-based testing library for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "hypothesis-6.155.3-py3-none-any.whl", hash = "sha256:ede5a3d142d9c5c9f70cb3075541905b228d6c3a682bcec3d4fe0722e9eda127"}, - {file = "hypothesis-6.155.3.tar.gz", hash = "sha256:1e34b17ae9873515384312cb7640abd773eb096c7eef8c0d9c614fa2c306e9bb"}, + {file = "hypothesis-6.155.5-py3-none-any.whl", hash = "sha256:b0eb5645f4c962dffdafef36d05329aa704a5322ee1273495fa98d5a986daf70"}, + {file = "hypothesis-6.155.5.tar.gz", hash = "sha256:f7f8f803e05a69cd70399b07ed89a89b673c9ff3530977c4a768ed3acb702169"}, ] [package.dependencies] @@ -1710,78 +1775,78 @@ files = [ [[package]] name = "msgpack" -version = "1.2.0" +version = "1.2.1" description = "MessagePack serializer" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "msgpack-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ed8c9495a0f12d17a2b4b69e23f895b88f26aabe40911c86594d3fbddecfff08"}, - {file = "msgpack-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7384859c90b45a28a4b31aa50b49cca84504c9f27df459cea6e072627650dcb"}, - {file = "msgpack-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b35e8e65f04ff7ad5c9c70885da587c74f51e4b4eb3db624eac6d250e8cf59"}, - {file = "msgpack-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004c5a02acd3eca4e15e1ae7b461c32e3711105a28b1ad78be2f6facff4c523"}, - {file = "msgpack-1.2.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e2032dacb0a973fcbf7bd088415a369dae31c5af40e199d234806be22e86765"}, - {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1feb100651fbe4b39826207cb20af065dfbfbfa43b1bafd7eaa2252abf7acfd"}, - {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:82487709d4c597d252311a65370220675fb1cc859e7da9269a3060c03ac02cf6"}, - {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0268c67a74f5f913f545a0fdbbfaa3f6ebcf23b4c3209bb99704a2ea87e13f90"}, - {file = "msgpack-1.2.0-cp310-cp310-win32.whl", hash = "sha256:7df87173b0e13ddd134919731f13525dbbf75204145597decf1cb86887ebb492"}, - {file = "msgpack-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6371edb47788fbfd8a22016f9a97b5616dd9849bc50abcbb8e82d38f71efa096"}, - {file = "msgpack-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec35cd3f127f50806aa10c3f74bf27b749f13ddf1d2217964ada8f38042d1653"}, - {file = "msgpack-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:317eb298297121bfad9173d748124a04a36af27b6ac39c2bbc1db1ce57608dcf"}, - {file = "msgpack-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fe6434de89073273026dd032a62e8b63f8857a261d7a2df5b07c9e72f3a8f7"}, - {file = "msgpack-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106c6d333ff3d4eda075b7d4b9695d1752c5bcc635e40d0dbaf4e276c9ed80e1"}, - {file = "msgpack-1.2.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:67055a611e871cb1bd0acb732f2e9f64ca8155ca0bba1d0a5bb362e7209e5541"}, - {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ceec7f8e633d5a4b4a32b0416bef90ee3cd1017ea36247f705e523072e576119"}, - {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7ec5851160a3c2c0f77d68ddec620318cd8e7d88d94f9c058190e8ce0dfa1d31"}, - {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd7140f7b09dbe1984a0dff3189375d840247e3e4cf4ac45c5a499b3b599c8d2"}, - {file = "msgpack-1.2.0-cp311-cp311-win32.whl", hash = "sha256:cbfd54018d386da0951c7a2be13de0f58559d251313e613b2155e52ed1cbd8f1"}, - {file = "msgpack-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:653373c4614c31463ba486a67776e4bb396af289921bd5353e209534b71467fa"}, - {file = "msgpack-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:7a260aea1e5e7d6c7f1d9284c7360d29021627b61dc4dd7df144b81210810537"}, - {file = "msgpack-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2d6047ccd11a12c96a69f2bfe026471abef67334c3d0494a93e5310e45140a2"}, - {file = "msgpack-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0347e3ac0dfee99086d3b68fe959da3f5f657c0019ddbaeaaa259a85f8603422"}, - {file = "msgpack-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25552ff1f2ff3dc8333e27eabb94f702da5929ed0e07969688194a3e9f12e151"}, - {file = "msgpack-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0d94420d9d52c56568159a69200af7e45eadb29615fa9d09fada140de1c38c7"}, - {file = "msgpack-1.2.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d16e1f2db4a9eebc07b7cc91898d71e710f2eed8358711a605fee802caff8923"}, - {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9cb2e700e85f1e27bbb5c9de6cc1c9a4bc5ac64d5404bdcbcb37a0dc7a947a3"}, - {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:717d0b166dd176a5f786aeafff081f6439680acf5af193eb63e6266c12b04d3d"}, - {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e87c7a21654d18111eb1a89bd5c42baba42e61887365d9e89585e112b4203f9e"}, - {file = "msgpack-1.2.0-cp312-cp312-win32.whl", hash = "sha256:967e0c891f5f23ab65762f2e5dc95922759c79f1ef99ef4c7e1fdd863e0d0af9"}, - {file = "msgpack-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:6c23e33cee28dcffa112ae205661da4636fd7b06bd9ad1559a890623b92d060b"}, - {file = "msgpack-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eeb771571f63f68045433b1a35c0256b946f31ed62f006997e40b8ad8b735af"}, - {file = "msgpack-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a1d30df1f302f2b7a7404afbac2ab76d510036c34cf34dffb01f704a7288e45"}, - {file = "msgpack-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:581e317112260d8ca488d490cad9290a5682276f309c41c7de237a85ed8799c8"}, - {file = "msgpack-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6827d12eacc16873eba62408a1b7bbe8ecfb4a8f7ed78a631ae9bae6ad43cf2"}, - {file = "msgpack-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a186027e4279efa4c8bf06ce30605498d7d0d3af0fba0b9799dce85a3fd4a93c"}, - {file = "msgpack-1.2.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a96142c14a11cf1a509e8b9aaf72858a3b742b7613e095ce646913e88ce7bd99"}, - {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50c220579b68a6085b95408b2eaa486b259520f55d8e363ddc9b5d7ba5a6ac6d"}, - {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4dcb9d12ab100ecacdfaaf37a3d72fe8392eacc7054afc1916b12d1b747c8446"}, - {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a804727188ab0ebb237fadb303b743f04925a69d8c3247292d1e33e679767c15"}, - {file = "msgpack-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1a1ac6ae1fe23298f79380e7b144c8a454e5d05616b0096584f353ba2d750114"}, - {file = "msgpack-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c3c80949d79578f9dc85fd9fb91edfe6694e8a729cd5744634d59d8455fdde3"}, - {file = "msgpack-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fcf8f76fa587c2395fd0057c7232dbf071241f9ad280b235adb7ab585289989e"}, - {file = "msgpack-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f854fa1a8b55d75d82ef9a905d9cdbeffdf7897c088f6020bd221867da5e56a5"}, - {file = "msgpack-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e90df581f80f53b372d5d9d9349078d729851a3a0d0bd74f53ccb598d01e45b8"}, - {file = "msgpack-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b276ed50d8ac75d1f134a433ae79af8557d0fa25ee5b4737da533dfc2ce382e8"}, - {file = "msgpack-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:544d972459c92aa32e63b800d07c2d9cf2734a3be29cee3a0b478a622850e9f5"}, - {file = "msgpack-1.2.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a070147cc2cf6b8a891734e0f5c8fe8f70ed8739ab30ba140b058005a6e86af4"}, - {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7685e23b0f51745a751629c31713fbefdef8896b31b2bb38299dfa4ae6c0740c"}, - {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b9204daeee8d91a7ae5acf2d2a8e3983be9a3025f38aa21bfaefbd7eea84a7dc"}, - {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bfc057248609742ebbabf6bcd27fea4fd99c4980584e613c168c9b002318298f"}, - {file = "msgpack-1.2.0-cp314-cp314-win32.whl", hash = "sha256:a3faa7edf2388337ae849239878e92f0298b4dab4488e4f1834062f9d0c410c9"}, - {file = "msgpack-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:1a3effc392a57744e4681e55d05f97d5ee7b598747d718340a9b4b8a970c40e1"}, - {file = "msgpack-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:56a318f7df6bec7b40928d6b0519961f20a510d8baabf6baa393a70444588f0a"}, - {file = "msgpack-1.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:afa4a65ab2097795e771a74a3a81ea49534aaeba874eaf426a3332268e045ae6"}, - {file = "msgpack-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:409550770632bb28daa70a11d0ed5763f7db38f40b06f7db9f11dd2794d01102"}, - {file = "msgpack-1.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf47e3cd11ce044965a9736a322afdd390b31ed602d1c1b10211d1a841f1d587"}, - {file = "msgpack-1.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:204bc9f5d6e59c1718c0a4a84fc8ff71b5b4562faac257c1a68bca611ecf9b72"}, - {file = "msgpack-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:610154307b27267266368bc1d1c7bb8aeb71da7be9356d403cb2442d9e6399f5"}, - {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6799f157bb63e79f11e2e590cfdb28423fc18dd60c270c3914b5b4586ae36f7e"}, - {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:72bd844902cf0a5ac3af2ef742f253cd0b1e5bcd184f49b4fb9a6a1f7bf305e8"}, - {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3c0bd450f78d0d81722c80da6cdbf674a856967870a9db2f6c4debc4d8b3c67c"}, - {file = "msgpack-1.2.0-cp314-cp314t-win32.whl", hash = "sha256:378caf74c4c718dfc17590ce68a6d710ed398ff6fcf08237de23b77755730b55"}, - {file = "msgpack-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:553b42598165c4dd3235994fd6e4b0dfb1ce5f3fd33d94ba9609442643015f38"}, - {file = "msgpack-1.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2825bb1da548d214ab8a810906b7dd69a10f3838b615a2cc46e5172d3cb44f6e"}, - {file = "msgpack-1.2.0.tar.gz", hash = "sha256:8e17af38197bf58e7e819041678f6178f4491493f5b8c8580414f40f7c2c3c41"}, + {file = "msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c"}, + {file = "msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce"}, + {file = "msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74"}, + {file = "msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb"}, + {file = "msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22"}, + {file = "msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f"}, + {file = "msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d"}, + {file = "msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8"}, + {file = "msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66"}, + {file = "msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35"}, + {file = "msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64"}, + {file = "msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac"}, + {file = "msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24"}, + {file = "msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07"}, + {file = "msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064"}, + {file = "msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6"}, + {file = "msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707"}, + {file = "msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9"}, + {file = "msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a"}, + {file = "msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d"}, + {file = "msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7"}, + {file = "msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb"}, + {file = "msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b"}, + {file = "msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7"}, + {file = "msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273"}, + {file = "msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c"}, + {file = "msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1"}, + {file = "msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2"}, + {file = "msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107"}, + {file = "msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647"}, ] [[package]] @@ -2967,6 +3032,136 @@ files = [ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] +[[package]] +name = "rasterio" +version = "1.4.4" +description = "Fast and direct raster I/O for use with Numpy and SciPy" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "rasterio-1.4.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:35401e84d4d0b239bd62b33d4ee68d7bb13b47c3b41078f4aad7ad7964e61c73"}, + {file = "rasterio-1.4.4-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:1f17fc9608b6b6666894a04e0118d3329e831a6347bc3650584d247a9d476fdd"}, + {file = "rasterio-1.4.4-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1f0edb8cb30ff8f5be341583f69c115b7c36ad52bbbe7582345d32af115bc6b3"}, + {file = "rasterio-1.4.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5197da0e3dd09907bdb343717a49e8fb5229ffdbff0e583b874959ec41fa9558"}, + {file = "rasterio-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:15109134c7b4770e6aeb8d45dc52c2603824805ba734323268a44f5a81756a7a"}, + {file = "rasterio-1.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b8eea428b5f0c78a963f6003a19b60777df83a0aba8c28231d65431e32ac160e"}, + {file = "rasterio-1.4.4-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:1cc0ea5aa0d22f5f349aa221674481de689b7b3a99607ce6bb58a29e5be54d17"}, + {file = "rasterio-1.4.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7eb25b23666b29dadfc49a59206cead62c99190584b61771bba0e95f7da06801"}, + {file = "rasterio-1.4.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e24b7b8c2df801dde2a1dffb44c58902bd76b5cab740dc11de4ff9963992a71a"}, + {file = "rasterio-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:0718630f607be2f5742d8e4b34b434746fd788a192d77eefc9bb924399fea802"}, + {file = "rasterio-1.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:0308ff4762ae9eb40a991f12d758626b59af4376b13675480391dd7295d17bbf"}, + {file = "rasterio-1.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3c4f0cbd188f893011f2a0a6dc2852b3892799b3a0d79eddf92f2b115ec7ed7"}, + {file = "rasterio-1.4.4-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:6fce26090b9f509eab337228420145947c491a13628965410f25bc3e6e05cf75"}, + {file = "rasterio-1.4.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c1c722da390dc264aeccdc0dc200ca37923875d910ca4cd5bec0fec351bb818e"}, + {file = "rasterio-1.4.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98b6dfb8282b2a54b9d75c3dc8d2520a69bbc66916c7d43de8e0bbf6e0240ca1"}, + {file = "rasterio-1.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:9513f4c7a6d93b45098f8dff2421fa9516604e3bfbf35aa144484a88d36a321f"}, + {file = "rasterio-1.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:60b49a482e0f12f12ce9d2cc3090add02f89f3d422e85f2cffaa9207adb83c04"}, + {file = "rasterio-1.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:df26c96aa81ffbd0b33189680859211eadf9950123c21579f84de73bb0f91d81"}, + {file = "rasterio-1.4.4-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:b3af0ecc922a80f3755516629f7948e37bade9077b5f5c12a3869a5e7f01619b"}, + {file = "rasterio-1.4.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7ce3b0f9a22e95a27790087908753973644d7c3877d495ec9bd6e04a25233ca4"}, + {file = "rasterio-1.4.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c072450caa96428b1218b030500bb908fd6f09bc013a88969ff81a124b6a112a"}, + {file = "rasterio-1.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:16ee92ef10c0ba89f45f9c2b40fca9f971f357385f04ee9b716fb09cbd9ce20c"}, + {file = "rasterio-1.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:65c10afe64b5e488185aaff0b659e08eda22c89285b54a3e433b80e6c6621770"}, + {file = "rasterio-1.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:18c2c1130e789dc2771d0aa5ec4b56d5b8a0097c648ccb94882d5ff3ab55c928"}, + {file = "rasterio-1.4.4-cp313-cp313t-macosx_15_0_x86_64.whl", hash = "sha256:2d1654b7ffa6f3dde42c5fd27159ae45148c11e352de26f12fe7313a3236aeed"}, + {file = "rasterio-1.4.4-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c4022cbddb659856e120603b12233cec8913ae760fff220657ce888c3c6b9f9d"}, + {file = "rasterio-1.4.4-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:96b88880551a07b7a3b50439483cefbd9af91a09e19ff2b736815994e5671314"}, + {file = "rasterio-1.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:def75d486d0ab8f306f918a913c425ed57159495518c54efe8e18d5164d37d90"}, + {file = "rasterio-1.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:770b7e86f6c565e6f9cf30f6fa4479a5a2bab4e10ff44fe7acfd518ca4a71d1b"}, + {file = "rasterio-1.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:019693f14a83ae9225cb57c16e466901d0e6284962dcf13a9f4bb1175b979011"}, + {file = "rasterio-1.4.4-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:87d7c3e97e3b40c9041d1602e2dcb4fc2d88abe6c645fccb4939dec297a91cf8"}, + {file = "rasterio-1.4.4-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a2401e4c43a31c7382154d4042b60a63b9bca5886802983c5c9362cdc5b09548"}, + {file = "rasterio-1.4.4-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6c4287d8934d953f7870b8e2a1df1096fbf47eba39ad0f777a31ea500f4e5010"}, + {file = "rasterio-1.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:c3ba1871549221140661227dd4fa1f9a472ded4a6d2f2c2e367b0648bb15b99d"}, + {file = "rasterio-1.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:7c9d7dc824cb8d222808be153643cd4e65ea3e1f66019ada1ccd630221edfe30"}, + {file = "rasterio-1.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:98e17bded830a59992d9f8f8d9f227ce1c4be0694930afcc4360358f5cb1a5db"}, + {file = "rasterio-1.4.4-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:56134ca203f952855e60774b06672033cf65057eb9810fcc5c1a75f1921053a3"}, + {file = "rasterio-1.4.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:52edde65515b33fe4314c8a44a9ee2fc00b550deed6d56e1a8d085d42bbca3e6"}, + {file = "rasterio-1.4.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d61d3f2c171c64050bd75e54a5d964ff7f165b3f5d2b92c9ee09b9716aa1b8bf"}, + {file = "rasterio-1.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:40137fe512c0d6e96c0167a0ae4e56d82c488f244163c45494b7392e51c844de"}, + {file = "rasterio-1.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:29ec3a794454b5bb255c9c0374cc380030a8a1e295c81eee7feb036802d2a9e3"}, + {file = "rasterio-1.4.4.tar.gz", hash = "sha256:c95424e2c7f009b8f7df1095d645c52895cd332c0c2e1b4c2e073ea28b930320"}, +] + +[package.dependencies] +affine = "*" +attrs = "*" +certifi = "*" +click = ">=4.0,<8.2.dev0 || >=8.3.dev0" +click-plugins = "*" +cligj = ">=0.5" +numpy = ">=1.24" +pyparsing = "*" + +[package.extras] +all = ["boto3 (>=1.2.4)", "fsspec", "ghp-import", "hypothesis", "ipython (>=2.0)", "matplotlib", "numpydoc", "packaging", "pytest (>=2.8.2)", "pytest-cov (>=2.2.0)", "shapely", "sphinx", "sphinx-click", "sphinx-rtd-theme"] +docs = ["ghp-import", "numpydoc", "sphinx", "sphinx-click", "sphinx-rtd-theme"] +ipython = ["ipython (>=2.0)"] +plot = ["matplotlib"] +s3 = ["boto3 (>=1.2.4)"] +test = ["boto3 (>=1.2.4)", "fsspec", "hypothesis", "packaging", "pytest (>=2.8.2)", "pytest-cov (>=2.2.0)", "shapely"] + +[[package]] +name = "rasterio" +version = "1.5.0" +description = "Fast and direct raster I/O for use with NumPy" +optional = false +python-versions = ">=3.12" +groups = ["main"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "rasterio-1.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:420656074897a460f5ef46f657b3061d2e004f9d99e613914b0671643e69d92c"}, + {file = "rasterio-1.5.0-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:c5c3597a783857e760550e8f26365d928b0377ac5ffc3e12ba447ac65ca5406d"}, + {file = "rasterio-1.5.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e14d07a09833b6df6024ce7a57aee1e1977b3aec682e30b1e58ce773462f2382"}, + {file = "rasterio-1.5.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:26dbcffcf0d01fc121cbb92186bc1cb78e16efe62b17be45ad7494446b325cf8"}, + {file = "rasterio-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac8d04eee66ca8060763ead607800e5611d857dd005905d920365e24a16ba20a"}, + {file = "rasterio-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:31f1edc45c781ebd087e60cc00a4fc37028dd3fe25cff4098e4139fc9d0565be"}, + {file = "rasterio-1.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e7b25b0a19975ccd511e507e6de45b0a2d8fb6802abe49bb726cf48588e34833"}, + {file = "rasterio-1.5.0-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:1162c18eaece9f6d2aa1c2ff6b373b99651d93f113f24120a991eaebf28aa4f4"}, + {file = "rasterio-1.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8eb87fd6f843eea109f3df9bef83f741b053b716b0465932276e2c0577dfb929"}, + {file = "rasterio-1.5.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:08a7580cbb9b3bd320bdf827e10c9b2424d0df066d8eef6f2feb37e154ce0c17"}, + {file = "rasterio-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:d7d6729c0739b5ec48c33686668a30e27f5bdb361093f180ee7818ff19665547"}, + {file = "rasterio-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:8af7c368c22f0a99d1259ccc5a5cd96c432c2bde6f132c1ac78508cd7445a745"}, + {file = "rasterio-1.5.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b4ccfcc8ed9400e4f14efdf2005533fcf72048748b727f85ff89b9291ecdf98a"}, + {file = "rasterio-1.5.0-cp313-cp313t-macosx_15_0_x86_64.whl", hash = "sha256:2f57c36ca4d3c896f7024226bd71eeb5cd10c8183c2a94508534d78cc05ff9e7"}, + {file = "rasterio-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cc1395475e4bb7032cd81dda4d5558061c4c7d5a50b1b5e146bdf9716d0b9353"}, + {file = "rasterio-1.5.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:592a485e2057b1aaeab4f843c9897628e60e3ff45e2509325c3e1479116599cb"}, + {file = "rasterio-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0c739e70a72fb080f039ee1570c5d02b974dde32ded1a3216e1f13fe38ac4844"}, + {file = "rasterio-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:a3539a2f401a7b4b2e94ff2db334878c0e15a2d1c9fe90bb0879c52f89367ae5"}, + {file = "rasterio-1.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:597be8df418d5ba7b6a927b6b9febfcb42b192882448a8d5b2e2e75a1296631f"}, + {file = "rasterio-1.5.0-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:dd292030d39d685c0b35eddef233e7f1cb8b43052578a3ec97a2da57799693be"}, + {file = "rasterio-1.5.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:62c3f97a3c72643c74f2d0f310621a09c35c0c412229c327ae6bcc1ee4b9c3bc"}, + {file = "rasterio-1.5.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:19577f0f0c5f1158af47b57f73356961cbd1782a5f6ae6f3adf6f2650f4eb369"}, + {file = "rasterio-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:015c1ab6e5453312c5e29692752e7ad73568fe4d13567cbd448d7893128cbd2d"}, + {file = "rasterio-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:ff677c0a9d3ba667c067227ef2b76872488b37ff29b061bc3e576fad9baa3286"}, + {file = "rasterio-1.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:508251b9c746d8d008771a30c2160ff321bfc3b41f6a1aa8e8ef1dd4a00d97ba"}, + {file = "rasterio-1.5.0-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:742841ed48bc70f6ef517b8fa3521f231780bf408fde0aa6d73770337a36374e"}, + {file = "rasterio-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c9a9eee49ce9410c2f352b34c370bb3a96bb518b6a7f97b3a72ee4c835fd4b5c"}, + {file = "rasterio-1.5.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b9fd87a0b63ab5c6267dfb0bc96f54fdf49d000651b9ee85ed37798141cff046"}, + {file = "rasterio-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f459db8953ba30ca04fcef2b5e1260eeeff0eae8158bd9c3d6adbe56289765cc"}, + {file = "rasterio-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f4b9c2c3b5f10469eb9588f105086e68f0279e62cc9095c4edd245e3f9b88c8a"}, + {file = "rasterio-1.5.0.tar.gz", hash = "sha256:1e0ea56b02eea4989b36edf8e58a5a3ef40e1b7edcb04def2603accd5ab3ee7b"}, +] + +[package.dependencies] +affine = "*" +attrs = "*" +certifi = "*" +click = ">=4.0,<8.2.dev0 || >=8.3.dev0" +cligj = ">=0.5" +numpy = ">=2" +pyparsing = "*" + +[package.extras] +all = ["rasterio[docs,ipython,plot,s3,test]"] +docs = ["ghp-import", "numpydoc", "sphinx", "sphinx-click", "sphinx-rtd-theme"] +ipython = ["ipython (>=2.0)"] +plot = ["matplotlib"] +s3 = ["boto3 (>=1.2.4)"] +test = ["aiohttp", "boto3 (>=1.2.4)", "fsspec", "hypothesis", "matplotlib", "packaging", "pytest (>=2.8.2)", "pytest-cov (>=2.2.0)", "requests", "shapely"] + [[package]] name = "requests" version = "2.34.2" @@ -3004,6 +3199,54 @@ files = [ [package.dependencies] packaging = ">=23.2" +[[package]] +name = "rioxarray" +version = "0.19.0" +description = "geospatial xarray extension powered by rasterio" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "rioxarray-0.19.0-py3-none-any.whl", hash = "sha256:494ee4fff1781072d55ee5276f5d07b63d93b05093cb33b926a12186ba5bb8ef"}, + {file = "rioxarray-0.19.0.tar.gz", hash = "sha256:7819a0036fd874c8c8e280447cbbe43d8dc72fc4a14ac7852a665b1bdb7d4b04"}, +] + +[package.dependencies] +numpy = ">=1.23" +packaging = "*" +pyproj = ">=3.3" +rasterio = ">=1.4.3" +xarray = ">=2024.7.0" + +[package.extras] +all = ["scipy"] +interp = ["scipy"] + +[[package]] +name = "rioxarray" +version = "0.22.0" +description = "geospatial xarray extension powered by rasterio" +optional = false +python-versions = ">=3.12" +groups = ["main"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "rioxarray-0.22.0-py3-none-any.whl", hash = "sha256:db0aa55cd36a95060968f2e6574107829def29d43a563560b90bc642d0bd6a3b"}, + {file = "rioxarray-0.22.0.tar.gz", hash = "sha256:3f55f23a632ffd9eff13463634227f4afbbcf298947536e161f6cf2ce88d4373"}, +] + +[package.dependencies] +numpy = ">=2" +packaging = "*" +pyproj = ">=3.3" +rasterio = ">=1.4.3" +xarray = ">=2026.2" + +[package.extras] +all = ["scipy"] +interp = ["scipy"] + [[package]] name = "roman-numerals" version = "4.1.0" @@ -3831,4 +4074,4 @@ type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "05a30edc4ff8c6f1639f6361ce4982b9a658b73337f8864aeb0461adc2c0cbba" +content-hash = "b0013cb89d0438194364bb3622303c909d13037732a324226202569ac83b7bac" diff --git a/pyproject.toml b/pyproject.toml index b5d0f77..0627d4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "cmocean (>=4.0.3,<5.0.0)", "pyyaml (>=6.0.3,<7.0.0)", "bitsea @ git+https://github.com/inogs/bit.sea.git", + "rioxarray (>=0.19.0,<1.0.0)", "tabulate (>=0.10.0,<1.0.0)", ] diff --git a/src/medunda/plots/maps.py b/src/medunda/plots/maps.py index 51ddf66..e305015 100644 --- a/src/medunda/plots/maps.py +++ b/src/medunda/plots/maps.py @@ -17,7 +17,7 @@ def configure_parser(subparsers): plotting_maps.add_argument( "--time", type=date_from_str, - required=True, + required=False, help="Date to plot the map for (format: YYYY-MM-DD)", ) @@ -44,37 +44,68 @@ def plotting_maps( aggregation_dimension, aggregation_method, ): - selected_time = pd.to_datetime(time) + if time is None: + if "band" in data.dims: + data_slice = data.isel(band=0) + else: + data_slice = data + + actual_time = None + + else: + selected_time = pd.to_datetime(time) - data["time"] = pd.to_datetime(data["time"].values) + data["time"] = pd.to_datetime(data["time"].values) - try: - data_slice = data.sel(time=selected_time) - except Exception: try: - data_slice = data.sel(time=selected_time, method="nearest") - except Exception as e: - raise ValueError(f"Could not select time {selected_time}: {e}") + data_slice = data.sel(time=selected_time) + except Exception: + try: + data_slice = data.sel(time=selected_time, method="nearest") + except Exception as e: + raise ValueError(f"Could not select time {selected_time}: {e}") + + n_dims = len(data_slice.dims) + + if n_dims == 2: + if ( + "latitude" not in data_slice.dims + or "longitude" not in data_slice.dims + ): + raise ValueError( + "Data must have 'latitude' and 'longitude' dimensions for 2D plotting." + ) + else: + LOGGER.debug( + "Data has 2 dimensions, proceeding with plotting." + ) - n_dims = len(data_slice.dims) + if aggregation_dimension: + if aggregation_dimension in data_slice.dims: + raise ValueError( + f"Aggregation dimension '{aggregation_dimension}' cannot be used for 2D data with dimensions {data_slice.dims}" + ) + LOGGER.debug( + f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" + ) - if n_dims == 2: - if ( - "latitude" not in data_slice.dims - or "longitude" not in data_slice.dims - ): - raise ValueError( - "Data must have 'latitude' and 'longitude' dimensions for 2D plotting." - ) - else: - LOGGER.debug("Data has 2 dimensions, proceeding with plotting.") + data_slice = getattr(data_slice, aggregation_method)( + dim=aggregation_dimension + ) + + elif n_dims > 2: + if not aggregation_method: + raise ValueError( + "Data has more than 2 dimensions, an aggregation method must be specified." + " Please provide an aggregation method using the '--aggregation-method'" + ) - if aggregation_dimension: - if aggregation_dimension in data_slice.dims: + if aggregation_dimension not in data_slice.dims: raise ValueError( - f"Aggregation dimension '{aggregation_dimension}' cannot be used for 2D data with dimensions {data_slice.dims}" + f"Aggregation dimension '{aggregation_dimension}' not found in data dimensions: {data_slice.dims}" ) - LOGGER.debug( + + LOGGER.info( f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" ) @@ -82,39 +113,25 @@ def plotting_maps( dim=aggregation_dimension ) - elif n_dims > 2: - if not aggregation_method: - raise ValueError( - "Data has more than 2 dimensions, an aggregation method must be specified." - " Please provide an aggregation method using the '--aggregation-method'" - ) - - if aggregation_dimension not in data_slice.dims: + else: raise ValueError( - f"Aggregation dimension '{aggregation_dimension}' not found in data dimensions: {data_slice.dims}" + f"Data has {n_dims} dimensions, expected at least 2 dimensions for plotting." ) - LOGGER.info( - f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" - ) - - data_slice = getattr(data_slice, aggregation_method)( - dim=aggregation_dimension - ) - - else: - raise ValueError( - f"Data has {n_dims} dimensions, expected at least 2 dimensions for plotting." - ) - - actual_time = pd.to_datetime(data_slice["time"].values) + actual_time = pd.to_datetime(data_slice["time"].values) fig, ax = plt.subplots(figsize=(10, 5)) cmap = metadata.get("cmap", "viridis") im = ax.pcolormesh(data_slice, cmap=cmap) - title_str = f"{metadata['label']} at {actual_time.strftime('%Y-%m-%d')}" + if actual_time is not None: + title_str = ( + f"{metadata['label']} at {actual_time.strftime('%Y-%m-%d')}" + ) + else: + title_str = metadata["label"] + ax.set_title(title_str) ax.set_xlabel("Longitude") ax.set_ylabel("Latitude") diff --git a/src/medunda/plots/timeseries.py b/src/medunda/plots/timeseries.py index 02ec6f9..5da768b 100644 --- a/src/medunda/plots/timeseries.py +++ b/src/medunda/plots/timeseries.py @@ -54,7 +54,7 @@ def plotting_timeseries( ts = data_mean.sel(time=slice(start_date, end_date)) - fig, ax = plt.subplots(figsize=(12, 6)) + _, ax = plt.subplots(figsize=(12, 6)) ax.plot(ts.time, ts) ylabel = f"{metadata['label']}" @@ -64,5 +64,4 @@ def plotting_timeseries( ax.set_title(f"Time Series of {metadata['label']}") ax.set_xlabel("Time") ax.set_ylabel(ylabel) - plt.show() diff --git a/src/medunda/plotter.py b/src/medunda/plotter.py index adb96d7..46a4596 100644 --- a/src/medunda/plotter.py +++ b/src/medunda/plotter.py @@ -2,41 +2,50 @@ import logging from pathlib import Path from types import MappingProxyType +from typing import TYPE_CHECKING +from typing import cast import medunda.tools.lazy_imports.cmocean as lazy_cmocean import medunda.tools.lazy_imports.colormap as clp +import medunda.tools.lazy_imports.rioxarray as rioxarray +from medunda.components.variables import Variable from medunda.components.variables import VariableDataset from medunda.plots import maps from medunda.plots import timeseries +from medunda.tools.lazy_imports import pd from medunda.tools.lazy_imports import xr from medunda.tools.logging_utils import configure_logger +if TYPE_CHECKING: + from matplotlib.colors import Colormap + + LOGGER = logging.getLogger(__name__) VAR_METADATA = MappingProxyType( { "o2": MappingProxyType( - {"label": "Oxygen", "unit": "µmol/m³", "cmap": "cmo:deep"} + {"label": "Oxygen", "unit": "µmol/m³", "cmap_name": "cmo:deep"} ), "chl": MappingProxyType( { "label": "Chlorophyll-a", "unit": "mg/m³", - "cmap": "cmo:algae", + "cmap_name": "cmo:algae", } ), "nppv": MappingProxyType( { "label": "Net Primary Production", "unit": "mg C/m²/day", - "cmap": "cmo:matter", + "cmap_name": "cmo:matter", } ), "thetao": MappingProxyType( - {"label": "Temperature", "unit": "°C", "cmap": "coolwarm"} + {"label": "Temperature", "unit": "°C", "cmap_name": "coolwarm"} ), "so": MappingProxyType( - {"label": "Salinity", "unit": "PSU", "cmap": "viridis"} + {"label": "Salinity", "unit": "PSU", "cmap_name": "viridis"} ), } ) @@ -44,7 +53,7 @@ DEFAULT_VAR = MappingProxyType( { "unit": "", - "cmap": "viridis", + "cmap_name": "viridis", } ) @@ -76,7 +85,7 @@ def configure_parser( "--variable", type=str, choices=VariableDataset.all_variables().get_variable_names(), - required=True, + required=False, help="Name of the variable to plot", ) # parser.add_argument( @@ -100,55 +109,63 @@ def configure_parser( return parser +def parse_colormap_description(cmap_desc: str) -> "Colormap": + if cmap_desc.startswith("cmo:"): + return lazy_cmocean.get_cmocean_map(cmap_desc[4:]) + else: + return clp.Colormap(cmap_desc) + + def check_variable(ds, var): - if var not in ds: - raise ValueError(f"Variable '{var}' not found in dataset") + if isinstance(ds, xr.Dataset): + if var not in ds: + raise ValueError(f"Variable '{var}' not found in dataset") + data = ds[var] + + elif isinstance(ds, xr.DataArray): + data = ds + + else: + raise ValueError("Unsupported dataset type") if var in VAR_METADATA: - var_metadata = dict(VAR_METADATA[var]) + var_metadata: dict[str, "str | Colormap"] = dict(VAR_METADATA[var]) else: - var_metadata = dict(DEFAULT_VAR) + var_metadata: dict[str, "str | Colormap"] = dict(DEFAULT_VAR) var_metadata["label"] = var - return ds[var], var_metadata + cmap_name = cast(str, var_metadata["cmap_name"]) + del var_metadata["cmap_name"] + + var_metadata["cmap"] = parse_colormap_description(cmap_name) + + return data, var_metadata def plotter(filepath: Path, variable: str, mode: str, args): - """Extracts and plots surface, bottom, and average layers of the given variable.""" + """The main function of the plotter module, which is responsible for plotting the data contained in the input file. + Depending on the mode specified by the user, this function calls the appropriate plotting function to plot the data. + The plotting functions are defined in the `plots` module and are responsible for plotting timeseries and maps. + Extracts and plots surface, bottom, and average layers of the given variable.""" if not filepath.exists(): raise FileNotFoundError(f"The file '{filepath}' does not exist.") - if filepath.suffix != ".nc": - raise ValueError( - f"File {filepath} is not a valid netcdf file; its suffix does " - 'not end with ".nc."' - ) - - with xr.open_dataset(filepath) as ds: - data_var, metadata = check_variable(ds, variable) - - # Ensure that the metadata contains a colormap object if a colormap - # name is provided - if "cmap" in metadata and isinstance(metadata["cmap"], str): - cmap_name = metadata["cmap"] - if cmap_name.startswith("cmo:"): - metadata["cmap"] = lazy_cmocean.get_cmocean_map(cmap_name[4:]) - else: - metadata["cmap"] = clp.Colormap(cmap_name) + if filepath.suffix == ".nc": + with xr.open_dataset(filepath) as ds: + data_var, metadata = check_variable(ds, variable) if mode == "plotting_timeseries": if "time" not in ds[variable].dims: raise ValueError( f"Variable '{variable} does not have the time dimension" ) - else: - timeseries.plotting_timeseries( - data=data_var, - metadata=metadata, - start_date=args.start_date, - end_date=args.end_date, - ) + timeseries.plotting_timeseries( + data=data_var, + metadata=metadata, + start_date=args.start_date, + end_date=args.end_date, + ) elif mode == "plotting_maps": maps.plotting_maps( @@ -158,10 +175,86 @@ def plotter(filepath: Path, variable: str, mode: str, args): aggregation_dimension=args.aggregation_dimension, aggregation_method=args.aggregation_method, ) + else: + raise ValueError("Invalid mode") + + elif filepath.suffix == ".csv": + df = pd.read_csv(filepath) + + if "time" in df.columns: + # Ensure that the time column is in datetime format + # and set the time column as the index + df["time"] = pd.to_datetime(df["time"]) + df.set_index("time", inplace=True) + df.sort_index(inplace=True) + else: + df.set_index( + [col for col in df.columns if col != variable], inplace=True + ) + if variable not in df.columns: + raise ValueError(f"Variable '{variable}' not found in CSV") + else: + metadata: dict[str, "str | Colormap"] = dict(DEFAULT_VAR) + try: + var_object = Variable.get_by_name(variable) + metadata["label"] = var_object.get_label() + except ValueError: + metadata["label"] = variable + + cmap_name = cast(str, metadata["cmap_name"]) + metadata["cmap"] = parse_colormap_description(cmap_name) + del metadata["cmap_name"] + + ds = df.to_xarray() + + data_var = ds[variable] + data_var, _ = check_variable(ds, variable) + + if mode == "plotting_timeseries": + if "time" not in ds[variable].dims: + raise ValueError( + f"Variable '{variable} does not have the time dimension" + ) + else: + timeseries.plotting_timeseries( + data=data_var, + metadata=metadata, + start_date=args.start_date, + end_date=args.end_date, + ) + elif mode == "plotting_maps": + raise ValueError("Plotting maps from CSV files is not supported") else: raise ValueError("Invalid mode") + elif filepath.suffix == ".tif": + with rioxarray.open_rasterio(filepath) as ds: + print(ds) + + data_var, metadata = check_variable(ds, variable) + + if mode == "plotting_timeseries": + raise ValueError( + "Plotting timeseries from GeoTIFF files is not supported" + ) + + elif mode == "plotting_maps": + maps.plotting_maps( + data=data_var, + metadata=metadata, + time=args.time, + aggregation_dimension=args.aggregation_dimension, + aggregation_method=args.aggregation_method, + ) + else: + raise ValueError("Invalid mode") + + else: + raise ValueError( + f"Unsupported file format '{filepath.suffix}'. Supported formats are: .nc, .csv, .tif" + ) + return 0 diff --git a/src/medunda/tools/lazy_imports/cmocean.py b/src/medunda/tools/lazy_imports/cmocean.py index c24e0a6..f3c4e7a 100644 --- a/src/medunda/tools/lazy_imports/cmocean.py +++ b/src/medunda/tools/lazy_imports/cmocean.py @@ -1,3 +1,9 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from matplotlib.colors import Colormap + + _CMOCEAN = None @@ -8,7 +14,7 @@ def __getattr__(name: str): return getattr(_CMOCEAN, name) -def get_cmocean_map(name: str): +def get_cmocean_map(name: str) -> "Colormap": global _CMOCEAN if _CMOCEAN is None: import cmocean as _CMOCEAN diff --git a/src/medunda/tools/lazy_imports/rioxarray.py b/src/medunda/tools/lazy_imports/rioxarray.py new file mode 100644 index 0000000..95413dd --- /dev/null +++ b/src/medunda/tools/lazy_imports/rioxarray.py @@ -0,0 +1,10 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rioxarray import * # noqa: F403 + + +def __getattr__(name: str): + import rioxarray + + return getattr(rioxarray, name) From ee7b5e577ae7fffb038030d5b1d98f6219bd115a Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Mon, 4 May 2026 10:24:24 +0200 Subject: [PATCH 4/7] Remove useless print --- src/medunda/plotter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/medunda/plotter.py b/src/medunda/plotter.py index 46a4596..4696b84 100644 --- a/src/medunda/plotter.py +++ b/src/medunda/plotter.py @@ -230,8 +230,6 @@ def plotter(filepath: Path, variable: str, mode: str, args): elif filepath.suffix == ".tif": with rioxarray.open_rasterio(filepath) as ds: - print(ds) - data_var, metadata = check_variable(ds, variable) if mode == "plotting_timeseries": From 1f29a31ecf65902b32e39642e140dc0483012a21 Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Wed, 6 May 2026 16:19:38 +0200 Subject: [PATCH 5/7] Added support for choosing between interactive plotting and directly saving the plots --- src/medunda/plots/maps.py | 8 +++- src/medunda/plots/timeseries.py | 14 ++++++- src/medunda/plotter.py | 74 ++++++++++++++++++++------------- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/medunda/plots/maps.py b/src/medunda/plots/maps.py index e305015..7ec9a4b 100644 --- a/src/medunda/plots/maps.py +++ b/src/medunda/plots/maps.py @@ -43,6 +43,8 @@ def plotting_maps( time, aggregation_dimension, aggregation_method, + output_dir, + show_plot, ): if time is None: if "band" in data.dims: @@ -139,4 +141,8 @@ def plotting_maps( cbar = fig.colorbar(im, ax=ax, orientation="vertical") cbar.set_label(f"{metadata['label']} ({metadata.get('unit', '')})") - plt.show() + if output_dir: + output_dir.mkdir(parents=True, exist_ok=True) + plt.savefig(output_dir / f"{metadata['label']}_map.png") + if show_plot: + plt.show() diff --git a/src/medunda/plots/timeseries.py b/src/medunda/plots/timeseries.py index 5da768b..5008a8b 100644 --- a/src/medunda/plots/timeseries.py +++ b/src/medunda/plots/timeseries.py @@ -28,7 +28,12 @@ def configure_parser(subparsers): def plotting_timeseries( - data: "xr.DataArray", metadata: dict, start_date, end_date + data: "xr.DataArray", + metadata: dict, + start_date, + end_date, + output_dir, + show_plot, ): if "time" not in data.dims: raise ValueError( @@ -64,4 +69,9 @@ def plotting_timeseries( ax.set_title(f"Time Series of {metadata['label']}") ax.set_xlabel("Time") ax.set_ylabel(ylabel) - plt.show() + + if output_dir: + output_dir.mkdir(parents=True, exist_ok=True) + plt.savefig(output_dir / f"{metadata['label']}_timeseries.png") + if show_plot: + plt.show() diff --git a/src/medunda/plotter.py b/src/medunda/plotter.py index 4696b84..fa0b2eb 100644 --- a/src/medunda/plotter.py +++ b/src/medunda/plotter.py @@ -88,12 +88,17 @@ def configure_parser( required=False, help="Name of the variable to plot", ) - # parser.add_argument( - # "--output-dir", - # type=Path, - # default=Path("."), - # help="Directory where the downloaded files are saved", - # ) + parser.add_argument( + "--output-dir", + type=Path, + required=False, + help="Directory where the plots generated are saved", + ) + parser.add_argument( + "--show-plot", + action="store_true", + help="Display the plot in an interactive window instead of saving it to a file", + ) subparsers = parser.add_subparsers( title="mode", @@ -155,28 +160,33 @@ def plotter(filepath: Path, variable: str, mode: str, args): with xr.open_dataset(filepath) as ds: data_var, metadata = check_variable(ds, variable) - if mode == "plotting_timeseries": - if "time" not in ds[variable].dims: - raise ValueError( - f"Variable '{variable} does not have the time dimension" + if mode == "plotting_timeseries": + if "time" not in ds[variable].dims: + raise ValueError( + f"Variable '{variable} does not have the time dimension" + ) + timeseries.plotting_timeseries( + data=data_var, + metadata=metadata, + start_date=args.start_date, + end_date=args.end_date, + output_dir=args.output_dir, + show_plot=args.show_plot, ) - timeseries.plotting_timeseries( - data=data_var, - metadata=metadata, - start_date=args.start_date, - end_date=args.end_date, - ) - elif mode == "plotting_maps": - maps.plotting_maps( - data=data_var, - metadata=metadata, - time=args.time, - aggregation_dimension=args.aggregation_dimension, - aggregation_method=args.aggregation_method, - ) - else: - raise ValueError("Invalid mode") + elif mode == "plotting_maps": + maps.plotting_maps( + data=data_var, + metadata=metadata, + time=args.time, + aggregation_dimension=args.aggregation_dimension, + aggregation_method=args.aggregation_method, + output_dir=args.output_dir, + show_plot=args.show_plot, + ) + + else: + raise ValueError("Invalid mode") elif filepath.suffix == ".csv": df = pd.read_csv(filepath) @@ -222,6 +232,8 @@ def plotter(filepath: Path, variable: str, mode: str, args): metadata=metadata, start_date=args.start_date, end_date=args.end_date, + output_dir=args.output_dir, + show_plot=args.show_plot, ) elif mode == "plotting_maps": raise ValueError("Plotting maps from CSV files is not supported") @@ -244,6 +256,8 @@ def plotter(filepath: Path, variable: str, mode: str, args): time=args.time, aggregation_dimension=args.aggregation_dimension, aggregation_method=args.aggregation_method, + output_dir=args.output_dir, + show_plot=args.show_plot, ) else: raise ValueError("Invalid mode") @@ -260,14 +274,18 @@ def main(): args = configure_parser().parse_args() configure_logger(LOGGER) - # output_dir = args.output_dir variable = args.variable mode = args.mode data_file = args.input_file LOGGER.info(f"Selected file: {data_file.name}") - plotter(filepath=data_file, variable=variable, mode=mode, args=args) + plotter( + filepath=data_file, + variable=variable, + mode=mode, + args=args, + ) LOGGER.info( f"Plotting completed for variable '{variable}' in mode '{mode}'" From 42e57dfd8fa5fe4b8a004c1cb16de9992f8c14d6 Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Mon, 6 Jul 2026 18:49:02 +0200 Subject: [PATCH 6/7] Simplified the logic of maps --- src/medunda/plots/maps.py | 82 ++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/src/medunda/plots/maps.py b/src/medunda/plots/maps.py index 7ec9a4b..36d41ea 100644 --- a/src/medunda/plots/maps.py +++ b/src/medunda/plots/maps.py @@ -46,80 +46,72 @@ def plotting_maps( output_dir, show_plot, ): - if time is None: - if "band" in data.dims: - data_slice = data.isel(band=0) - else: - data_slice = data + # If data is from a raster file, we always want to + # select the first band for plotting, because we do not + # generate files with multiple bands. + if "band" in data.dims: + data_slice = data.isel(band=0) + else: + data_slice = data + if time is None: actual_time = None - else: selected_time = pd.to_datetime(time) + # Convert the time coordinate to datetime if it's not already data["time"] = pd.to_datetime(data["time"].values) try: - data_slice = data.sel(time=selected_time) - except Exception: - try: - data_slice = data.sel(time=selected_time, method="nearest") - except Exception as e: - raise ValueError(f"Could not select time {selected_time}: {e}") + data_slice = data.sel(time=selected_time, method="nearest") + except Exception as e: + raise ValueError(f"Could not select time {selected_time}: {e}") n_dims = len(data_slice.dims) - if n_dims == 2: - if ( - "latitude" not in data_slice.dims - or "longitude" not in data_slice.dims - ): - raise ValueError( - "Data must have 'latitude' and 'longitude' dimensions for 2D plotting." - ) - else: - LOGGER.debug( - "Data has 2 dimensions, proceeding with plotting." - ) - - if aggregation_dimension: - if aggregation_dimension in data_slice.dims: - raise ValueError( - f"Aggregation dimension '{aggregation_dimension}' cannot be used for 2D data with dimensions {data_slice.dims}" - ) - LOGGER.debug( - f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" - ) - - data_slice = getattr(data_slice, aggregation_method)( - dim=aggregation_dimension - ) + if n_dims == 2 and aggregation_dimension is not None: + LOGGER.warning( + "Data has 2 dimensions, but an aggregation dimension was specified. " + "Aggregation will be skipped." + ) - elif n_dims > 2: - if not aggregation_method: + if n_dims > 2: + if aggregation_dimension is None: raise ValueError( - "Data has more than 2 dimensions, an aggregation method must be specified." - " Please provide an aggregation method using the '--aggregation-method'" + "Data has more than 2 dimensions, an aggregation dimension must be specified." + " Please provide an aggregation dimension using the '--aggregation-dimension'" ) if aggregation_dimension not in data_slice.dims: raise ValueError( - f"Aggregation dimension '{aggregation_dimension}' not found in data dimensions: {data_slice.dims}" + f"Aggregation dimension '{aggregation_dimension}' not found in data " + f"dimensions: {data_slice.dims}" ) LOGGER.info( - f"Applying {aggregation_method} aggregation along dimension '{aggregation_dimension}'" + f"Applying {aggregation_method} aggregation along dimension " + f"'{aggregation_dimension}'" ) data_slice = getattr(data_slice, aggregation_method)( dim=aggregation_dimension ) - else: + if n_dims != 2: raise ValueError( - f"Data has {n_dims} dimensions, expected at least 2 dimensions for plotting." + f"Data has {n_dims} dimensions after aggregation, expected 2 dimensions for plotting" ) + if ( + "latitude" not in data_slice.dims + or "longitude" not in data_slice.dims + ): + raise ValueError( + "Data must have 'latitude' and 'longitude' dimensions for 2D plotting." + ) + else: + LOGGER.debug("Data has 2 dimensions, proceeding with plotting.") + actual_time = pd.to_datetime(data_slice["time"].values) fig, ax = plt.subplots(figsize=(10, 5)) From 4b5749955244c671b65cdf9816bd97542b6d9eaf Mon Sep 17 00:00:00 2001 From: Nada Akkari Date: Mon, 6 Jul 2026 19:09:27 +0200 Subject: [PATCH 7/7] Fixed colormap import --- src/medunda/plots/maps.py | 1 - src/medunda/plotter.py | 2 +- src/medunda/tools/lazy_imports/colormap.py | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/medunda/plots/maps.py b/src/medunda/plots/maps.py index 36d41ea..f056558 100644 --- a/src/medunda/plots/maps.py +++ b/src/medunda/plots/maps.py @@ -116,7 +116,6 @@ def plotting_maps( fig, ax = plt.subplots(figsize=(10, 5)) cmap = metadata.get("cmap", "viridis") - im = ax.pcolormesh(data_slice, cmap=cmap) if actual_time is not None: diff --git a/src/medunda/plotter.py b/src/medunda/plotter.py index fa0b2eb..19b0c10 100644 --- a/src/medunda/plotter.py +++ b/src/medunda/plotter.py @@ -118,7 +118,7 @@ def parse_colormap_description(cmap_desc: str) -> "Colormap": if cmap_desc.startswith("cmo:"): return lazy_cmocean.get_cmocean_map(cmap_desc[4:]) else: - return clp.Colormap(cmap_desc) + return clp.get_cmap(cmap_desc) def check_variable(ds, var): diff --git a/src/medunda/tools/lazy_imports/colormap.py b/src/medunda/tools/lazy_imports/colormap.py index 10e9b60..6386f6c 100644 --- a/src/medunda/tools/lazy_imports/colormap.py +++ b/src/medunda/tools/lazy_imports/colormap.py @@ -9,4 +9,9 @@ def __getattr__(name: str): from matplotlib.colors import Colormap return Colormap + + if name == "get_cmap": + from matplotlib.pyplot import get_cmap + + return get_cmap raise AttributeError(f"module {__name__} has no attribute {name}")