grib2sail 0.1.0__tar.gz → 0.2.0__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.
Files changed (24) hide show
  1. {grib2sail-0.1.0 → grib2sail-0.2.0}/.gitignore +1 -1
  2. {grib2sail-0.1.0/grib2sail.egg-info → grib2sail-0.2.0}/PKG-INFO +3 -3
  3. {grib2sail-0.1.0 → grib2sail-0.2.0}/README.md +2 -2
  4. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/_version.py +3 -3
  5. grib2sail-0.2.0/grib2sail/downloader.py +54 -0
  6. grib2sail-0.2.0/grib2sail/downloader_arom.py +115 -0
  7. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/token.py +6 -4
  8. grib2sail-0.2.0/grib2sail/variables.py +3 -0
  9. grib2sail-0.2.0/grib2sail/variables_arom.py +17 -0
  10. {grib2sail-0.1.0 → grib2sail-0.2.0/grib2sail.egg-info}/PKG-INFO +3 -3
  11. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail.egg-info/SOURCES.txt +2 -0
  12. grib2sail-0.1.0/grib2sail/downloader.py +0 -102
  13. grib2sail-0.1.0/grib2sail/variables.py +0 -23
  14. {grib2sail-0.1.0 → grib2sail-0.2.0}/.github/workflows/release.yml +0 -0
  15. {grib2sail-0.1.0 → grib2sail-0.2.0}/LICENSE +0 -0
  16. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/__init__.py +0 -0
  17. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/__main__.py +0 -0
  18. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/cli.py +0 -0
  19. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail/logger.py +0 -0
  20. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail.egg-info/dependency_links.txt +0 -0
  21. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail.egg-info/requires.txt +0 -0
  22. {grib2sail-0.1.0 → grib2sail-0.2.0}/grib2sail.egg-info/top_level.txt +0 -0
  23. {grib2sail-0.1.0 → grib2sail-0.2.0}/pyproject.toml +0 -0
  24. {grib2sail-0.1.0 → grib2sail-0.2.0}/setup.cfg +0 -0
@@ -1,5 +1,5 @@
1
1
  .dev/
2
2
  __pycache__/
3
3
  dist/
4
- grib4sail.egg-info/
4
+ grib2sail.egg-info/
5
5
  _version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grib2sail
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Grib files downloader for sailing purposes
5
5
  Author-email: Chinkara <poubelledechinkara@outlook.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -17,7 +17,7 @@ Dynamic: license-file
17
17
  # GRIB2Sail
18
18
 
