|
24 | 24 | from pyschism.forcing.bctides.tpxo import TPXO_VELOCITY as PySCHISMTPXO_VEL |
25 | 25 | from pyschism.forcing.bctides.tides import TidalDatabase as PySCHISMTidalDatabase |
26 | 26 | from pyschism.forcing.nws import BestTrackForcing as PySCHISMBestTrackForcing |
| 27 | +from pyschism.forcing.nws.nws2 import NWS2 as PySCHISMSfluxForcing |
27 | 28 | from pyschism.forcing.base import ModelForcing as PySCHISMForcing |
28 | 29 | from pyschism.forcing import NWM as PySCHISMNWM |
29 | 30 | from pyschism.forcing.source_sink.nwm import NWMElementPairings |
|
50 | 51 | PYSCHISM_FORCINGS = { |
51 | 52 | 'Tides': 'TidalForcingJSON', |
52 | 53 | 'BestTrackForcing': 'BestTrackForcingJSON', |
53 | | - 'NationalWaterModel': 'NationalWaterModelFocringJSON' |
54 | | - # 'NWM : , |
55 | | - # 'GFS, etc.': 'ATMESHForcingJSON', |
56 | | - # 'AtmosphericMeshForcing': 'ATMESHForcingJSON', |
| 54 | + 'NationalWaterModel': 'NationalWaterModelFocringJSON', |
| 55 | + 'NWS2': 'SfluxFileForcingJSON', |
57 | 56 | } |
58 | 57 |
|
59 | | -PYSCHISM_FORCING_CLASSES = (PySCHISMTides, PySCHISMForcing, PySCHISMNWM) |
| 58 | +PYSCHISM_FORCING_CLASSES = (PySCHISMTides, PySCHISMForcing, PySCHISMNWM, PySCHISMSfluxForcing) |
60 | 59 |
|
61 | 60 |
|
62 | 61 | class TidalSource(IntEnum): |
@@ -845,7 +844,7 @@ def __init__( |
845 | 844 | kwargs['fields'].update(NationalWaterModelFocringJSON.field_types) |
846 | 845 |
|
847 | 846 | HydrologyForcingJSON.__init__(self, **kwargs) |
848 | | - FileForcingJSON.__init__(self, resource=resource, **kwargs) |
| 847 | + FileGenForcingJSON.__init__(self, resource=resource, **kwargs) |
849 | 848 |
|
850 | 849 | @property |
851 | 850 | def adcircpy_forcing(self) -> None: |
@@ -894,3 +893,99 @@ def from_pyschism(cls, forcing: PySCHISMNWM) -> 'ForcingJSON': |
894 | 893 | # sink_json=sink_json_path, |
895 | 894 | # pairing_hgrid=pairing_hgrid_path |
896 | 895 | ) |
| 896 | + |
| 897 | + |
| 898 | +class SfluxFileForcingJSON(WindForcingJSON, FileForcingJSON): |
| 899 | + |
| 900 | + name = 'Sflux' |
| 901 | + default_filename = f'configure_sfluxfiles.json' |
| 902 | + default_nws = 2 # SCHISM NWS |
| 903 | + default_sflux_1_glob = '*_1.*' |
| 904 | + default_sflux_2_glob = '*_2.*' |
| 905 | + field_types = { |
| 906 | + 'sflux_1_glob': str, |
| 907 | + 'sflux_2_glob': str, |
| 908 | + } |
| 909 | + |
| 910 | + def __init__( |
| 911 | + self, resource: PathLike, sflux_1_glob: str = None, sflux_2_glob: str = None, **kwargs, |
| 912 | + ): |
| 913 | + |
| 914 | + # TODO: Add windrot? |
| 915 | + if sflux_1_glob is None: |
| 916 | + sflux_1_glob = self.default_sflux_1_glob |
| 917 | + if sflux_2_glob is None: |
| 918 | + sflux_2_glob = self.default_sflux_2_glob |
| 919 | + |
| 920 | + if 'fields' not in kwargs: |
| 921 | + kwargs['fields'] = {} |
| 922 | + kwargs['fields'].update(SfluxFileForcingJSON.field_types) |
| 923 | + |
| 924 | + WindForcingJSON.__init__(self, **kwargs) |
| 925 | + FileForcingJSON.__init__(self, resource=resource, **kwargs) |
| 926 | + |
| 927 | + self['sflux_1_glob'] = sflux_1_glob |
| 928 | + self['sflux_2_glob'] = sflux_2_glob |
| 929 | + |
| 930 | + @property |
| 931 | + def adcircpy_forcing(self) -> None: |
| 932 | + raise NotImplementedError('ADCIRC does NOT support Sflux forcing!') |
| 933 | + |
| 934 | + @classmethod |
| 935 | + def from_adcircpy(cls, forcing: None) -> 'None': |
| 936 | + raise NotImplementedError('ADCIRC does NOT support Sflux forcing!') |
| 937 | + |
| 938 | + @property |
| 939 | + def pyschism_forcing(self) -> PySCHISMSfluxForcing: |
| 940 | + |
| 941 | + return PySCHISMSfluxForcing.read( |
| 942 | + path=self['resource'], |
| 943 | + sflux_1_glob=self['sflux_1_glob'], |
| 944 | + sflux_2_glob=self['sflux_2_glob'], |
| 945 | + ) |
| 946 | + |
| 947 | + @classmethod |
| 948 | + def from_pyschism(cls, forcing: PySCHISMSfluxForcing) -> 'ForcingJSON': |
| 949 | + sf1_paths = forcing.sflux_1.resource |
| 950 | + if isinstance(sf1_paths, (str, Path)): |
| 951 | + sf1_paths = [sf1_paths] |
| 952 | + sf2_paths = forcing.sflux_2.resource |
| 953 | + if isinstance(sf2_paths, (str, Path)): |
| 954 | + sf2_paths = [sf2_paths] |
| 955 | + |
| 956 | + path = cls._find_shared_root(*sf1_paths, *sf2_paths) |
| 957 | + sf1_pat = cls._find_simple_pattern(sf1_paths, path) |
| 958 | + sf2_pat = cls._find_simple_pattern(sf2_paths, path) |
| 959 | + |
| 960 | + return cls(resource=path, sflux_1_glob=sf1_pat, sflux_2_glob=sf2_pat,) |
| 961 | + |
| 962 | + def _find_shared_root(*paths): |
| 963 | + roots = {Path(i).parent for i in paths} |
| 964 | + while len(roots) != 0: |
| 965 | + roots = {Path(i).parent for i in roots} |
| 966 | + |
| 967 | + return roots.pop() |
| 968 | + |
| 969 | + def _find_simple_pattern(paths, root): |
| 970 | + # Note: CANNOT match complicated patterns, depth MUST be the same |
| 971 | + |
| 972 | + if len(paths) == 0: |
| 973 | + return '' |
| 974 | + |
| 975 | + relpaths = [Path(p).relative_to(root) for p in paths] |
| 976 | + if len(relpaths) == 1: |
| 977 | + return relpaths[0] |
| 978 | + |
| 979 | + exts = {rp.suffix for rp in relpaths} |
| 980 | + ext = '.*' |
| 981 | + if len(exts) == 1: |
| 982 | + ext = exts.pop() |
| 983 | + |
| 984 | + ns_parts = {len(rp.parts) - 1 for rp in relpaths} |
| 985 | + if len(ns_parts) > 1: |
| 986 | + raise ValueError('Cannot deduce pattern for different directory depths!') |
| 987 | + n_parts = ns_parts.pop() |
| 988 | + |
| 989 | + pat = ('*/' * n_parts) + '*' + ext |
| 990 | + |
| 991 | + return pat |
0 commit comments