eccodes 2.44.0__tar.gz → 2.46.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 (130) hide show
  1. {eccodes-2.44.0 → eccodes-2.46.0}/CHANGELOG.rst +16 -0
  2. {eccodes-2.44.0 → eccodes-2.46.0}/CONTRIBUTING.rst +1 -1
  3. {eccodes-2.44.0 → eccodes-2.46.0}/MANIFEST.in +4 -0
  4. {eccodes-2.44.0 → eccodes-2.46.0}/PKG-INFO +20 -3
  5. {eccodes-2.44.0 → eccodes-2.46.0}/README.rst +2 -2
  6. {eccodes-2.44.0 → eccodes-2.46.0}/ci/requirements-docs.txt +1 -1
  7. {eccodes-2.44.0 → eccodes-2.46.0}/docs/conf.py +1 -1
  8. eccodes-2.46.0/eccodes/highlevel/_bufr/BUFR_Snippets.md +463 -0
  9. eccodes-2.46.0/eccodes/highlevel/_bufr/BUFR_Tour.md +377 -0
  10. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/coder.py +1 -5
  11. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/data.py +1 -1
  12. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes.egg-info/PKG-INFO +20 -3
  13. eccodes-2.46.0/eccodes.egg-info/SOURCES.txt +128 -0
  14. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/bindings.py +1 -1
  15. {eccodes-2.44.0 → eccodes-2.46.0}/setup.py +9 -1
  16. eccodes-2.46.0/tests/sample-data/acars.attributes +291 -0
  17. eccodes-2.46.0/tests/sample-data/acars.bufr +0 -0
  18. eccodes-2.46.0/tests/sample-data/acars.items +82 -0
  19. eccodes-2.46.0/tests/sample-data/ahi-himawari-8.attributes +181 -0
  20. eccodes-2.46.0/tests/sample-data/ahi-himawari-8.bufr +0 -0
  21. eccodes-2.46.0/tests/sample-data/ahi-himawari-8.items +614 -0
  22. eccodes-2.46.0/tests/sample-data/amsu-a-noaa-19.attributes +241 -0
  23. eccodes-2.46.0/tests/sample-data/amsu-a-noaa-19.bufr +0 -0
  24. eccodes-2.46.0/tests/sample-data/amsu-a-noaa-19.items +459 -0
  25. eccodes-2.46.0/tests/sample-data/amv-goes-9.attributes +331 -0
  26. eccodes-2.46.0/tests/sample-data/amv-goes-9.bufr +0 -0
  27. eccodes-2.46.0/tests/sample-data/amv-goes-9.items +763 -0
  28. eccodes-2.46.0/tests/sample-data/amv-insat-3d.attributes +241 -0
  29. eccodes-2.46.0/tests/sample-data/amv-insat-3d.bufr +0 -0
  30. eccodes-2.46.0/tests/sample-data/amv-insat-3d.items +906 -0
  31. eccodes-2.46.0/tests/sample-data/amv-meteosat-9.attributes +331 -0
  32. eccodes-2.46.0/tests/sample-data/amv-meteosat-9.bufr +0 -0
  33. eccodes-2.46.0/tests/sample-data/amv-meteosat-9.items +1232 -0
  34. eccodes-2.46.0/tests/sample-data/amv-noaa-20.attributes +326 -0
  35. eccodes-2.46.0/tests/sample-data/amv-noaa-20.bufr +0 -0
  36. eccodes-2.46.0/tests/sample-data/amv-noaa-20.items +1008 -0
  37. eccodes-2.46.0/tests/sample-data/aura-omi-ak.attributes +191 -0
  38. eccodes-2.46.0/tests/sample-data/aura-omi-ak.bufr +0 -0
  39. eccodes-2.46.0/tests/sample-data/aura-omi-ak.items +231 -0
  40. eccodes-2.46.0/tests/sample-data/buoy-drifting.attributes +427 -0
  41. eccodes-2.46.0/tests/sample-data/buoy-drifting.bufr +0 -0
  42. eccodes-2.46.0/tests/sample-data/buoy-drifting.items +158 -0
  43. eccodes-2.46.0/tests/sample-data/geos-abi-goes-16.attributes +296 -0
  44. eccodes-2.46.0/tests/sample-data/geos-abi-goes-16.bufr +0 -0
  45. eccodes-2.46.0/tests/sample-data/geos-abi-goes-16.items +520 -0
  46. eccodes-2.46.0/tests/sample-data/geos-mviri-meteosat-7.attributes +236 -0
  47. eccodes-2.46.0/tests/sample-data/geos-mviri-meteosat-7.bufr +0 -0
  48. eccodes-2.46.0/tests/sample-data/geos-mviri-meteosat-7.items +205 -0
  49. eccodes-2.46.0/tests/sample-data/hdob.attributes +416 -0
  50. eccodes-2.46.0/tests/sample-data/hdob.bufr +0 -0
  51. eccodes-2.46.0/tests/sample-data/hdob.items +150 -0
  52. eccodes-2.46.0/tests/sample-data/rwp.attributes +151 -0
  53. eccodes-2.46.0/tests/sample-data/rwp.bufr +0 -0
  54. eccodes-2.46.0/tests/sample-data/rwp.items +90 -0
  55. eccodes-2.46.0/tests/sample-data/rwp_jma.attributes +96 -0
  56. eccodes-2.46.0/tests/sample-data/rwp_jma.bufr +0 -0
  57. eccodes-2.46.0/tests/sample-data/rwp_jma.items +233 -0
  58. eccodes-2.46.0/tests/sample-data/saral-altika.attributes +371 -0
  59. eccodes-2.46.0/tests/sample-data/saral-altika.bufr +0 -0
  60. eccodes-2.46.0/tests/sample-data/saral-altika.items +905 -0
  61. eccodes-2.46.0/tests/sample-data/sral_sentinel_3a.attributes +536 -0
  62. eccodes-2.46.0/tests/sample-data/sral_sentinel_3a.bufr +0 -0
  63. eccodes-2.46.0/tests/sample-data/sral_sentinel_3a.items +929 -0
  64. eccodes-2.46.0/tests/sample-data/synop.attributes +963 -0
  65. eccodes-2.46.0/tests/sample-data/synop.bufr +0 -0
  66. eccodes-2.46.0/tests/sample-data/synop.items +348 -0
  67. eccodes-2.46.0/tests/sample-data/synop_multi_subset.attributes +266 -0
  68. eccodes-2.46.0/tests/sample-data/synop_multi_subset.bufr +0 -0
  69. eccodes-2.46.0/tests/sample-data/synop_multi_subset.items +671 -0
  70. eccodes-2.46.0/tests/sample-data/temp.attributes +743 -0
  71. eccodes-2.46.0/tests/sample-data/temp.bufr +0 -0
  72. eccodes-2.46.0/tests/sample-data/temp.items +361 -0
  73. eccodes-2.46.0/tests/sample-data/terra-modis-aerosol.attributes +156 -0
  74. eccodes-2.46.0/tests/sample-data/terra-modis-aerosol.bufr +0 -0
  75. eccodes-2.46.0/tests/sample-data/terra-modis-aerosol.items +161 -0
  76. eccodes-2.46.0/tests/sample-data/wave.attributes +201 -0
  77. eccodes-2.46.0/tests/sample-data/wave.bufr +0 -0
  78. eccodes-2.46.0/tests/sample-data/wave.items +128 -0
  79. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_data.py +19 -0
  80. eccodes-2.44.0/eccodes.egg-info/SOURCES.txt +0 -63
  81. {eccodes-2.44.0 → eccodes-2.46.0}/.dockerignore +0 -0
  82. {eccodes-2.44.0 → eccodes-2.46.0}/Dockerfile +0 -0
  83. {eccodes-2.44.0 → eccodes-2.46.0}/LICENSE +0 -0
  84. {eccodes-2.44.0 → eccodes-2.46.0}/Makefile +0 -0
  85. {eccodes-2.44.0 → eccodes-2.46.0}/builder.py +0 -0
  86. {eccodes-2.44.0 → eccodes-2.46.0}/ci/requirements-dev.txt +0 -0
  87. {eccodes-2.44.0 → eccodes-2.46.0}/ci/requirements-docs.in +0 -0
  88. {eccodes-2.44.0 → eccodes-2.46.0}/ci/requirements-tests.in +0 -0
  89. {eccodes-2.44.0 → eccodes-2.46.0}/ci/requirements-tests.txt +0 -0
  90. {eccodes-2.44.0 → eccodes-2.46.0}/docs/_static/.gitkeep +0 -0
  91. {eccodes-2.44.0 → eccodes-2.46.0}/docs/index.rst +0 -0
  92. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/__init__.py +0 -0
  93. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/__main__.py +0 -0
  94. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/eccodes.py +0 -0
  95. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/__init__.py +0 -0
  96. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/__init__.py +0 -0
  97. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/common.py +0 -0
  98. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/header.py +0 -0
  99. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/helpers.py +0 -0
  100. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/message.py +0 -0
  101. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/tables.py +0 -0
  102. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/tree.py +0 -0
  103. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/_bufr/view.py +0 -0
  104. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/message.py +0 -0
  105. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes/highlevel/reader.py +0 -0
  106. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes.egg-info/dependency_links.txt +0 -0
  107. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes.egg-info/requires.txt +0 -0
  108. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes.egg-info/top_level.txt +0 -0
  109. {eccodes-2.44.0 → eccodes-2.46.0}/eccodes.egg-info/zip-safe +0 -0
  110. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/__init__.py +0 -0
  111. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/eccodes.h +0 -0
  112. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/errors.py +0 -0
  113. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/grib_api.h +0 -0
  114. {eccodes-2.44.0 → eccodes-2.46.0}/gribapi/gribapi.py +0 -0
  115. {eccodes-2.44.0 → eccodes-2.46.0}/setup.cfg +0 -0
  116. {eccodes-2.44.0 → eccodes-2.46.0}/tests/examples/__init__.py +0 -0
  117. {eccodes-2.44.0 → eccodes-2.46.0}/tests/examples/attributes.py +0 -0
  118. {eccodes-2.44.0 → eccodes-2.46.0}/tests/examples/items.py +0 -0
  119. {eccodes-2.44.0 → eccodes-2.46.0}/tests/sample-data/era5-levels-members.grib +0 -0
  120. {eccodes-2.44.0 → eccodes-2.46.0}/tests/sample-data/tiggelam_cnmc_sfc.grib2 +0 -0
  121. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_20_main.py +0 -0
  122. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_20_messages.py +0 -0
  123. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_examples.py +0 -0
  124. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_header.py +0 -0
  125. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_helpers.py +0 -0
  126. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_message.py +0 -0
  127. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_bufr_workarounds.py +0 -0
  128. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_eccodes.py +0 -0
  129. {eccodes-2.44.0 → eccodes-2.46.0}/tests/test_highlevel.py +0 -0
  130. {eccodes-2.44.0 → eccodes-2.46.0}/tox.ini +0 -0
