Skip to content

Commit 6b29b53

Browse files
committed
Python tools are now smart about longitude format.
* Require that user specify whether longitude is in [-180, 180] format (i.e., centered around Prime Meridian) or [0, 360] format (i.e., centered around International Date Line). * Specified as --lon-type, either 180 or 360 (respectively) * Resolves #2017 * Resolves #3001
1 parent 01a0fb7 commit 6b29b53

23 files changed

+627
-371
lines changed

python/ctsm/args_utils.py

-24
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import logging
88
import argparse
99

10-
from ctsm.config_utils import lon_range_0_to_360
11-
1210
logger = logging.getLogger(__name__)
1311

1412

@@ -29,25 +27,3 @@ def plat_type(plat):
2927
if plat_out < -90 or plat_out > 90:
3028
raise argparse.ArgumentTypeError("ERROR: Latitude should be between -90 and 90.")
3129
return plat_out
32-
33-
34-
def plon_type(plon):
35-
"""
36-
Function to define lon type for the parser and
37-
convert negative longitudes to 0-360 and
38-
raise error if lon is not between -180 and 360.
39-
40-
Args:
41-
plon (str): longitude
42-
Raises:
43-
Error (ArgumentTypeError): when longitude is <-180 and >360.
44-
Returns:
45-
plon_out (float): converted longitude between 0 and 360
46-
"""
47-
plon_float = float(plon)
48-
if plon_float < -180 or plon_float > 360:
49-
raise argparse.ArgumentTypeError(
50-
"ERROR: Longitude should be between 0 and 360 or -180 and 180."
51-
)
52-
plon_out = lon_range_0_to_360(plon_float)
53-
return plon_out

python/ctsm/config_utils.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,21 @@
1818
_CONFIG_UNSET = "UNSET"
1919

2020

21-
def lon_range_0_to_360(lon_in):
21+
def convert_lon_0to360(lon_in):
2222
"""
2323
Description
2424
-----------
25-
Restrict longitude to 0 to 360 when given as -180 to 180.
25+
Convert a longitude from [-180, 180] format (i.e., centered around Prime Meridian) to [0, 360]
26+
format (i.e., centered around International Date Line).
2627
"""
27-
if -180 <= lon_in < 0:
28-
raise NotImplementedError(
29-
"A negative longitude suggests you input longitudes in the range [-180, 0)---"
30-
"i.e., centered around the Prime Meridian. This code requires longitudes in the "
31-
"range [0, 360)---i.e., starting at the International Date Line."
32-
)
33-
if not (0 <= lon_in <= 360 or lon_in is None):
34-
errmsg = "lon_in needs to be in the range 0 to 360"
35-
abort(errmsg)
36-
lon_out = lon_in
28+
if not -180 <= lon_in <= 180:
29+
raise ValueError(f"lon_in needs to be in the range [-180, 180]: {lon_in}")
30+
lon_out = 180 + lon_in
31+
logger.info(
32+
"Converting longitude from [-180, 180] to [0, 360]: %s to %s",
33+
str(lon_in),
34+
str(lon_out),
35+
)
3736

3837
return lon_out
3938

python/ctsm/modify_input_files/fsurdat_modifier.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,13 @@ def read_cfg_required_basic_opts(config, section, cfg_path):
498498
file_path=cfg_path,
499499
convert_to_type=float,
500500
)
501+
lon_type = get_config_value(
502+
config=config,
503+
section=section,
504+
item="lon_type",
505+
file_path=cfg_path,
506+
convert_to_type=int,
507+
)
501508

