wxdata 1.6__tar.gz → 1.7__tar.gz
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.
- {wxdata-1.6/src/wxdata.egg-info → wxdata-1.7}/PKG-INFO +6 -1
- {wxdata-1.6 → wxdata-1.7}/README.md +5 -0
- {wxdata-1.6 → wxdata-1.7}/pyproject.toml +1 -1
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/__init__.py +1 -0
- wxdata-1.7/src/wxdata/client/byte_range.py +204 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/client/client.py +167 -1
- wxdata-1.7/src/wxdata/client/level_coords.py +141 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/noaa/nws.py +2 -2
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/process.py +1 -1
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/rtma.py +1 -1
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/progress_bar.py +2 -5
- {wxdata-1.6 → wxdata-1.7/src/wxdata.egg-info}/PKG-INFO +6 -1
- {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/SOURCES.txt +2 -0
- {wxdata-1.6 → wxdata-1.7}/LICENSE +0 -0
- {wxdata-1.6 → wxdata-1.7}/setup.cfg +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/aigefs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/aigfs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/derived_fields.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/kinematics.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/thermodynamics.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/unit_conversion.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/cfs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/file_scanner.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/client/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/ecmwf.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/file_funcs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/keys.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/parsers.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/meta_data.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/observations.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/exception_messages.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/file_funcs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/gefs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/process.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/exception_messages.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/gfs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/hgefs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/paths.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/url_scanner.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/_clean_data.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/metar_obs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/noaa/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/aigefs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/aigfs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/cfs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/ecmwf_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/gefs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/gfs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/hgefs_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/rtma_post_processing.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/nexrad2.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/url_scanner.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/file_funcs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/keys.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/url_scanners.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/_exceptions.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/wyoming_soundings.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/__init__.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/coords.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/exceptions.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/file_funcs.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/file_scanner.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/nomads_gribfilter.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/recycle_bin.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/scripts.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/tools.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/xmacis2_cleanup.py +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/dependency_links.txt +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/requires.txt +0 -0
- {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wxdata
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data.
|
|
5
5
|
Author: Eric J. Drewitz
|
|
6
6
|
Project-URL: Documentation, https://github.com/edrewitz/wxdata/blob/main/Documentation/Landing%20Page.md
|
|
@@ -204,6 +204,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
204
204
|
*Advanced Users*
|
|
205
205
|
1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
|
|
206
206
|
2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
|
|
207
|
+
3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
|
|
207
208
|
|
|
208
209
|
---------------------------------------------------
|
|
209
210
|
|
|
@@ -340,6 +341,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
340
341
|
***AWS Open Data***
|
|
341
342
|
1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
|
|
342
343
|
|
|
344
|
+
***Byte-Range Requests***
|
|
345
|
+
1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
|
|
346
|
+
|
|
343
347
|
---------------------------------------------------------------
|
|
344
348
|
|
|
345
349
|
### Data Querying Tools
|
|
@@ -626,6 +630,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
626
630
|
# - get_excel_data()
|
|
627
631
|
# - get_xmacis_data()
|
|
628
632
|
# - get_aws_open_data()
|
|
633
|
+
# - byte_range_request()
|
|
629
634
|
import wxdata.client.client as client
|
|
630
635
|
|
|
631
636
|
-------------------------------------------
|
|
@@ -172,6 +172,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
172
172
|
*Advanced Users*
|
|
173
173
|
1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
|
|
174
174
|
2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
|
|
175
|
+
3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
|
|
175
176
|
|
|
176
177
|
---------------------------------------------------
|
|
177
178
|
|
|
@@ -308,6 +309,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
308
309
|
***AWS Open Data***
|
|
309
310
|
1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
|
|
310
311
|
|
|
312
|
+
***Byte-Range Requests***
|
|
313
|
+
1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
|
|
314
|
+
|
|
311
315
|
---------------------------------------------------------------
|
|
312
316
|
|
|
313
317
|
### Data Querying Tools
|
|
@@ -594,6 +598,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
594
598
|
# - get_excel_data()
|
|
595
599
|
# - get_xmacis_data()
|
|
596
600
|
# - get_aws_open_data()
|
|
601
|
+
# - byte_range_request()
|
|
597
602
|
import wxdata.client.client as client
|
|
598
603
|
|
|
599
604
|
-------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "wxdata"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.7"
|
|
4
4
|
description = "A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file hosts the function that fetches the byte-range for a variable.
|
|
3
|
+
|
|
4
|
+
(C) Eric J. Drewitz 2025-2026
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from tqdm.auto import tqdm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def fetch_range(url,
|
|
13
|
+
start,
|
|
14
|
+
end,
|
|
15
|
+
proxies):
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
This function downloads the data corresponding to the bytes-range for each variable
|
|
19
|
+
|
|
20
|
+
Required Arguments:
|
|
21
|
+
|
|
22
|
+
1) url (String) - The URL of the GRIB file that will be downloaded.
|
|
23
|
+
|
|
24
|
+
2) start (Integer) - The starting byte.
|
|
25
|
+
|
|
26
|
+
3) end (Integer) - The ending byte.
|
|
27
|
+
|
|
28
|
+
4) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
|
|
29
|
+
|
|
30
|
+
proxies=None ---> proxies={
|
|
31
|
+
'http':'http://your-proxy-address:port',
|
|
32
|
+
'https':'http://your-proxy-address:port'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Optional Arguments: None
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
|
|
40
|
+
The data to be written to a GRIB file.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
headers = {"Range": f"bytes={start}-{end}" if end else f"bytes={start}-"}
|
|
44
|
+
|
|
45
|
+
if proxies == None:
|
|
46
|
+
response = requests.get(url,
|
|
47
|
+
headers=headers,
|
|
48
|
+
stream=True)
|
|
49
|
+
else:
|
|
50
|
+
response = requests.get(url,
|
|
51
|
+
headers=headers,
|
|
52
|
+
proxies=proxies,
|
|
53
|
+
stream=True)
|
|
54
|
+
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
response.close()
|
|
57
|
+
|
|
58
|
+
if len(response.content) == 0:
|
|
59
|
+
if proxies == None:
|
|
60
|
+
response = requests.get(url,
|
|
61
|
+
headers=headers)
|
|
62
|
+
else:
|
|
63
|
+
response = requests.get(url,
|
|
64
|
+
headers=headers,
|
|
65
|
+
proxies=proxies)
|
|
66
|
+
else:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
return response.content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def fetch_data(ranges,
|
|
73
|
+
url,
|
|
74
|
+
start,
|
|
75
|
+
end,
|
|
76
|
+
proxies):
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
This function downloads the data corresponding to the bytes-range for each variable
|
|
80
|
+
|
|
81
|
+
Required Arguments:
|
|
82
|
+
|
|
83
|
+
1) url (String) - The URL of the GRIB file that will be downloaded.
|
|
84
|
+
|
|
85
|
+
2) start (Integer) - The starting byte.
|
|
86
|
+
|
|
87
|
+
3) end (Integer) - The ending byte.
|
|
88
|
+
|
|
89
|
+
4) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
|
|
90
|
+
|
|
91
|
+
proxies=None ---> proxies={
|
|
92
|
+
'http':'http://your-proxy-address:port',
|
|
93
|
+
'https':'http://your-proxy-address:port'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Optional Arguments: None
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
|
|
101
|
+
The data to be written to a GRIB file.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
results = {}
|
|
105
|
+
for (v, l), (start, end) in ranges.items():
|
|
106
|
+
results[(v, l)] = fetch_range(
|
|
107
|
+
url,
|
|
108
|
+
start,
|
|
109
|
+
end,
|
|
110
|
+
proxies
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return results
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def download_grib_data_by_byte_range(ranges,
|
|
117
|
+
chunk_size,
|
|
118
|
+
path,
|
|
119
|
+
filename,
|
|
120
|
+
grib_url,
|
|
121
|
+
start,
|
|
122
|
+
end,
|
|
123
|
+
proxies):
|
|
124
|
+
"""
|
|
125
|
+
results: dict[(var, level)] = bytes
|
|
126
|
+
Writes all GRIB messages to a single file with polished progress bars.
|
|
127
|
+
|
|
128
|
+
This function writes the GRIB file with a progress bar.
|
|
129
|
+
This is a progress bar for the byte-range requests.
|
|
130
|
+
|
|
131
|
+
Required Arguments:
|
|
132
|
+
|
|
133
|
+
1) ranges (Tuple List) - A tuple list of byte-ranges.
|
|
134
|
+
|
|
135
|
+
2) chunk_size (Integer) - The size of the chunks when writing the GRIB/NETCDF data to a file.
|
|
136
|
+
|
|
137
|
+
3) path (String) - The directory where the file is saved to.
|
|
138
|
+
|
|
139
|
+
4) filename (String) - The name the user wishes to save the file as.
|
|
140
|
+
|
|
141
|
+
5) grib_url (String) - The URL of the GRIB file that will be downloaded.
|
|
142
|
+
|
|
143
|
+
6) start (Integer) - The starting byte.
|
|
144
|
+
|
|
145
|
+
7) end (Integer) - The ending byte.
|
|
146
|
+
|
|
147
|
+
8) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
|
|
148
|
+
|
|
149
|
+
proxies=None ---> proxies={
|
|
150
|
+
'http':'http://your-proxy-address:port',
|
|
151
|
+
'https':'http://your-proxy-address:port'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Optional Arguments: None
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
|
|
159
|
+
A GRIB file of {filename} saved to {path}
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
results = fetch_data(ranges,
|
|
163
|
+
grib_url,
|
|
164
|
+
start,
|
|
165
|
+
end,
|
|
166
|
+
proxies)
|
|
167
|
+
|
|
168
|
+
ordered = sorted(results.items(), key=lambda x: (x[0][0], x[0][1]))
|
|
169
|
+
|
|
170
|
+
# Master progress bar (stays visible)
|
|
171
|
+
master = tqdm(
|
|
172
|
+
total=len(ordered),
|
|
173
|
+
desc="Writing GRIB messages",
|
|
174
|
+
position=0,
|
|
175
|
+
leave=False,
|
|
176
|
+
bar_format="{desc:<25} |{bar:30}| {n}/{total}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
with open(f"{path}/{filename}", "wb") as f:
|
|
180
|
+
for (var, level), data in ordered:
|
|
181
|
+
|
|
182
|
+
total_bytes = len(data)
|
|
183
|
+
label = f"{var} @ {level}"
|
|
184
|
+
|
|
185
|
+
# Inner bar (disappears after finishing)
|
|
186
|
+
with tqdm(
|
|
187
|
+
total=total_bytes,
|
|
188
|
+
unit="B",
|
|
189
|
+
unit_scale=True,
|
|
190
|
+
unit_divisor=chunk_size,
|
|
191
|
+
desc=label,
|
|
192
|
+
position=1,
|
|
193
|
+
leave=False,
|
|
194
|
+
bar_format="{desc:<30} |{bar:30}| {percentage:3.0f}% [{n_fmt}/{total_fmt}]"
|
|
195
|
+
) as pbar:
|
|
196
|
+
|
|
197
|
+
for i in range(0, total_bytes, chunk_size):
|
|
198
|
+
chunk = data[i:i+chunk_size]
|
|
199
|
+
f.write(chunk)
|
|
200
|
+
pbar.update(len(chunk))
|
|
201
|
+
|
|
202
|
+
master.update(1)
|
|
203
|
+
|
|
204
|
+
master.close()
|
|
@@ -8,10 +8,12 @@ These functions are compatible with users on VPN/PROXY connections as well as no
|
|
|
8
8
|
3) get_excel_data
|
|
9
9
|
4) get_xmacis_data
|
|
10
10
|
5) get_open_aws_data
|
|
11
|
+
6) byte_range_request
|
|
11
12
|
|
|
12
13
|
(C) Eric J. Drewitz 2025-2026
|
|
13
14
|
"""
|
|
14
15
|
|
|
16
|
+
import itertools as _itertools
|
|
15
17
|
import requests as _requests
|
|
16
18
|
import time as _time
|
|
17
19
|
import sys as _sys
|
|
@@ -27,8 +29,11 @@ from datetime import(
|
|
|
27
29
|
timedelta as _timedelta
|
|
28
30
|
)
|
|
29
31
|
|
|
30
|
-
from wxdata.utils.
|
|
32
|
+
from wxdata.utils.nomads_gribfilter import key_list as _key_list
|
|
33
|
+
from wxdata.client.level_coords import get_level_expression as _get_level_expression
|
|
31
34
|
from wxdata.utils.xmacis2_cleanup import clean_pandas_dataframe as _clean_pandas_dataframe
|
|
35
|
+
from wxdata.utils.progress_bar import progress_bar as _progress_bar
|
|
36
|
+
from wxdata.client.byte_range import download_grib_data_by_byte_range as _download_grib_data_by_byte_range
|
|
32
37
|
from wxdata.utils.recycle_bin import(
|
|
33
38
|
clear_recycle_bin_windows as _clear_recycle_bin_windows,
|
|
34
39
|
clear_trash_bin_mac as _clear_trash_bin_mac,
|
|
@@ -802,4 +807,165 @@ def get_aws_open_data(url,
|
|
|
802
807
|
finally:
|
|
803
808
|
if r:
|
|
804
809
|
r.close() # Ensure the connection is closed.
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def byte_range_request(grib_url,
|
|
813
|
+
idx_url,
|
|
814
|
+
variables,
|
|
815
|
+
levels,
|
|
816
|
+
level_type,
|
|
817
|
+
path,
|
|
818
|
+
filename,
|
|
819
|
+
proxies=None,
|
|
820
|
+
chunk_size=1024,
|
|
821
|
+
notifications='on',
|
|
822
|
+
clear_recycle_bin=False):
|
|
823
|
+
|
|
824
|
+
"""
|
|
825
|
+
This client downloads GRIB data for a specific variable that is defined by the range in bytes in that GRIB file.
|
|
826
|
+
This is useful when the user wants to download a GRIB file where there is no GRIB filter present, especially when the file size is large.
|
|
827
|
+
This will allow users to download the variable they are interested in and filter out all other variables prior to downloading.
|
|
828
|
+
|
|
829
|
+
Required Arguments:
|
|
830
|
+
|
|
831
|
+
1) grib_url (String) - The URL of the GRIB file that will be downloaded.
|
|
832
|
+
|
|
833
|
+
2) idx_url (String) - The URL of the index file that corresponds to the GRIB file (ends in .idx).
|
|
834
|
+
|
|
835
|
+
3) variables (String List) - The list of variables to be downloaded.
|
|
836
|
+
|
|
837
|
+
4) levels (Float or Integer List) - The list of pressure or height levels.
|
|
838
|
+
|
|
839
|
+
5) level_type (String) - The type of level.
|
|
840
|
+
|
|
841
|
+
Level Types
|
|
842
|
+
-----------
|
|
843
|
+
|
|
844
|
+
'hybrid'
|
|
845
|
+
'entire atmosphere'
|
|
846
|
+
'surface':'surface',
|
|
847
|
+
'boundary layer'
|
|
848
|
+
'pressure'
|
|
849
|
+
'mean sea level'
|
|
850
|
+
'height above ground'
|
|
851
|
+
'height below ground'
|
|
852
|
+
'height above sea level'
|
|
853
|
+
'entire atmosphere single layer'
|
|
854
|
+
'low cloud layer'
|
|
855
|
+
'middle cloud layer'
|
|
856
|
+
'high cloud layer'
|
|
857
|
+
'cloud ceiling'
|
|
858
|
+
'tropopause'
|
|
859
|
+
'max wind'
|
|
860
|
+
'isothermal'
|
|
861
|
+
'highest tropospheric freezing level'
|
|
862
|
+
'sigma layer'
|
|
863
|
+
'sigma level'
|
|
864
|
+
'potential vorticity surface'
|
|
865
|
+
'reserved'
|
|
866
|
+
|
|
867
|
+
6) path (String) - The directory where the file is saved to.
|
|
868
|
+
|
|
869
|
+
7) filename (String) - The name the user wishes to save the file as.
|
|
870
|
+
|
|
871
|
+
Optional Arguments:
|
|
872
|
+
|
|
873
|
+
1) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
|
|
874
|
+
|
|
875
|
+
proxies=None ---> proxies={
|
|
876
|
+
'http':'http://your-proxy-address:port',
|
|
877
|
+
'https':'http://your-proxy-address:port'
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
2) chunk_size (Integer) - Default=8192. The size of the chunks when writing the GRIB/NETCDF data to a file.
|
|
881
|
+
|
|
882
|
+
3) notifications (String) - Default='on'. Notification when a file is downloaded and saved to {path}
|
|
883
|
+
|
|
884
|
+
4) clear_recycle_bin (Boolean) - (Default=False in WxData >= 1.2.5) (Default=True in WxData < 1.2.5). When set to True,
|
|
885
|
+
the contents in your recycle/trash bin will be deleted with each run of the program you are calling WxData.
|
|
886
|
+
This setting is to help preserve memory on the machine.
|
|
887
|
+
|
|
888
|
+
Returns
|
|
889
|
+
-------
|
|
890
|
+
|
|
891
|
+
Downloads a partial GRIB file consisting of the variable the user specifies.
|
|
892
|
+
"""
|
|
893
|
+
if clear_recycle_bin == True:
|
|
894
|
+
_clear_recycle_bin_windows()
|
|
895
|
+
_clear_trash_bin_mac()
|
|
896
|
+
_clear_trash_bin_linux()
|
|
897
|
+
else:
|
|
898
|
+
pass
|
|
899
|
+
|
|
900
|
+
try:
|
|
901
|
+
_os.makedirs(f"{path}")
|
|
902
|
+
except Exception as e:
|
|
903
|
+
pass
|
|
904
|
+
|
|
905
|
+
variables = _key_list(variables)
|
|
906
|
+
|
|
907
|
+
if proxies == None:
|
|
908
|
+
idx_text = _requests.get(idx_url).text
|
|
909
|
+
else:
|
|
910
|
+
idx_text = _requests.get(idx_url, proxies=proxies).text
|
|
911
|
+
|
|
912
|
+
records = []
|
|
913
|
+
for line in idx_text.strip().splitlines():
|
|
914
|
+
parts = line.split(':')
|
|
915
|
+
msg_no = int(parts[0])
|
|
916
|
+
offset = int(parts[1])
|
|
917
|
+
var = parts[3]
|
|
918
|
+
lev = parts[4]
|
|
919
|
+
records.append({
|
|
920
|
+
"msg": msg_no,
|
|
921
|
+
"offset": offset,
|
|
922
|
+
"var": var,
|
|
923
|
+
"lev": lev
|
|
924
|
+
})
|
|
925
|
+
|
|
926
|
+
req_levels, levels = _get_level_expression(levels,
|
|
927
|
+
level_type)
|
|
928
|
+
|
|
929
|
+
if levels is not None:
|
|
930
|
+
reqs = list(_itertools.product(variables, req_levels))
|
|
931
|
+
else:
|
|
932
|
+
reqs = []
|
|
933
|
+
for v in variables:
|
|
934
|
+
req = (v, req_levels)
|
|
935
|
+
reqs.append(req)
|
|
936
|
+
|
|
937
|
+
ranges = {}
|
|
938
|
+
for v, l in reqs:
|
|
939
|
+
matches = [r for r in records if r["var"] == v and r["lev"] == l]
|
|
940
|
+
if not matches:
|
|
941
|
+
print(f"{v} is not a valid variable OR {l} is not a valid level.")
|
|
942
|
+
print(f"Please visit {idx_url} to look at the variables in the GRIB file meta-data")
|
|
943
|
+
_sys.exit(1)
|
|
944
|
+
else:
|
|
945
|
+
pass
|
|
946
|
+
|
|
947
|
+
rec = matches[0]
|
|
948
|
+
start = rec["offset"]
|
|
949
|
+
|
|
950
|
+
idx = records.index(rec)
|
|
951
|
+
if idx < len(records) - 1:
|
|
952
|
+
end = records[idx + 1]["offset"] - 1
|
|
953
|
+
else:
|
|
954
|
+
end = None
|
|
955
|
+
|
|
956
|
+
ranges[(v, l)] = (start, end)
|
|
957
|
+
|
|
958
|
+
_download_grib_data_by_byte_range(ranges,
|
|
959
|
+
chunk_size,
|
|
960
|
+
path,
|
|
961
|
+
filename,
|
|
962
|
+
grib_url,
|
|
963
|
+
start,
|
|
964
|
+
end,
|
|
965
|
+
proxies)
|
|
966
|
+
|
|
967
|
+
if notifications == 'on':
|
|
968
|
+
print(f"{filename} saved to {path}")
|
|
969
|
+
|
|
970
|
+
|
|
805
971
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file hosts the functions that handle the level coordinates for byte-range requests.
|
|
3
|
+
|
|
4
|
+
(C) Eric J. Drewitz 2025-2026
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
def get_level_suffix(level_type):
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
This function returns the level type suffix.
|
|
11
|
+
|
|
12
|
+
Required Arguments:
|
|
13
|
+
|
|
14
|
+
1) level_type (String) - The type of level the coordinates are on.
|
|
15
|
+
|
|
16
|
+
Level Types
|
|
17
|
+
-----------
|
|
18
|
+
|
|
19
|
+
'hybrid'
|
|
20
|
+
'entire atmosphere'
|
|
21
|
+
'surface':'surface',
|
|
22
|
+
'boundary layer'
|
|
23
|
+
'pressure'
|
|
24
|
+
'mean sea level'
|
|
25
|
+
'height above ground'
|
|
26
|
+
'height below ground'
|
|
27
|
+
'height above sea level'
|
|
28
|
+
'entire atmosphere single layer'
|
|
29
|
+
'low cloud layer'
|
|
30
|
+
'middle cloud layer'
|
|
31
|
+
'high cloud layer'
|
|
32
|
+
'cloud ceiling'
|
|
33
|
+
'tropopause'
|
|
34
|
+
'max wind'
|
|
35
|
+
'isothermal'
|
|
36
|
+
'highest tropospheric freezing level'
|
|
37
|
+
'sigma layer'
|
|
38
|
+
'sigma level'
|
|
39
|
+
'potential vorticity surface'
|
|
40
|
+
'reserved'
|
|
41
|
+
|
|
42
|
+
Optional Arguments: None
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
|
|
47
|
+
The level type suffix.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
level_types = {
|
|
51
|
+
|
|
52
|
+
'hybrid':'hybrid level',
|
|
53
|
+
'entire atmosphere':'entire atmosphere',
|
|
54
|
+
'surface':'surface',
|
|
55
|
+
'boundary layer':'planetary boundary layer',
|
|
56
|
+
'pressure':'mb',
|
|
57
|
+
'mean sea level':'mean sea level',
|
|
58
|
+
'height above ground':'m above ground',
|
|
59
|
+
'height below ground':'m below ground',
|
|
60
|
+
'height above sea level':'m above mean sea level',
|
|
61
|
+
'entire atmosphere single layer':'entire atmosphere (considered as a single layer)',
|
|
62
|
+
'low cloud layer':'low cloud layer',
|
|
63
|
+
'middle cloud layer':'middle cloud layer',
|
|
64
|
+
'high cloud layer':'high cloud layer',
|
|
65
|
+
'cloud ceiling':'cloud ceiling',
|
|
66
|
+
'tropopause':'tropopause',
|
|
67
|
+
'max wind':'max wind',
|
|
68
|
+
'isothermal':'0C isotherm',
|
|
69
|
+
'highest tropospheric freezing level':'highest tropospheric freezing level',
|
|
70
|
+
'sigma layer':'sigma layer',
|
|
71
|
+
'sigma level':'sigma level',
|
|
72
|
+
'potential vorticity surface':'PV=2e-06 (Km^2/kg/s) surface',
|
|
73
|
+
'reserved':'reserved'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return level_types[level_type]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_level_expression(levels,
|
|
80
|
+
level_type):
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
This function returns the full expression for the level.
|
|
84
|
+
|
|
85
|
+
Required Arguments:
|
|
86
|
+
|
|
87
|
+
1) levels (Float or Integer List) - The pressure or height levels corresponding to level type.
|
|
88
|
+
(i.e. [700, 500, 250] if level type is 'pressure' or [2, 10] if level type is 'height above ground')
|
|
89
|
+
|
|
90
|
+
2) level_type (String) - The type of level the coordinates are on.
|
|
91
|
+
|
|
92
|
+
Level Types
|
|
93
|
+
-----------
|
|
94
|
+
|
|
95
|
+
'hybrid'
|
|
96
|
+
'entire atmosphere'
|
|
97
|
+
'surface':'surface',
|
|
98
|
+
'boundary layer'
|
|
99
|
+
'pressure'
|
|
100
|
+
'mean sea level'
|
|
101
|
+
'height above ground'
|
|
102
|
+
'height below ground'
|
|
103
|
+
'height above sea level'
|
|
104
|
+
'entire atmosphere single layer'
|
|
105
|
+
'low cloud layer'
|
|
106
|
+
'middle cloud layer'
|
|
107
|
+
'high cloud layer'
|
|
108
|
+
'cloud ceiling'
|
|
109
|
+
'tropopause'
|
|
110
|
+
'max wind'
|
|
111
|
+
'isothermal'
|
|
112
|
+
'highest tropospheric freezing level'
|
|
113
|
+
'sigma layer'
|
|
114
|
+
'sigma level'
|
|
115
|
+
'potential vorticity surface'
|
|
116
|
+
'reserved'
|
|
117
|
+
|
|
118
|
+
Optional Arguments: None
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
|
|
123
|
+
The full expression for the level to make the bytes-range request.
|
|
124
|
+
"""
|
|
125
|
+
level_type = level_type.lower()
|
|
126
|
+
|
|
127
|
+
suffix = get_level_suffix(level_type)
|
|
128
|
+
if levels is not None:
|
|
129
|
+
expression = []
|
|
130
|
+
for level in levels:
|
|
131
|
+
ex = f"{level} {suffix}"
|
|
132
|
+
expression.append(ex)
|
|
133
|
+
levels = levels
|
|
134
|
+
else:
|
|
135
|
+
expression = suffix
|
|
136
|
+
levels = None
|
|
137
|
+
|
|
138
|
+
return expression, levels
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
@@ -199,7 +199,7 @@ def _FIX_1D_GRIB_DATA(ds_short,
|
|
|
199
199
|
|
|
200
200
|
dims = ("step", "latitude", "longitude")
|
|
201
201
|
short_coords = {
|
|
202
|
-
"step":
|
|
202
|
+
"step": ds_short['step'],
|
|
203
203
|
"latitude": lat1d,
|
|
204
204
|
"longitude": lon1d,
|
|
205
205
|
}
|
|
@@ -239,7 +239,7 @@ def _FIX_1D_GRIB_DATA(ds_short,
|
|
|
239
239
|
|
|
240
240
|
dims = ("step", "latitude", "longitude")
|
|
241
241
|
extended_coords = {
|
|
242
|
-
"step":
|
|
242
|
+
"step": ds_extended['step'],
|
|
243
243
|
"latitude": lat1d,
|
|
244
244
|
"longitude": lon1d,
|
|
245
245
|
}
|
|
@@ -4,10 +4,7 @@ This file hosts a function that displays a progress bar for downloading a file.
|
|
|
4
4
|
(C) Eric J. Drewitz 2025-2026
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
from tqdm import tqdm
|
|
7
|
+
from tqdm.auto import tqdm
|
|
11
8
|
|
|
12
9
|
def progress_bar(response,
|
|
13
10
|
path,
|
|
@@ -41,4 +38,4 @@ def progress_bar(response,
|
|
|
41
38
|
with open(f"{path}/{filename}", "wb") as file:
|
|
42
39
|
for data in response.iter_content(blocksize):
|
|
43
40
|
file.write(data)
|
|
44
|
-
bar.update(len(data))
|
|
41
|
+
bar.update(len(data))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wxdata
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data.
|
|
5
5
|
Author: Eric J. Drewitz
|
|
6
6
|
Project-URL: Documentation, https://github.com/edrewitz/wxdata/blob/main/Documentation/Landing%20Page.md
|
|
@@ -204,6 +204,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
204
204
|
*Advanced Users*
|
|
205
205
|
1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
|
|
206
206
|
2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
|
|
207
|
+
3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
|
|
207
208
|
|
|
208
209
|
---------------------------------------------------
|
|
209
210
|
|
|
@@ -340,6 +341,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
340
341
|
***AWS Open Data***
|
|
341
342
|
1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
|
|
342
343
|
|
|
344
|
+
***Byte-Range Requests***
|
|
345
|
+
1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
|
|
346
|
+
|
|
343
347
|
---------------------------------------------------------------
|
|
344
348
|
|
|
345
349
|
### Data Querying Tools
|
|
@@ -626,6 +630,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
|
|
|
626
630
|
# - get_excel_data()
|
|
627
631
|
# - get_xmacis_data()
|
|
628
632
|
# - get_aws_open_data()
|
|
633
|
+
# - byte_range_request()
|
|
629
634
|
import wxdata.client.client as client
|
|
630
635
|
|
|
631
636
|
-------------------------------------------
|
|
@@ -25,7 +25,9 @@ src/wxdata/cfs/cfs.py
|
|
|
25
25
|
src/wxdata/cfs/file_scanner.py
|
|
26
26
|
src/wxdata/cfs/url_scanners.py
|
|
27
27
|
src/wxdata/client/__init__.py
|
|
28
|
+
src/wxdata/client/byte_range.py
|
|
28
29
|
src/wxdata/client/client.py
|
|
30
|
+
src/wxdata/client/level_coords.py
|
|
29
31
|
src/wxdata/ecmwf/__init__.py
|
|
30
32
|
src/wxdata/ecmwf/ecmwf.py
|
|
31
33
|
src/wxdata/ecmwf/file_funcs.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|