> \[!IMPORTANT\]
> This software is **Emerging** and subject to ECMWF's guidelines on [Software Maturity](https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity).
-The Hydrological Analysis Toolkit (HAT) is a software suite for hydrologists working with simulated and observed river discharge. HAT performs data analysis on hydrological datasets, with its main features being:
-- mapping station locations into hydrological model grids
-- interactive visualizations
+The Hydrological Analysis Toolkit (HAT) is a toolkit for interactive visualizations for hydrological applications.
> [!NOTE]
-> The station extraction and hydrostats functionality formerly in HAT now live in [ecmwf/hyve](https://github.com/ecmwf/hyve).
+> The station extraction and hydrostats functionality formerly in HAT now live in [ecmwf/hyve](https://github.com/ecmwf/hyve), and the station mapping functionality now lives in [ecmwf/hydro-station-mapping](https://github.com/ecmwf/hydro-station-mapping).
### Installation
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 3c3c736..0000000
--- a/docs/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Welcome to HAT
-
-The Hydrological Analysis Toolkit (HAT) is a software suite for hydrologists working with simulated and observed river discharge. HAT performs data analysis on hydrological datasets, with its main feature being:
-
-- mapping station locations into hydrological model grids
diff --git a/docs/installation.md b/docs/installation.md
deleted file mode 100644
index 030b5f6..0000000
--- a/docs/installation.md
+++ /dev/null
@@ -1,17 +0,0 @@
-### Installation
-
-Clone source code repository
-
- git clone https://github.com/ecmwf/hat.git
- cd hat
-
-Create conda python environment
-
- # If on HPC..
- # module load conda
- conda create -n hat python=3.10
- conda activate hat
-
-Installation of required dependencies
-
- pip install .
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 016bb16..0000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-mkdocs
diff --git a/docs/station_mapping.md b/docs/station_mapping.md
deleted file mode 100644
index b5564c3..0000000
--- a/docs/station_mapping.md
+++ /dev/null
@@ -1,216 +0,0 @@
-
-`station_mapping` documentation
-===============================
-
-The `station_mapping` library is designed for mapping the location of hydrological station data onto the optimum location of a hydrological model grid (netcdf).
-This tool is available as both [command line](#station-mapping-with-command-line) and [Python API](#station-mapping-as-python-script-eg-called-within-jupyter-notebook-or-python-file)
-
-The optimum grid cell location is searched through optimising the upstream area error and the cell distance(s) from the station nearest grid cells. In this tool, users can define their acceptable area difference/ error using the parameter `max_area_difference` (%) and the maximum cell radius parameter: `max_neighboring_cell` (number of cells) to search for this optimum grid. The tool can also be parameterised to ignore further searching of optimum cells when upstream area difference of a station nearest grid is below, i.e. when the uspteam area of the nearest cell to the station is already deemed acceptable by defining `min_area_diff`(%).
-
-For instance, refer to illustration example below, if the specified `max_area_difference` is 10%, then the optimum grid to be returned when specified `max_neighboring_cell` = 1 cell, is the one with 7% upstream area difference (blue). While if the `max_neighboring_cell` = 2 cell, then the cell with 5% upstream area difference will be returned as the optimum grid instead.
-
-
-
-In conclusion, the tool only searches for grid cell with optimal upstream area between the user defined `min_area_diff` and `max_area_diff` that are within the `max_neighboring_cell` radius from the station location.
-
-
-How to use
------
-#### Station Mapping with Command Line
-To use the `station_mapping` as command line, follow these steps:
-
-1. Prepare your data input: station data and grid data in the appropriate format. Station data should be in a CSV file, and grid data should be in a NetCDF file.
- PLease ensure all lattitudes and longitude values in [ decimal degree format/ DD](https://en.wikipedia.org/wiki/Decimal_degrees).
-
-2. Create a [JSON configuration](https://github.com/ecmwf/hat/tree/main/notebooks/examples/station_mapping_config_example.json) file specifying the paths to your data files, column names, and other relevant parameters.
-
-3. Run the `station_mapping.py` script with the path to your configuration file:
-
-`./station_mapping.py path/to/your/config.json`
-
-
-#### Station Mapping as python script, e.g. called within jupyter notebook or python file
-1. Prepare your data input: station data and grid data in the appropriate format. Station data should be in a CSV file, and grid data should be in a NetCDF file.
-Ensure that all the values of the lattitude and longitude in CSV files columns are in [ decimal degree format/ DD](https://en.wikipedia.org/wiki/Decimal_degrees) (NOT Degree Minute Seconds/ DMS). These affect both stations and manual mapping locations.
-
-2. Create a configuration dictionary
-
-```
-config = {
- # Netcdf information
- "upstream_area_file": "upArea.nc", #file path to netcdf of upstream area
-
- # Station Metadata CSV information
- "csv_file": "outlets.csv", #file path to csv station metadata
- "csv_lat_col": "StationLat", # column name for latitude (string)
- "csv_lon_col": "StationLon", # column name for longitude (string)
- "csv_station_name_col": "StationName", # column name for station (string)
- "csv_ups_col": "DrainingArea.km2.Provider", # column name for metadata of upstream (string)
-
- # Mapping parameters (3x)
- "max_neighboring_cells": 5, # Parameter 1: maximum radius to search for best cells (no. of cells)
- "max_area_diff": 20, # Parameter 2: acceptable/ optimum upstream area difference (%)
- "min_area_diff": 0, # Parameter 3: minimum upstream area difference (%) between nearest grid and the station metadata
-
- # manual mapping as reference for evaluation (optional)
- "manual_lat_col": "LisfloodY", # column name for latitude of manually mapped station (string)
- "manual_lon_col": "LisfloodX", # column name for longitude of manually mapped station (string)
- "manual_area": "DrainingArea.km2.LDD", # column name for area of manually mapped station (string)
-
- # if Output directory is provided, it will save the geodataframe outputs to geojson and csv readable by GIS or jupyter interactive
- # "out_directory": None # put none if you don't want to save the output
- "out_directory": "output"
-}
-```
-3. Run the `station_mapping` function with the config dictionary input and store result as dataframe (df)
-Since in the above example, the out_directory is not empty/ None, i.e. hence geojson and csv output of the station mapping tool will be saved in the specified directory.
-
-```
-# import station mapping
-from hat.mapping.station_mapping import station_mapping
-# call station_mapping function and apply on the created config dictionary
-df = station_mapping(config)
-```
-
-Process Overview
-----------------
-
-* **Read and Validate Input Data**: Loads station metadata from the specified station metadata (CSV file) and upstream area grid data (NetCDF file), importing data, based on column name defined and the parameters in the configuration.
-* **Nearest Grid Cell Search**: For each station, calculates the nearest grid cell based on latitude and longitude of each station.
-* **Optimum Grid Cell Search**: Searches each neighboring cells at a +1 cell radius at a time until a specified maximum radius `max_neighboring_cells` is reached. At every +1 cell radius this searches for grid where its upstream area difference from the recorded station metadata is minimum, or until it reaches desired value of `max_area_diff` (%). When this minimum value of `max_area_diff` is reached, the search will be stopped and the particular cell location will be stored. It is also possible to ignore searching for optimum grid when the upstream area of nearest grid cell is already below or equal to `min_area_diff`.
-* **Upstream Area and Distance Calculation**: For both nearest and optimum grids found for each station, upstream area is retrieved. Cell distance(s) from optimum grid to the stations grid (same as nearest grid) is calculated.
-* **Manual Mapping Output** (Optional): If manual mapping data is provided, it will be stored to the result dataframe and later could be used as reference to compare automated mapping results to evaluate mapping performance. This can be done through evaluation module.
-* **Save Results**: If an output directory is specified, saves the processed data as GeoJSON and CSV files for further analysis or visualization. Otherwise it only returns result as dataframe.
-
-
-Outputs
-------
-
-The following elements (column) will be written as dataframe as the expected `station_mapping` output.
-Note: `_lat` and `_lon` refer to the actual lattitude and longitude of the location, while `_lat_idx` and `_lon_idx` refer to the lat and lon grid ID.
-
-* Station data
-`station_name`, `station_lat`, `station_lon`, `station_area`
-
-* Near grid data
-`near_grid_lat_idx`, `near_grid_lon_idx`, `near_grid_lat`, `near_grid_lon`, `near_grid_area`, `near_grid_polygon`
-
-* Optimum grid from search routine
-`optimum_grid_lat_idx`, `optimum_grid_lon_idx`, `optimum_grid_lat`, `optimum_grid_lon`, `optimum_grid_area`, `optimum_area_diff`, `optimum_distance_km`, `optimum_grid_polygon`
-
-* Manually mapped variable
-`manual_lat`, `manual_lon`, `manual_lat_idx`, `manual_lon_idx`, `manual_area`
-
-* GIS compatble output files (optional)
-if the "out_directory" in the `configuration` is specified, then the following files will be written in the directory:
-
- 1. `stations.geojson`: stations point vector in geojson (readable in GIS)
- 2. `near_grid.geojson`: nearest grid vector (readable in GIS)
- 3. `optimum_grid.geojson`: optimum grid vector (readable in GIS)
- 4. `stations2grid_optimum`: polyline connecting each station location to the optimum grid's centroid (readable in GIS)
- 5. `stations.csv`: the dataframe containing all the data column mentioned above in csv format. (readable in GIS/ spreadsheet)
-
-
-Other Related Module
---------------------
-
-#### `evaluation`
-An additional module is available to evaluate the performance of the station mapping, in particular the optimum grid cell found for each station. The common case for this evaluation is to compare the resulting optimum grid cells with the manually mapped station, based on their upstream area difference (%), and cell distance(s).
-
-* Main function: `def count_and_analyze_area_distance`
-
-* How to use:
-```
-from hat.mapping.evaluation import count_and_analyze_area_distance # import library
-
-fig = count_and_analyze_area_distance(df, area_diff_limit, distance_limit, ref_name='manual', eval_name='optimum_grid', y_scale='log')
-```
-* Parameters:
- * `df`: dataframe resulted from running station_mapping tool
- * `area_diff_limit`: margin of acceptable error or differece of upstream area (%) between the reference manual mapping and the evaluated optimum grid
- * `distance_limit`: distance margin that defines the acceptable location distance between reference and the evaluated grid. E.g. distance = 0 means perfect mapping, where optimum grid and manual mapping locations are at the same cell.
- * `ref_name` and `eval_name`: name of the reference and evaluated variable, the common case would be 'manual' for manual mapping as reference and `optimum_grid` as the evaluated optimum grids.
- * `y_scale`: scale for y axis or the number of counts/ frequency found for histogram figure, default is 'log', with option for 'linear'.
-
-* Outputs:
-Message display the counts of stations that are not found and found within the `error_margin`.
-It also breaks down the count of found stations into those that are found within and outside the `distance_limit`. When this 'distance_limit' is set as 0, it counts for perfectly mapped station.
-
-In addition to these counts message display, the function also returns histogram of found stations counts (y-axis) for every cell distance (x-axis), see example screenshot below.
-
-
-
-
-
-#### `visualisation`
-A simple interactive map to be implemented on jupyter notebook to overlay the GIS output resulting from the `station_mapping` is also available.
-This module is based on ipyleaflet.
-
-Main function example of the module to overlay a geojson file as a map layer:
-```
-# Define map vector layer from geojson file
-map_layer = GeoJSONLayerManager(
- "layer.geojson", # geojson file
- style_callback=lambda feature: vector_style(feature, "blue", 0.5), # constant style
- name="", # name for legend
-
-# Create an InteractiveMap instance
-my_map = InteractiveMap()
-
-# Overlay layer to the interactive map
-my_map.add_layer(map_layer)
-```
-
-Additional feature of this module include:
-* Modifying the style of the vector layer to a varying color (colormap) based on the specified variable. For choices of colormap classes, refer to [matplotlib doc](https://matplotlib.org/stable/users/explain/colors/colormaps.html)
-
-```
-# define color map and normalize values based on lower and upper limit
-cmap = cm["PRGn"] # define colormap class
-
-vmin, vmax = -10, 10
-norm = plt.Normalize(vmin=vmin, vmax=vmax) #normalize to lower & upper limit variable values
-
-map_layer = GeoJSONLayerManager(
- "layer.geojson", # geojson file
- style_callback=make_style_callback("", cmap, norm), # based on colormap instead of constant
- name="", # name for legend
-
-# ... add layer to interactive map
-```
-
-* Adding attribute table popup when clicked (line vector only)
-
-```
-# Add line click handlers after the layer has been added to the map
-if line_layer.layer:
- line_layer.layer.on_click(
- make_line_click_handler(
- "station_name", # station name column name
- "station_area", # station area column name
- "near_grid_area", # near grid area column name
- "optimum_grid_area", # optimum grid area column name
- "optimum_distance_cells", # optimum distance column name
- my_map.map, # Pass the map object as an argument
- )
- )
-```
-
-* Create and adding customized legend to the map
-```
-legend_widget = create_gradient_legend(cmap, vmin, vmax)
-my_map.map.add_control(WidgetControl(widget=legend_widget, position="bottomright"))
-```
-
-Screenshot below is the example of the visualisation output in the notebook example:
-
-In this example the station is indicated by the blue marker, with the dark grey rectangle as its nearest grid, and the other grid with varying color based on area difference colormap (legend, i.e. currently white) is the optimum grid. The black line connects the station location and the centroid of the optimum grid, and can be clickable to show the result attributes if the click handler is added.
-
-
-Implementation Example in Jupyter notebook
----------------------------
-
-For the implementation example of station mapping in Jupyter notebook, an example is created in [station mapping notebook](https://github.com/ecmwf/hat/tree/main/notebooks/examples/5a_station_mapping_evaluate.ipynb)
-This configuration is based on DESTINE project, and you shall modify your netcdf and csv input file location accordingly. The example of evaluation module implementation is also attached to this jupyter notebook as well.
-
-Additionally, please refer to this the [station mapping visualisation notebook example here](https://github.com/ecmwf/hat/tree/main/notebooks/examples/5b_station_mapping_visualise.ipynb)
diff --git a/docs/station_mapping/station_mapping_histogram.jpg b/docs/station_mapping/station_mapping_histogram.jpg
deleted file mode 100644
index fded74c..0000000
Binary files a/docs/station_mapping/station_mapping_histogram.jpg and /dev/null differ
diff --git a/docs/station_mapping/station_mapping_search_algo.svg b/docs/station_mapping/station_mapping_search_algo.svg
deleted file mode 100644
index 5a4a7e2..0000000
--- a/docs/station_mapping/station_mapping_search_algo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/docs/station_mapping/station_mapping_visualisation.jpg b/docs/station_mapping/station_mapping_visualisation.jpg
deleted file mode 100644
index 5ab1f00..0000000
Binary files a/docs/station_mapping/station_mapping_visualisation.jpg and /dev/null differ
diff --git a/docs/usage.md b/docs/usage.md
deleted file mode 100644
index 00a5f5c..0000000
--- a/docs/usage.md
+++ /dev/null
@@ -1,23 +0,0 @@
-### Usage
-
-If you have already [installed](installation.md) hat then
-
-#### Activate Environment
-
- $ conda activate hat
-
-#### Command Line Tool
-
-Run a command line tool, for example
-
- $ hat-station-mapping --help
-
-For more information on individual command line tools, use the `--help` option at the command line or read the documentation, for instance for the [station mapping](station_mapping.md) tool.
-
-#### Python API
-
-In your python code you can import the hat module
-
- import hat
-
-For examples, please see these [jupyter notebooks](https://github.com/ecmwf-projects/hat/tree/main/notebooks)
diff --git a/hat/cli.py b/hat/cli.py
deleted file mode 100644
index 587e8b8..0000000
--- a/hat/cli.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import yaml
-import argparse
-import sys
-
-from hat import _LOGGER as logger
-from hat.station_mapping.mapper import mapper
-
-
-def commandlineify(func):
- def wrapper(args=None):
- if args is None:
- args = sys.argv[1:]
- parser = argparse.ArgumentParser(description="Run tool with YAML config")
- parser.add_argument("config", help="Path to the YAML config file")
- args = parser.parse_args(args)
- confpath = args.config
- with open(confpath, "r") as file:
- config = yaml.safe_load(file)
- func(config)
-
- return wrapper
-
-
-mapper_cli = commandlineify(mapper)
-
-
-if __name__ == "__main__":
- from importlib.metadata import entry_points
-
- eps = entry_points().select(group="console_scripts")
- tools = {ep.name: ep.load() for ep in eps if ep.module.startswith("hat.")}
- tool_name = sys.argv[1]
- if tool_name in tools:
- tools[tool_name](sys.argv[2:])
- else:
- logger.error(f"Tool '{tool_name}' not found. Available tools: {', '.join(tools.keys())}")
- sys.exit(1)
diff --git a/hat/station_mapping/mapper.py b/hat/station_mapping/mapper.py
deleted file mode 100644
index 4a63d50..0000000
--- a/hat/station_mapping/mapper.py
+++ /dev/null
@@ -1,121 +0,0 @@
-import pandas as pd
-import xarray as xr
-import earthkit.data as ekd
-import numpy as np
-import plotly.express as px
-from plotly.colors import get_colorscale
-from earthkit.hydro._readers import find_main_var
-from .station_mapping import StationMapping
-
-
-def get_grid_inputs(grid_config):
- ds = ekd.from_source(*grid_config["source"]).to_xarray()
- nc_variable = find_main_var(ds, 2)
- metric_grid = ds[nc_variable].values
-
- coord_dict = grid_config.get("coords", None)
- coord_x = "lat" if coord_dict is None else coord_dict["x"]
- coord_y = "lon" if coord_dict is None else coord_dict["y"]
-
- grid_area_coords1, grid_area_coords2 = xr.broadcast(ds[coord_x], ds[coord_y])
- grid_area_coords1 = grid_area_coords1.values.copy()
- grid_area_coords2 = grid_area_coords2.values.copy()
-
- return metric_grid, grid_area_coords1, grid_area_coords2
-
-
-def get_station_inputs(station_config):
- df = pd.read_csv(station_config["file"])
- filters = station_config.get("filter")
- if filters is not None:
- df = df.query(filters)
- coord_x = station_config["coords"]["x"]
- coord_y = station_config["coords"]["y"]
- station_coords1 = df[coord_x].values
- station_coords2 = df[coord_y].values
- station_metric = df[station_config["metric"]].values
- return station_metric, station_coords1, station_coords2, df
-
-
-def apply_blacklist(blacklist_config, metric_grid, grid_area_coords1, grid_area_coords2):
- if blacklist_config is not None:
- ds = ekd.from_source(*blacklist_config["source"]).to_xarray()
- nc_variable = find_main_var(ds, 2)
- mask = ds[nc_variable].values
- metric_grid[mask] = np.nan
-
- return metric_grid, grid_area_coords1, grid_area_coords2
-
-
-def outputs_to_df(
- df, indx, indy, area, cindx, cindy, carae, errors, grid_area_coords1, grid_area_coords2, shape, filename
-):
- df["opt_x_index"] = indx
- df["opt_y_index"] = indy
- df["near_x_index"] = cindx
- df["near_y_index"] = cindy
- df["near_area"] = carae
- df["opt_error"] = errors
- df["opt_area"] = area
- df["opt_x_coord"] = grid_area_coords1[indx, 0]
- df["opt_y_coord"] = grid_area_coords2[0, indy]
- df["opt_1d_index"] = indy + shape[1] * indx
- if filename is not None:
- df.to_csv(filename, index=False)
- return df
-
-
-def light_zero_color(colorscale_name="Viridis", zero_color="rgba(0,0,0,0)"):
- base = get_colorscale(colorscale_name)
- n = len(base)
- scaled = [[i / (n - 1), color] for i, (_, color) in enumerate(base)]
- scaled[0] = [0.0, zero_color]
- return scaled
-
-
-def generate_summary_plots(df, plot_config):
- if plot_config is None:
- return
-
- distance_plot_config = plot_config.get("error", None)
- if distance_plot_config is not None:
- df["grid_offset_x"] = df["opt_x_index"] - df["near_x_index"]
- df["grid_offset_y"] = df["opt_y_index"] - df["near_y_index"]
- custom_scale = light_zero_color("Viridis")
- fig = px.density_heatmap(
- df,
- x="grid_offset_x",
- y="grid_offset_y",
- marginal_x="histogram",
- marginal_y="histogram",
- color_continuous_scale=custom_scale,
- )
- fig.write_html(distance_plot_config["file"])
- fig.show()
-
- error_plot_config = plot_config.get("error", None)
- if error_plot_config is not None:
- fig = px.histogram(df, x="opt_error")
- fig.write_html(error_plot_config["file"])
- fig.show()
-
-
-def mapper(config):
- metric_grid, grid_area_coords1, grid_area_coords2 = get_grid_inputs(config["grid"])
- station_metric, station_coords1, station_coords2, df = get_station_inputs(config["station"])
- metric_grid, grid_area_coords1, grid_area_coords2 = apply_blacklist(
- config.get("blacklist", None), metric_grid, grid_area_coords1, grid_area_coords2
- )
- mapping_outputs = StationMapping(config["parameters"]).conduct_mapping(
- station_coords1, station_coords2, grid_area_coords1, grid_area_coords2, station_metric, metric_grid
- )
- df = outputs_to_df(
- df,
- *mapping_outputs,
- grid_area_coords1,
- grid_area_coords2,
- shape=grid_area_coords1.shape,
- filename=config["output"]["file"] if config.get("output", None) is not None else None,
- )
- generate_summary_plots(df, config.get("plot", None))
- return df
diff --git a/hat/station_mapping/metrics.py b/hat/station_mapping/metrics.py
deleted file mode 100644
index 413c7e5..0000000
--- a/hat/station_mapping/metrics.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import numpy as np
-
-
-def metric_wrapper(func):
- def wrapper(val, grid):
- # prep grid
- if grid.ndim == 1:
- grid = grid[np.newaxis, :] # (2,n) for dist, (1,n) for metric
- # prep val
- if np.isscalar(val):
- val = np.array([val])
- if val.ndim == 1:
- val = val[:, np.newaxis] # (2,1) for dist, (1,1) for metric
-
- return func(val, grid)
-
- return wrapper
-
-
-@metric_wrapper
-def zero(val, grid):
- """
- Zero Error
- """
- return np.zeros(grid.shape[1:])
-
-
-@metric_wrapper
-def mape(val, grid):
- """
- Mean Absolute Percentage Error
- """
- denominator = np.where(np.abs(val) < 1e-8, 1e-8, np.abs(val))
- return np.mean(np.abs((val - grid) / denominator), axis=0)
-
-
-@metric_wrapper
-def mspe(val, grid):
- """
- Mean Absolute Percentage Error
- """
- denominator = np.where(np.abs(val) < 1e-8, 1e-8, np.abs(val))
- return np.mean(((val - grid) / denominator) ** 2, axis=0)
-
-
-@metric_wrapper
-def mae(val, grid):
- """
- Mean Absolute Error
- """
- return np.mean(np.abs(val - grid), axis=0)
-
-
-@metric_wrapper
-def mse(val, grid):
- """
- Mean Squared Error
- """
- return np.mean((val - grid) ** 2, axis=0)
-
-
-@metric_wrapper
-def rmse(val, grid):
- """
- Root Mean Squared Error
- """
- return np.sqrt(np.mean((val - grid) ** 2, axis=0))
diff --git a/hat/station_mapping/station_mapping.py b/hat/station_mapping/station_mapping.py
deleted file mode 100644
index 864b536..0000000
--- a/hat/station_mapping/station_mapping.py
+++ /dev/null
@@ -1,125 +0,0 @@
-import numpy as np
-
-from hat.station_mapping import metrics
-
-
-class StationMapping:
- def __init__(self, config):
- self.max_search_distance = config.get("max_search_distance", 5)
- self.metric_error_func = getattr(metrics, config.get("metric_error_func", "mape"))
- self.distance_error_func = getattr(metrics, config.get("distance_error_func", "no_error"))
- self.lambd = config.get("lambda", 0)
- self.max_error = config.get("max_error", np.inf)
- self.min_error = config.get("min_error", 0)
-
- def conduct_mapping(
- self,
- station_coords1,
- station_coords2,
- grid_area_coords1,
- grid_area_coords2,
- station_metric=None,
- grid_metric=None,
- ):
- num_stations = len(station_coords1)
-
- indxs = np.empty(num_stations, dtype=int)
- indys = np.empty(num_stations, dtype=int)
- closest_indxs = np.empty(num_stations, dtype=int)
- closest_indys = np.empty(num_stations, dtype=int)
- errors = np.empty(num_stations, dtype=float)
- closest_areas = np.empty(num_stations, dtype=float)
- best_areas = np.empty(num_stations, dtype=float)
-
- for i in range(num_stations):
- station_x, station_y = station_coords1[i], station_coords2[i]
-
- # get all grid cells within max_search_distance
- closest_idx = np.nanargmin(np.abs(grid_area_coords1[:, 0] - station_x))
- closest_idy = np.nanargmin(np.abs(grid_area_coords2[0, :] - station_y))
-
- searchbox_min_x = closest_idx - self.max_search_distance
- searchbox_max_x = closest_idx + self.max_search_distance + 1
- searchbox_min_y = closest_idy - self.max_search_distance
- searchbox_max_y = closest_idy + self.max_search_distance + 1
-
- # TODO: add option to wrap domain
- searchbox_min_x = max(0, searchbox_min_x)
- searchbox_max_x = min(grid_area_coords1.shape[0], searchbox_max_x)
- searchbox_min_y = max(0, searchbox_min_y)
- searchbox_max_y = min(grid_area_coords1.shape[1], searchbox_max_y)
-
- subset_x = grid_area_coords1[searchbox_min_x:searchbox_max_x, searchbox_min_y:searchbox_max_y]
- subset_y = grid_area_coords2[searchbox_min_x:searchbox_max_x, searchbox_min_y:searchbox_max_y]
-
- shape = subset_x.shape
-
- subset_x = subset_x.flatten()
- subset_y = subset_y.flatten()
-
- subset_coords = np.stack((subset_x, subset_y), axis=0)
- coords_vec = np.array([station_x, station_y])
-
- distance_error = self.distance_error_func(coords_vec, subset_coords)
-
- if station_metric is None:
- area_error = 0
- else:
- assert grid_metric is not None
- subset_metric = grid_metric[searchbox_min_x:searchbox_max_x, searchbox_min_y:searchbox_max_y].flatten()
- area_error = self.metric_error_func(station_metric[i], subset_metric)
-
- error = area_error + self.lambd * distance_error
- area = subset_metric
-
- try:
- best_error_1d_index = np.nanargmin(error)
- min_index = np.unravel_index(best_error_1d_index, shape)
- best_error = error[best_error_1d_index]
- best_area = area[best_error_1d_index]
-
- center_offset_x = closest_idx - searchbox_min_x
- center_offset_y = closest_idy - searchbox_min_y
- subset_width = searchbox_max_y - searchbox_min_y
- closest_1d_index = center_offset_x * subset_width + center_offset_y
-
- closest_error = error[closest_1d_index]
- closest_area = area[closest_1d_index]
-
- if closest_error <= self.min_error: # if nearest cell is good enough
- indx = closest_idx
- indy = closest_idy
- best_error = closest_error
- best_area = closest_area
- elif best_error <= self.max_error:
- indx = (min_index[0] + searchbox_min_x) % grid_area_coords1.shape[0]
- indy = (min_index[1] + searchbox_min_y) % grid_area_coords1.shape[1]
- else: # if best match is still too bad, revert to closest cell
- indx = closest_idx
- indy = closest_idy
- best_error = closest_error
- best_area = closest_area
- except ValueError:
- center_offset_x = closest_idx - searchbox_min_x
- center_offset_y = closest_idy - searchbox_min_y
- subset_width = searchbox_max_y - searchbox_min_y
- closest_1d_index = center_offset_x * subset_width + center_offset_y
-
- closest_error = error[closest_1d_index]
- closest_area = area[closest_1d_index]
- indx = closest_idx
- indy = closest_idy
- best_error = closest_error
- best_area = closest_area
-
- indxs[i] = indx
- indys[i] = indy
-
- closest_indxs[i] = closest_idx
- closest_indys[i] = closest_idy
-
- errors[i] = best_error
- best_areas[i] = best_area
- closest_areas[i] = closest_area
-
- return indxs, indys, best_areas, closest_indxs, closest_indys, closest_areas, errors
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 11d64c5..0000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-site_name: Hydrological Analysis Toolkit
-site_url: https://ecmwf.github.io/hat/
-
-nav:
- - Welcome: index.md
- - Installation: installation.md
- - Usage: usage.md
- - Command Line Interface:
- - station_mapping: station_mapping.md
-
-theme: readthedocs
diff --git a/notebooks/workflow/station_mapping.ipynb b/notebooks/workflow/station_mapping.ipynb
deleted file mode 100644
index 71438db..0000000
--- a/notebooks/workflow/station_mapping.ipynb
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6908e7ba",
- "metadata": {},
- "outputs": [],
- "source": [
- "from hat.station_mapping import mapper"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8dbd8301",
- "metadata": {},
- "outputs": [],
- "source": [
- "config = {\n",
- " \"station\": {\n",
- " \"file\": \"stations.csv\",\n",
- " \"filter\": \"(StationLon >= 0) and (drainage_area_provided >= 0)\",\n",
- " \"coords\": {\n",
- " \"x\": \"StationLat\",\n",
- " \"y\": \"StationLon\"\n",
- " },\n",
- " \"metric\": \"drainage_area_provided\"\n",
- " },\n",
- " \"grid\": {\n",
- " \"source\": [\"file\", \"upstream_area.nc\"],\n",
- " \"coords\": {\n",
- " \"x\": \"lat\",\n",
- " \"y\": \"lon\"\n",
- " }\n",
- " },\n",
- " \"blacklist\": {\n",
- " \"source\": [\"file\", \"mask.nc\"]\n",
- " },\n",
- " \"parameters\": {\n",
- " \"max_search_distance\": 5,\n",
- " \"metric_error_func\": \"mape\", #options are mse, mape, mspe, mae, zero, rmse\n",
- " \"distance_error_func\": \"mse\",\n",
- " \"lambda\": 0, # error = metric_error_func + lambda * distance_error_func\n",
- " \"max_error\": 0.2, # if error>max_error, just take nearest point since we have no idea\n",
- " \"min_error\": 0 # if error[closest_point] <= min_error, don't bother searching (this is good enough)\n",
- " },\n",
- " \"output\": {\n",
- " \"file\": \"mapped_stations.csv\"\n",
- " },\n",
- " \"plot\": {\n",
- " \"error\": {\n",
- " \"file\": \"hist.html\"\n",
- " },\n",
- " \"distance\": {\n",
- " \"file\": \"heatmap.html\"\n",
- " }\n",
- " }\n",
- "}\n",
- "\n",
- "df = mapper.mapper(config)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "hat",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.0"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/pyproject.toml b/pyproject.toml
index ef05a90..0d78344 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,13 +4,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "hydro_analysis_toolkit"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
authors = [
{name = "European Centre for Medium-Range Weather Forecasts (ECMWF)", email = "software.support@ecmwf.int"},
]
maintainers = [
{name = "Corentin Carton de Wiart", email = "corentin.carton@ecmwf.int"},
- {name = "Oisín M. Morrison", email = "oisin.morrison@ecmwf.int"}
]
description = "ECMWF's Hydrological Analysis Toolkit"
license = { text = "Apache License Version 2.0" }
@@ -21,11 +20,11 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering"
]
dynamic = ["version", "readme"]
@@ -56,7 +55,7 @@ dependencies = [
[project.urls]
repository = "https://github.com/ecmwf/hat"
- documentation = "https://hydro-analysis-toolkit.readthedocs.io"
+ documentation = "https://github.com/ecmwf/hat"
issues = "https://github.com/ecmwf/hat/issues"
[project.optional-dependencies]
@@ -71,9 +70,6 @@ dependencies = [
"pre-commit"
]
-[project.scripts]
- hat-station-mapping = "hat.cli:mapper_cli"
-
# Linting settings
[tool.ruff]
line-length = 120
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_import.py b/tests/test_import.py
new file mode 100644
index 0000000..076e401
--- /dev/null
+++ b/tests/test_import.py
@@ -0,0 +1,2 @@
+def test_import():
+ import hat # noqa: F401
diff --git a/tests/test_imports.py b/tests/test_imports.py
deleted file mode 100644
index bdd65cd..0000000
--- a/tests/test_imports.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def test_mapper_import():
- from hat.station_mapping.mapper import mapper