19
19
  ![PyPI version](https://img.shields.io/pypi/v/grib2sail.svg)
20
- ![CI](https://img.shields.io/github/workflow/status/Ch1nkara/GRIB2Sail/CI)
20
+ ![CI](https://img.shields.io/github/actions/workflow/status/Ch1nkara/GRIB2Sail/release.yml)
21
21
  ![License](https://img.shields.io/badge/license-GPL%20v3-blue.svg)
22
22
 
23
23
  Grib files downloader for sailing purposes.
@@ -37,7 +37,7 @@ To download GRIB from meteofrance's models (Aome), you must create a free
37
37
  account on meteofrance.fr. The procedure is as follow:
38
38
  1. Create an account on [the Météo-France API portal](https://portail-api.meteofrance.fr)
39
39
  2. Subscribe to the desired service (Arome)
40
- 3. Go to "My API" then, from your subscribe model: "Generate Token"
40
+ 3. Go to "My API" then, from your subscribed model: "Generate Token"
41
41
  4. Checkout the curl field at the bottom, it looks like :
42
42
  ```bash
43
43
  curl -k -X POST https://portal-api.meteofrance.fr/token -d "grant_type=client_credentials" -H "Authorization: Basic ABCDEF1234abcdef"
@@ -1,7 +1,7 @@
1
1
  # GRIB2Sail
2
2
 
3
3
  ![PyPI version](https://img.shields.io/pypi/v/grib2sail.svg)
4
- ![CI](https://img.shields.io/github/workflow/status/Ch1nkara/GRIB2Sail/CI)
4
+ ![CI](https://img.shields.io/github/actions/workflow/status/Ch1nkara/GRIB2Sail/release.yml)
5
5
  ![License](https://img.shields.io/badge/license-GPL%20v3-blue.svg)
6
6
 
7
7
  Grib files downloader for sailing purposes.
@@ -21,7 +21,7 @@ To download GRIB from meteofrance's models (Aome), you must create a free
21
21
  account on meteofrance.fr. The procedure is as follow:
22
22
  1. Create an account on [the Météo-France API portal](https://portail-api.meteofrance.fr)
23
23
  2. Subscribe to the desired service (Arome)
24
- 3. Go to "My API" then, from your subscribe model: "Generate Token"
24
+ 3. Go to "My API" then, from your subscribed model: "Generate Token"
25
25
  4. Checkout the curl field at the bottom, it looks like :
26
26
  ```bash
27
27
  curl -k -X POST https://portal-api.meteofrance.fr/token -d "grant_type=client_credentials" -H "Authorization: Basic ABCDEF1234abcdef"
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.0'
32
- __version_tuple__ = version_tuple = (0, 1, 0)
31
+ __version__ = version = '0.2.0'
32
+ __version_tuple__ = version_tuple = (0, 2, 0)
33
33
 
34
- __commit_id__ = commit_id = 'gd4f4423ed'
34
+ __commit_id__ = commit_id = 'g31ec6ed97'
@@ -0,0 +1,54 @@
1
+ from concurrent.futures import ThreadPoolExecutor, as_completed
2
+ import threading
3
+ import requests
4
+ from rich.progress import Progress
5
+
6
+ from grib2sail.logger import logger
7
+ from grib2sail.downloader_arom import handle_fetch_error_arom, download_arom
8
+ import grib2sail.variables as v
9
+
10
+ thread_local = threading.local()
11
+
12
+ def get_session():
13
+ if not hasattr(thread_local, 'session'):
14
+ thread_local.session = requests.Session()
15
+ return thread_local.session
16
+
17
+ def download_gribs(m, s, d, lat, lon):
18
+ if m.startswith('arome'):
19
+ download_arom(m, s, d, lat, lon)
20
+ else:
21
+ logger.error_exit(f"Downloader failed: unexpected model: {m}")
22
+
23
+ def get_layers(model, urls, header):
24
+ # Downloading every layers
25
+ layers = [None] * len(urls)
26
+ with Progress() as progress:
27
+ # Showing a progress bar
28
+ task = progress.add_task('Downloading layers...', total=len(urls))
29
+
30
+ # Downloading the layer
31
+ with ThreadPoolExecutor(max_workers=10) as executor:
32
+ futures = [
33
+ executor.submit(fetch, i, url, header, model)
34
+ for i, url in enumerate(urls)
35
+ ]
36
+
37
+ for future in as_completed(futures):
38
+ idx, layer = future.result()
39
+ layers[idx] = layer
40
+ progress.advance(task)
41
+ return layers
42
+
43
+ def fetch(idx, url, headers, model):
44
+ try:
45
+ session = get_session()
46
+ r = session.get(url, headers=headers,timeout = 60)
47
+ r.raise_for_status()
48
+ return idx, r.content
49
+ except Exception as e:
50
+ if model in v.MODELS[:2]:
51
+ handle_fetch_error_arom(e)
52
+ else:
53
+ logger.error_exit(f"Download failed: {e}")
54
+ return idx, None
@@ -0,0 +1,115 @@
1
+ from pathlib import Path
2
+ import re
3
+ import requests
4
+ import time as t
5
+
6
+ import grib2sail.variables as v
7
+ import grib2sail.variables_arom as va
8
+ import grib2sail.downloader as d
9
+ from grib2sail.logger import logger
10
+ from grib2sail.token import get_arome_token
11
+
12
+ def download_arom(model, step, data, lat, lon):
13
+ token = get_arome_token()
14
+
15
+ # Coverages list all the individual layers categories to download
16
+ coverages = []
17
+ if v.DATAS[0] in data:
18
+ coverages += [va.AROM_DATAS['wind_u'], va.AROM_DATAS['wind_v']]
19
+ if v.DATAS[1] in data:
20
+ coverages += [va.AROM_DATAS['wind_gust']]
21
+ if v.DATAS[2] in data:
22
+ coverages += [va.AROM_DATAS['pressure']]
23
+ if v.DATAS[3] in data:
24
+ coverages += [va.AROM_DATAS['cloud']]
25
+
26
+ # Get latest available forecast date from arome /GetCapabilities api endpoint
27
+ logger.info('Finding latest available forecast')
28
+ session = d.get_session()
29
+ try:
30
+ capa = session.get(
31
+ va.AROM_URLS[f"{model}_capa"],
32
+ headers = {'Authorization': f"Bearer {token}"},
33
+ timeout = 60,
34
+ )
35
+ except Exception as e:
36
+ logger.error_exit(f"Failed to contact METEO FRANCE servers: {e}")
37
+
38
+ # Parse the GetCapabilities XML response to find the latest available coverage
39
+ lines = [line for line in capa.text.splitlines() if coverages[0] in line]
40
+ if lines:
41
+ # Forecast available dates look like 1970-01-01T00:00:00Z
42
+ # The last line holds the lastest available forecast run
43
+ latestRun = re.search(
44
+ r"\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}Z",
45
+ lines[-1]
46
+ )
47
+ if latestRun:
48
+ latestRun = latestRun.group()
49
+ else:
50
+ msg = "Error fetching AROM capabilities, couldn't find latest date"
51
+ logger.error_exit(msg)
52
+ else:
53
+ msg = "Error fetching AROM capabilities, couldn't find latest run"
54
+ logger.error_exit(msg)
55
+
56
+ # Select forecast prevision time based on user input
57
+ # 3600 means layer is the prevision for 1h after latestRun
58
+ times = list(range(
59
+ int(step[:-1]) * 3600,
60
+ 172800+1,
61
+ int(step[:-1]) * 3600)
62
+ )
63
+ logger.debug(f"Forecast to download are {times}")
64
+
65
+ # Generating the urls to retreive requested layers
66
+ header = {'Authorization': f"Bearer {token}"}
67
+ urls = []
68
+ for coverage in coverages:
69
+ for time in times:
70
+ paramCovId = f"&coverageid={coverage}{latestRun}"
71
+ subTime = f"&subset=time({time})"
72
+ subLat = f"&subset=lat({lat[0]},{lat[1]})"
73
+ subLon = f"&subset=long({lon[0]},{lon[1]})"
74
+ if 'SPECIFIC_HEIGHT' in coverage:
75
+ subHeight = '&subset=height(10)'
76
+ else:
77
+ subHeight = ''
78
+ paramSubset = subTime + subLat + subLon + subHeight
79
+ urls.append(va.AROM_URLS[f"{model}_cov"]+ paramCovId + paramSubset)
80
+
81
+ # Downloading the layers
82
+ layers = []
83
+ if len(urls) < 100:
84
+ layers = d.get_layers(model, urls, header)
85
+ else:
86
+ msg = f"The requested grib has {len(urls)} layers, but MeteoFrance"
87
+ msg += ' servers limit requests to 100 per minute. This program will'
88
+ msg += ' sleep 1 minute every 100 layer util the complete grib file'
89
+ msg += ' is downloaded. You might want to consider reducing the number'
90
+ msg += ' of layers by increasing the step or reducing the number of'
91
+ msg += ' data'
92
+ logger.warning(msg)
93
+ for i in range(0, len(urls), 100):
94
+ layers.extend(d.get_layers(model, urls[i:i+100], header))
95
+ if i+100 < len(urls):
96
+ logger.info('Sleeping 1 minute...')
97
+ t.sleep(60)
98
+
99
+ # Output the file once all the layers have been downloaded
100
+ file = Path(f"{model}_{latestRun}_{step}.grib2")
101
+ file.unlink(missing_ok=True)
102
+ with open(file, "wb") as outfile:
103
+ for layer in layers:
104
+ if layer:
105
+ outfile.write(layer)
106
+
107
+ def handle_fetch_error_arom(e):
108
+ if isinstance(e, requests.exceptions.HTTPError):
109
+ url = e.response.url
110
+ layer = re.search(r"coverageid=(.*?)__", url).group(1)
111
+ time = int(re.search(r"subset=time\(([^()]*)", url).group(1)) / 3600
112
+ logger.warning(f"Missing layer: {layer} at time: {int(time)}h")
113
+ logger.debug(f"Error was {e}")
114
+ else:
115
+ logger.error_exit(f"Download failed: {e}")
@@ -1,16 +1,18 @@
1
1
  import os
2
2
  import keyring
3
3
  import getpass
4
- import requests
5
4
 
6
- from grib2sail import variables as v
5
+ import grib2sail.downloader as d
6
+ from grib2sail import variables_arom as va
7
7
  from grib2sail.logger import logger
8
8
 
9
9
  def get_arome_token():
10
+ logger.info('Authenticating to MeteoFrance')
10
11
  appId = get_arome_appid()
12
+ session = d.get_session()
11
13
  try:
12
- response = requests.post(
13
- v.AROM_URLS['token'],
14
+ response = session.post(
15
+ va.AROM_URLS['token'],
14
16
  data = { 'grant_type': 'client_credentials' },
15
17
  headers = { 'Authorization': f"Basic {appId}" },
16
18
  timeout = 60,
@@ -0,0 +1,3 @@
1
+ MODELS = ['arome_antilles', 'arome001']
2
+ STEPS = ['1h', '3h', '6h', '12h']
3
+ DATAS = ['wind', 'wind_gust', 'pressure', 'cloud', 'rain']
@@ -0,0 +1,17 @@
1
+ import grib2sail.variables as v
2
+
3
+ AROM_DATAS = {
4
+ 'wind_u': 'U_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
5
+ 'wind_v': 'V_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
6
+ 'wind_gust': 'WIND_SPEED_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
7
+ 'pressure': 'PRESSURE__MEAN_SEA_LEVEL___',
8
+ 'cloud': 'TOTAL_CLOUD_COVER__GROUND_OR_WATER_SURFACE___'
9
+ }
10
+
11
+ AROM_URLS = {
12
+ 'token': 'https://portail-api.meteofrance.fr/token',
13
+ f"{v.MODELS[0]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
14
+ f"{v.MODELS[1]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
15
+ f"{v.MODELS[0]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
16
+ f"{v.MODELS[1]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
17
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grib2sail
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Grib files downloader for sailing purposes
5
5
  Author-email: Chinkara <poubelledechinkara@outlook.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -17,7 +17,7 @@ Dynamic: license-file
17
17
  # GRIB2Sail
18
18
 
19
19
  ![PyPI version](https://img.shields.io/pypi/v/grib2sail.svg)
20
- ![CI](https://img.shields.io/github/workflow/status/Ch1nkara/GRIB2Sail/CI)
20
+ ![CI](https://img.shields.io/github/actions/workflow/status/Ch1nkara/GRIB2Sail/release.yml)
21
21
  ![License](https://img.shields.io/badge/license-GPL%20v3-blue.svg)
22
22
 
23
23
  Grib files downloader for sailing purposes.
@@ -37,7 +37,7 @@ To download GRIB from meteofrance's models (Aome), you must create a free
37
37
  account on meteofrance.fr. The procedure is as follow:
38
38
  1. Create an account on [the Météo-France API portal](https://portail-api.meteofrance.fr)
39
39
  2. Subscribe to the desired service (Arome)
40
- 3. Go to "My API" then, from your subscribe model: "Generate Token"
40
+ 3. Go to "My API" then, from your subscribed model: "Generate Token"
41
41
  4. Checkout the curl field at the bottom, it looks like :
42
42
  ```bash
43
43
  curl -k -X POST https://portal-api.meteofrance.fr/token -d "grant_type=client_credentials" -H "Authorization: Basic ABCDEF1234abcdef"
@@ -8,9 +8,11 @@ grib2sail/__main__.py
8
8
  grib2sail/_version.py
9
9
  grib2sail/cli.py
10
10
  grib2sail/downloader.py
11
+ grib2sail/downloader_arom.py
11
12
  grib2sail/logger.py
12
13
  grib2sail/token.py
13
14
  grib2sail/variables.py
15
+ grib2sail/variables_arom.py
14
16
  grib2sail.egg-info/PKG-INFO
15
17
  grib2sail.egg-info/SOURCES.txt
16
18
  grib2sail.egg-info/dependency_links.txt
@@ -1,102 +0,0 @@
1
- from rich.progress import Progress
2
- from pathlib import Path
3
- import requests
4
- import re
5
-
6
- from grib2sail.logger import logger
7
- import grib2sail.variables as v
8
- from grib2sail.token import get_arome_token
9
-
10
- def download_gribs(m, s, d, lat, lon):
11
- if m.startswith('arome'):
12
- download_arome(m, s, d, lat, lon)
13
- else:
14
- logger.error_exit(f"Downloader failed: unexpected model: {m}")
15
-
16
- def download_arome(model, step, data, lat, lon):
17
- token = get_arome_token()
18
- logger.debug('Token for AROME API retrieived')
19
-
20
- # Coverages list all the individual layers categories to download
21
- coverages = []
22
- if v.DATAS[0] in data:
23
- coverages += [v.AROM_DATAS['wind_u'], v.AROM_DATAS['wind_v']]
24
- if v.DATAS[1] in data:
25
- coverages += [v.AROM_DATAS['wind_gust']]
26
- if v.DATAS[2] in data:
27
- coverages += [v.AROM_DATAS['pressure']]
28
- if v.DATAS[3] in data:
29
- coverages += [v.AROM_DATAS['cloud']]
30
-
31
- # Get latest available forecast date from arome /GetCapabilities api endpoint
32
- try:
33
- capa = requests.get(
34
- v.AROM_URLS[f"{model}_capa"],
35
- headers = {'Authorization': f"Bearer {token}"},
36
- timeout = 60,
37
- )
38
- except Exception as e:
39
- logger.error_exit(f"Failed to contact METEO FRANCE servers: {e}")
40
-
41
- # Parse the GetCapabilities XML response to find the latest available coverage
42
- lines = [line for line in capa.text.splitlines() if coverages[0] in line]
43
- if lines:
44
- # Forecast available dates look like 1970-01-01T00:00:00Z
45
- # The last line holds the lastest available forecast run
46
- latestRun = re.search(
47
- r"\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}Z",
48
- lines[-1]
49
- )
50
- if latestRun:
51
- latestRun = latestRun.group()
52
- else:
53
- msg = "Error fetching AROM capabilities, couldn't find latest date"
54
- logger.error_exit(msg)
55
- else:
56
- msg = "Error fetching AROM capabilities, couldn't find latest run"
57
- logger.error_exit(msg)
58
-
59
- # Download all layers as individual grib files into one output file
60
- file = Path(f"{model}_{latestRun}_{step}.grib2")
61
- file.unlink(missing_ok=True)
62
- with open(file, "ab") as outfile, Progress() as progress:
63
- # Select forecast prevision time based on user input
64
- # 3600 means layer is the prevision for 1h after latestRun
65
- times = list(range(
66
- int(step[:-1]) * 3600,
67
- 172800+1,
68
- int(step[:-1]) * 3600)
69
- )
70
- logger.debug(f"forecast to downloads are {times}")
71
- # Showing a progress bar
72
- task = progress.add_task(
73
- 'Downloading layers...',
74
- total = len(coverages) * len(times)
75
- )
76
- for coverage in coverages:
77
- for time in times:
78
- paramCovId = f"&coverageid={coverage}{latestRun}"
79
- subTime = f"&subset=time({time})"
80
- subLat = f"&subset=lat({lat[0]},{lat[1]})"
81
- subLon = f"&subset=long({lon[0]},{lon[1]})"
82
- if 'SPECIFIC_HEIGHT' in coverage:
83
- subHeight = '&subset=height(10)'
84
- else:
85
- subHeight = ''
86
- paramSubset = subTime + subLat + subLon + subHeight
87
- url=v.AROM_URLS[f"{model}_cov"]+ paramCovId + paramSubset
88
- logger.debug(f"Downloading {url}")
89
- try:
90
- r = requests.get(
91
- url,
92
- headers = {'Authorization': f"Bearer {token}"},
93
- timeout = 60)
94
- r.raise_for_status()
95
- outfile.write(r.content)
96
- except requests.exceptions.HTTPError:
97
- logger.warning(
98
- f"Failed to download {url} status code is {r.status_code}"
99
- )
100
- except Exception as e:
101
- logger.error_exit(f"Download failed: {e}", to_clean=[file])
102
- progress.update(task, advance=1)
@@ -1,23 +0,0 @@
1
- MODELS = ['arome_antilles', 'arome001']
2
- STEPS = ['1h', '3h', '6h', '12h']
3
- DATAS = ['wind', 'wind_gust', 'pressure', 'cloud', 'rain']
4
-
5
- AROM_DATAS = {
6
- 'wind_u': 'U_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
7
- 'wind_v': 'V_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
8
- 'wind_gust': 'WIND_SPEED_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___',
9
- 'pressure': 'PRESSURE__MEAN_SEA_LEVEL___',
10
- 'cloud': 'TOTAL_CLOUD_COVER__GROUND_OR_WATER_SURFACE___'
11
- }
12
-
13
- AROM_URLS = {
14
- 'token': 'https://portail-api.meteofrance.fr/token',
15
- f"{MODELS[0]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
16
- f"{MODELS[1]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
17
- f"{MODELS[0]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
18
- f"{MODELS[1]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
19
- }
20
-
21
-
22
-
23
-
File without changes
File without changes
File without changes
File without changes
File without changes