@@ -2,6 +2,22 @@
2
2
  Changelog for eccodes-python
3
3
  ============================
4
4
 
5
+ 2.46.0 (2026-02-24)
6
+ --------------------
7
+
8
+ - ECC-2219: High-level BUFR interface: set method not working for data keys
9
+ - ECC-2220: Fix inadvertent string truncation in uncompressed BUFR messages
10
+ - Fix path to BUFR md files
11
+ - Add support for Python version 3.14
12
+
13
+ 2.45.0 (2025-01-16)
14
+ --------------------
15
+
16
+ - GitHub issue 372: Option "--no-binary" does not work
17
+ See: `#372 <https://github.com/ecmwf/eccodes/issues/372>`_.
18
+ - GitHub issue 145: test run fails
19
+ See: `#145 <https://github.com/ecmwf/eccodes/issues/145>`_.
20
+
5
21
  2.44.0 (2025-10-03)
6
22
  --------------------
7
23
 
@@ -105,7 +105,7 @@ Before you submit a pull request, check that it meets these guidelines:
105
105
  your new functionality into a function with a docstring, and add the
106
106
  feature to the list in README.rst.
107
107
 
108
- 3. The pull request should work for Python 3.9, 3.10, 3.11, 3.12, 3.13 and for PyPy2 and PyPy3.
108
+ 3. The pull request should work for Python 3.9, 3.10, 3.11, 3.12, 3.13, 3.14 and for PyPy2 and PyPy3.
109
109
  Check the tox results and make sure that the tests pass for all supported Python versions.
