Skip to content

Commit ecf8deb

Browse files
committed
Merge release/1.2.0
2 parents e7e7138 + 8a5823e commit ecf8deb

File tree

9 files changed

+175
-34
lines changed

9 files changed

+175
-34
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,4 @@ venv.bak/
7777

7878
# local ignores
7979
.docker-tox/
80-
tests/sample-data/cds*.grib
81-
tests/sample-data/cds*.nc
8280
*.idx

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Changelog for eccodes-python
33
============================
44

5+
1.2.0 (2021-03-23)
6+
--------------------
7+
8+
- Added test for multi-field GRIBs
9+
- Fix deprecation warning: `np.float` is a deprecated alias for the builtin `float`
10+
- Experimental feature: grib_nearest_find
11+
12+
513
1.1.0 (2021-01-20)
614
--------------------
715

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ https://software.ecmwf.int/wiki/display/ECC/ecCodes+installation
4747
You may run a simple selfcheck command to ensure that your system is set up correctly::
4848

4949
$ python -m eccodes selfcheck
50-
Found: ecCodes v2.20.0.
50+
Found: ecCodes v2.21.0.
5151
Your system is ready.
5252

5353

eccodes/eccodes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
from gribapi import CODES_PRODUCT_ANY
2222
from gribapi import GRIB_MISSING_DOUBLE as CODES_MISSING_DOUBLE
2323
from gribapi import GRIB_MISSING_LONG as CODES_MISSING_LONG
24+
from gribapi import GRIB_NEAREST_SAME_GRID as CODES_GRIB_NEAREST_SAME_GRID
25+
from gribapi import GRIB_NEAREST_SAME_DATA as CODES_GRIB_NEAREST_SAME_DATA
26+
from gribapi import GRIB_NEAREST_SAME_POINT as CODES_GRIB_NEAREST_SAME_POINT
2427

2528
from gribapi import gts_new_from_file as codes_gts_new_from_file
2629
from gribapi import metar_new_from_file as codes_metar_new_from_file
@@ -102,6 +105,11 @@
102105
from gribapi import grib_is_defined as codes_is_defined
103106
from gribapi import grib_find_nearest as codes_grib_find_nearest
104107
from gribapi import grib_find_nearest_multiple as codes_grib_find_nearest_multiple
108+
109+
from gribapi import grib_nearest_new as codes_grib_nearest_new
110+
from gribapi import grib_nearest_delete as codes_grib_nearest_delete
111+
from gribapi import grib_nearest_find as codes_grib_nearest_find
112+
105113
from gribapi import grib_get_native_type as codes_get_native_type
106114
from gribapi import grib_get as codes_get
107115
from gribapi import grib_get_array as codes_get_array

gribapi/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from .gribapi import bindings_version
1616

1717
# The minimum required version for the ecCodes package
18-
min_reqd_version_str = "2.20.0"
19-
min_reqd_version_int = 22000
18+
min_reqd_version_str = "2.21.0"
19+
min_reqd_version_int = 22100
2020