502509
landmask_file = get_config_value(
503510
config=config,
@@ -513,7 +520,16 @@ def read_cfg_required_basic_opts(config, section, cfg_path):
513520
lon_dimname = get_config_value(
514521
config=config, section=section, item="lon_dimname", file_path=cfg_path, can_be_unset=True
515522
)
516-
return (lnd_lat_1, lnd_lat_2, lnd_lon_1, lnd_lon_2, landmask_file, lat_dimname, lon_dimname)
523+
return (
524+
lnd_lat_1,
525+
lnd_lat_2,
526+
lnd_lon_1,
527+
lnd_lon_2,
528+
landmask_file,
529+
lat_dimname,
530+
lon_dimname,
531+
lon_type,
532+
)
517533

518534

519535
def fsurdat_modifier(parser):
@@ -568,6 +584,7 @@ def fsurdat_modifier(parser):
568584
landmask_file,
569585
lat_dimname,
570586
lon_dimname,
587+
lon_type,
571588
) = read_cfg_required_basic_opts(config, section, cfg_path)
572589
# Create ModifyFsurdat object
573590
modify_fsurdat = ModifyFsurdat.init_from_file(
@@ -579,6 +596,7 @@ def fsurdat_modifier(parser):
579596
landmask_file,
580597
lat_dimname,
581598
lon_dimname,
599+
lon_type,
582600
)
583601

584602
# Read control information about the optional sections

python/ctsm/modify_input_files/mesh_mask_modifier.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ def mesh_mask_modifier(cfg_path):
6666
lon_varname = get_config_value(
6767
config=config, section=section, item="lon_varname", file_path=cfg_path
6868
)
69+
lon_type = get_config_value(config=config, section=section, item="lon_type", file_path=cfg_path)
6970

7071
# Create ModifyMeshMask object
7172
modify_mesh_mask = ModifyMeshMask.init_from_file(
72-
mesh_mask_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname
73+
mesh_mask_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
7374
)
7475

7576
# If output file exists, abort before starting work

python/ctsm/modify_input_files/modify_fsurdat.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from ctsm.utils import abort, update_metadata
1616
from ctsm.git_utils import get_ctsm_git_short_hash
17-
from ctsm.config_utils import lon_range_0_to_360
17+
from ctsm.config_utils import convert_lon_0to360
1818

1919
logger = logging.getLogger(__name__)
2020