110
110
 
111
111
 
@@ -1,5 +1,6 @@
1
1
  include .dockerignore
2
2
  include *.rst
3
+ recursive-include eccodes *.md
3
4
  include *.yml
4
5
  include Dockerfile
5
6
  include LICENSE
@@ -16,5 +17,8 @@ recursive-include docs *.rst
16
17
  recursive-include gribapi *.h
17
18
  recursive-include tests *.grib2
18
19
  recursive-include tests *.grib
20
+ recursive-include tests *.bufr
21
+ recursive-include tests *.items
22
+ recursive-include tests *.attributes
19
23
  recursive-include tests *.ipynb
20
24
  recursive-include tests *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eccodes
3
- Version: 2.44.0
3
+ Version: 2.46.0
4
4
  Summary: Python interface to the ecCodes GRIB and BUFR decoder/encoder
5
5
  Home-page: https://github.com/ecmwf/eccodes-python
6
6
  Author: European Centre for Medium-Range Weather Forecasts (ECMWF)
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
18
19
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
20
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
21
  Classifier: Operating System :: OS Independent
@@ -164,8 +165,8 @@ the following environment variable before importing eccodes::
164
165
  Usage
165
166
  -----
166
167
 
167
- See examples of how to use the high-level BUFR interface: `BUFR_Tour.md <eccodes/highlevel/_bufr/BUFR_Tour.md>`_
168
- and `BUFR_Snippets.md <eccodes/highlevel/_bufr/BUFR_Snippets.md>`_.
168
+ See examples of how to use the high-level BUFR interface: `BUFR_Tour.md <highlevel/_bufr/BUFR_Tour.md>`_
169
+ and `BUFR_Snippets.md <highlevel/_bufr/BUFR_Snippets.md>`_.
169
170
 
