wrfrun 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wrfrun/__init__.py +3 -0
- wrfrun/core/__init__.py +5 -0
- wrfrun/core/base.py +680 -0
- wrfrun/core/config.py +717 -0
- wrfrun/core/error.py +80 -0
- wrfrun/core/replay.py +113 -0
- wrfrun/core/server.py +212 -0
- wrfrun/data.py +418 -0
- wrfrun/extension/__init__.py +1 -0
- wrfrun/extension/littler/__init__.py +1 -0
- wrfrun/extension/littler/utils.py +599 -0
- wrfrun/extension/utils.py +66 -0
- wrfrun/model/__init__.py +7 -0
- wrfrun/model/base.py +14 -0
- wrfrun/model/plot.py +54 -0
- wrfrun/model/utils.py +34 -0
- wrfrun/model/wrf/__init__.py +6 -0
- wrfrun/model/wrf/_metgrid.py +71 -0
- wrfrun/model/wrf/_ndown.py +39 -0
- wrfrun/model/wrf/core.py +805 -0
- wrfrun/model/wrf/exec_wrap.py +101 -0
- wrfrun/model/wrf/geodata.py +301 -0
- wrfrun/model/wrf/namelist.py +377 -0
- wrfrun/model/wrf/scheme.py +311 -0
- wrfrun/model/wrf/vtable.py +65 -0
- wrfrun/pbs.py +86 -0
- wrfrun/plot/__init__.py +1 -0
- wrfrun/plot/wps.py +188 -0
- wrfrun/res/__init__.py +22 -0
- wrfrun/res/config.toml.template +136 -0
- wrfrun/res/extension/plotgrids.ncl +216 -0
- wrfrun/res/job_scheduler/pbs.template +6 -0
- wrfrun/res/job_scheduler/slurm.template +6 -0
- wrfrun/res/namelist/namelist.input.da_wrfvar.template +261 -0
- wrfrun/res/namelist/namelist.input.dfi.template +260 -0
- wrfrun/res/namelist/namelist.input.real.template +256 -0
- wrfrun/res/namelist/namelist.input.wrf.template +256 -0
- wrfrun/res/namelist/namelist.wps.template +44 -0
- wrfrun/res/namelist/parame.in.template +11 -0
- wrfrun/res/run.sh.template +16 -0
- wrfrun/run.py +264 -0
- wrfrun/utils.py +257 -0
- wrfrun/workspace.py +88 -0
- wrfrun-0.1.7.dist-info/METADATA +67 -0
- wrfrun-0.1.7.dist-info/RECORD +46 -0
- wrfrun-0.1.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from os.path import basename
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from wrfrun import WRFRUNConfig
|
|
5
|
+
from .core import DFI, GeoGrid, MetGrid, Real, UnGrib, WRF, NDown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def geogrid(geogrid_tbl_file: Union[str, None] = None):
|
|
9
|
+
"""
|
|
10
|
+
Interface to execute geogrid.exe.
|
|
11
|
+
|
|
12
|
+
:param geogrid_tbl_file: Custom GEOGRID.TBL file path. Defaults to None.
|
|
13
|
+
"""
|
|
14
|
+
GeoGrid(geogrid_tbl_file, WRFRUNConfig.get_core_num())()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def ungrib(vtable_file: Union[str, None] = None, input_data_path: Optional[str] = None, prefix="FILE"):
|
|
18
|
+
"""
|
|
19
|
+
Interface to execute ungrib.exe.
|
|
20
|
+
|
|
21
|
+
:param vtable_file: Vtable file used to run ungrib. Defaults to None.
|
|
22
|
+
:type vtable_file: str
|
|
23
|
+
:param input_data_path: Directory path of the input data. If None, ``wrfrun`` will read its value from the config file.
|
|
24
|
+
:type input_data_path: str
|
|
25
|
+
:param prefix: Prefix of ungrib output.
|
|
26
|
+
:type prefix: str
|
|
27
|
+
"""
|
|
28
|
+
prefix = basename(prefix)
|
|
29
|
+
WRFRUNConfig.set_ungrib_out_prefix(prefix)
|
|
30
|
+
|
|
31
|
+
UnGrib(vtable_file, input_data_path)()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def metgrid(geogrid_data_path: Optional[str] = None, ungrib_data_path: Optional[str] = None, fg_names: Union[str, list[str]] = "FILE"):
|
|
35
|
+
"""
|
|
36
|
+
Interface to execute metgrid.exe.
|
|
37
|
+
|
|
38
|
+
:param geogrid_data_path: Directory path of outputs from geogrid.exe. If None, tries to use the output path specified by config file.
|
|
39
|
+
:type geogrid_data_path: str
|
|
40
|
+
:param ungrib_data_path: Directory path of outputs from ungrib.exe. If None, tries to use the output path specified by config file.
|
|
41
|
+
:type ungrib_data_path: str
|
|
42
|
+
:param fg_names: Set ``fg_name`` of metgrid, a single prefix string or a string list.
|
|
43
|
+
:type fg_names: str | list
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(fg_names, str):
|
|
46
|
+
fg_names = [fg_names, ]
|
|
47
|
+
fg_names = [basename(x) for x in fg_names]
|
|
48
|
+
WRFRUNConfig.set_metgrid_fg_names(fg_names)
|
|
49
|
+
|
|
50
|
+
MetGrid(geogrid_data_path, ungrib_data_path, WRFRUNConfig.get_core_num())()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def real(metgrid_data_path: Union[str, None] = None):
|
|
54
|
+
"""
|
|
55
|
+
Interface to execute real.exe.
|
|
56
|
+
|
|
57
|
+
:param metgrid_data_path: The path store output from metgrid.exe. If it is None, the default output path will be used.
|
|
58
|
+
"""
|
|
59
|
+
Real(metgrid_data_path, WRFRUNConfig.get_core_num())()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def wrf(input_file_dir_path: Union[str, None] = None, restart_file_dir_path: Optional[str] = None, save_restarts=False):
|
|
63
|
+
"""
|
|
64
|
+
Interface to execute wrf.exe.
|
|
65
|
+
|
|
66
|
+
:param input_file_dir_path: The path store input data which will be feed into wrf.exe. Defaults to None.
|
|
67
|
+
:param restart_file_dir_path: The path store WRF restart files. This parameter will be ignored if ``restart=False`` in your config.
|
|
68
|
+
:param save_restarts: Also save restart files to the output directory.
|
|
69
|
+
"""
|
|
70
|
+
WRF(input_file_dir_path, restart_file_dir_path, save_restarts, WRFRUNConfig.get_core_num())()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dfi(input_file_dir_path: Optional[str] = None, update_real_output=True):
|
|
74
|
+
"""
|
|
75
|
+
Execute "wrf.exe" to run DFI.
|
|
76
|
+
|
|
77
|
+
:param input_file_dir_path: Path of the directory that stores input data for "wrf.exe".
|
|
78
|
+
:type input_file_dir_path: str
|
|
79
|
+
:param update_real_output: If update the corresponding file in real.exe output directory.
|
|
80
|
+
:type update_real_output: bool
|
|
81
|
+
"""
|
|
82
|
+
DFI(input_file_dir_path, update_real_output, WRFRUNConfig.get_core_num())()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def ndown(wrfout_file_path: str, real_output_dir_path: Optional[str] = None, update_namelist=True):
|
|
86
|
+
"""
|
|
87
|
+
Execute "ndown.exe".
|
|
88
|
+
|
|
89
|
+
:param wrfout_file_path: wrfout file path.
|
|
90
|
+
:type wrfout_file_path: str
|
|
91
|
+
:param real_output_dir_path: Path of the directory that contains output of "real.exe".
|
|
92
|
+
:type real_output_dir_path: str
|
|
93
|
+
:param update_namelist: If update wrf's namelist for the final integral.
|
|
94
|
+
:type update_namelist: bool
|
|
95
|
+
:return:
|
|
96
|
+
:rtype:
|
|
97
|
+
"""
|
|
98
|
+
NDown(wrfout_file_path, real_output_dir_path, update_namelist, WRFRUNConfig.get_core_num())()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = ["geogrid", "ungrib", "metgrid", "real", "wrf", "dfi", "ndown"]
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from os import listdir
|
|
2
|
+
from os.path import exists
|
|
3
|
+
from typing import OrderedDict, Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from xarray import DataArray
|
|
7
|
+
|
|
8
|
+
from wrfrun.core import WRFRUNConfig
|
|
9
|
+
from wrfrun.utils import logger
|
|
10
|
+
|
|
11
|
+
# for example: 00001-00200.00201-00400
|
|
12
|
+
DATA_NAME_TEMPLATE = "{}-{}.{}-{}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_data_type(wordsize: int) -> type:
|
|
16
|
+
"""Get data type based on wordsize value in index file.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
wordsize (int): Wordsize in index file.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
type: NumPy value type.
|
|
23
|
+
"""
|
|
24
|
+
# define map dict
|
|
25
|
+
map_dict = {
|
|
26
|
+
1: np.int8,
|
|
27
|
+
2: np.int16,
|
|
28
|
+
4: np.int32
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return map_dict[wordsize]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_clip_area(index_area: tuple[int, int, int, int], row_num: int, col_num: int, tile_x: int, tile_y: int) -> tuple[int, int, int, int]:
|
|
35
|
+
"""Get clip area.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
index_area (tuple[int, int, int, int]): Full area index.
|
|
39
|
+
row_num (int): Row number of the tile.
|
|
40
|
+
col_num (int): Column number of the file.
|
|
41
|
+
tile_x (int): X size of the tile.
|
|
42
|
+
tile_y (int): Y size of the tile.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
tuple[int, int, int, int]: Clip area.
|
|
46
|
+
"""
|
|
47
|
+
# calculate tile area
|
|
48
|
+
tile_area = (
|
|
49
|
+
col_num * tile_x + 1,
|
|
50
|
+
col_num * tile_x + tile_x,
|
|
51
|
+
row_num * tile_y + 1,
|
|
52
|
+
row_num * tile_y + tile_y,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# generate clip area
|
|
56
|
+
clip_area = (
|
|
57
|
+
0 if index_area[0] <= tile_area[0] else index_area[0] % tile_x - 1,
|
|
58
|
+
tile_x if index_area[1] >= tile_area[1] else index_area[1] % tile_x - 1,
|
|
59
|
+
0 if index_area[2] <= tile_area[2] else index_area[2] % tile_y - 1,
|
|
60
|
+
tile_y if index_area[3] >= tile_area[3] else index_area[3] % tile_y - 1,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return clip_area
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_geographical_data_index(index_path: str) -> OrderedDict:
|
|
67
|
+
"""Read geographical data index file.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
index_path (str): Index file path.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
dict: Info stored in dict.
|
|
74
|
+
"""
|
|
75
|
+
# since the index file is very similar to fortran namelist file,
|
|
76
|
+
# we can manually add "&index" and "/" and parse it as a namelist
|
|
77
|
+
# temp file store path
|
|
78
|
+
temp_file = f"{WRFRUNConfig.WRFRUN_TEMP_PATH}/geogrid_data.index"
|
|
79
|
+
temp_file = WRFRUNConfig.parse_resource_uri(temp_file)
|
|
80
|
+
|
|
81
|
+
# open file and add header and tail
|
|
82
|
+
with open(index_path, "r") as _index_file:
|
|
83
|
+
with open(temp_file, "w") as _temp_file:
|
|
84
|
+
|
|
85
|
+
_temp_file.write("&index\n")
|
|
86
|
+
_temp_file.write(_index_file.read())
|
|
87
|
+
_temp_file.write("/")
|
|
88
|
+
|
|
89
|
+
# read index
|
|
90
|
+
WRFRUNConfig.read_namelist(temp_file, "geog_static_data")
|
|
91
|
+
|
|
92
|
+
return WRFRUNConfig.get_namelist("geog_static_data")["index"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def parse_geographical_data_file(file_path: str, wordsize: int, endian: str, tile_shape: tuple[int, ...],
|
|
96
|
+
area: Union[tuple[int, ...], None] = None, miss_value: Union[int, float, None] = None) -> np.ndarray:
|
|
97
|
+
"""Read geographical data file.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
file_path (str): File path.
|
|
101
|
+
wordsize (int): How many bytes are used to store value in data file.
|
|
102
|
+
endian (str): "big" or "little".
|
|
103
|
+
tile_shape (tuple[int, ...]): The raw shape of the tile. Can be 2D or 3D.
|
|
104
|
+
area (Union[tuple[int, ...], None], optional): The range (x_start, x_stop, y_start, y_stop, ...) of data you want to read. Defaults to None.
|
|
105
|
+
miss_value (Union[int, float, None], optional): The value which represents NaN. Defaults to None.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
np.ndarray: NumPy array object.
|
|
109
|
+
"""
|
|
110
|
+
# get data type
|
|
111
|
+
data_type = _get_data_type(wordsize)
|
|
112
|
+
|
|
113
|
+
# read data
|
|
114
|
+
data = np.fromfile(file_path, dtype=data_type)
|
|
115
|
+
|
|
116
|
+
# swap byte if need
|
|
117
|
+
if endian == "big":
|
|
118
|
+
data = data.byteswap()
|
|
119
|
+
|
|
120
|
+
# reshape
|
|
121
|
+
data = data.reshape(tile_shape)
|
|
122
|
+
|
|
123
|
+
# clip
|
|
124
|
+
if area:
|
|
125
|
+
# check area
|
|
126
|
+
if len(area) % 2 != 0:
|
|
127
|
+
logger.error(
|
|
128
|
+
f"The length of `area` must be even, but is {len(area)}")
|
|
129
|
+
exit(1)
|
|
130
|
+
|
|
131
|
+
area_array = np.asarray(area).reshape(-1, 2)
|
|
132
|
+
slice_index = tuple((slice(i[0], i[1]) for i in area_array))
|
|
133
|
+
|
|
134
|
+
if len(slice_index) == 2:
|
|
135
|
+
slice_index += (slice(None), )
|
|
136
|
+
|
|
137
|
+
data = data[slice_index[::-1]] # type: ignore
|
|
138
|
+
|
|
139
|
+
# fill nan
|
|
140
|
+
if miss_value:
|
|
141
|
+
data[data == miss_value] = np.nan
|
|
142
|
+
|
|
143
|
+
return data
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_geographical_static_data(geog_data_folder_path: str, name: str, area: Union[tuple[float, float, float, float], None] = None) -> DataArray:
|
|
147
|
+
"""Read WPS geographical static data
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
geog_data_folder_path (str): Data folder path.
|
|
151
|
+
name (str): Name that will be used to create DataArray.
|
|
152
|
+
area (Union[tuple[float, float, float, float], None]): Longitude and latitude area (lon_start, lon_stop, lat_start, lat_stop). Defaults to None.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
DataArray: DataArray object.
|
|
156
|
+
"""
|
|
157
|
+
# check if folder exists
|
|
158
|
+
if not exists(geog_data_folder_path):
|
|
159
|
+
logger.error(f"Can't find folder {geog_data_folder_path}")
|
|
160
|
+
exit(1)
|
|
161
|
+
|
|
162
|
+
# parse index file first
|
|
163
|
+
index_path = f"{geog_data_folder_path}/index"
|
|
164
|
+
index_data = parse_geographical_data_index(index_path)
|
|
165
|
+
|
|
166
|
+
# extract info to read data
|
|
167
|
+
# # check essential key
|
|
168
|
+
if "wordsize" not in index_data:
|
|
169
|
+
logger.error(
|
|
170
|
+
f"Can't find key `wordsize` in index file, maybe it is corrupted.")
|
|
171
|
+
exit(1)
|
|
172
|
+
# # extract info
|
|
173
|
+
wordsize = index_data["wordsize"]
|
|
174
|
+
endian = "little" if "endian" not in index_data else index_data["endian"]
|
|
175
|
+
miss_value = None if "missing_value" not in index_data else index_data["missing_value"]
|
|
176
|
+
tile_shape = []
|
|
177
|
+
for key in ["tile_z", "tile_y", "tile_x"]:
|
|
178
|
+
if key in index_data:
|
|
179
|
+
tile_shape.append(index_data[key])
|
|
180
|
+
tile_shape = tuple(tile_shape)
|
|
181
|
+
known_lat = index_data["known_lat"]
|
|
182
|
+
known_lon = index_data["known_lon"]
|
|
183
|
+
dx = index_data["dx"]
|
|
184
|
+
dy = index_data["dy"]
|
|
185
|
+
|
|
186
|
+
# calculate area
|
|
187
|
+
if area:
|
|
188
|
+
# read resolution, latitude and longitude
|
|
189
|
+
index_area = (
|
|
190
|
+
int((area[0] - known_lon) // dx),
|
|
191
|
+
int((area[1] - known_lon) // dx),
|
|
192
|
+
int((area[2] - known_lat) // dy),
|
|
193
|
+
int((area[3] - known_lat) // dy),
|
|
194
|
+
)
|
|
195
|
+
# check if negative value exists
|
|
196
|
+
if (
|
|
197
|
+
index_area[0] < 0 or
|
|
198
|
+
index_area[2] < 0
|
|
199
|
+
):
|
|
200
|
+
logger.warning(f"Part of your area has exceeded data's area")
|
|
201
|
+
# set negative value to 0
|
|
202
|
+
index_area = tuple((i if i >= 0 else 0 for i in index_area))
|
|
203
|
+
else:
|
|
204
|
+
logger.warning(f"You want to read all data, which may be very large")
|
|
205
|
+
index_area = None
|
|
206
|
+
|
|
207
|
+
# find the file we need to read
|
|
208
|
+
if index_area:
|
|
209
|
+
# # calculate tile index number
|
|
210
|
+
tile_index_num = (
|
|
211
|
+
index_area[0] // tile_shape[-1],
|
|
212
|
+
index_area[1] // tile_shape[-1],
|
|
213
|
+
index_area[2] // tile_shape[-2],
|
|
214
|
+
index_area[3] // tile_shape[-2],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
filenames = []
|
|
218
|
+
# # generate filenames and clip area
|
|
219
|
+
for row_num in range(tile_index_num[2], tile_index_num[3] + 1):
|
|
220
|
+
|
|
221
|
+
_names = []
|
|
222
|
+
for col_num in range(tile_index_num[0], tile_index_num[1] + 1):
|
|
223
|
+
|
|
224
|
+
_names.append(
|
|
225
|
+
[
|
|
226
|
+
DATA_NAME_TEMPLATE.format(
|
|
227
|
+
str(col_num * tile_shape[-1] + 1).rjust(5, '0'),
|
|
228
|
+
str((col_num + 1) * tile_shape[-1]).rjust(5, '0'),
|
|
229
|
+
str(row_num * tile_shape[-2] + 1).rjust(5, '0'),
|
|
230
|
+
str((row_num + 1) * tile_shape[-2]).rjust(5, '0'),
|
|
231
|
+
), _get_clip_area(index_area, row_num, col_num, tile_shape[-1], tile_shape[-2]) # type: ignore
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
filenames.append(_names)
|
|
236
|
+
else:
|
|
237
|
+
raw_filenames = [x for x in listdir(
|
|
238
|
+
geog_data_folder_path) if x != "index"]
|
|
239
|
+
raw_filenames.sort()
|
|
240
|
+
|
|
241
|
+
# parse the last file to get row number and column number
|
|
242
|
+
_last_filename = raw_filenames[-1]
|
|
243
|
+
total_col_num = int(_last_filename.split(
|
|
244
|
+
".")[0].split("-")[1]) // tile_shape[-1]
|
|
245
|
+
total_row_num = int(_last_filename.split(
|
|
246
|
+
".")[1].split("-")[1]) // tile_shape[-2]
|
|
247
|
+
|
|
248
|
+
filenames = []
|
|
249
|
+
|
|
250
|
+
for row_num in range(total_row_num):
|
|
251
|
+
|
|
252
|
+
_names = []
|
|
253
|
+
for col_num in range(total_col_num):
|
|
254
|
+
|
|
255
|
+
_names.append(
|
|
256
|
+
[
|
|
257
|
+
raw_filenames[row_num * total_col_num + col_num], None
|
|
258
|
+
]
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
filenames.append(_names)
|
|
262
|
+
|
|
263
|
+
# read and concatenate
|
|
264
|
+
array = []
|
|
265
|
+
for _row in filenames:
|
|
266
|
+
|
|
267
|
+
_array = []
|
|
268
|
+
for _col in _row:
|
|
269
|
+
|
|
270
|
+
_array.append(parse_geographical_data_file(
|
|
271
|
+
f"{geog_data_folder_path}/{_col[0]}", wordsize, endian, tile_shape, _col[1], miss_value))
|
|
272
|
+
|
|
273
|
+
# concatenate _array
|
|
274
|
+
array.append(np.concatenate(_array, axis=-1))
|
|
275
|
+
|
|
276
|
+
array = np.concatenate(array, axis=-2)
|
|
277
|
+
|
|
278
|
+
# get the longitude and latitude of the start point
|
|
279
|
+
if index_area:
|
|
280
|
+
longitude = known_lon + dx * index_area[0]
|
|
281
|
+
latitude = known_lat + dy * index_area[2]
|
|
282
|
+
else:
|
|
283
|
+
longitude = known_lon
|
|
284
|
+
latitude = known_lat
|
|
285
|
+
|
|
286
|
+
longitude = np.arange(array.shape[-1]) * dx + longitude
|
|
287
|
+
latitude = np.arange(array.shape[-2]) * dy + latitude
|
|
288
|
+
levels = np.arange(array.shape[-3])
|
|
289
|
+
|
|
290
|
+
return DataArray(
|
|
291
|
+
name=name, data=array,
|
|
292
|
+
dims=["levels", "latitude", "longitude"],
|
|
293
|
+
coords={
|
|
294
|
+
"longitude": longitude,
|
|
295
|
+
"latitude": latitude,
|
|
296
|
+
"levels": levels
|
|
297
|
+
}, attrs=index_data
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
__all__ = ["parse_geographical_data_index", "parse_geographical_data_file", "read_geographical_static_data"]
|