diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7ea64e1..6570c85 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,28 +16,26 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
+ python-version: ['3.9', '3.11']
env:
OS: ${{ matrix.os }}
- PYTHON: '3.9'
+ PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v1
- name: Set up Python
- uses: actions/setup-python@master
+ uses: actions/setup-python@v5
with:
- python-version: 3.9
+ python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install click-default-group
- pip install .
+ pip install -e .[dev]
- name: Test and generate coverage report on Linux
run: |
- pip install pytest
- pip install pytest-cov
pytest --cov=./ --cov-report=xml
diff --git a/.github/workflows/paper.yml b/.github/workflows/paper.yml
index 80800ce..6f2e8a8 100644
--- a/.github/workflows/paper.yml
+++ b/.github/workflows/paper.yml
@@ -1,5 +1,13 @@
---
-on: [push]
+name: paper
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
jobs:
paper:
@@ -15,7 +23,7 @@ jobs:
# This should be the path to the paper within your repo.
paper-path: paper/paper.md
- name: Upload
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v4
with:
name: paper
# This is the output path where Pandoc will write the compiled
diff --git a/README.md b/README.md
index b110684..6f5fc5b 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Details on methodology and advanced usage are available on the [User Guide](http
Install `tethys` using pip:
-`pip install tethys-downscale`
+`pip install tethys-downscaling`
## Contributing to Tethys
diff --git a/docs/source/conf.py b/docs/source/conf.py
index b84a011..4051ccf 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -26,7 +26,7 @@
project = 'tethys'
copyright = '2021, Battelle Memorial Institute'
-author = 'Chris R. Vernon'
+author = 'Isaac Thompson, Chris R. Vernon, Hassan Niazi'
# The full version, including alpha/beta/rc tags
release = version
diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst
index 4d234e0..64a7032 100644
--- a/docs/source/getting_started.rst
+++ b/docs/source/getting_started.rst
@@ -21,7 +21,7 @@ As a prerequisite, you'll need to have `Python `_ configuration file. The options in this file correspond to the arguments passed to the :ref:`Tethys class `. Options not present in the config file will use the default. An overview is provided in the following table, with more details and examples below.
-======================== =========================================================
-Option Description
-======================== =========================================================
-:ref:`years` list of years to be included spatial downscaling
-:ref:`resolution` resolution in degrees for spatial downscaling
-:ref:`demand_type` choice between "withdrawals" (default) or "consumption"
-:ref:`perform_temporal` choice between "false" (default) or "true"
-:ref:`gcam_db` relative path to a GCAM database
-:ref:`csv` relative path to csv file containing inputs
-:ref:`output_file` name of file to write outputs to
-:ref:`downscaling_rules` mapping from water demand sectors to proxy variables
-:ref:`proxy_files` mapping of spatial proxy files to their years/variables
-:ref:`map_files` list of files containing region maps
-:ref:`temporal_methods` mapping of sector to temporal downscaling method
-:ref:`temporal_files` files that will be accessible during temporal downscaling
-======================== =========================================================
+======================================= =============================================================================
+Option Description
+======================================= =============================================================================
+:ref:`years` list of years to be included spatial downscaling
+:ref:`resolution` resolution in degrees for spatial downscaling
+:ref:`demand_type` choice between "withdrawals" (default) or "consumption"
+:ref:`perform_temporal` choice between "false" (default) or "true"
+:ref:`gcam_db` relative path to a GCAM database
+:ref:`csv` relative path to csv file containing inputs
+:ref:`output_file` name of file to write outputs to
+:ref:`downscaling_rules` mapping from water demand sectors to proxy variables
+:ref:`proxy_files` mapping of spatial proxy files to their years/variables
+:ref:`map_files` list of files containing region maps
+:ref:`temporal_methods` mapping of sector to temporal downscaling method
+:ref:`temporal_files` files that will be accessible during temporal downscaling
+:ref:`source_disaggregation` if true, share of surface water vs groundwater withdrawal will be written out
+:ref:`irrigation_conveyance_efficiency` if set, irrigation withdrawal will be divided by this number
+======================================= =============================================================================
years
@@ -272,6 +274,23 @@ Mapping of files that will be accessible to temporal downscaling methods.
CDD: data/temporal/CDD_monthly.nc
+source_disaggregation
+^^^^^^^^^^^^^^^^^^^^
+If true, share of surface water vs groundwater withdrawal will be written out to a file called gridded_runoff_shares.nc
+.. code-block:: yaml
+
+ source_disaggregation: true
+
+
+irrigation_conveyance_efficiency
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+If set, irrigation withdrawal will be divided by this number, representing GCAM's irrigation conveyance efficiency
+
+.. code-block:: yaml
+
+ irrigation_conveyance_efficiency: 0.829937
+
+
Generalization
--------------
**tethys** was developed with consideration for GCAM's breakdown of water demand, but was designed to be as flexible as possible with support for user-specified downscaling configurations.
diff --git a/requirements.txt b/requirements.txt
index 57c8033..dd9e8c9 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
PyYAML>=6.0
-gcamreader>=1.2.5
+gcamreader>=1.4.0
numpy>=1.22
pandas>=1.2.4
netCDF4>=1.6
@@ -7,8 +7,6 @@ xarray>=2022.09.0
rioxarray>=0.12.4
tqdm>=4.66.2
matplotlib>=3.8.3
-dask[complete]>=2022.12.1
-bokeh!=3.0.*,>=2.4.2
-geopandas>=0.14.3
-dask_geopandas>=0.3.1
-hvplot>=0.9.2
+dask>=2022.12.1
+distributed>=2022.12.1
+setuptools<81
diff --git a/setup.py b/setup.py
index 2ea637e..63f95ee 100644
--- a/setup.py
+++ b/setup.py
@@ -23,35 +23,37 @@ def get_requirements():
url='https://github.com/JGCRI/tethys',
packages=find_packages(),
license='BSD-2-Clause',
- author='Isaac Thompson, Chris Vernon',
+ author='Isaac Thompson, Chris Vernon, Hassan Niazi',
python_requires='>=3.9, <4',
include_package_data=True,
install_requires=[
'PyYAML>=6.0',
- 'gcamreader>=1.2.5',
+ 'gcamreader==1.4.0',
'numpy>=1.22',
'pandas>=1.2.4',
'netCDF4>=1.6',
- 'dask[complete]>=2022.12.1',
- 'dask_geopandas>=0.3.1',
+ 'dask==2024.8.0',
+ 'distributed==2024.8.0',
'xarray>=2022.09.0',
'rioxarray>=0.12.4',
'tqdm>=4.66.2',
'matplotlib>=3.8.3',
+ 'setuptools<81',
'bokeh!=3.0.*,>=2.4.2',
'geopandas>=0.14.3',
+ 'dask_geopandas>=0.3.1',
'hvplot>=0.9.2',
],
extras_require={
'dev': [
- 'build~=0.5.1',
- 'nbsphinx~=0.8.6',
- 'setuptools~=57.0.0',
- 'sphinx~=4.0.2',
- 'sphinx-panels~=0.6.0',
- 'sphinx-rtd-theme~=0.5.2',
- 'sphinx-mathjax-offline~=0.0.1',
- 'twine>=3.4.1',
+ 'build==0.10.0',
+ 'nbsphinx==0.8.12',
+ 'sphinx==4.5.0',
+ 'sphinx-panels==0.6.0',
+ 'sphinx-rtd-theme==0.5.2',
+ 'sphinx-mathjax-offline==0.0.2',
+ 'pytest>=8.3.0',
+ 'pytest-cov>=5.0.0',
'click-default-group>=1.2.4',
]
}
diff --git a/tethys/__init__.py b/tethys/__init__.py
index a88df2d..3fab7a4 100644
--- a/tethys/__init__.py
+++ b/tethys/__init__.py
@@ -3,4 +3,4 @@
from .utils.spatial import InputViewer
-__version__ = "2.1"
+__version__ = "2.2.0"
diff --git a/tethys/datareader/easy_query.py b/tethys/datareader/easy_query.py
index 723578e..68c660b 100644
--- a/tethys/datareader/easy_query.py
+++ b/tethys/datareader/easy_query.py
@@ -1,7 +1,7 @@
import gcamreader
-def easy_query(variable, year_axis=None, **kwargs):
+def easy_query(variable, year_axis=None, replace_filters=False, **kwargs):
"""Build a query for a GCAM database
kwargs act as filters on nodes, eg. "sector='Beef'" is converted to the xpath "[@type='sector' and @name='Beef']"
@@ -24,8 +24,11 @@ def easy_query(variable, year_axis=None, **kwargs):
:return: gcamreader Query object
"""
- filters = dict(sector=None) # match nodes where @type='sector' by default
- filters.update(kwargs) # update the filters with user-provided kwargs
+ if replace_filters:
+ filters = kwargs
+ else:
+ filters = dict(sector=None) # match nodes where @type='sector' by default
+ filters.update(kwargs) # update the filters with user-provided kwargs
# filters are separated by "//*" to match any descendant
xpath = "*" + "//*".join(handle_filter(k, v) for k, v in filters.items()) + f"//{variable}/node()"
diff --git a/tethys/datareader/regional.py b/tethys/datareader/regional.py
index ea6b9a6..ddd618f 100644
--- a/tethys/datareader/regional.py
+++ b/tethys/datareader/regional.py
@@ -41,6 +41,19 @@ def load_region_data(gcam_db, sectors, demand_type='withdrawals'):
return df
+def extract_resource_name(x):
+ """Removes '_water withdrawals' or '_water consumption' from GCAM sector or resource name.
+
+ :param x: resource name string.
+ :return: cleaned resource name without suffix.
+ """
+ if x.endswith('_water withdrawals'):
+ return x.removesuffix('_water withdrawals')
+ if x.endswith('_water consumption'):
+ return x.removesuffix('_water consumption')
+ return x
+
+
def extract_basin_name(x):
"""Maps 'water_td_irr_basin_C' to '_basin', and water_td_elec_C to ''"""
if x.startswith('water_td_irr_'):
diff --git a/tethys/model.py b/tethys/model.py
index 54f6365..dbaf361 100644
--- a/tethys/model.py
+++ b/tethys/model.py
@@ -19,6 +19,7 @@
from tethys.datareader.gridded import load_file, interp_helper
from tethys.datareader.maps import load_region_map
+from tethys.utils.source_disaggregation import get_source_shares
class Tethys:
"""Model wrapper for Tethys"""
@@ -30,6 +31,7 @@ def __init__(
resolution=0.125,
bounds=None,
demand_type='withdrawals',
+ source_disaggregation=None,
gcam_db=None,
csv=None,
output_dir=None,
@@ -37,15 +39,19 @@ def __init__(
downscaling_rules=None,
proxy_files=None,
map_files=None,
- temporal_config=None
+ temporal_config=None,
+ perform_temporal=False,
+ temporal_methods=None,
+ temporal_files=None,
):
"""Parameters can be specified in a YAML file or passed directly, with the config file taking precedence
:param config_file: path to YAML configuration file containing these parameters
- :param years: list of years to be included spatial downscaling
+ :param years: list of years to be included for spatial downscaling
:param resolution: resolution in degrees for spatial downscaling
:param bounds: list [lat_min, lat_max, lon_min, lon_max] to crop to
:param demand_type: choice between “withdrawals” (default) or “consumption”
+ :param source_disaggregation: decide whether to disaggregate water demand by source (runoff, groundwater, desal)
:param gcam_db: relative path to a GCAM database
:param csv: relative path to csv file containing inputs
:param output_dir: directory to write outputs to
@@ -53,6 +59,9 @@ def __init__(
:param downscaling_rules: mapping from water demand sectors to proxy variables
:param proxy_files: mapping of spatial proxy files to their years/variables
:param map_files: list of files containing region maps
+ :param perform_temporal: if True, apply temporal downscaling
+ :param temporal_methods: legacy mapping of sector to method names in tethys.tdmethods
+ :param temporal_files: legacy mapping of file aliases used by temporal methods
:param temporal_config: mapping of sector to temporal downscaling method and arguments
"""
self.root = None
@@ -62,6 +71,7 @@ def __init__(
self.resolution = resolution
self.bounds = bounds
self.demand_type = demand_type
+ self.source_disaggregation = source_disaggregation
# GCAM database info
self.gcam_db = gcam_db
@@ -78,6 +88,9 @@ def __init__(
self.proxy_files = proxy_files
self.map_files = map_files
+ self.perform_temporal = perform_temporal
+ self.temporal_methods = temporal_methods
+ self.temporal_files = temporal_files
self.temporal_config = temporal_config
# data we'll load or generate later
@@ -85,6 +98,11 @@ def __init__(
self.proxies = None
self.inputs = None
self.outputs = None
+ self.shares = None
+ self.griddedshares = None
+ self.disaggregated_sw = None
+ self.disaggregated_gw = None
+ self.irrigation_conveyance_efficiency = None
# settings in YAML override settings passed directly to __init__
if config_file is not None:
@@ -103,6 +121,47 @@ def __init__(
self.output_dir = os.path.join(self.root, self.output_dir)
self._parse_proxy_files()
+ self._normalize_temporal_config()
+
+ def _resolve_path(self, value):
+ if value is None:
+ return None
+ return value if os.path.isabs(value) else os.path.join(self.root, value)
+
+ # maps method name → {kwarg_name: temporal_files key}
+ _TEMPORAL_FILE_MAP = {
+ 'domestic': {'tasfile': 'tas', 'rfile': 'domr'},
+ 'electricity': {'hddfile': 'hdd', 'cddfile': 'cdd'},
+ 'irrigation': {'irrfile': 'irr'},
+ 'weights': {'weightfile': 'weight'},
+ }
+
+ def _normalize_temporal_config(self):
+ if self.temporal_config is not None:
+ for cfg in self.temporal_config.values():
+ cfg['kwargs'] = {k: self._resolve_path(v) if k.endswith('file') or k == 'gcam_db' else v
+ for k, v in cfg.get('kwargs', {}).items()}
+ return
+
+ if not self.perform_temporal and not self.temporal_methods and not self.temporal_files:
+ return
+
+ files = self.temporal_files or {}
+ methods = self.temporal_methods or {
+ 'Municipal': 'domestic', 'Electricity': 'electricity', 'Irrigation': 'irrigation'}
+
+ config = {}
+ for sector, method in methods.items():
+ kwargs = {k: self._resolve_path(files.get(v))
+ for k, v in self._TEMPORAL_FILE_MAP.get(method, {}).items()}
+ if method in ('electricity', 'irrigation') and self.map_files:
+ kwargs['regionfile'] = self._resolve_path(self.map_files[0])
+ if method == 'electricity':
+ kwargs['gcam_db'] = self._resolve_path(self.gcam_db)
+ config[sector] = {'method': method,
+ 'kwargs': {k: v for k, v in kwargs.items() if v is not None}}
+
+ self.temporal_config = config or None
def _parse_proxy_files(self):
"""Handle several shorthand expressions in the proxy catalog"""
@@ -198,13 +257,45 @@ def downscale(self, distribution, inputs, region_masks):
return out
+ def disaggregate_source(self):
+ """Disaggregate water demand by source (runoff, groundwater, desal)"""
+
+ print('Disaggregating Source')
+
+ # initialize source shares object
+ get_shares = get_source_shares(gcam_db=self.gcam_db, demand_type=self.demand_type,
+ region_masks=self.region_masks, years=self.years)
+
+ # query and calculate shares
+ shares_df = get_shares.calculate_shares()
+
+ # apply shares to the gridded region masks
+ gridded_shares = get_shares.generate_gridded_shares(shares_df)
+
+ return gridded_shares
+
+
def run_model(self):
self.outputs = xr.Dataset()
+ self.griddedshares = xr.Dataset()
+ self.disaggregated_sw = xr.Dataset()
+ self.disaggregated_gw = xr.Dataset()
self._load_proxies()
self._load_region_masks()
self._load_inputs()
+ # disaggregate water supply source
+ # disaggregate water supply source
+ if self.source_disaggregation:
+ gshares = xr.Dataset(self.disaggregate_source())
+
+ # store the disaggregated results
+ self.griddedshares = gshares
+ if self.output_dir is not None:
+ filename = os.path.join(self.output_dir, f'gridded_runoff_shares.nc')
+ gshares['runoff'].to_netcdf(filename)
+
for supersector, rules in self.downscaling_rules.items():
print(f'Downscaling {supersector}')
@@ -244,6 +335,10 @@ def run_model(self):
# in a lot of cases this could be optimized by solving the intersections at region scale first,
# then downscaling once, but harder to implement, especially if differing regions are not subsets
+ # handle irrigation withdrawal conveyance efficiency if set
+ if (supersector=='Irrigation') and (self.demand_type=='withdrawals') and (self.irrigation_conveyance_efficiency is not None):
+ downscaled = downscaled / self.irrigation_conveyance_efficiency
+
# write spatial downscaling outputs
if self.output_dir is not None:
filename = os.path.join(self.output_dir, f'{supersector}_{self.demand_type}.nc')
@@ -261,7 +356,7 @@ def run_model(self):
years = range(self.years[0], self.years[-1] + 1)
kwargs = self.temporal_config[supersector]['kwargs']
distribution = temporal_distribution(years=years, resolution=self.resolution,
- bounds=self.bounds, **kwargs)
+ bounds=self.bounds, **kwargs)
else:
# fall back to uniform distribution
distribution = xr.DataArray(np.full(12, 1/12, np.float32), coords=dict(month=range(1, 13)))
@@ -272,7 +367,7 @@ def run_model(self):
if self.output_dir is not None:
filename = os.path.join(self.output_dir, f'{supersector}_{self.demand_type}_monthly.nc')
encoding = {sector: {'zlib': True, 'complevel': 5} for sector in downscaled.sector.data}
- downscaled.to_dataset(dim='sector').to_netcdf(filename, encoding=encoding)
+ downscaled.to_dataset(dim='sector').compute().to_netcdf(filename, encoding=encoding)
downscaled = xr.open_dataset(filename).to_array(dim='sector') # hopefully this keeps dask happy
self.outputs.update(downscaled.to_dataset(dim='sector'))
diff --git a/tethys/tdmethods/domestic.py b/tethys/tdmethods/domestic.py
index 443683b..01d7d8b 100644
--- a/tethys/tdmethods/domestic.py
+++ b/tethys/tdmethods/domestic.py
@@ -2,9 +2,15 @@
from tethys.datareader.gridded import load_file
-def temporal_distribution(years, resolution, tasfile, rfile, tasvar='tas', rvar='amplitude', bounds=None):
+def temporal_distribution(years, resolution=None, tasfile=None, rfile=None, tasvar='tas', rvar='amplitude', bounds=None):
"""Temporal downscaling for domestic water demand using algorithm from Wada et al. (2011)"""
+ if hasattr(years, 'temporal_config'):
+ model = years
+ cfg = dict((model.temporal_config or {}).get('Municipal', {}).get('kwargs', {}) or {})
+ cfg.setdefault('bounds', model.bounds)
+ return temporal_distribution(range(model.years[0], model.years[-1] + 1), model.resolution, **cfg)
+
tas = load_file(tasfile, resolution, years, bounds=bounds, regrid_method='intensive', variables=[tasvar])[tasvar]
amplitude = load_file(rfile, resolution, years, bounds=bounds, regrid_method='label', variables=[rvar])[rvar]
diff --git a/tethys/tdmethods/electricity.py b/tethys/tdmethods/electricity.py
index e8aedcb..4a92d0d 100644
--- a/tethys/tdmethods/electricity.py
+++ b/tethys/tdmethods/electricity.py
@@ -6,10 +6,16 @@
from tethys.datareader.maps import load_region_map
-def temporal_distribution(years, resolution, hddfile, cddfile, regionfile, gcam_db, hddvar='hdd', cddvar='cdd',
- bounds=None):
+def temporal_distribution(years, resolution=None, hddfile=None, cddfile=None, regionfile=None, gcam_db=None,
+ hddvar='hdd', cddvar='cdd', bounds=None):
"""Temporal downscaling of water demand for electricity generation using algorithm from Voisin et al. (2013)"""
+ if hasattr(years, 'temporal_config'):
+ model = years
+ cfg = dict((model.temporal_config or {}).get('Electricity', {}).get('kwargs', {}) or {})
+ cfg.setdefault('bounds', model.bounds)
+ return temporal_distribution(range(model.years[0], model.years[-1] + 1), model.resolution, **cfg)
+
# get weights of heating/cooling/other by location and time
regions = load_region_map(regionfile, masks=True, target_resolution=resolution, bounds=bounds)
weights = elec_sector_weights(gcam_db)
diff --git a/tethys/tdmethods/irrigation.py b/tethys/tdmethods/irrigation.py
index 4915bad..59781fd 100644
--- a/tethys/tdmethods/irrigation.py
+++ b/tethys/tdmethods/irrigation.py
@@ -2,9 +2,15 @@
from tethys.datareader.maps import load_region_map
-def temporal_distribution(years, resolution, regionfile, irrfile, irrvar='pirrww', bounds=None):
+def temporal_distribution(years, resolution=None, regionfile=None, irrfile=None, irrvar='pirrww', bounds=None):
"""Temporal downscaling of irrigation water demand"""
+ if hasattr(years, 'temporal_config'):
+ model = years
+ cfg = dict((model.temporal_config or {}).get('Irrigation', {}).get('kwargs', {}) or {})
+ cfg.setdefault('bounds', model.bounds)
+ return temporal_distribution(range(model.years[0], model.years[-1] + 1), model.resolution, **cfg)
+
irr = load_file(irrfile, resolution, years, bounds=bounds, regrid_method='label', variables=[irrvar])[irrvar]
region_masks = load_region_map(regionfile, masks=True, target_resolution=resolution, bounds=bounds)
diff --git a/tethys/tdmethods/weights.py b/tethys/tdmethods/weights.py
index bc65799..4af7e33 100644
--- a/tethys/tdmethods/weights.py
+++ b/tethys/tdmethods/weights.py
@@ -1,10 +1,16 @@
from tethys.datareader.gridded import load_file
-def temporal_distribution(years, resolution, weightfile, weightvar='weight', regrid_method='intensive',
+def temporal_distribution(years, resolution=None, weightfile=None, weightvar='weight', regrid_method='intensive',
prenormalized=False, bounds=None):
"""Load a monthly distribution from filename"""
+ if hasattr(years, 'temporal_config'):
+ model = years
+ cfg = dict((model.temporal_config or {}).get('Weights', {}).get('kwargs', {}) or {})
+ cfg.setdefault('bounds', model.bounds)
+ return temporal_distribution(range(model.years[0], model.years[-1] + 1), model.resolution, **cfg)
+
distribution = load_file(weightfile, resolution, years, bounds=bounds, regrid_method=regrid_method,
variables=[weightvar])[weightvar]
if prenormalized is False:
diff --git a/tethys/utils/install_supplement.py b/tethys/utils/install_supplement.py
index 793d191..d7aef66 100644
--- a/tethys/utils/install_supplement.py
+++ b/tethys/utils/install_supplement.py
@@ -17,6 +17,8 @@
'2.0.0': 'https://zenodo.org/record/7569652/files/example.zip?download=1',
'2.0.1': 'https://zenodo.org/record/7569652/files/example.zip?download=1',
'2.0.2': 'https://zenodo.org/record/7569652/files/example.zip?download=1',
+ '2.1.0': 'https://zenodo.org/record/7569652/files/example.zip?download=1',
+ '2.2.0': 'https://zenodo.org/record/7569652/files/example.zip?download=1'
}
@@ -47,7 +49,7 @@ def get_example_data(
example_data_directory = default_download_dir
# get the current version of tethys that is installed
- current_version = importlib.metadata.version('tethys')
+ current_version = importlib.metadata.version('tethys-downscaling')
try:
url = DATA_VERSION_URLS[current_version]
diff --git a/tethys/utils/region_to_id_mapping.py b/tethys/utils/region_to_id_mapping.py
index 3992f56..1457917 100644
--- a/tethys/utils/region_to_id_mapping.py
+++ b/tethys/utils/region_to_id_mapping.py
@@ -507,5 +507,479 @@
'Papua New Guinea Coast': 173,
'Tasmania': 211,
'Solomon Islands': 176
+ },
+ "basin_name_mapping": {
+ 'Arctic Ocean Islands': 'ArcticIsl',
+ 'Northwest Territories': 'NWTerr',
+ 'Siberia North Coast': 'SiberiaN',
+ 'Siberia West Coast': 'SiberiaW',
+ 'Kara Sea Coast': 'KaraSea',
+ 'Lena': 'LenaR',
+ 'Pacific and Arctic Coast': 'PacArctic',
+ 'Scandinavia North Coast': 'ScndnvN',
+ 'Russia Barents Sea Coast': 'BarentsSea',
+ 'Mackenzie': 'Mackenzie',
+ 'Iceland': 'Iceland',
+ 'Sweden': 'Sweden',
+ 'Finland': 'Finland',
+ 'Northern Dvina': 'DvinaRN',
+ 'Hudson Bay Coast': 'HudsonBay',
+ 'Scotland': 'Scotland',
+ 'Neva': 'NevaR',
+ 'Volga': 'VolgaR',
+ 'Atlantic Ocean Seaboard': 'CanAtl',
+ 'Baltic Sea Coast': 'BalticSea',
+ 'Denmark Germany Coast': 'DnkGrmCst',
+ 'Narva': 'NarvaR',
+ 'Saskatchewan Nelson': 'NelsonR',
+ 'Ireland': 'Ireland',
+ 'Daugava': 'DaugavaR',
+ 'England and Wales': 'EngWales',
+ 'Fraser': 'FraserR',
+ 'Ems Weser': 'EmsWeserR',
+ 'Oder': 'OderR',
+ 'Wisla': 'WislaR',
+ 'Elbe': 'ElbeR',
+ 'Rhine': 'RhineR',
+ 'Poland Coast': 'PolandCst',
+ 'Churchill': 'ChurchillR',
+ 'Neman': 'NemanR',
+ 'Scheldt': 'ScheldtR',
+ 'Russia South East Coast': 'RusCstSE',
+ 'Ural': 'UralR',
+ 'Dnieper': 'DnieperR',
+ 'St Lawrence': 'StLwrncR',
+ 'France West Coast': 'FranceCstW',
+ 'Gobi Interior': 'Gobi',
+ 'Amur': 'AmurR',
+ 'Loire': 'LoireR',
+ 'Caspian Sea Coast': 'CaspianNE',
+ 'Seine': 'SeineR',
+ 'Black Sea North Coast': 'BlackSeaN',
+ 'Yenisei': 'YeniseiR',
+ 'Dniester': 'DniesterR',
+ 'Italy East Coast': 'ItalyCstE',
+ 'Japan': 'Japan',
+ 'Caspian Sea East Coast': 'CaspianE',
+ 'Don': 'DonR',
+ 'Danube': 'DanubeR',
+ 'Adriatic Sea Greece Black Sea Coast': 'AdrBlkSea',
+ 'Ob': 'ObR',
+ 'Po': 'PoR',
+ 'Amu Darya': 'AmuDaryaR',
+ 'Italy West Coast': 'ItalyCstW',
+ 'Spain Portugal Atlantic Coast': 'IberiaCst',
+ 'France South Coast': 'FranceCstS',
+ 'Rhone': 'RhoneR',
+ 'Mediterranean Sea Islands': 'MeditIsl',
+ 'Gironde': 'Gironde',
+ 'North and South Korea': 'Korea',
+ 'Bo Hai Korean Bay North Coast': 'BoHai',
+ 'Spain South and East Coast': 'SpainCstSE',
+ 'Lake Balkash': 'LBalkash',
+ 'Tiber': 'TiberR',
+ 'Black Sea South Coast': 'BlackSeaS',
+ 'Tagus': 'TagusR',
+ 'Caspian Sea South West Coast': 'CaspianSW',
+ 'Ebro': 'EbroR',
+ 'Douro': 'DouroR',
+ 'Mediterranean Sea East Coast': 'MeditE',
+ 'Syr Darya': 'SyrDaryaR',
+ 'Ziya He Interior': 'ZiyaHe',
+ 'China Coast': 'ChinaCst',
+ 'Huang He': 'HuangHeR',
+ 'Mediterranean South Coast': 'MeditS',
+ 'Guadiana': 'GuadianaR',
+ 'Central Iran': 'Iran',
+ 'Guadalquivir': 'GuadalqR',
+ 'Tigris Euphrates': 'TigrEuphR',
+ 'Tarim Interior': 'Tarim',
+ 'Africa North West Coast': 'AfrCstNW',
+ 'Nile': 'NileR',
+ 'Persian Gulf Coast': 'PersianGulf',
+ 'Indus': 'IndusR',
+ 'Farahrud': 'FarahrudR',
+ 'Baja California': 'MexBaja',
+ 'Plateau of Tibet Interior': 'Tibet',
+ 'Red Sea East Coast': 'RedSeaE',
+ 'Arabian Peninsula': 'ArabianP',
+ 'Dead Sea': 'DeadSea',
+ 'Mexico Northwest Coast': 'MexCstNW',
+ 'Helmand': 'Helmand',
+ 'Sinai Peninsula': 'SinaiP',
+ 'Eastern Jordan Syria': 'EJrdnSyr',
+ 'Africa Red Sea Gulf of Aden Coast': 'AfrCstNE',
+ 'Caribbean': 'Caribbean',
+ 'HamuniMashkel': 'HamuMashR',
+ 'Taiwan': 'Taiwan',
+ 'Arabian Sea Coast': 'ArabianSea',
+ 'North Gulf': 'MexGulf',
+ 'Yangtze': 'Yangtze',
+ 'Sabarmati': 'SabarmatiR',
+ 'Xun Jiang': 'XunJiang',
+ 'Hong (Red River)': 'Hong',
+ 'Ganges Bramaputra': 'GangesR',
+ 'Yucatan Peninsula': 'YucatanP',
+ 'South China Sea Coast': 'SChinaSea',
+ 'Mahi': 'MahiR',
+ 'Mexico Interior': 'MexInt',
+ 'Pacific Central Coast': 'MexCstW',
+ 'Bay of Bengal North East Coast': 'BengalBay',
+ 'Tapti': 'TaptiR',
+ 'Yasai': 'BengalW',
+ 'Philippines': 'Phlppns',
+ 'Brahmani': 'BrahmaniR',
+ 'North Marina Islands and Guam': 'Guam',
+ 'Mahanadi': 'MahanadiR',
+ 'Godavari': 'GodavariR',
+ 'Hainan': 'Hainan',
+ 'Mekong': 'Mekong',
+ 'Viet Nam Coast': 'VietnamCst',
+ 'Salween': 'Salween',
+ 'India North East Coast': 'IndCstNE',
+ 'India West Coast': 'IndCstW',
+ 'Papaloapan': 'Papaloapan',
+ 'Rio Lerma': 'RioLerma',
+ 'Rio Verde': 'RioVerde',
+ 'Grijalva Usumacinta': 'GrijUsuR',
+ 'Rio Balsas': 'RioBalsas',
+ 'Southern Central America': 'CntAmer',
+ 'Isthmus of Tehuantepec': 'Tehuantpc',
+ 'Irrawaddy': 'IrrawaddyR',
+ 'Sittaung': 'SittaungR',
+ 'Peninsula Malaysia': 'MalaysiaP',
+ 'Krishna': 'KrishnaR',
+ 'Andaman Nicobar Islands': 'AdnNicIsl',
+ 'Africa West Coast': 'AfrCstW',
+ 'Caribbean Coast': 'SAmerCstN',
+ 'Africa North Interior': 'AfrIntN',
+ 'India East Coast': 'IndCstE',
+ 'Chao Phraya': 'ChaoPhrR',
+ 'Pennar': 'PennarR',
+ 'Gulf of Thailand Coast': 'ThaiGulf',
+ 'Niger': 'NigerR',
+ 'Micronesia': 'Micronesia',
+ 'Lake Chad': 'LChad',
+ 'Senegal': 'SenegalR',
+ 'Cauvery': 'CauveryR',
+ 'Sri Lanka': 'SriLanka',
+ 'India South Coast': 'IndCstS',
+ 'Orinoco': 'OrinocoR',
+ 'Colombia Ecuador Pacific Coast': 'ColEcuaCst',
+ 'Palau and East Indonesia': 'IdnE',
+ 'North Borneo Coast': 'BorneoCstN',
+ 'Volta': 'VoltaR',
+ 'Northeast South America South Atlantic Coast': 'SAmerCstNE',
+ 'Gulf of Guinea': 'GuineaGulf',
+ 'Sumatra': 'Sumatra',
+ 'Sulawesi': 'Sulawesi',
+ 'Kalimantan': 'Kalimantan',
+ 'Magdalena': 'MagdalenaR',
+ 'Irian Jaya Coast': 'IrianJaya',
+ 'Amazon': 'AmazonR',
+ 'South Chile Pacific Coast': 'ChileCstS',
+ 'Shebelli Juba': 'ShebJubR',
+ 'Africa East Central Coast': 'AfrCstE',
+ 'North Brazil South Atlantic Coast': 'BrzCstN',
+ 'Papua New Guinea Coast': 'PapuaCst',
+ 'Tocantins': 'TocantinsR',
+ 'Java Timor': 'JavaTimor',
+ 'Solomon Islands': 'SolomonIsl',
+ 'Madagascar': 'Madagascar',
+ 'Sepik': 'SepikR',
+ 'Rift Valley': 'RiftValley',
+ 'Peru Pacific Coast': 'PeruCst',
+ 'Fly': 'FlyR',
+ 'Angola Coast': 'AngolaCst',
+ 'Congo': 'CongoR',
+ 'Australia North Coast': 'AusCstN',
+ 'South Pacific Islands': 'NewCaledn',
+ 'East Brazil South Atlantic Coast': 'BrzCstE',
+ 'Parnaiba': 'ParnaibaR',
+ 'Zambezi': 'ZambeziR',
+ 'Australia East Coast': 'AusCstE',
+ 'Africa Indian Ocean Coast': 'AfrCstSE',
+ 'Australia West Coast': 'AusCstW',
+ 'Sao Francisco': 'SaoFrancR',
+ 'Australia Interior': 'AusInt',
+ 'Orange': 'OrangeR',
+ 'Uruguay Brazil South Atlantic Coast': 'BrzCstS',
+ 'Namibia Coast': 'AfrCstSW',
+ 'Africa South Interior': 'AfrIntS',
+ 'South Africa South Coast': 'AfrCstS',
+ 'Limpopo': 'LimpopoR',
+ 'La Puna Region': 'LaPuna',
+ 'New Zealand': 'NewZealand',
+ 'Australia South Coast': 'AusCstS',
+ 'Mar Chiquita': 'MarChiq',
+ 'South Africa West Coast': 'AfrCstSSW',
+ 'Salinas Grandes': 'Salinas',
+ 'La Plata': 'RioLaPlata',
+ 'North Chile Pacific Coast': 'ChileCstN',
+ 'Murray Darling': 'MurrayDrlg',
+ 'Pampas Region': 'Pampas',
+ 'North Argentina South Atlantic Coast': 'ArgCstN',
+ 'Tasmania': 'Tasmania',
+ 'South America Colorado': 'ArgColoR',
+ 'Negro': 'NegroR',
+ 'Central Patagonia Highlands': 'Patagonia',
+ 'South Argentina South Atlantic Coast': 'ArgCstS',
+ 'Antarctica': 'Antarctica',
+ 'California River Basin': 'California',
+ 'Upper Mississippi Basin': 'MissppRN',
+ 'Lower Mississippi River Basin': 'MissppRS',
+ 'Upper Colorado River Basin': 'UsaColoRN',
+ 'Lower Colorado River Basin': 'UsaColoRS',
+ 'Great Basin': 'GreatBasin',
+ 'Missouri River Basin': 'MissouriR',
+ 'Arkansas White Red Basin': 'ArkWhtRedR',
+ 'Texas Gulf Coast Basin': 'TexasCst',
+ 'South Atlantic Gulf Basin': 'UsaCstSE',
+ 'Great Lakes Basin': 'GreatLakes',
+ 'Ohio River Basin': 'OhioR',
+ 'Pacific Northwest Basin': 'UsaPacNW',
+ 'Tennessee River Basin': 'TennR',
+ 'Rio Grande River Basin': 'RioGrande',
+ 'New England Basin': 'UsaCstNE',
+ 'Mid Atlantic Basin': 'UsaCstE',
+ 'Hawaii': 'Hawaii',
+ 'Narmada': 'NarmadaR'
+ },
+ "basin_name_mapping_im3": {
+ 'Arctic Ocean Islands': 'ArcticIsl',
+ 'Northwest Territories': 'NWTerr',
+ 'Siberia North Coast': 'SiberiaN',
+ 'Siberia West Coast': 'SiberiaW',
+ 'Kara Sea Coast': 'KaraSea',
+ 'Lena': 'LenaR',
+ 'Pacific and Arctic Coast': 'PacArctic',
+ 'Scandinavia North Coast': 'ScndnvN',
+ 'Russia Barents Sea Coast': 'BarentsSea',
+ 'Mackenzie': 'Mackenzie',
+ 'Iceland': 'Iceland',
+ 'Sweden': 'Sweden',
+ 'Finland': 'Finland',
+ 'Northern Dvina': 'DvinaRN',
+ 'Hudson Bay Coast': 'HudsonBay',
+ 'Scotland': 'Scotland',
+ 'Neva': 'NevaR',
+ 'Volga': 'VolgaR',
+ 'Atlantic Ocean Seaboard': 'CanAtl',
+ 'Baltic Sea Coast': 'BalticSea',
+ 'Denmark Germany Coast': 'DnkGrmCst',
+ 'Narva': 'NarvaR',
+ 'Saskatchewan Nelson': 'NelsonR',
+ 'Ireland': 'Ireland',
+ 'Daugava': 'DaugavaR',
+ 'England and Wales': 'EngWales',
+ 'Fraser': 'FraserR',
+ 'Ems Weser': 'EmsWeserR',
+ 'Oder': 'OderR',
+ 'Wisla': 'WislaR',
+ 'Elbe': 'ElbeR',
+ 'Rhine': 'RhineR',
+ 'Poland Coast': 'PolandCst',
+ 'Churchill': 'ChurchillR',
+ 'Neman': 'NemanR',
+ 'Scheldt': 'ScheldtR',
+ 'Russia South East Coast': 'RusCstSE',
+ 'Ural': 'UralR',
+ 'Dnieper': 'DnieperR',
+ 'St Lawrence': 'StLwrncR',
+ 'France West Coast': 'FranceCstW',
+ 'Gobi Interior': 'Gobi',
+ 'Amur': 'AmurR',
+ 'Loire': 'LoireR',
+ 'Caspian Sea Coast': 'CaspianNE',
+ 'Seine': 'SeineR',
+ 'Black Sea North Coast': 'BlackSeaN',
+ 'Yenisey': 'YeniseiR',
+ 'Dniester': 'DniesterR',
+ 'Italy East Coast': 'ItalyCstE',
+ 'Japan': 'Japan',
+ 'Caspian Sea East Coast': 'CaspianE',
+ 'Don': 'DonR',
+ 'Danube': 'DanubeR',
+ 'Adriatic Sea Greece Black Sea Coast': 'AdrBlkSea',
+ 'Ob': 'ObR',
+ 'Po': 'PoR',
+ 'Amu Darya': 'AmuDaryaR',
+ 'Italy West Coast': 'ItalyCstW',
+ 'Spain Portugal Atlantic Coast': 'IberiaCst',
+ 'France South Coast': 'FranceCstS',
+ 'Rhone': 'RhoneR',
+ 'Mediterranean Sea Islands': 'MeditIsl',
+ 'Gironde': 'Gironde',
+ 'North and South Korea': 'Korea',
+ 'Bo Hai Korean Bay North Coast': 'BoHai',
+ 'Spain South and East Coast': 'SpainCstSE',
+ 'Lake Balkash': 'LBalkash',
+ 'Tiber': 'TiberR',
+ 'Black Sea South Coast': 'BlackSeaS',
+ 'Tagus': 'TagusR',
+ 'Caspian Sea South West Coast': 'CaspianSW',
+ 'Ebro': 'EbroR',
+ 'Douro': 'DouroR',
+ 'Mediterranean Sea East Coast': 'MeditE',
+ 'Syr Darya': 'SyrDaryaR',
+ 'Ziya He Interior': 'ZiyaHe',
+ 'China Coast': 'ChinaCst',
+ 'Huang He': 'HuangHeR',
+ 'Mediterranean South Coast': 'MeditS',
+ 'Guadiana': 'GuadianaR',
+ 'Central Iran': 'Iran',
+ 'Guadalquivir': 'GuadalqR',
+ 'Tigris Euphrates': 'TigrEuphR',
+ 'Tarim Interior': 'Tarim',
+ 'Africa North West Coast': 'AfrCstNW',
+ 'Nile': 'NileR',
+ 'Persian Gulf Coast': 'PersianGulf',
+ 'Indus': 'IndusR',
+ 'Farahrud': 'FarahrudR',
+ 'Baja California': 'MexBaja',
+ 'Plateau of Tibet Interior': 'Tibet',
+ 'Red Sea East Coast': 'RedSeaE',
+ 'Arabian Peninsula': 'ArabianP',
+ 'Dead Sea': 'DeadSea',
+ 'Mexico Northwest Coast': 'MexCstNW',
+ 'Helmand': 'Helmand',
+ 'Sinai Peninsula': 'SinaiP',
+ 'Eastern Jordan Syria': 'EJrdnSyr',
+ 'Africa Red Sea Gulf of Aden Coast': 'AfrCstNE',
+ 'Caribbean': 'Caribbean',
+ 'Hamun i Mashkel': 'HamuMashR',
+ 'Taiwan': 'Taiwan',
+ 'Arabian Sea Coast': 'ArabianSea',
+ 'North Gulf': 'MexGulf',
+ 'Yangtze': 'Yangtze',
+ 'Sabarmati': 'SabarmatiR',
+ 'Xun Jiang': 'XunJiang',
+ 'Hong Red River': 'Hong',
+ 'Ganges Bramaputra': 'GangesR',
+ 'Yucatan Peninsula': 'YucatanP',
+ 'South China Sea Coast': 'SChinaSea',
+ 'Mahi': 'MahiR',
+ 'Mexico Interior': 'MexInt',
+ 'Pacific Central Coast': 'MexCstW',
+ 'Bay of Bengal North East Coast': 'BengalBay',
+ 'Tapti': 'TaptiR',
+ 'Yasai': 'BengalW',
+ 'Philippines': 'Phlppns',
+ 'Brahamani': 'BrahmaniR',
+ 'North Marina Islands and Guam': 'Guam',
+ 'Mahandi': 'MahanadiR',
+ 'Godavari': 'GodavariR',
+ 'Hainan': 'Hainan',
+ 'Mekong': 'Mekong',
+ 'Viet Nam Coast': 'VietnamCst',
+ 'Salween': 'Salween',
+ 'India North East Coast': 'IndCstNE',
+ 'India West Coast': 'IndCstW',
+ 'Papaloapan': 'Papaloapan',
+ 'Rio Lerma': 'RioLerma',
+ 'Rio Verde': 'RioVerde',
+ 'Grijalva Usumacinta': 'GrijUsuR',
+ 'Rio Balsas': 'RioBalsas',
+ 'Southern Central America': 'CntAmer',
+ 'Isthmus of Tehuantepec': 'Tehuantpc',
+ 'Irrawaddy': 'IrrawaddyR',
+ 'Sittang': 'SittaungR',
+ 'Peninsula Malaysia': 'MalaysiaP',
+ 'Krishna': 'KrishnaR',
+ 'Andaman Nicobar Islands': 'AdnNicIsl',
+ 'Africa West Coast': 'AfrCstW',
+ 'Caribbean Coast': 'SAmerCstN',
+ 'Africa North Interior': 'AfrIntN',
+ 'India East Coast': 'IndCstE',
+ 'Chao Phraya': 'ChaoPhrR',
+ 'Pennar': 'PennarR',
+ 'Gulf of Thailand Coast': 'ThaiGulf',
+ 'Niger': 'NigerR',
+ 'Micronesia': 'Micronesia',
+ 'Lake Chad': 'LChad',
+ 'Senegal': 'SenegalR',
+ 'Cauvery': 'CauveryR',
+ 'Sri Lanka': 'SriLanka',
+ 'India South Coast': 'IndCstS',
+ 'Orinoco': 'OrinocoR',
+ 'Colombia Ecuador Pacific Coast': 'ColEcuaCst',
+ 'Palau and East Indonesia': 'IdnE',
+ 'North Borneo Coast': 'BorneoCstN',
+ 'Volta': 'VoltaR',
+ 'Northeast South America South Atlantic Coast': 'SAmerCstNE',
+ 'Gulf of Guinea': 'GuineaGulf',
+ 'Sumatra': 'Sumatra',
+ 'Sulawesi': 'Sulawesi',
+ 'Kalimantan': 'Kalimantan',
+ 'Magdalena': 'MagdalenaR',
+ 'Irian Jaya Coast': 'IrianJaya',
+ 'Amazon': 'AmazonR',
+ 'South Chile Pacific Coast': 'ChileCstS',
+ 'Shebelli Juba': 'ShebJubR',
+ 'Africa East Central Coast': 'AfrCstE',
+ 'North Brazil South Atlantic Coast': 'BrzCstN',
+ 'Papua New Guinea Coast': 'PapuaCst',
+ 'Tocantins': 'TocantinsR',
+ 'Java Timor': 'JavaTimor',
+ 'Solomon Islands': 'SolomonIsl',
+ 'Madasgacar': 'Madagascar',
+ 'Sepik': 'SepikR',
+ 'Rift Valley': 'RiftValley',
+ 'Peru Pacific Coast': 'PeruCst',
+ 'Fly': 'FlyR',
+ 'Angola Coast': 'AngolaCst',
+ 'Congo': 'CongoR',
+ 'Australia North Coast': 'AusCstN',
+ 'South Pacific Islands': 'NewCaledn',
+ 'East Brazil South Atlantic Coast': 'BrzCstE',
+ 'Parnaiba': 'ParnaibaR',
+ 'Zambezi': 'ZambeziR',
+ 'Australia East Coast': 'AusCstE',
+ 'Africa Indian Ocean Coast': 'AfrCstSE',
+ 'Australia West Coast': 'AusCstW',
+ 'Sao Francisco': 'SaoFrancR',
+ 'Australia Interior': 'AusInt',
+ 'Orange': 'OrangeR',
+ 'Uruguay Brazil South Atlantic Coast': 'BrzCstS',
+ 'Namibia Coast': 'AfrCstSW',
+ 'Africa South Interior': 'AfrIntS',
+ 'South Africa South Coast': 'AfrCstS',
+ 'Limpopo': 'LimpopoR',
+ 'La Puna Region': 'LaPuna',
+ 'New Zealand': 'NewZealand',
+ 'Australia South Coast': 'AusCstS',
+ 'Mar Chiquita': 'MarChiq',
+ 'South Africa West Coast': 'AfrCstSSW',
+ 'Salinas Grandes': 'Salinas',
+ 'La Plata': 'RioLaPlata',
+ 'North Chile Pacific Coast': 'ChileCstN',
+ 'Murray Darling': 'MurrayDrlg',
+ 'Pampas Region': 'Pampas',
+ 'North Argentina South Atlantic Coast': 'ArgCstN',
+ 'Tasmania': 'Tasmania',
+ 'South America Colorado': 'ArgColoR',
+ 'Negro': 'NegroR',
+ 'Central Patagonia Highlands': 'Patagonia',
+ 'South Argentina South Atlantic Coast': 'ArgCstS',
+ 'Antarctica': 'Antarctica',
+ 'California River': 'California',
+ 'Upper Mississippi': 'MissppRN',
+ 'Lower Mississippi River': 'MissppRS',
+ 'Upper Colorado River': 'UsaColoRN',
+ 'Lower Colorado River': 'UsaColoRS',
+ 'Great': 'GreatBasin',
+ 'Missouri River': 'MissouriR',
+ 'Arkansas White Red': 'ArkWhtRedR',
+ 'Texas Gulf Coast': 'TexasCst',
+ 'South Atlantic Gulf': 'UsaCstSE',
+ 'Great Lakes': 'GreatLakes',
+ 'Ohio River': 'OhioR',
+ 'Pacific Northwest': 'UsaPacNW',
+ 'Tennessee River': 'TennR',
+ 'Rio Grande River': 'RioGrande',
+ 'New England': 'UsaCstNE',
+ 'Mid Atlantic': 'UsaCstE',
+ 'Hawaii': 'Hawaii',
+ 'Narmada': 'NarmadaR'
}
}
diff --git a/tethys/utils/source_disaggregation.py b/tethys/utils/source_disaggregation.py
new file mode 100644
index 0000000..cb0976e
--- /dev/null
+++ b/tethys/utils/source_disaggregation.py
@@ -0,0 +1,110 @@
+import os
+import pandas as pd
+import xarray as xr
+
+import gcamreader
+
+from tethys.datareader.easy_query import easy_query
+from tethys.datareader.regional import extract_resource_name
+from tethys.utils.region_to_id_mapping import name_to_id_mapping
+
+
+class get_source_shares:
+ def __init__(self, gcam_db, basin_name_mapping="basin_name_mapping_im3", demand_type='withdrawals', region_masks=None, years=None):
+ """
+ Initialize get_source_shares with parameters for GCAM database and basin mapping.
+
+ :param gcam_db: Path to the GCAM database file.
+ :param basin_name_mapping: Basin name mapping dictionary, can change to other mapping in name_to_id_mapping.
+ :param demand_type: Type of demand, either 'withdrawals' or 'consumption'.
+ :param region_masks: xarray DataArray containing region masks.
+ :param years: List of years to be included in the shares.
+ """
+ if region_masks is None:
+ raise ValueError("region_masks is required to compute gridded shares")
+ if years is None:
+ raise ValueError("years is required to compute gridded shares")
+
+ dbpath, dbfile = os.path.split(gcam_db)
+ self.conn = gcamreader.LocalDBConn(dbpath, dbfile)
+ self.basin_name_mapping = basin_name_mapping
+ self.demand_type = demand_type
+ self.region_masks = region_masks
+ self.years = list(years)
+
+ def calculate_shares(self):
+ """
+ Query the GCAM database, process water data for the specified demand type,
+ and calculate runoff and groundwater shares by region and year.
+
+ :return: Processed DataFrame with calculated shares.
+ """
+ # query GCAM database for water used by source for the configured demand type
+ if self.demand_type not in ('withdrawals', 'consumption'):
+ raise ValueError(f"Unsupported demand_type: {self.demand_type!r}")
+ query_type = f"*_water {self.demand_type}"
+ shares_df = self.conn.runQuery(easy_query('production', replace_filters=True, resource=query_type))
+ # extract and clean resource names (e.g., remove '_water withdrawals')
+ shares_df['resource'] = shares_df['resource'].apply(extract_resource_name)
+
+ # rest of the mappings should be fine, old GCAM versions have typos in basin names
+ if self.basin_name_mapping == "basin_name_mapping_im3":
+ shares_df['resource'] = shares_df['resource'].str.replace('_', ' ').str.replace('-', ' ') # replace '_' and '-' with spaces
+
+ # map resource names to short basin names and concatenate region and resource to work with region_masks
+ shares_df['resourcemap'] = shares_df['resource'].map(name_to_id_mapping[self.basin_name_mapping])
+ shares_df['region_resourcemap'] = shares_df['region'] + '_' + shares_df['resourcemap']
+
+ # calculate shares of runoff and groundwater
+ shares_df['share'] = shares_df.groupby(
+ ['scenario', 'region', 'region_resourcemap', 'year']
+ )['value'].transform(lambda x: x / x.sum() if x.sum() != 0 else 0)
+
+ # capture basins that are mostly belonging to other countries
+ if self.basin_name_mapping == 'basin_name_mapping_im3':
+ shares_df = shares_df.replace({'region_resourcemap': {
+ 'Canada_FraserR': 'USA_FraserR',
+ 'Canada_GreatLakes': 'USA_GreatLakes',
+ 'Mexico_MexCstNW': 'USA_MexCstNW',
+ 'Canada_NelsonR': 'USA_NelsonR',
+ }})
+
+ # filter shares based on the region masks from the config file, otherwise all regions would be included
+ shares_df = shares_df[shares_df['region_resourcemap'].isin(self.region_masks.region.values)]
+
+ return shares_df
+
+ def generate_gridded_shares(self, shares_df):
+ """
+ Generate gridded shares by subresource (e.g., runoff, groundwater).
+
+ :param shares_df: DataFrame containing shares calculated by `calculate_shares`.
+ :return: Dictionary of gridded shares by subresource.
+ """
+ gridded_shares_by_subresource = {}
+ for subresource in shares_df.subresource.unique():
+ gridded_shares_by_year = []
+ for year in self.years:
+ gridded_shares = None
+ for basin in shares_df.region_resourcemap.unique():
+ sel = shares_df[
+ (shares_df['year'] == year) &
+ (shares_df['region_resourcemap'] == basin) &
+ (shares_df['subresource'] == subresource)
+ ]
+ share_val = 0.0 if sel.empty else float(sel.iloc[0].share)
+ gridded_shares = xr.where(
+ self.region_masks.sel(region=basin).load(),
+ share_val,
+ 0 if gridded_shares is None else gridded_shares,
+ keep_attrs=False,
+ )
+ gridded_shares_by_year.append(gridded_shares)
+
+ # Stack years for the subresource
+ gridded_shares_by_subresource[subresource] = xr.concat(
+ gridded_shares_by_year,
+ dim=pd.Index(self.years, name="year"),
+ ).rename('share')
+
+ return gridded_shares_by_subresource