2121
if lib.grib_get_api_version() < min_reqd_version_int:
2222
print(

gribapi/bindings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import cffi
2323

24-
__version__ = "1.1.0"
24+
__version__ = "1.2.0"
2525

2626
LOG = logging.getLogger(__name__)
2727

gribapi/gribapi.py

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
GRIB_MISSING_DOUBLE = -1e100
6565
GRIB_MISSING_LONG = 2147483647
6666

67+
# Constants for GRIB nearest neighbour
68+
GRIB_NEAREST_SAME_GRID = 1 << 0
69+
GRIB_NEAREST_SAME_DATA = 1 << 1
70+
GRIB_NEAREST_SAME_POINT = 1 << 2
6771

6872
# ECC-1029: Disable function-arguments type-checking unless
6973
# environment variable is defined and equal to 1
@@ -813,7 +817,6 @@ def grib_keys_iterator_delete(iterid):
813817
@param iterid keys iterator id created with @ref grib_keys_iterator_new
814818
@exception CodesInternalError
815819
"""
816-
# aa: THIS LEAKS MEMORY as it doesn't free all the connected iterators
817820
kih = get_grib_keys_iterator(iterid)
818821
lib.grib_keys_iterator_delete(kih)
819822

@@ -829,7 +832,6 @@ def grib_keys_iterator_get_name(iterid):
829832
@return key name to be retrieved
830833
@exception CodesInternalError
831834
"""
832-
# aa: missing call to grib_keys_iterator_get_accessor
833835
kih = get_grib_keys_iterator(iterid)
834836
name = lib.grib_keys_iterator_get_name(kih)
835837
return ffi.string(name).decode(ENC)
@@ -1582,7 +1584,7 @@ def grib_get_double_element(msgid, key, index):
15821584
"""
15831585
h = get_handle(msgid)
15841586
value_p = ffi.new("double*")
1585-
err = lib.grib_get_double_element(h, key.encode(ENC), index, value_p) # TODO
1587+
err = lib.grib_get_double_element(h, key.encode(ENC), index, value_p)
15861588
GRIB_CHECK(err)
15871589
return value_p[0]
15881590

@@ -2356,7 +2358,7 @@ def _convert_struct_to_dict(s):
23562358

23572359
def codes_bufr_extract_headers(filepath, is_strict=True):
23582360
"""
2359-
@brief BUFR header extraction (EXPERIMENTAL FEATURE)
2361+
@brief BUFR header extraction
23602362
23612363
@param filepath path of input BUFR file
23622364
@param is_strict fail as soon as any invalid BUFR message is encountered
@@ -2407,7 +2409,7 @@ def codes_bufr_key_is_header(msgid, key):
24072409

24082410
def codes_extract_offsets(filepath, product_kind, is_strict=True):
24092411
"""
2410-
@brief Message offset extraction (EXPERIMENTAL FEATURE)
2412+
@brief Message offset extraction
24112413
24122414
@param filepath path of input file
24132415
@param is_strict fail as soon as any invalid message is encountered
@@ -2430,3 +2432,80 @@ def codes_extract_offsets(filepath, product_kind, is_strict=True):
24302432
while i < num_messages:
24312433
yield offsets[i]
24322434
i += 1
2435+
2436+
2437+
# -------------------------------
2438+
# EXPERIMENTAL FEATURES
2439+
# -------------------------------
2440+
@require(msgid=int)
2441+
def grib_nearest_new(msgid):
2442+
h = get_handle(msgid)
2443+
err, nid = err_last(lib.grib_nearest_new)(h)
2444+
GRIB_CHECK(err)
2445+
return put_grib_nearest(nid)
2446+
2447+
2448+
def put_grib_nearest(nid):
2449+
return int(ffi.cast("size_t", nid))
2450+
2451+
2452+
def get_grib_nearest(nid):
2453+
return ffi.cast("grib_nearest*", nid)
2454+
2455+
2456+
@require(nid=int)
2457+
def grib_nearest_delete(nid):
2458+
nh = get_grib_nearest(nid)
2459+
lib.grib_nearest_delete(nh)
2460+
2461+
2462+
@require(nid=int, gribid=int)
2463+
def grib_nearest_find(nid, gribid, inlat, inlon, flags, is_lsm=False, npoints=4):
2464+
# flags has to be one of:
2465+
# GRIB_NEAREST_SAME_GRID
2466+
# GRIB_NEAREST_SAME_DATA
2467+
# GRIB_NEAREST_SAME_POINT
2468+
if npoints != 4:
2469+
raise errors.FunctionNotImplementedError(
2470+
"grib_nearest_find npoints argument: Only 4 points supported"
2471+
)
2472+
if is_lsm:
2473+
raise errors.FunctionNotImplementedError(
2474+
"grib_nearest_find is_lsm argument: Land sea mask not supported"
2475+
)
2476+
2477+
h = get_handle(gribid)
2478+
outlats_p = ffi.new("double[]", npoints)
2479+
outlons_p = ffi.new("double[]", npoints)
2480+
values_p = ffi.new("double[]", npoints)
2481+
distances_p = ffi.new("double[]", npoints)
2482+
indexes_p = ffi.new("int[]", npoints)
2483+
size = ffi.new("size_t *")
2484+
nh = get_grib_nearest(nid)
2485+
err = lib.grib_nearest_find(
2486+
nh,
2487+
h,
2488+
inlat,
2489+
inlon,
2490+
flags,
2491+
outlats_p,
2492+
outlons_p,
2493+
values_p,
2494+
distances_p,
2495+
indexes_p,
2496+
size,
2497+
)
2498+
GRIB_CHECK(err)
2499+
result = []
2500+
for i in range(npoints):
2501+
result.append(
2502+
Bunch(
2503+
lat=outlats_p[i],
2504+
lon=outlons_p[i],
2505+
value=values_p[i],
2506+
distance=distances_p[i],
2507+
index=indexes_p[i],
2508+
)
2509+
)
2510+
2511+
return tuple(result)

tests/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ channels:
44
dependencies:
55
- attrs>=19.2
66
- cffi
7-
- eccodes=>2.20.0
7+
- eccodes>=2.20.0
88
- numpy

tests/test_eccodes.py

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from eccodes import *
2222

2323
SAMPLE_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "sample-data")
24-
TEST_DATA = os.path.join(SAMPLE_DATA_FOLDER, "era5-levels-members.grib")
24+
TEST_DATA = os.path.join(SAMPLE_DATA_FOLDER, "multi_field.grib2")
2525

2626

2727
def get_sample_fullpath(sample):
@@ -148,6 +148,17 @@ def test_gts_header():
148148
codes_gts_header(False)
149149

150150

151+
def test_extract_offsets():
152+
fpath = get_sample_fullpath("BUFR4.tmpl")
153+
if fpath is None:
154+
return
155+
is_strict = True
156+
offsets = codes_extract_offsets(fpath, CODES_PRODUCT_ANY, is_strict)
157+
offsets_list = list(offsets)
158+
assert len(offsets_list) == 1
159+
assert offsets_list[0] == 0
160+
161+
151162
# ---------------------------------------------
152163
# GRIB
153164
# ---------------------------------------------
@@ -320,6 +331,7 @@ def test_grib_get_double_element():
320331
gid = codes_grib_new_from_samples("gg_sfc_grib2")
321332
elem = codes_get_double_element(gid, "values", 1)
322333
assert math.isclose(elem, 259.9865, abs_tol=0.001)
334+
codes_release(gid)
323335

324336

325337
def test_grib_get_double_elements():
@@ -407,7 +419,7 @@ def test_grib_ecc_1042():
407419
2,
408420
3,
409421
],
410-
dtype=np.float,
422+
dtype=float,
411423
)
412424
codes_set_values(gid, write_vals)
413425
read_vals = codes_get_values(gid)
@@ -492,14 +504,35 @@ def test_grib_index_new_from_file(tmpdir):
492504

493505

494506
def test_grib_multi_support_reset_file():
495-
# TODO: read an actual multi-field GRIB here
496-
fpath = get_sample_fullpath("GRIB2.tmpl")
497-
if fpath is None:
498-
return
499-
codes_grib_multi_support_on()
500-
with open(fpath, "rb") as f:
501-
codes_grib_multi_support_reset_file(f)
502-
codes_grib_multi_support_off()
507+
try:
508+
# TODO: read an actual multi-field GRIB here
509+
fpath = get_sample_fullpath("GRIB2.tmpl")
510+
if fpath is None:
511+
return
512+
codes_grib_multi_support_on()
513+
with open(fpath, "rb") as f:
514+
codes_grib_multi_support_reset_file(f)
515+
finally:
516+
codes_grib_multi_support_off()
517+
518+
519+
def test_grib_multi_field_write(tmpdir):
520+
# Note: codes_grib_multi_new() calls codes_grib_multi_support_on()
521+
# hence the 'finally' block
522+
try:
523+
gid = codes_grib_new_from_samples("GRIB2")
524+
mgid = codes_grib_multi_new()
525+
section_num = 4
526+
for step in range(12, 132, 12):
527+
codes_set(gid, "step", step)
528+
codes_grib_multi_append(gid, section_num, mgid)
529+
output = tmpdir.join("test_grib_multi_field_write.grib2")
530+
with open(str(output), "wb") as fout:
531+
codes_grib_multi_write(mgid, fout)
532+
codes_grib_multi_release(mgid)
533+
codes_release(gid)
534+
finally:
535+
codes_grib_multi_support_off()
503536

504537

505538
def test_grib_uuid_get_set():
@@ -654,7 +687,6 @@ def test_bufr_get_message_offset():
654687
assert codes_get_message_offset(gid) == 0
655688

656689

657-
# Experimental features
658690
def test_codes_bufr_key_is_header():
659691
bid = codes_bufr_new_from_samples("BUFR4_local_satellite")
660692
assert codes_bufr_key_is_header(bid, "edition")
@@ -670,17 +702,6 @@ def test_codes_bufr_key_is_header():
670702
assert not codes_bufr_key_is_header(bid, "#6#brightnessTemperature")
671703

672704

673-
def test_extract_offsets():
674-
fpath = get_sample_fullpath("BUFR4.tmpl")
675-
if fpath is None:
676-
return
677-
is_strict = True
678-
offsets = codes_extract_offsets(fpath, CODES_PRODUCT_ANY, is_strict)
679-
offsets_list = list(offsets)
680-
assert len(offsets_list) == 1
681-
assert offsets_list[0] == 0
682-
683-
684705
def test_bufr_extract_headers():
685706
fpath = get_sample_fullpath("BUFR4_local.tmpl")
686707
if fpath is None:
@@ -695,3 +716,30 @@ def test_bufr_extract_headers():
695716
assert header["ident"].strip() == "91334"
696717
assert header["rdbtimeSecond"] == 19
697718
assert math.isclose(header["localLongitude"], 151.83)
719+
720+
721+
# ---------------------------------------------
722+
# Experimental features
723+
# ---------------------------------------------
724+
def test_grib_nearest2():
725+
if "codes_grib_nearest_new" not in dir(eccodes):
726+
return
727+
gid = codes_grib_new_from_samples("gg_sfc_grib2")
728+
lat, lon = 40, 20
729+
flags = CODES_GRIB_NEAREST_SAME_GRID | CODES_GRIB_NEAREST_SAME_POINT
730+
nid = codes_grib_nearest_new(gid)
731+
assert nid > 0
732+
nearest = codes_grib_nearest_find(nid, gid, lat, lon, flags)
733+
assert len(nearest) == 4
734+
expected_indexes = (2679, 2678, 2517, 2516)
735+
returned_indexes = (
736+
nearest[0].index,
737+
nearest[1].index,
738+
nearest[2].index,
739+
nearest[3].index,
740+
)
741+
assert sorted(expected_indexes) == sorted(returned_indexes)
742+
assert math.isclose(nearest[0].value, 295.22085, abs_tol=0.0001)
743+
assert math.isclose(nearest[2].distance, 24.16520, abs_tol=0.0001)
744+
codes_release(gid)
745+
codes_grib_nearest_delete(nid)

0 commit comments

Comments
 (0)