170
171
  Refer to the *ecCodes* `documentation pages <https://confluence.ecmwf.int/display/ECC/Documentation>`_
171
172
  for usage.
@@ -242,6 +243,22 @@ does it submit to any jurisdiction.
242
243
  Changelog for eccodes-python
243
244
  ============================
244
245
 
246
+ 2.46.0 (2026-02-24)
247
+ --------------------
248
+
249
+ - ECC-2219: High-level BUFR interface: set method not working for data keys
250
+ - ECC-2220: Fix inadvertent string truncation in uncompressed BUFR messages
251
+ - Fix path to BUFR md files
252
+ - Add support for Python version 3.14
253
+
254
+ 2.45.0 (2025-01-16)
255
+ --------------------
256
+
257
+ - GitHub issue 372: Option "--no-binary" does not work
258
+ See: `#372 <https://github.com/ecmwf/eccodes/issues/372>`_.
259
+ - GitHub issue 145: test run fails
260
+ See: `#145 <https://github.com/ecmwf/eccodes/issues/145>`_.
261
+
245
262
  2.44.0 (2025-10-03)
246
263
  --------------------
247
264
 
@@ -127,8 +127,8 @@ the following environment variable before importing eccodes::
127
127
  Usage
128
128
  -----
129
129
 
130
- See examples of how to use the high-level BUFR interface: `BUFR_Tour.md <eccodes/highlevel/_bufr/BUFR_Tour.md>`_
131
- and `BUFR_Snippets.md <eccodes/highlevel/_bufr/BUFR_Snippets.md>`_.
130
+ See examples of how to use the high-level BUFR interface: `BUFR_Tour.md <highlevel/_bufr/BUFR_Tour.md>`_
131
+ and `BUFR_Snippets.md <highlevel/_bufr/BUFR_Snippets.md>`_.
132
132
 
133
133
  Refer to the *ecCodes* `documentation pages <https://confluence.ecmwf.int/display/ECC/Documentation>`_
134
134
  for usage.
@@ -44,5 +44,5 @@ sphinx==1.8.5
44
44
  # via -r requirements-docs.in
45
45
  sphinxcontrib-websupport==1.1.0
46
46
  # via sphinx
47
- urllib3==2.4.0
47
+ urllib3==2.6.3
48
48
  # via requests
@@ -40,7 +40,7 @@ copyright = "2017-, European Centre for Medium-Range Weather Forecasts (ECMWF)."
40
40
  # the built documents.
41
41
  #
42
42
  # The full version, including alpha/beta/rc tags.
43
- release = pkg_resources.get_distribution("eccodes-python").version
43
+ release = pkg_resources.get_distribution("eccodes").version
44
44
  # The short X.Y version.
45
45
  version = ".".join(release.split(".")[:2])
46
46
 
