diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 467e1e9eaafa8f69a9ebe3c0d6ebe6672a19e8a7..d6c268605f07eb1e8dcd3c8188a5e01c32d274c5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,19 @@ CHANGELOG .. start-here +1.1.11 +============ + +* Release date: 2025/07/07 +* Changes and new features: + + * New Command Line Interface nes with: + + * nes nc2geostructure + * nes check + * nes reorder + * nes interpolate + 1.1.10 ============ diff --git a/Makefile b/Makefile index 6c8788b3237f5f519e8f901cd95b8d817d93263b..8d83dfaa494ee6f4f274493963b3584ddeac406c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -version=1.1.10 +version=1.1.11 ### PATHS: Change them if needed # Paths to the NES software @@ -47,6 +47,7 @@ create_activate_deactivate_dirs: write_activate_vars_script: echo '#!/bin/bash' > $(CONDA_ENV_PATH)/etc/conda/activate.d/env_vars.sh echo 'export SLURM_CPU_BIND=none' >> $(CONDA_ENV_PATH)/etc/conda/activate.d/env_vars.sh + echo 'eval "$$(register-python-argcomplete nes)"' >> $(CONDA_ENV_PATH)/etc/conda/activate.d/env_vars.sh #Task to write the env_vars.sh inside deactivate.d to unset those variables when the environment is deactivated write_deactivate_vars_script: diff --git a/environment.yml b/environment.yml index e2e658aa526698599359a006bdc13c43f1f204e9..a1943e8ce1013cdf57b04669f6dd57b40cf68a53 100755 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,9 @@ dependencies: - openpyxl - jupyter - ipykernel + - pip + - pip: + - argcomplete variables: CC: mpicc diff --git a/nes/__init__.py b/nes/__init__.py index 8bc6b4333dbc3e3b19c6cf5303c1de1e23274514..818c5fe7adf52a5f4c6d4a3e9735b9378756e6d5 100644 --- a/nes/__init__.py +++ b/nes/__init__.py @@ -1,5 +1,5 @@ -__date__ = "2025-07-03" -__version__ = "1.1.10" +__date__ = "2025-07-10" +__version__ = "1.1.11" __all__ = [ 'open_netcdf', 'concatenate_netcdfs', 'create_nes', 'from_shapefile', 'calculate_geometry_area', 'Nes', 'LatLonNes', 'LCCNes', 'RotatedNes', 'RotatedNestedNes', 'MercatorNes', 'PointsNesProvidentia', 'PointsNesGHOST', 'PointsNes' diff --git a/nes/cli/__init__.py b/nes/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..110a2569338c398d302830e7492361a74b61defc --- /dev/null +++ b/nes/cli/__init__.py @@ -0,0 +1,8 @@ +from .checker import run_checks +from .reorder_longitudes import reorder_longitudes +from .interpolate import interpolate +from .geostructure import nc2geostructure + +__all__ = [ + 'run_checks', 'reorder_longitudes', 'interpolate', 'nc2geostructure' +] \ No newline at end of file diff --git a/nes/cli/checker.py b/nes/cli/checker.py new file mode 100644 index 0000000000000000000000000000000000000000..3651710701c8cd603facf021cdb9c425c385d2eb --- /dev/null +++ b/nes/cli/checker.py @@ -0,0 +1,47 @@ +from nes.load_nes import open_netcdf +from numpy import isinf, isnan + + +def run_checks(input_file: str, check_nan: bool = True, check_inf: bool = True): + """ + Check for NaN and/or Inf values in all variables of a NetCDF file. + + Parameters + ---------- + input_file : str + Path to the input NetCDF file. + check_nan : bool, optional + Whether to check for NaN (Not a Number) values. Default is True. + check_inf : bool, optional + Whether to check for infinite (Inf) values. Default is True. + + Raises + ------ + ValueError + If any variable contains NaN or Inf values, a ValueError is raised + indicating the variable name. + + Notes + ----- + This function uses the NES `open_netcdf()` interface to load the file and + reads all variables into memory before performing the checks. + """ + # Open the NetCDF file with NES (metadata only) + dataset = open_netcdf(input_file) + + # Load all variable data into memory + dataset.load() + + # Loop over all variables in the dataset + for var_name, var_info in dataset.variables.items(): + # Check for Inf values, if requested + has_inf = isinf(var_info["data"]).any() if check_inf else False + + # Check for NaN values, if requested + has_nan = isnan(var_info["data"]).any() if check_nan else False + + # Raise an error if problematic values are found + if has_nan or has_inf: + raise ValueError(f"Variable '{var_name}' contains NaN or Inf values.") + + return True \ No newline at end of file diff --git a/nes/cli/cli.py b/nes/cli/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..7574a6755646ebf14fa2310803be17f16471089e --- /dev/null +++ b/nes/cli/cli.py @@ -0,0 +1,217 @@ +import traceback +from mpi4py import MPI +from configargparse import ArgParser +import argcomplete + + +def _add_nc2geostructure_subparser(subparsers): + """ + Add the 'geostructure' subcommand to the NES CLI. + + This command extracts geospatial features from a NetCDF file and saves them as a geostructure. + + Parameters + ---------- + subparsers : argparse._SubParsersAction + The subparsers object returned by `add_subparsers()` on the main parser. + """ + from nes.cli import nc2geostructure + + # TODO: TEST + geo_parser = subparsers.add_parser("nc2geostructure", help="Convert NetCDF to geospatial structure (GeoJSON, shapefile) (TESTING PHASE)") + geo_parser.add_argument("-i", "--input_file", required=True, help="Path to input NetCDF file") + geo_parser.add_argument("-o", "--output_file", required=True, help="Path to output geostructure") + geo_parser.add_argument( + "--var-list", nargs="+", + help="List of variable names to include in the geostructure. If omitted, all variables will be included." + ) + geo_parser.add_argument("--time-step", type=int, default=0, help="Time step index to extract (default: 0)") + geo_parser.add_argument("--level", type=int, default=0, help="Vertical level index to extract (default: 0)") + geo_parser.set_defaults(func=nc2geostructure) + + +def _add_interpolate_subparser(subparsers): + """ + Add the 'interpolate' subcommand to the NES CLI. + + Parameters + ---------- + subparsers : argparse._SubParsersAction + The subparsers object returned by `add_subparsers()` on the main parser. + + This subcommand supports interpolation of NetCDF data along either horizontal or vertical axes. + Destination grid can be loaded from a file or created from projection parameters. + """ + from nes.cli import interpolate + + # TODO: TEST + interp_parser = subparsers.add_parser("interpolate", help="Interpolate data onto a different grid (TESTING PHASE)") + + # Main input/output + general = interp_parser.add_argument_group("General options") + general.add_argument("-i", "--input_file", required=True, help="Path to source NetCDF file") + general.add_argument("-o", "--output_file", help="Path to output NetCDF file") + general.add_argument("--axis", choices=["horizontal", "vertical"], default="horizontal", + help="Interpolation axis (default: horizontal)") + + dst_group = general.add_mutually_exclusive_group(required=True) + dst_group.add_argument("-d", "--destination", help="Path to destination grid NetCDF file") + dst_group.add_argument("--projection", help="Projection type to generate destination grid (e.g. regular, rotated, lcc)") + + # Horizontal interpolation options + horizontal = interp_parser.add_argument_group("Horizontal interpolation options") + horizontal.add_argument("--kind", choices=["NearestNeighbour", "Conservative"], + help="Interpolation method for horizontal axis") + horizontal.add_argument("--n-neighbours", type=int, help="Number of neighbors (NearestNeighbour)") + horizontal.add_argument("--flux", action="store_true", help="Treat variables as fluxes (Conservative only)") + horizontal.add_argument("--keep-nan", action="store_true", help="Keep NaN values after interpolation") + horizontal.add_argument("--fix-border", action="store_true", help="Fix border effects (NearestNeighbour only)") + horizontal.add_argument("--weight-matrix-path", help="Path to weight matrix file") + horizontal.add_argument("--only-create-wm", action="store_true", help="Only generate weight matrix") + horizontal.add_argument("--to-providentia", action="store_true", help="Format output for Providentia") + + # Vertical interpolation options + vertical = interp_parser.add_argument_group("Vertical interpolation options") + vertical.add_argument("--method", help="Interpolation method for vertical axis (e.g. linear)") + vertical.add_argument("--extrapolate", action="store_true", help="Allow extrapolation in vertical interpolation") + + # Grid creation arguments + grid = interp_parser.add_argument_group("Grid creation options (for --projection)") + grid.add_argument("--lat_orig", type=float, help="Latitude origin (regular/global)") + grid.add_argument("--lon_orig", type=float, help="Longitude origin (regular/global)") + grid.add_argument("--inc_lat", type=float, help="Latitude increment (regular/global)") + grid.add_argument("--inc_lon", type=float, help="Longitude increment (regular/global)") + grid.add_argument("--n_lat", type=int, help="Number of latitude points (regular/global)") + grid.add_argument("--n_lon", type=int, help="Number of longitude points (regular/global)") + + grid.add_argument("--centre_lat", type=float, help="Rotated pole latitude (rotated)") + grid.add_argument("--centre_lon", type=float, help="Rotated pole longitude (rotated)") + grid.add_argument("--west_boundary", type=float, help="Western boundary (rotated)") + grid.add_argument("--south_boundary", type=float, help="Southern boundary (rotated)") + grid.add_argument("--inc_rlat", type=float, help="Latitude increment (rotated)") + grid.add_argument("--inc_rlon", type=float, help="Longitude increment (rotated)") + + grid.add_argument("--parent_grid_path", help="Path to parent grid NetCDF (rotated_nested)") + grid.add_argument("--parent_ratio", type=int, help="Parent ratio (rotated_nested)") + grid.add_argument("--i_parent_start", type=int, help="Parent grid i index start (rotated_nested)") + grid.add_argument("--j_parent_start", type=int, help="Parent grid j index start (rotated_nested)") + grid.add_argument("--n_rlat", type=int, help="Number of lat points (rotated_nested)") + grid.add_argument("--n_rlon", type=int, help="Number of lon points (rotated_nested)") + + grid.add_argument("--lat_1", type=float, help="First standard parallel (LCC projection)") + grid.add_argument("--lat_2", type=float, help="Second standard parallel (LCC projection)") + grid.add_argument("--lon_0", type=float, help="Central meridian (LCC projection)") + grid.add_argument("--x_0", type=float, help="False easting (LCC projection)") + grid.add_argument("--y_0", type=float, help="False northing (LCC projection)") + grid.add_argument("--dx", type=float, help="Grid spacing in x direction (LCC projection)") + grid.add_argument("--dy", type=float, help="Grid spacing in y direction (LCC projection)") + grid.add_argument("--nx", type=int, help="Number of grid points in x (LCC projection)") + grid.add_argument("--ny", type=int, help="Number of grid points in y (LCC projection)") + + grid.add_argument("--lat_ts", type=float, help="Latitude of true scale (Mercator projection)") + + interp_parser.set_defaults(func=interpolate) + + +def _add_check_subparser(subparsers): + """ + Add the 'check' subcommand to the NES CLI. + + Parameters + ---------- + subparsers : argparse._SubParsersAction + The subparsers object returned by `add_subparsers()` on the main parser. + + This subcommand checks for the presence of NaN and Inf values in a NetCDF file. + """ + from nes.cli import run_checks + + check_parser = subparsers.add_parser("check", help="Run checks on a NetCDF file") + check_parser.add_argument("-i", "--input_file", required=True, help="Input NetCDF file path") + check_parser.add_argument("--nan", dest="check_nan", action="store_true", help="Check for NaN values") + check_parser.add_argument("--no-nan", dest="check_nan", action="store_false", help="Do not check NaN values") + check_parser.add_argument("--inf", dest="check_inf", action="store_true", help="Check for Inf values") + check_parser.add_argument("--no-inf", dest="check_inf", action="store_false", help="Do not check Inf values") + check_parser.set_defaults(check_nan=True, check_inf=True) + check_parser.set_defaults(func=run_checks) + + +def _add_reorder_subparser(subparsers): + """ + Add the 'reorder' subcommand to the NES CLI. + + Parameters + ---------- + subparsers : argparse._SubParsersAction + The subparsers object returned by `add_subparsers()` on the main parser. + + This subcommand reorders longitudes in a NetCDF file to ensure standard representation. + """ + from nes.cli import reorder_longitudes + + # TODO: Add support for parallel version + reorder_parser = subparsers.add_parser("reorder", help="Reorder longitudes in a NetCDF file (ONLY SERIAL)") + reorder_parser.add_argument("-i", "--input_file", required=True, help="Input NetCDF file path") + reorder_parser.add_argument("-o", "--output_file", help="Output NetCDF file path") + reorder_parser.set_defaults(func=reorder_longitudes) + + +def _filter_args(func, args_namespace): + """ + Filters arguments from argparse to only include those relevant to the target function. + + Parameters + ---------- + func : Callable + The function to match arguments against. + args_namespace : argparse.Namespace + The full set of parsed CLI arguments. + + Returns + ------- + dict + A dictionary containing only the arguments accepted by the function. + """ + import inspect + sig = inspect.signature(func) + arg_keys = set(sig.parameters.keys()) + args_dict = vars(args_namespace) + filtered = {k: args_dict[k] for k in arg_keys if k in args_dict} + + return filtered + + +def main(): + """ + Main entry point for the NES command-line interface. + + Sets up the available subcommands, parses user input from the CLI, + and dispatches execution to the appropriate subcommand handler. + """ + parser = ArgParser( + description="NES - NetCDF for Earth Science utilities", + default_config_files=['~/.nes_config'] + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + # Add subcommands + _add_nc2geostructure_subparser(subparsers) + _add_check_subparser(subparsers) + _add_reorder_subparser(subparsers) + _add_interpolate_subparser(subparsers) + + # Enable autocomplete + argcomplete.autocomplete(parser) + + args = parser.parse_args() + + try: + filtered_args = _filter_args(args.func, args) + args.func(**filtered_args) + except Exception as e: + print(f"Process {MPI.COMM_WORLD.Get_rank()}: NES critical error detected {e}, aborting MPI.", flush=True) + print(f"Process {MPI.COMM_WORLD.Get_rank()}: Traceback:\n{traceback.format_exc()}", flush=True) + + MPI.COMM_WORLD.Abort(1) + + return \ No newline at end of file diff --git a/nes/cli/geostructure.py b/nes/cli/geostructure.py new file mode 100644 index 0000000000000000000000000000000000000000..ca4810a9e6e036f45117a62fcbedfee533142ffd --- /dev/null +++ b/nes/cli/geostructure.py @@ -0,0 +1,60 @@ +""" +NES CLI Utility: Convert NetCDF to geospatial vector format (Shapefile, GeoJSON) + +This script defines a function `nc2geostructure` that extracts a selected time step and level +from a NetCDF file and writes the corresponding geospatial structure as a shapefile, +optionally filtering specific variables. + +Intended to be used as part of a CLI interface (e.g. via `nes geostructure`). +""" + +from nes import open_netcdf + +def nc2geostructure(input_file: str, output_file: str, var_list: list=None, time_step:int=0, level:int=0): + """ + Extracts geospatial data from a NetCDF file and writes it as a shapefile. + + Parameters + ---------- + input_file : str + Path to the source NetCDF file. + output_file : str + Path where the output shapefile will be written. + var_list : list, optional + List of variable names to include in the shapefile. If None, all variables are used. + time_step : int, default=0 + Index of the time step to extract. + level : int, default=0 + Index of the vertical level to extract. + + Returns + ------- + None + """ + # Open the NetCDF file using NES + nessy = open_netcdf(input_file) + + # If no variable list is provided, use all available variables in the file + if var_list is None: + var_list = nessy.variables.keys() + + # Select the desired time step and vertical level for extraction + nessy.sel( + time_min=nessy.time[time_step], # Minimum time index to extract + time_max=nessy.time[time_step], # Maximum time index to extract (same as min for single step) + lev_min=level, # Minimum vertical level to extract + lev_max=level # Maximum vertical level to extract (same as min for single level) + ) + + # Filter to keep only the specified variables and load the data into memory + nessy.keep_vars(var_list) + nessy.load() + + # Create the base geospatial structure (shapefile) and attach the selected variables + nessy.create_shapefile() + nessy.add_variables_to_shapefile(idx_time=0, idx_lev=0, var_list=var_list) + + # Write the geospatial structure to the output shapefile + nessy.write_shapefile(output_file) + + return None diff --git a/nes/cli/interpolate.py b/nes/cli/interpolate.py new file mode 100644 index 0000000000000000000000000000000000000000..d8d73b56d463351a33d50bc90aba3ae08e7993a8 --- /dev/null +++ b/nes/cli/interpolate.py @@ -0,0 +1,183 @@ +from nes import open_netcdf, create_nes + + +def validate_axis_options(args): + """ + Validates that interpolation axis and related options are consistent. + + This function ensures that horizontal and vertical interpolation options are not mixed. + It raises an error if options intended for one axis are used with the other, or if the axis is not supported. + + Parameters + ---------- + args : Namespace + Parsed arguments from the CLI, including interpolation axis and all related options. + + Raises + ------ + ValueError + If vertical options are used with horizontal axis or vice versa, or if an invalid axis is specified. + """ + if args.axis == "horizontal": + if args.method or args.extrapolate: + raise ValueError("Vertical options (--method, --extrapolate) cannot be used with horizontal interpolation.") + elif args.axis == "vertical": + if args.kind or args.n_neighbours or args.flux or args.weight_matrix_path or args.fix_border: + raise ValueError("Horizontal interpolation options cannot be used with vertical interpolation.") + else: + raise ValueError(f"Unsupported interpolation axis: {args.axis}") + + +def get_destination(args): + """ + Determines the destination grid for interpolation. + + This function either loads an existing destination file (if --destination is provided), + or creates a new grid using the specified projection parameters (if --projection is provided). + Only the relevant parameters for each projection type are passed to `create_nes`. + + Parameters + ---------- + args : Namespace + Parsed arguments from the CLI, containing options such as destination file path, + projection type, and all necessary projection parameters. + + Returns + ------- + nes.NES + A loaded or newly created NES object representing the destination grid. + + Raises + ------ + ValueError + If neither --destination nor --projection is provided, + or if the projection type is unsupported. + """ + if args.destination: + destination = open_netcdf(args.destination) + nessy = destination + + if not args.projection: + raise ValueError("Either --destination or --projection must be specified.") + + valid_projections = ["regular", "global", "rotated", "rotated_nested", "lcc", "mercator"] + proj = args.projection.lower() + if proj not in valid_projections: + raise ValueError(f"Unsupported projection type: {args.projection}. Valid options are: {', '.join(valid_projections)}") + + if proj == "regular": + nessy = create_nes( + projection=proj, + lat_orig=args.lat_orig, + lon_orig=args.lon_orig, + inc_lat=args.inc_lat, + inc_lon=args.inc_lon, + n_lat=args.n_lat, + n_lon=args.n_lon, + ) + elif proj == "global": + nessy = create_nes( + projection=proj, + inc_lat=args.inc_lat, + inc_lon=args.inc_lon, + ) + elif proj == "rotated": + nessy = create_nes( + projection=proj, + centre_lat=args.centre_lat, + centre_lon=args.centre_lon, + west_boundary=args.west_boundary, + south_boundary=args.south_boundary, + inc_rlat=args.inc_rlat, + inc_rlon=args.inc_rlon, + ) + elif proj == "rotated_nested": + nessy = create_nes( + projection=proj, + parent_grid_path=args.parent_grid_path, + parent_ratio=args.parent_ratio, + i_parent_start=args.i_parent_start, + j_parent_start=args.j_parent_start, + n_rlat=args.n_rlat, + n_rlon=args.n_rlon, + ) + elif proj == "lcc": + nessy = create_nes( + projection=proj, + lat_1=args.lat_1, + lat_2=args.lat_2, + lon_0=args.lon_0, + lat_0=args.lat_0, + nx=args.nx, + ny=args.ny, + inc_x=args.inc_x, + inc_y=args.inc_y, + x_0=args.x_0, + y_0=args.y_0, + ) + elif proj == "mercator": + nessy = create_nes( + projection=proj, + lat_ts=args.lat_ts, + lon_0=args.lon_0, + nx=args.nx, + ny=args.ny, + inc_x=args.inc_x, + inc_y=args.inc_y, + x_0=args.x_0, + y_0=args.y_0, + ) + else: + raise ValueError(f"Unsupported projection type: {args.projection}") + + return nessy + + +def interpolate(args): + """ + Main entry point for the NES interpolation CLI command. + + Depending on the selected interpolation axis (horizontal or vertical), this function: + - Loads the source NetCDF dataset. + - Loads or creates the destination grid based on CLI options. + - Calls the appropriate interpolation method. + + Parameters + ---------- + args : Namespace + Parsed arguments from the CLI, including interpolation settings and input/output file paths. + + Raises + ------ + ValueError + If axis-specific options are misused or required arguments are missing. + """ + validate_axis_options(args) + + source = open_netcdf(args.source) + source.load() + + destination = get_destination(args) + + if args.axis == "horizontal": + source.interpolate_horizontal( + source, + destination, + kind=args.kind, + n_neighbours=args.n_neighbours, + flux=args.flux, + keep_nan=args.keep_nan, + fix_border=args.fix_border, + weight_matrix_path=args.weight_matrix_path, + only_create_wm=args.only_create_wm, + to_providentia=args.to_providentia, + output=args.output + ) + elif args.axis == "vertical": + source.interpolate_vertical( + source, + destination, + method=args.method, + extrapolate=args.extrapolate, + output=args.output + ) \ No newline at end of file diff --git a/nes/cli/reorder_longitudes.py b/nes/cli/reorder_longitudes.py new file mode 100644 index 0000000000000000000000000000000000000000..b40d17fb7e69f33774199f9290f9c8fecb2b0b7a --- /dev/null +++ b/nes/cli/reorder_longitudes.py @@ -0,0 +1,44 @@ +from nes.load_nes import open_netcdf +from mpi4py import MPI + + +def reorder_longitudes(input_file: str, output_file: str): + """ + Convert longitudes in a NetCDF file to the [-180, 180] range and save the modified file. + + Parameters + ---------- + input_file : str + Path to the input NetCDF file. + outfile : str + Path where the reordered NetCDF file will be saved. + + Raises + ------ + ValueError + If the script is run using more than one MPI process. + + Notes + ----- + This function must be executed in serial mode only. + It uses `convert_longitudes()` from the NES API, which updates coordinate values + and ensures variables depending on longitude are shifted accordingly. + """ + # Enforce serial execution (1 MPI process) + comm = MPI.COMM_WORLD + if comm.Get_size() > 1: + raise ValueError("This script must be run with a single process (serial mode only).") + + # Open the input NetCDF file + nc = open_netcdf(input_file) + + # Load data into memory + nc.load() + + # Reorder longitudes from [0, 360] to [-180, 180] + nc.convert_longitudes() + + # Save the result to the output path + nc.to_netcdf(outfile) + + return True \ No newline at end of file diff --git a/nes/create_nes.py b/nes/create_nes.py index ce8b619fa7d6b231cb2dcb53121e58016c8e2fc5..7e9594d23603353dcfe17339a5617229d7dde33f 100644 --- a/nes/create_nes.py +++ b/nes/create_nes.py @@ -97,7 +97,7 @@ def create_nes(comm=None, info=False, projection=None, parallel_method="Y", bala required_vars = ["inc_lat", "inc_lon"] elif projection == "rotated": required_vars = ["centre_lat", "centre_lon", "west_boundary", "south_boundary", "inc_rlat", "inc_rlon"] - elif projection == "rotated-nested": + elif projection in ["rotated-nested", "rotated_nested"]: required_vars = ["parent_grid_path", "parent_ratio", "i_parent_start", "j_parent_start", "n_rlat", "n_rlon"] elif projection == "lcc": required_vars = ["lat_1", "lat_2", "lon_0", "lat_0", "nx", "ny", "inc_x", "inc_y", "x_0", "y_0"] diff --git a/nes/methods/spatial_join.py b/nes/methods/spatial_join.py index bfe9cc16ad09b09091c1958c0d99ac9fc25569b8..6e16cb674e26759b2d70269283349e6cff58ca69 100644 --- a/nes/methods/spatial_join.py +++ b/nes/methods/spatial_join.py @@ -41,8 +41,12 @@ def spatial_join(self, ext_shp, method=None, var_list=None, info=False, apply_bb sys.stdout.flush() self.create_shapefile() - ext_shp = __prepare_external_shapefile(self, ext_shp=ext_shp, var_list=var_list, info=info, - apply_bbox=apply_bbox) + if method == "nearest": + ext_shp = __prepare_external_shapefile(self, ext_shp=ext_shp, var_list=var_list, info=info, + apply_bbox=False) + else: + ext_shp = __prepare_external_shapefile(self, ext_shp=ext_shp, var_list=var_list, info=info, + apply_bbox=apply_bbox) if method == "nearest": # Nearest centroids to the shapefile polygons diff --git a/nes/nc_projections/default_nes.py b/nes/nc_projections/default_nes.py index 66b126025c8b51d9236a717a1b9bd974e2b08c40..cf1a1e22068002185c7a75651211e53fed801b33 100644 --- a/nes/nc_projections/default_nes.py +++ b/nes/nc_projections/default_nes.py @@ -4728,7 +4728,7 @@ class Nes(object): chunking : bool Indicates if you want a chunked netCDF output. Only available with non-serial writes. Default: False. nc_type : str - Type to NetCDf to write. "CAMS_RA" or "NES" + Type to NetCDf to write. "CAMS_RA" or "NES", MONARCH, MOCAGE, WRF_CHEM, CMAQ. keep_open : bool Indicates if you want to keep open the NetCDH to fill the data by time-step """ diff --git a/nes/utilities/checker.py b/nes/utilities/checker.py deleted file mode 100644 index 673400c7966b40dff5c268badc19553847711ace..0000000000000000000000000000000000000000 --- a/nes/utilities/checker.py +++ /dev/null @@ -1,49 +0,0 @@ -from ..load_nes import open_netcdf -import argparse -from numpy import isinf, isnan - -def run_checks(): - parser = argparse.ArgumentParser(description="Check NaN in a NetCDF file.") - - # Define expected arguments - parser.add_argument("--file", "-f", type=str, help="Input NetCDF file path") - parser.add_argument("--nan", "-n", type=bool, default=True, help="Check NaNs") - parser.add_argument("--inf", "-i", type=bool, default=True, help="Check infs") - - - # Parse arguments - args = parser.parse_args() - - # Call your function with parsed arguments - infile = args.file - do_nans = args.nan - do_infs = args.inf - - # Lee solo metadatos - nessy = open_netcdf(infile) - - # nessy.variables = {'var1': {'data': None, units= kg}} - # Lee matrices - nessy.load() - # nessy.variables = {'var1': {'data': ARRAY, units= kg}} - - for var_name, var_info in nessy.variables.items(): - # var_name = 'var_1' - # var_info = {'data: np.array, units: 'kg'} - if do_infs: - has_inf = isinf(var_info['data']).any() - else: - has_inf = False - - if do_nans: - has_nan = isnan(var_info['data']).any() - else: - has_nan = False - - if has_inf or has_nan: - ValueError(f"{var_name} contains NaN or Inf") - else: - pass - return - -# bash_funcion -f -n My_File \ No newline at end of file diff --git a/nes/utilities/reorder_longitudes_cli.py b/nes/utilities/reorder_longitudes_cli.py deleted file mode 100644 index 8f97c80366270ba535fe7d34a912146256e513bc..0000000000000000000000000000000000000000 --- a/nes/utilities/reorder_longitudes_cli.py +++ /dev/null @@ -1,38 +0,0 @@ -from ..load_nes import open_netcdf -import argparse -from mpi4py import MPI - - -def reorder_longitudes_cli(): - """ - Converts longitudes in a NetCDF file and saves the modified file. - - Returns: - None - """ - comm = MPI.COMM_WORLD - if comm.Get_size() > 1: - raise ValueError("Parallel not implemented yet. This script must be run with a single process.") - - parser = argparse.ArgumentParser(description="Reorder longitudes in a NetCDF file.") - - # Define expected arguments - parser.add_argument("infile", help="Input NetCDF file path") - parser.add_argument("outfile", help="Output NetCDF file path") - - # Parse arguments - args = parser.parse_args() - - # Call your function with parsed arguments - infile = args.infile - outfile = args.outfile - - # open - nc = open_netcdf(infile) - # load - nc.load() - # convert longitudes from default_projections - nc.convert_longitudes() - # save - nc.to_netcdf(outfile) - return None diff --git a/requirements.txt b/requirements.txt index 82c385c138135dbe328773825495b866cfff11fb..d85263846dbaa211a162b1082fda58f28936f5ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,11 @@ filelock eccodes mpi4py shapely -python-dateutil \ No newline at end of file +python-dateutil +configargparse +tomli +toml +importlib-metadata +packaging +pytest +argcomplete \ No newline at end of file diff --git a/setup.py b/setup.py index 1252cbe42ea42beab64bcdb6b5ace8de07a2943a..75d0eeac04d1fe50153e759d35f1aa08017afbe7 100755 --- a/setup.py +++ b/setup.py @@ -67,8 +67,7 @@ setup( entry_points={ "console_scripts": [ - "nes_reorder_longitudes=nes.utilities.reorder_longitudes_cli:reorder_longitudes_cli", - "nes_check=nes.utilities.checker:run_checks" + "nes=nes.cli.cli:main", ] } ) \ No newline at end of file