@@ -26,7 +26,7 @@ class ModifyFsurdat:
2626
"""
2727

2828
def __init__(
29-
self, my_data, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname
29+
self, my_data, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname, lon_type
3030
):
3131

3232
self.numurbl = 3 # Number of urban density types
@@ -36,13 +36,15 @@ def __init__(
3636
else:
3737
abort("numurbl is not a dimension on the input surface dataset file and needs to be")
3838

39+
self.lon_type = lon_type
3940
self.rectangle = self._get_rectangle(
4041
lon_1=lon_1,
4142
lon_2=lon_2,
4243
lat_1=lat_1,
4344
lat_2=lat_2,
4445
longxy=self.file.LONGXY,
4546
latixy=self.file.LATIXY,
47+
lon_type=self.lon_type,
4648
)
4749

4850
if landmask_file is not None:
@@ -72,23 +74,37 @@ def __init__(
7274

7375
@classmethod
7476
def init_from_file(
75-
cls, fsurdat_in, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname
77+
cls,
78+
fsurdat_in,
79+
lon_1,
80+
lon_2,
81+
lat_1,
82+
lat_2,
83+
landmask_file,
84+
lat_dimname,
85+
lon_dimname,
86+
lon_type,
7687
):
7788
"""Initialize a ModifyFsurdat object from file fsurdat_in"""
7889
logger.info("Opening fsurdat_in file to be modified: %s", fsurdat_in)
7990
my_file = xr.open_dataset(fsurdat_in)
80-
return cls(my_file, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname)
91+
return cls(
92+
my_file, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname, lon_type
93+
)
8194

8295
@staticmethod
83-
def _get_rectangle(lon_1, lon_2, lat_1, lat_2, longxy, latixy):
96+
def _get_rectangle(lon_1, lon_2, lat_1, lat_2, longxy, latixy, lon_type):
8497
"""
8598
Description
8699
-----------
87100
"""
88101

89102
# ensure that lon ranges 0-360 in case user entered -180 to 180
90-
lon_1 = lon_range_0_to_360(lon_1)
91-
lon_2 = lon_range_0_to_360(lon_2)
103+
if lon_type == 180:
104+
lon_1 = convert_lon_0to360(lon_1)
105+
lon_2 = convert_lon_0to360(lon_2)
106+
elif lon_type != 360:
107+
raise ValueError("lon_type must be either 180 or 360")
92108

93109
# determine the rectangle(s)
94110
# TODO This is not really "nearest" for the edges but isel didn't work

python/ctsm/modify_input_files/modify_mesh_mask.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import xarray as xr
1313

1414
from ctsm.utils import abort
15-
from ctsm.config_utils import lon_range_0_to_360
15+
from ctsm.config_utils import convert_lon_0to360
1616

1717
logger = logging.getLogger(__name__)
1818

@@ -29,7 +29,9 @@ class ModifyMeshMask:
2929
# /glade/work/slevis/git/mksurfdata_toolchain/tools/modify_input_files ...
3030
# ... /islas_examples/modify_fsurdat/fill_indian_ocean/
3131
# Read mod_lnd_props here only for consistency checks
32-
def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname):
32+
def __init__(
33+
self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
34+
):
3335

3436
self.file = my_data
3537

@@ -46,6 +48,7 @@ def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname
4648
self.lonvar = self._landmask_file[lon_varname][..., :]
4749
self.lsmlat = self._landmask_file.dims[lat_dimname]
4850
self.lsmlon = self._landmask_file.dims[lon_dimname]
51+
self.lon_type = lon_type
4952

5053
lonvar_first = self.lonvar[..., 0].data.max()
5154
lonvar_last = self.lonvar[..., -1].data.max()
@@ -75,12 +78,14 @@ def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname
7578

7679
@classmethod
7780
def init_from_file(
78-
cls, file_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname
81+
cls, file_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
7982
):
8083
"""Initialize a ModifyMeshMask object from file_in"""
8184
logger.info("Opening file to be modified: %s", file_in)
8285
my_file = xr.open_dataset(file_in)
83-
return cls(my_file, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname)
86+
return cls(
87+
my_file, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
88+
)
8489

8590
def set_mesh_mask(self, var):
8691
"""
@@ -134,13 +139,13 @@ def set_mesh_mask(self, var):
134139
+ f"{len(self.latvar.sizes)}"
135140
)
136141
abort(errmsg)
137-
# ensure lon range of 0-360 rather than -180 to 180
138-
lonvar_scalar = lon_range_0_to_360(lonvar_scalar)
139142
# lon and lat from the mesh file
140143
lat_mesh = float(self.file["centerCoords"][ncount, 1])
141144
lon_mesh = float(self.file["centerCoords"][ncount, 0])
142145
# ensure lon range of 0-360 rather than -180 to 180
143-
lon_mesh = lon_range_0_to_360(lon_mesh)
146+
if self.lon_type == 180:
147+
lonvar_scalar = convert_lon_0to360(lonvar_scalar)
148+
lon_mesh = convert_lon_0to360(lon_mesh)
144149

145150
errmsg = (
146151
"Must be equal: "

python/ctsm/site_and_regional/regional_case.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,20 @@ def check_region_lons(self):
154154
Check for the regional lon bounds
155155
"""
156156
if self.lon1 >= self.lon2:
157-
err_msg = """
158-
\n
159-
ERROR: lon1 is bigger than lon2.
160-
lon1 points to the westernmost longitude of the region. {}
161-
lon2 points to the easternmost longitude of the region. {}
162-
Please make sure lon1 is smaller than lon2.
163-
164-
Please note that if longitude in -180-0, the code automatically
165-
convert it to 0-360.
166-
""".format(
167-
self.lon1, self.lon2
168-
)
169-
raise argparse.ArgumentTypeError(err_msg)
157+
pass
158+
# err_msg = """
159+
# \n
160+
# ERROR: lon1 is bigger than lon2.
161+
# lon1 points to the westernmost longitude of the region. {}
162+
# lon2 points to the easternmost longitude of the region. {}
163+
# Please make sure lon1 is smaller than lon2.
164+
165+
# Please note that if longitude in -180-0, the code automatically
166+
# convert it to 0-360.
167+
# """.format(
168+
# self.lon1, self.lon2
169+
# )
170+
# raise argparse.ArgumentTypeError(err_msg)
170171

171172
def check_region_lats(self):
172173
"""

0 commit comments

Comments
 (0)