@@ -0,0 +1,463 @@
1
+ # BUFR Snippets
2
+
3
+ This document seeks to compile a collection of code snippets demonstrating common
4
+ (but also less common) BUFR-handling tasks, utilising both the low-level and the
5
+ high-level interface.
6
+
7
+ ## Reading from file
8
+
9
+ ### The low-level way
10
+
11
+ Read a single message:
12
+
13
+ ```python
14
+ bufr = codes_bufr_new_from_file(file)
15
+ ```
16
+
17
+ Iterate over all messages in the file:
18
+
19
+ ```python
20
+ while bufr := codes_bufr_new_from_file(file):
21
+ ...
22
+ codes_release(bufr)
23
+ ```
24
+
25
+ ### The high-level way
26
+
27
+ Read a single message:
28
+
29
+ ```python
30
+ bufr = BUFRMessage(file)
31
+ ```
32
+
33
+ Iterate over all messages in the file:
34
+
35
+ ```python
36
+ for bufr in FileReader(path, eccodes.CODES_PRODUCT_BUFR):
37
+ ...
38
+ ```
39
+
40
+ We can also use Python's `with` statement (aka context manager) to ensure
41
+ that `BUFRMessage` resources are properly released after its use:
42
+
43
+ ```python
44
+ for bufr in FileReader(path, eccodes.CODES_PRODUCT_BUFR):
45
+ with bufr:
46
+ ...
47
+ ```
48
+
49
+ ## Accessing items
50
+
51
+ ### The low-level way
52
+
53
+ Get value of a header key:
54
+
55
+ ```python
56
+ value = codes_get(bufr, 'numberOfSubsets')
57
+ ```
58
+
59
+ Get value of a data key:
60
+
61
+ ```python
62
+ codes_set(bufr, 'unpack', 1)
63
+ value = codes_get_array(bufr, 'latitude')
64
+ ```
65
+
66
+ ### The high-level way
67
+
68
+ Get value of a header key:
69
+
70
+ ```python
71
+ value = bufr['numberOfSubsets']
72
+ ```
73
+
74
+ Get value of a data key:
75
+
76
+ ```python
77
+ value = bufr['latitude']
78
+ ```
79
+
80
+ Note that we didn't need to do explicit unpacking in order to access data section keys.
81
+ This was done for us automatically.
82
+ However, had we had a needed for a more precise control over when and where the message
83
+ gets unpacked, we could do that manually by calling the `unpack()` method:
84
+
85
+ ```python
86
+ bufr.unpack()
87
+ # guaranteed to be in the unpacked state henceforth
88
+ ```
89
+
90
+ After the first invocation, subsequent calls to `unpack()` will have no effect.
91
+
92
+ ## Iterating over items
93
+
94
+ ### The low-level way
95
+
96
+ ```python
97
+ codes_set(bufr, 'unpack', 1)
98
+ it = codes_bufr_keys_iterator_new(bufr)
99
+ while codes_bufr_keys_iterator_next(it):
100
+ key = codes_bufr_keys_iterator_get_name(it)
101
+ try:
102
+ value = codes_get(bufr, key)
103
+ except ArrayTooSmallError:
104
+ value = codes_get_array(bufr, key)
105
+ ...
106
+ codes_bufr_keys_iterator_release(it)
107
+ ```
108
+
109
+ ### The high-level way
110
+
111
+ ```python
112
+ for key, value in bufr.items():
113
+ ...
114
+ ```
115
+
116
+ ## Iterating over data items only
117
+
118
+ ### The low-level way
119
+
120
+ ```python
121
+ codes_set(bufr, 'unpack', 1)
122
+ it = codes_bufr_keys_iterator_new(bufr)
123
+ while codes_bufr_keys_iterator_next(it):
124
+ key = codes_bufr_keys_iterator_get_name(it)
125
+ if codes_bufr_key_is_header(bufr, key):
126
+ continue
127
+ try:
128
+ value = codes_get(bufr, key)
129
+ except ArrayTooSmallError:
130
+ value = codes_get_array(bufr, key)
131
+ ...
132
+ codes_bufr_keys_iterator_release(it)
133
+ ```
134
+
135
+ ### The high-level way
136
+
137
+ ```python
138
+ for key, value in bufr.data.items():
139
+ ...
140
+ ```
141
+
142
+ ## Creating a new message from samples
143
+
144
+ ### The low-level way
145
+
146
+ Note that 'numberOfSubsets', 'compressedData' and 'input...ReplicationFactor' must
147
+ be set _before_ the 'unexpandedDescriptor' key!
148
+
149
+ ```python
150
+ bufr = codes_bufr_new_from_samples('BUFR4_local')
151
+ codes_set('masterTablesVersionNumber', 18)
152
+ codes_set('numberOfSubsets', 5)
153
+ codes_set('compressedData', 1)
154
+ codes_set_array('inputShortDelayedDescriptorReplicationFactor', [1, 1, 1, 1, 1, 1])
155
+ codes_set_array('unexpandedDescriptors', [311010])
156
+ ```
157
+
158
+ ### The high-level way
159
+
160
+ The same restrictions apply in the high-level case:
161
+
162
+ ```python
163
+ bufr = BUFRMessage('BUFR4_local')
164
+ bufr['masterTablesVersionNumber'] = 18
165
+ bufr['numberOfSubsets'] = 5
166
+ bufr['compressedData'] = 1
167
+ bufr['inputShortDelayedDescriptorReplicationFactor'] = [1, 1, 1, 1, 1, 1]
168
+ bufr['unexpandedDescriptors'] = [311010]
169
+ ```
170
+
171
+ (In the future version of eccodes-python we are planning to relax some of these restrictions.
172
+ Specifically, the need to set 'input...ReplicationFactor' upfront.)
173
+
174
+ ## Creating a message copy with ECMWF's local section 2
175
+
176
+ ### The low-level way
177
+
178
+ ```python
179
+ old = codes_bufr_new_from_file(foreign_file)
180
+ new = codes_bufr_new_from_samples('BUFR4_local_satellite')
181
+ # Copy header keys (section 1 only)
182
+ it = codes_bufr_keys_iterator_new(old)
183
+ codes_skip_read_only(it)
184
+ while codes_bufr_keys_iterator_next(it):
185
+ key = codes_bufr_keys_iterator_get_name(it)
186
+ if key == 'unexpandedDescriptors':
187
+ break
188
+ value = codes_get(old, key)
189
+ codes_set(new, value)
190
+ # Copy template-related keys
191
+ codes_set(old, 'unpack', 1)
192
+ value = codes_get_array(old, 'dataPresentIndicator')
193
+ codes_set_array(new, 'inputDataPresentIndicator', value)
194
+ value = codes_get_array(old, 'delayedDescriptorReplicationFactor')
195
+ codes_set_array(new, 'inputDelayedDescriptorReplicationFactor', value)
196
+ value = codes_get_array(old, 'unexpandedDescriptors')
197
+ codes_set_array(new, 'unexpandedDescriptors', value)
198
+ # Copy data keys
199
+ codes_copy_data(old, new)
200
+ # Set section 2 header keys
201
+ ...
202
+ value = codes_get_array(old, 'latitude')'
203
+ codes_set(new, 'localLatitude1', min(value))
204
+ codes_set(new, 'localLatitude2', max(value))
205
+ ...
206
+ codes_set(new, 'satelliteID', 123)
207
+ ```
208
+
209
+ ### The high-level way
210
+
211
+ ```python
212
+ old = BUFRMessage(foreign_file)
213
+ new = BUFRMessage('BUFR4_local_satellite')
214
+ old.copy_to(new)
215
+ new['satelliteID'] = 123
216
+ ```
217
+ Note that unlike in the low-level example, we don't need to set every section 2 key
218
+ explicitly. The only exception is the 'satelliteID' (or, in the case of uncompressed
219
+ messages, the 'ident' key).
220
+ All other section 2 keys are set automatically on message packing.
221
+
222
+ ## Iterating over subsets of uncompressed multi-subset messages
223
+
224
+ ### The low-level way
225
+
226
+ ```python
227
+ subset_count = codes_get(bufr, 'numberOfSubsets')
228
+ codes_set(bufr, 'unpack', 1)
229
+ for n in range(1, subset_count + 1):
230
+ print(f'Working on subset {n}')
231
+ block_number = codes_get(bufr, f'/subsetNumber={n}/blockNumber')
232
+ ...
233
+ # BUT THIS IS CURRENTLY NOT ALLOWED!!!
234
+ # pressure1 = codes_get(bufr, f'/subsetNumber={n}/#1#pressure')
235
+ ...
236
+ ```
237
+
238
+ ### The high-level way
239
+
240
+ ```python
241
+ for n, subset in enumerate(bufr.data, start=1):
242
+ print(f'Working on subset {n}')
243
+ block_number = subset['blockNumber']
244
+ ...
245
+ pressure1 = subset['#1#pressure']
246
+ ...
247
+ ```
248
+
249
+ ## Working with uncompressed multi-subset messages with nested replications
250
+
251
+ The following is an example of a wind profiler BUFR message (see tests/sample-data/rwp\_jma.bufr).
252
+ Each subset in this uncompressed BUFR message represents a unique station.
253
+ Within each subset there is one static block at the top which contains some
254
+ common metadata, followed by a doubly-nested delayed replication block with
255
+ the actual profile data.
256
+ The outer replication corresponds to profiles measured at different times, and
257
+ the inner replication contains the individual level data for each of the profiles.
258
+ The tricky part about this message is that the number of profiles and the number
259
+ of levels varies across stations, which means we can't navigate the structure by doing a simple
260
+ rank arithmetic.
261
+ We have to employ more involved calculations in order to navigate the structure correctly.
262
+
263
+ ### The low-level way
264
+
265
+ ```python
266
+ subset_count = codes_get(bufr, 'numberOfSubsets')
267
+ codes_set(bufr, 'unpack', 1)
268
+ for n in range(1, subset_count + 1):
269
+ codes_set(bufr, 'extractSubset', n)
270
+ codes_set(bufr, 'doExtractSubsets', 1)
271
+ sbufr = codes_clone(bufr)
272
+ codes_set(sbufr, 'unpack', 1)
273
+ # Station info
274
+ ...
275
+ latitude = codes_get(sbufr, 'latitude')
276
+ ...
277
+ factors = codes_get_array(sbufr, 'delayedDescriptorReplicationFactor')
278
+ station_counts = factors[0]
279
+ level_counts = factors[1:]
280
+ assert len(level_counts) == station_counts
281
+ level_offsets = [0] + list(itertools.accumulate(level_counts[:-1]))
282
+ us = codes_get_array(sbufr, 'u')
283
+ vs = codes_get_array(sbufr, 'u')
284
+ ...
285
+ for prof, (off, len) in enumerate(zip(level_offsets, level_counts)):
286
+ # Profile info
287
+ ...
288
+ hour = codes_get_array(sbufr, 'hour')[prof]
289
+ ...
290
+ # Profile data
291
+ ...
292
+ u = np.ma.masked_equal(us[off:off+len], CODES_MISSING_DOUBLE)
293
+ v = np.ma.masked_equal(vs[off:off+len], CODES_MISSING_DOUBLE)
294
+ ...
295
+ codes_release(sbufr)
296
+ ```
297
+
298
+ ### The high-level way
299
+
300
+ With the high-level interface, navigating the hierarchical structure is much simpler:
301
+
302
+ ```python
303
+ for subset in bufr.data:
304
+ station_info, station_profiles = subset
305
+ # Station info
306
+ ...
307
+ latitude = station_info['latitude']
308
+ ...
309
+ for (profile_info, profile_data) in station_profiles:
310
+ # Profile info
311
+ ...
312
+ hour = profile_info['hour']
313
+ ...
314
+ # Profile data
315
+ ...
316
+ u = profile_data['u']
317
+ ...
318
+ ```
319
+
320
+ ## Extracting subsets by index
321
+
322
+ ### The low-level way
323
+
324
+ ```python
325
+ codes_set(bufr, 'unpack', 1)
326
+ codes_set(bufr, 'extractSubsetList', [2, 4, 8])
327
+ codes_set(bufr, 'doExtractSubsets', 1)
328
+ new = codes_clone(bufr)
329
+ ```
330
+
331
+ ### The high-level way
332
+
333
+ ```python
334
+ new = bufr.copy(subsets=[1, 3, 7])
335
+ ```
336
+ Note that unlike in the low-level example where we used 1-based subset
337
+ numbers, here we use subset indices which are 0-based!
338
+
339
+ ## Extracting subsets by range / slice
340
+
341
+ ### The low-level way
342
+
343
+ ```python
344
+ codes_set(bufr, 'unpack', 1)
345
+ codes_set(bufr, 'extractSubsetIntervalStart', 2)
346
+ codes_set(bufr, 'extractSubsetIntervalEnd', 8)
347
+ codes_set(bufr, 'doExtractSubsets', 1)
348
+ new = codes_clone(bufr)
349
+ ```
350
+
351
+ ### The high-level way
352
+
353
+ ```python
354
+ new = bufr.copy(subsets=range(1, 9))
355
+ ```
356
+ or
357
+ ```python
358
+ new = bufr.copy(subsets=slice(1, 9))
359
+ ```
360
+ Note that the bounds in the high-level example follow standard Python
361
+ convention, whereas in the low-level example they are 1-based and inclusive!
362
+
363
+ ## Extracting subsets by date & time
364
+
365
+ ### The low-level way
366
+
367
+ ```python
368
+ codes_set(bufr, 'unpack', 1)
369
+ start = datetime(2000, 2, 4, 8, 16)
370
+ end = start + timedelta(seconds=32)
371
+ for suffix, value in zip(('Start', 'End'), (start, end)):
372
+ codes_set(bufr, 'extractDateTimeYear' + suffix, value.year)
373
+ codes_set(bufr, 'extractDateTimeMonth' + suffix, value.month)
374
+ codes_set(bufr, 'extractDateTimeDay' + suffix, value.day)
375
+ codes_set(bufr, 'extractDateTimeHour' + suffix, value.hour)
376
+ codes_set(bufr, 'extractDateTimeMinute' + suffix, value.minute)
377
+ codes_set(bufr, 'extractDateTimeSecond' + suffix, value.second)
378
+ codes_set(bufr, 'doExtractDateTime', 1)
379
+ new = codes_clone(bufr)
380
+ ```
381
+
382
+ ### The high-level way
383
+
384
+ ```python
385
+ start = datetime(2000, 2, 4, 8, 16)
386
+ end = start + timedelta(seconds=32)
387
+ new = bufr.copy(subsets=slice(start, end))
388
+ ```
389
+ Note that the bounds of datetime slices are inclusive on both sides!
390
+
391
+ ## Extracting subsets within a lat-lon area
392
+
393
+ ### The low-level way
394
+
395
+ ```python
396
+ codes_set(bufr, 'unpack', 1)
397
+ codes_set(bufr, 'extractAreaNorthLatitude', 64.0)
398
+ codes_set(bufr, 'extractAreaSouthLatitude', 32.0)
399
+ codes_set(bufr, 'extractAreaEastLongitude', 16.0)
400
+ codes_set(bufr, 'extractAreaWestLongitude', 8.0)
401
+ codes_set(bufr, 'doExtractArea', 1)
402
+ new = codes_clone(bufr)
403
+ ```
404
+
405
+ ### The high-level way
406
+
407
+ Unlike in the previous examples, there is no built-in option to extract subsets by area.
408
+ However, we can pass an arbitrary boolean mask argument to the `copy()`
409
+ method, which provides unlimited flexibility:
410
+
411
+ ```python
412
+ lat = bufr['latitude']
413
+ lon = bufr['longitude']
414
+ lat_mask = np.logical_and(lat <= 64.0, lat >= 32.0)
415
+ lon_mask = np.logical_and(lon <= 16.0, lon >= 8.0)
416
+ mask = np.logical_and(lat_mask, lon_mask)
417
+ new = bufr.copy(subsets=mask)
418
+ ```
419
+
420
+ ## Thinning subsets
421
+
422
+ ### The low-level way
423
+
424
+ ```python
425
+ codes_set(bufr, 'unpack', 1)
426
+ codes_set(bufr, 'simpleThinningSkip', 4)
427
+ codes_set(bufr, 'doSimpleThinning', 1)
428
+ new = codes_clone(bufr)
429
+ ```
430
+ This will extract subset numbers 1, 6, 11, etc.
431
+
432
+ ### The high-level way
433
+
434
+ Although the high-level interface doesn't provide bespoke option for
435
+ the thinning, we can achieve the same by using slices:
436
+
437
+ ```python
438
+ new = bufr.copy(subsets=slice(None, None, 5))
439
+ ```
440
+ Note that the slice step must be 1 larger than the thinning step to get the
441
+ same result.
442
+
443
+ ## Writing to file
444
+
445
+ ### The low-level way
446
+
447
+ ```python
448
+ file = open('output.bufr', 'wb')
449
+ codes_set(bufr, 'pack', 1)
450
+ codes_write(bufr, file)
451
+ ```
452
+
453
+ ### The high-level way
454
+
455
+ ```python
456
+ file = open('output.bufr', 'wb')
457
+ bufr.write_to(file)
458
+ ```
459
+ Notice that in contrast to the low-level example, we didn't need to
460
+ pack the message explicitly before writing.
461
+ This was done for us automatically.
462
+ But, if needed, packing can also be triggered manually at any time by calling
463
+ the `pack()` method.