ecmwf-eccodes-python-b43a0f2/ 0000775 0001750 0001750 00000000000 15172126764 016337 5 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/setup.cfg 0000664 0001750 0001750 00000001113 15172126764 020154 0 ustar alastair alastair [aliases]
test = pytest
[tool:pytest]
norecursedirs =
build
dist
.tox
.docker-tox
.eggs
pep8maxlinelength = 99
mccabe-complexity = 11
filterwarnings =
ignore::FutureWarning
pep8ignore =
* E203 W503
*/__init__.py E402
eccodes/eccodes.py ALL
gribapi/errors.py ALL
gribapi/gribapi.py E501
flakes-ignore =
*/__init__.py UnusedImport
*/__init__.py ImportStarUsed
eccodes/eccodes.py ALL
eccodes/high_level/* ALL
gribapi/errors.py ALL
[coverage:run]
branch = True
[zest.releaser]
python-file-with-version = gribapi/bindings.py
ecmwf-eccodes-python-b43a0f2/gribapi/ 0000775 0001750 0001750 00000000000 15172126764 017754 5 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/gribapi/__init__.py 0000664 0001750 0001750 00000001500 15172126764 022061 0 ustar alastair alastair #
# (C) Copyright 2017- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
#
#
from .gribapi import * # noqa
from .gribapi import __version__, lib
# The minimum recommended version for the ecCodes package
min_recommended_version_str = "2.42.0"
min_recommended_version_int = 24200
if lib.grib_get_api_version() < min_recommended_version_int:
import warnings
warnings.warn(
"ecCodes {} or higher is recommended. "
"You are running version {}".format(min_recommended_version_str, __version__)
)
ecmwf-eccodes-python-b43a0f2/gribapi/grib_api.h 0000664 0001750 0001750 00000035310 15172126764 021703 0 ustar alastair alastair /*
* (C) Copyright 2017- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
typedef enum ProductKind {PRODUCT_ANY, PRODUCT_GRIB, PRODUCT_BUFR, PRODUCT_METAR, PRODUCT_GTS, PRODUCT_TAF} ProductKind;
#define GRIB_TYPE_UNDEFINED 0
#define GRIB_TYPE_LONG 1
#define GRIB_TYPE_DOUBLE 2
#define GRIB_TYPE_STRING 3
#define GRIB_TYPE_BYTES 4
#define GRIB_TYPE_SECTION 5
#define GRIB_TYPE_LABEL 6
#define GRIB_TYPE_MISSING 7
#define GRIB_KEYS_ITERATOR_SKIP_READ_ONLY 1
#define GRIB_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC 4
#define GRIB_KEYS_ITERATOR_SKIP_CODED 8
#define GRIB_KEYS_ITERATOR_SKIP_COMPUTED 16
#define GRIB_KEYS_ITERATOR_SKIP_DUPLICATES 32
#define GRIB_KEYS_ITERATOR_SKIP_FUNCTION 64
typedef struct grib_values grib_values;
struct grib_values {
const char* name;
int type;
long long_value;
double double_value;
const char* string_value;
int error;
int has_value;
int equal;
grib_values* next;
};
typedef struct grib_handle grib_handle;
typedef struct grib_multi_handle grib_multi_handle;
typedef struct grib_context grib_context;
typedef struct grib_iterator grib_iterator;
typedef struct grib_nearest grib_nearest;
typedef struct grib_keys_iterator grib_keys_iterator;
typedef struct bufr_keys_iterator bufr_keys_iterator;
typedef struct grib_index grib_index;
grib_index* grib_index_new_from_file(grib_context* c,
char* filename,const char* keys,int *err);
int grib_index_add_file(grib_index *index, const char *filename);
int grib_index_write(grib_index *index, const char *filename);
grib_index* grib_index_read(grib_context* c,const char* filename,int *err);
int grib_index_get_size(const grib_index* index,const char* key,size_t* size);
int grib_index_get_long(const grib_index* index,const char* key,
long* values,size_t *size);
int grib_index_get_double(const grib_index* index, const char* key,
double* values, size_t* size);
int grib_index_get_string(const grib_index* index,const char* key,
char** values,size_t *size);
int grib_index_select_long(grib_index* index,const char* key,long value);
int grib_index_select_double(grib_index* index,const char* key,double value);
int grib_index_select_string(grib_index* index,const char* key,char* value);
grib_handle* grib_handle_new_from_index(grib_index* index,int *err);
void grib_index_delete(grib_index* index);
int grib_count_in_file(grib_context* c, FILE* f,int* n);
grib_handle* grib_handle_new_from_file(grib_context* c, FILE* f, int* error);
grib_handle* grib_handle_new_from_message_copy(grib_context* c, const void* data, size_t data_len);
grib_handle* grib_handle_new_from_samples (grib_context* c, const char* sample_name);
grib_handle* grib_handle_clone(const grib_handle* h);
grib_handle* grib_handle_clone_headers_only(const grib_handle* h);
int grib_handle_delete(grib_handle* h);
grib_multi_handle* grib_multi_handle_new(grib_context* c);
int grib_multi_handle_append(grib_handle* h,int start_section,grib_multi_handle* mh);
int grib_multi_handle_delete(grib_multi_handle* mh);
int grib_multi_handle_write(grib_multi_handle* mh,FILE* f);
int grib_get_message(const grib_handle* h ,const void** message, size_t *message_length);
grib_iterator* grib_iterator_new(const grib_handle* h, unsigned long flags, int* error);
int grib_iterator_next(grib_iterator *i, double* lat,double* lon,double* value);
int grib_iterator_delete(grib_iterator *i);
grib_nearest* grib_nearest_new(const grib_handle* h, int* error);
int grib_nearest_find(grib_nearest *nearest, const grib_handle* h, double inlat, double inlon,
unsigned long flags,double* outlats,double* outlons,
double* values,double* distances,int* indexes,size_t *len);
int grib_nearest_delete(grib_nearest *nearest);
int grib_nearest_find_multiple(const grib_handle* h, int is_lsm,
const double* inlats, const double* inlons, long npoints,
double* outlats, double* outlons,
double* values, double* distances, int* indexes);
int grib_get_offset(const grib_handle* h, const char* key, size_t* offset);
int grib_get_size(const grib_handle* h, const char* key,size_t *size);
int grib_get_length(const grib_handle* h, const char* key,size_t *length);
int grib_get_long(const grib_handle* h, const char* key, long* value);
int grib_get_double(const grib_handle* h, const char* key, double* value);
int grib_get_double_element(const grib_handle* h, const char* key, int i, double* value);
int grib_get_double_elements(const grib_handle* h, const char* key, const int* index_array, long size, double* value);
int grib_get_string(const grib_handle* h, const char* key, char* value, size_t *length);
int grib_get_string_array(const grib_handle* h, const char* key, char** vals, size_t *length);
int grib_get_double_array(const grib_handle* h, const char* key, double* vals, size_t *length);
int grib_get_float_array(const grib_handle* h, const char* key, float* vals, size_t *length);
int grib_get_long_array(const grib_handle* h, const char* key, long* vals, size_t *length);
int grib_copy_namespace(grib_handle* dest, const char* name, grib_handle* src);
int grib_set_long(grib_handle* h, const char* key, long val);
int grib_set_double(grib_handle* h, const char* key, double val);
int grib_set_string(grib_handle* h, const char* key, const char* mesg, size_t *length);
int grib_set_double_array(grib_handle* h, const char* key , const double* vals , size_t length);
int grib_set_long_array(grib_handle* h, const char* key , const long* vals, size_t length);
int grib_set_string_array(grib_handle* h, const char *key, const char **vals, size_t length);
void grib_dump_content(const grib_handle* h, FILE* out, const char* mode, unsigned long option_flags, void* arg);
grib_context* grib_context_get_default(void);
void grib_context_delete(grib_context* c);
void grib_gts_header_on(grib_context* c);
void grib_gts_header_off(grib_context* c);
void grib_gribex_mode_on(grib_context* c);
void grib_gribex_mode_off(grib_context* c);
void grib_context_set_definitions_path(grib_context* c, const char* path);
void grib_context_set_debug(grib_context* c, int mode);
void grib_context_set_data_quality_checks(grib_context* c, int val);
void grib_context_set_samples_path(grib_context* c, const char* path);
void grib_multi_support_on(grib_context* c);
void grib_multi_support_off(grib_context* c);
void grib_multi_support_reset_file(grib_context* c, FILE* f);
long grib_get_api_version(void);
void grib_context_set_logging_file(grib_context* c, FILE* f);
char* grib_samples_path(const grib_context *c);
char* grib_definition_path(const grib_context *c);
char* grib_context_full_defs_path(grib_context* c, const char* basename);
grib_keys_iterator* grib_keys_iterator_new(grib_handle* h,unsigned long filter_flags, const char* name_space);
bufr_keys_iterator* codes_bufr_keys_iterator_new(grib_handle* h, unsigned long filter_flags);
int grib_keys_iterator_next(grib_keys_iterator* kiter);
int codes_bufr_keys_iterator_next(bufr_keys_iterator* kiter);
const char* grib_keys_iterator_get_name(const grib_keys_iterator *kiter);
char* codes_bufr_keys_iterator_get_name(const bufr_keys_iterator* kiter);
int grib_keys_iterator_delete(grib_keys_iterator* kiter);
int codes_bufr_keys_iterator_delete(bufr_keys_iterator* kiter);
int grib_keys_iterator_rewind(grib_keys_iterator* kiter);
int codes_bufr_keys_iterator_rewind(bufr_keys_iterator* kiter);
int grib_keys_iterator_set_flags(grib_keys_iterator *kiter,unsigned long flags);
const char* grib_get_error_message(int code);
int grib_get_native_type(const grib_handle* h, const char* name,int* type);
/* aa: changed off_t to long int */
int grib_get_message_offset(const grib_handle* h,long int* offset);
int grib_set_values(grib_handle* h,grib_values* grib_values , size_t arg_count);
int grib_is_missing(const grib_handle* h, const char* key, int* err);
int grib_is_defined(const grib_handle* h, const char* key);
int grib_set_missing(grib_handle* h, const char* key);
int grib_get_message_size(const grib_handle* h,size_t* size);
int parse_keyval_string(const char *grib_tool, char *arg, int values_required, int default_type, grib_values values[], int *count);
int grib_get_data(const grib_handle *h, double *lats, double *lons, double *values);
int grib_get_gaussian_latitudes(long trunc, double* lats);
int codes_is_feature_enabled(const char* feature);
int codes_get_features(char* result, size_t* length, int select);
/* EXPERIMENTAL */
typedef struct codes_bufr_header {
unsigned long message_offset;
unsigned long message_size;
/* Section 0 keys */
long edition;
/* Section 1 keys */
long masterTableNumber;
long bufrHeaderSubCentre;
long bufrHeaderCentre;
long updateSequenceNumber;
long dataCategory;
long dataSubCategory;
long masterTablesVersionNumber;
long localTablesVersionNumber;
long typicalYear;
long typicalMonth;
long typicalDay;
long typicalHour;
long typicalMinute;
long typicalSecond;
long typicalDate;
long typicalTime;
long internationalDataSubCategory;
long localSectionPresent;
long ecmwfLocalSectionPresent;
/* ECMWF local section keys */
long rdbType;
long oldSubtype;
long rdbSubtype;
char ident[9];
long localYear;
long localMonth;
long localDay;
long localHour;
long localMinute;
long localSecond;
long rdbtimeDay;
long rdbtimeHour;
long rdbtimeMinute;
long rdbtimeSecond;
long rectimeDay;
long rectimeHour;
long rectimeMinute;
long rectimeSecond;
long restricted;
long isSatellite;
double localLongitude1;
double localLatitude1;
double localLongitude2;
double localLatitude2;
double localLatitude;
double localLongitude;
long localNumberOfObservations;
long satelliteID;
long qualityControl;
long newSubtype;
long daLoop;
/* Section 3 keys */
unsigned long numberOfSubsets;
long observedData;
long compressedData;
} codes_bufr_header;
/** No error */
#define GRIB_SUCCESS 0
/** End of resource reached */
#define GRIB_END_OF_FILE -1
/** Internal error */
#define GRIB_INTERNAL_ERROR -2
/** Passed buffer is too small */
#define GRIB_BUFFER_TOO_SMALL -3
/** Function not yet implemented */
#define GRIB_NOT_IMPLEMENTED -4
/** Missing 7777 at end of message */
#define GRIB_7777_NOT_FOUND -5
/** Passed array is too small */
#define GRIB_ARRAY_TOO_SMALL -6
/** File not found */
#define GRIB_FILE_NOT_FOUND -7
/** Code not found in code table */
#define GRIB_CODE_NOT_FOUND_IN_TABLE -8
/** Array size mismatch */
#define GRIB_WRONG_ARRAY_SIZE -9
/** Key/value not found */
#define GRIB_NOT_FOUND -10
/** Input output problem */
#define GRIB_IO_PROBLEM -11
/** Message invalid */
#define GRIB_INVALID_MESSAGE -12
/** Decoding invalid */
#define GRIB_DECODING_ERROR -13
/** Encoding invalid */
#define GRIB_ENCODING_ERROR -14
/** Code cannot unpack because of string too small */
#define GRIB_NO_MORE_IN_SET -15
/** Problem with calculation of geographic attributes */
#define GRIB_GEOCALCULUS_PROBLEM -16
/** Memory allocation error */
#define GRIB_OUT_OF_MEMORY -17
/** Value is read only */
#define GRIB_READ_ONLY -18
/** Invalid argument */
#define GRIB_INVALID_ARGUMENT -19
/** Null handle */
#define GRIB_NULL_HANDLE -20
/** Invalid section number */
#define GRIB_INVALID_SECTION_NUMBER -21
/** Value cannot be missing */
#define GRIB_VALUE_CANNOT_BE_MISSING -22
/** Wrong message length */
#define GRIB_WRONG_LENGTH -23
/** Invalid key type */
#define GRIB_INVALID_TYPE -24
/** Unable to set step */
#define GRIB_WRONG_STEP -25
/** Wrong units for step (step must be integer) */
#define GRIB_WRONG_STEP_UNIT -26
/** Invalid file id */
#define GRIB_INVALID_FILE -27
/** Invalid grib id */
#define GRIB_INVALID_GRIB -28
/** Invalid index id */
#define GRIB_INVALID_INDEX -29
/** Invalid iterator id */
#define GRIB_INVALID_ITERATOR -30
/** Invalid keys iterator id */
#define GRIB_INVALID_KEYS_ITERATOR -31
/** Invalid nearest id */
#define GRIB_INVALID_NEAREST -32
/** Invalid order by */
#define GRIB_INVALID_ORDERBY -33
/** Missing a key from the fieldset */
#define GRIB_MISSING_KEY -34
/** The point is out of the grid area */
#define GRIB_OUT_OF_AREA -35
/** Concept no match */
#define GRIB_CONCEPT_NO_MATCH -36
/** Hash array no match */
#define GRIB_HASH_ARRAY_NO_MATCH -37
/** Definitions files not found */
#define GRIB_NO_DEFINITIONS -38
/** Wrong type while packing */
#define GRIB_WRONG_TYPE -39
/** End of resource */
#define GRIB_END -40
/** Unable to code a field without values */
#define GRIB_NO_VALUES -41
/** Grid description is wrong or inconsistent */
#define GRIB_WRONG_GRID -42
/** End of index reached */
#define GRIB_END_OF_INDEX -43
/** Null index */
#define GRIB_NULL_INDEX -44
/** End of resource reached when reading message */
#define GRIB_PREMATURE_END_OF_FILE -45
/** An internal array is too small */
#define GRIB_INTERNAL_ARRAY_TOO_SMALL -46
/** Message is too large for the current architecture */
#define GRIB_MESSAGE_TOO_LARGE -47
/** Constant field */
#define GRIB_CONSTANT_FIELD -48
/** Switch unable to find a matching case */
#define GRIB_SWITCH_NO_MATCH -49
/** Underflow */
#define GRIB_UNDERFLOW -50
/** Message malformed */
#define GRIB_MESSAGE_MALFORMED -51
/** Index is corrupted */
#define GRIB_CORRUPTED_INDEX -52
/** Invalid number of bits per value */
#define GRIB_INVALID_BPV -53
/** Edition of two messages is different */
#define GRIB_DIFFERENT_EDITION -54
/** Value is different */
#define GRIB_VALUE_DIFFERENT -55
/** Invalid key value */
#define GRIB_INVALID_KEY_VALUE -56
/** String is smaller than requested */
#define GRIB_STRING_TOO_SMALL -57
/** Wrong type conversion */
#define GRIB_WRONG_CONVERSION -58
/** Missing BUFR table entry for descriptor */
#define GRIB_MISSING_BUFR_ENTRY -59
/** Null pointer */
#define GRIB_NULL_POINTER -60
/** Attribute is already present, cannot add */
#define GRIB_ATTRIBUTE_CLASH -61
/** Too many attributes. Increase MAX_ACCESSOR_ATTRIBUTES */
#define GRIB_TOO_MANY_ATTRIBUTES -62
/** Attribute not found. */
#define GRIB_ATTRIBUTE_NOT_FOUND -63
/** Edition not supported. */
#define GRIB_UNSUPPORTED_EDITION -64
/** Value out of coding range */
#define GRIB_OUT_OF_RANGE -65
/** Size of bitmap is incorrect */
#define GRIB_WRONG_BITMAP_SIZE -66
/** Functionality not enabled */
#define GRIB_FUNCTIONALITY_NOT_ENABLED -67
/** Runtime error */
#define GRIB_RUNTIME_ERROR -80
ecmwf-eccodes-python-b43a0f2/gribapi/eccodes.h 0000664 0001750 0001750 00000003247 15172126764 021540 0 ustar alastair alastair /*
* (C) Copyright 2017- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
typedef struct grib_handle codes_handle;
typedef struct grib_context codes_context;
grib_handle* codes_handle_new_from_file(codes_context* c, FILE* f, ProductKind product, int* error);
codes_handle* codes_bufr_handle_new_from_samples(codes_context* c, const char* sample_name);
codes_handle* codes_handle_new_from_samples(codes_context* c, const char* sample_name);
int codes_bufr_copy_data(grib_handle* hin, grib_handle* hout);
void codes_bufr_multi_element_constant_arrays_on(codes_context* c);
void codes_bufr_multi_element_constant_arrays_off(codes_context* c);
int codes_bufr_extract_headers_malloc(codes_context* c, const char* filename, codes_bufr_header** result, int* num_messages, int strict_mode);
int codes_extract_offsets_malloc(codes_context* c, const char* filename, ProductKind product, long int** offsets, int* num_messages, int strict_mode);
int codes_extract_offsets_sizes_malloc(codes_context* c, const char* filename, ProductKind product, long int** offsets, size_t** sizes, int* num_messages, int strict_mode);
int codes_bufr_key_is_header(const codes_handle* h, const char* key, int* err);
int codes_bufr_key_is_coordinate(const codes_handle* h, const char* key, int* err);
char* codes_samples_path(const codes_context *c);
char* codes_definition_path(const codes_context *c);
ecmwf-eccodes-python-b43a0f2/gribapi/bindings.py 0000664 0001750 0001750 00000007223 15172126764 022127 0 ustar alastair alastair #
# (C) Copyright 2017- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
#
# Authors:
# Alessandro Amici - B-Open - https://bopen.eu
# Shahram Najm - ECMWF - https://www.ecmwf.int
#
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
import os
import pkgutil
import sys
import cffi
__version__ = "2.47.0"
LOG = logging.getLogger(__name__)
_MAP = {
"grib_api": "eccodes",
"gribapi": "eccodes",
}
EXTENSIONS = {
"darwin": ".dylib",
"win32": ".dll",
}
# convenient way to trace the search for the library
if int(os.environ.get("ECCODES_PYTHON_TRACE_LIB_SEARCH", "0")):
LOG.setLevel(logging.DEBUG)
LOG.addHandler(logging.StreamHandler())
def _lookup(name):
return _MAP.get(name, name)
def find_binary_libs(name):
name = _lookup(name)
env_var = "ECCODES_PYTHON_USE_FINDLIBS"
if int(os.environ.get(env_var, "0")):
LOG.debug(f"{name} lib search: {env_var} set, so using findlibs")
elif os.name == "nt":
LOG.debug(f"{name} lib search: trying to find binary wheel")
here = os.path.dirname(__file__)
# eccodes libs are actually in eccodes dir, not gribapi dir
here = os.path.abspath(os.path.join(here, os.path.pardir, "eccodes"))
extension = EXTENSIONS.get(sys.platform, ".so")
for libdir in [here + ".libs", os.path.join(here, ".dylibs"), here]:
LOG.debug(f"{name} lib search: looking in {libdir}")
if not name.startswith("lib"):
libnames = ["lib" + name, name]
else:
libnames = [name, name[3:]]
if os.path.exists(libdir):
for file in os.listdir(libdir):
if file.endswith(extension):
for libname in libnames:
if libname == file.split("-")[0].split(".")[0]:
foundlib = os.path.join(libdir, file)
LOG.debug(
f"{name} lib search: returning wheel library from {foundlib}"
)
# force linking with the C++ 'glue' library
try:
from eccodes._eccodes import versions as _versions
except ImportError as e:
LOG.warn(str(e))
raise
LOG.debug(
f"{name} lib search: versions: %s", _versions()
)
return foundlib
LOG.debug(
f"{name} lib search: did not find library from wheel; try to find as separate lib"
)
# if did not find the binary wheel, or the env var is set, fall back to findlibs
import findlibs
foundlib = findlibs.find(name)
LOG.debug(f"{name} lib search: findlibs returned {foundlib}")
return foundlib
library_path = find_binary_libs("eccodes")
if library_path is None:
raise RuntimeError("Cannot find the ecCodes library")
# default encoding for ecCodes strings
ENC = "ascii"
ffi = cffi.FFI()
CDEF = pkgutil.get_data(__name__, "grib_api.h")
CDEF += pkgutil.get_data(__name__, "eccodes.h")
ffi.cdef(CDEF.decode("utf-8").replace("\r", "\n"))
lib = ffi.dlopen(library_path)
ecmwf-eccodes-python-b43a0f2/gribapi/gribapi.py 0000664 0001750 0001750 00000247267 15172126764 021765 0 ustar alastair alastair #
# (C) Copyright 2017- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
#
"""
@package gribapi
@brief This package is the \b Python 3 interface to ecCodes.
It offers almost one to one bindings to the C API functions.
The Python 3 interface to ecCodes uses the NumPy package
as the container of choice for the possible arrays of values that can be encoded/decoded in and from a message.
Numpy is a package used for scientific computing in Python and an efficient container for generic data.
@em Requirements:
- Python 3.9 or higher
- NumPy
"""
import os
import sys
from functools import wraps
import numpy as np
from gribapi.errors import GribInternalError
from . import errors
from .bindings import ENC
from .bindings import __version__ as bindings_version # noqa
from .bindings import ffi, lib, library_path
try:
type(file)
except NameError:
import io
file = io.IOBase
long = int
KEYTYPES = {1: int, 2: float, 3: str, 4: bytes}
CODES_PRODUCT_ANY = 0
""" Generic product kind """
CODES_PRODUCT_GRIB = 1
""" GRIB product kind """
CODES_PRODUCT_BUFR = 2
""" BUFR product kind """
CODES_PRODUCT_METAR = 3
""" METAR product kind """
CODES_PRODUCT_GTS = 4
""" GTS product kind """
CODES_PRODUCT_TAF = 5
""" TAF product kind """
# Constants for 'missing'
GRIB_MISSING_DOUBLE = -1e100
GRIB_MISSING_LONG = 2147483647
# Constants for GRIB nearest neighbour
GRIB_NEAREST_SAME_GRID = 1 << 0
GRIB_NEAREST_SAME_DATA = 1 << 1
GRIB_NEAREST_SAME_POINT = 1 << 2
# Constants for feature selection
CODES_FEATURES_ALL = 0
CODES_FEATURES_ENABLED = 1
CODES_FEATURES_DISABLED = 2
# ECC-1029: Disable function-arguments type-checking unless
# environment variable is defined and equal to 1
enable_type_checks = os.environ.get("ECCODES_PYTHON_ENABLE_TYPE_CHECKS") == "1"
# Function-arguments type-checking decorator
# inspired from http://code.activestate.com/recipes/454322-type-checking-decorator/
# modified to support multiple allowed types and all types in the same decorator call
# This returns a decorator. _params_ is the dict with the type specs
def require(**_params_):
"""
The actual decorator. Receives the target function in _func_
"""
def check_types(_func_, _params_=_params_):
if not enable_type_checks:
return _func_
@wraps(_func_)
# The wrapper function. Replaces the target function and receives its args
def modified(*args, **kw):
arg_names = _func_.__code__.co_varnames
# argnames, varargs, kwargs, defaults = inspect.getargspec(_func_)
kw.update(zip(arg_names, args))
for name, allowed_types in _params_.items():
param = kw[name]
if isinstance(allowed_types, type):
allowed_types = (allowed_types,)
assert any(
[isinstance(param, type1) for type1 in allowed_types]
), "Parameter '%s' should be of type %s (instead of %s)" % (
name,
" or ".join([t.__name__ for t in allowed_types]),
type(param).__name__,
)
return _func_(**kw)
return modified
return check_types
# @cond
class Bunch(dict):
"""
The collector of a bunch of named stuff :).
"""
def __init__(self, **kw):
dict.__init__(self, kw)
self.__dict__.update(kw)
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
self.__dict__[key] = value
def __setattr__(self, key, value):
dict.__setitem__(self, key, value)
self.__dict__[key] = value
def __delitem__(self, key):
dict.__delitem__(self, key)
del self.__dict__[key]
def __delattr__(self, key):
dict.__delitem__(self, key)
del self.__dict__[key]
def __str__(self):
state = [
"%s=%r" % (attribute, value) for (attribute, value) in self.__dict__.items()
]
return "\n".join(state)
# @endcond
def err_last(func):
@wraps(func)
def wrapper(*args):
err = ffi.new("int *")
args += (err,)
retval = func(*args)
return err[0], retval
return wrapper
def get_handle(msgid):
h = ffi.cast("grib_handle*", msgid)
if h == ffi.NULL:
raise errors.NullHandleError(f"get_handle: Bad message ID {msgid}")
return h
def put_handle(handle):
if handle == ffi.NULL:
raise errors.NullHandleError("put_handle: Bad message ID (handle is NULL)")
return int(ffi.cast("size_t", handle))
def get_multi_handle(msgid):
return ffi.cast("grib_multi_handle*", msgid)
def put_multi_handle(handle):
return int(ffi.cast("size_t", handle))
def get_index(indexid):
return ffi.cast("grib_index*", indexid)
def put_index(indexh):
return int(ffi.cast("size_t", indexh))
def get_iterator(iterid):
return ffi.cast("grib_iterator*", iterid)
def put_iterator(iterh):
return int(ffi.cast("size_t", iterh))
def get_grib_keys_iterator(iterid):
return ffi.cast("grib_keys_iterator*", iterid)
def put_grib_keys_iterator(iterh):
return int(ffi.cast("size_t", iterh))
def get_bufr_keys_iterator(iterid):
return ffi.cast("bufr_keys_iterator*", iterid)
def put_bufr_keys_iterator(iterh):
return int(ffi.cast("size_t", iterh))
# @cond
@require(errid=int)
def GRIB_CHECK(errid):
"""
Utility function checking the ecCodes error code and raising
an error if that was set.
@param errid the C interface error id to check
@exception CodesInternalError
"""
if errid:
errors.raise_grib_error(errid)
# @endcond
@require(fileobj=file)
def gts_new_from_file(fileobj, headers_only=False):
"""
@brief Load in memory a GTS message from a file.
The message can be accessed through its id and will be available\n
until @ref codes_release is called.\n
@param fileobj python file object
@param headers_only whether or not to load the message with the headers only
@return id of the GTS loaded in memory or None
@exception CodesInternalError
"""
err, h = err_last(lib.codes_handle_new_from_file)(
ffi.NULL, fileobj, CODES_PRODUCT_GTS
)
if err:
if err == lib.GRIB_END_OF_FILE:
return None
else:
GRIB_CHECK(err)
return None
if h == ffi.NULL:
return None
else:
return put_handle(h)
@require(fileobj=file)
def metar_new_from_file(fileobj, headers_only=False):
"""
@brief Load in memory a METAR message from a file.
The message can be accessed through its id and will be available\n
until @ref codes_release is called.\n
@param fileobj python file object
@param headers_only whether or not to load the message with the headers only
@return id of the METAR loaded in memory or None
@exception CodesInternalError
"""
err, h = err_last(lib.codes_handle_new_from_file)(
ffi.NULL, fileobj, CODES_PRODUCT_METAR
)
if err:
if err == lib.GRIB_END_OF_FILE:
return None
else:
GRIB_CHECK(err)
return None
if h == ffi.NULL:
return None
else:
return put_handle(h)
@require(fileobj=file, product_kind=int)
def codes_new_from_file(fileobj, product_kind, headers_only=False):
"""
@brief Load in memory a message from a file for a given product.
The message can be accessed through its id and will be available\n
until @ref codes_release is called.\n
\b Examples: \ref get_product_kind.py "get_product_kind.py"
@param fileobj python file object
@param product_kind one of CODES_PRODUCT_GRIB, CODES_PRODUCT_BUFR, CODES_PRODUCT_METAR or CODES_PRODUCT_GTS
@param headers_only whether or not to load the message with the headers only
@return id of the message loaded in memory or None
@exception CodesInternalError
"""
if product_kind == CODES_PRODUCT_GRIB:
return grib_new_from_file(fileobj, headers_only)
if product_kind == CODES_PRODUCT_BUFR:
return bufr_new_from_file(fileobj, headers_only)
if product_kind == CODES_PRODUCT_METAR:
return metar_new_from_file(fileobj, headers_only)
if product_kind == CODES_PRODUCT_GTS:
return gts_new_from_file(fileobj, headers_only)
if product_kind == CODES_PRODUCT_ANY:
return any_new_from_file(fileobj, headers_only)
raise ValueError("Invalid product kind %d" % product_kind)
@require(fileobj=file)
def any_new_from_file(fileobj, headers_only=False):
"""
@brief Load in memory a message from a file.
The message can be accessed through its id and will be available\n
until @ref codes_release is called.\n
\b Examples: \ref grib_get_keys.py "grib_get_keys.py"
@param fileobj python file object
@param headers_only whether or not to load the message with the headers only
@return id of the message loaded in memory or None
@exception CodesInternalError
"""
err, h = err_last(lib.codes_handle_new_from_file)(
ffi.NULL, fileobj, CODES_PRODUCT_ANY
)
if err:
if err == lib.GRIB_END_OF_FILE:
return None
else:
GRIB_CHECK(err)
return None
if h == ffi.NULL:
return None
else:
return put_handle(h)
@require(fileobj=file)
def bufr_new_from_file(fileobj, headers_only=False):
"""
@brief Load in memory a BUFR message from a file.
The message can be accessed through its id and will be available\n
until @ref codes_release is called.\n
\b Examples: \ref bufr_get_keys.py "bufr_get_keys.py"
@param fileobj python file object
@param headers_only whether or not to load the message with the headers only
@return id of the BUFR loaded in memory or None
@exception CodesInternalError
"""
err, h = err_last(lib.codes_handle_new_from_file)(
ffi.NULL, fileobj, CODES_PRODUCT_BUFR
)
if err:
if err == lib.GRIB_END_OF_FILE:
return None
else:
GRIB_CHECK(err)
return None
if h == ffi.NULL:
return None
else:
return put_handle(h)
@require(fileobj=file)
def grib_new_from_file(fileobj, headers_only=False):
"""
@brief Load in memory a GRIB message from a file.
The message can be accessed through its gribid and will be available\n
until @ref codes_release is called.\n
The message can be loaded headers only by using the headers_only argument.
Default is to have the headers only option set to off (False). If set to on (True),
data values will be skipped. This will result in a significant performance gain
if one is only interested in browsing through messages to retrieve metadata.
Any attempt to retrieve data values keys when in the headers only mode will
result in a key not found error.
\b Examples: \ref grib_get_keys.py "grib_get_keys.py"
@param fileobj python file object
@param headers_only whether or not to load the message with the headers only
@return id of the grib loaded in memory or None
@exception CodesInternalError
"""
err, h = err_last(lib.codes_handle_new_from_file)(
ffi.NULL, fileobj, CODES_PRODUCT_GRIB
)
if err:
if err == lib.GRIB_END_OF_FILE:
return None
else:
GRIB_CHECK(err)
return None
if h == ffi.NULL:
return None
else:
return put_handle(h)
@require(fileobj=file)
def grib_count_in_file(fileobj):
"""
@brief Count the messages in a file.
\b Examples: \ref count_messages.py "count_messages.py"
@param fileobj python file object
@return number of messages in the file
@exception CodesInternalError
"""
num_p = ffi.new("int*")
err = lib.grib_count_in_file(ffi.NULL, fileobj, num_p)
GRIB_CHECK(err)
return num_p[0]
def grib_multi_support_on():
"""
@brief Turn on the support for multiple fields in a single GRIB message.
@exception CodesInternalError
"""
lib.grib_multi_support_on(ffi.NULL)
def grib_multi_support_off():
"""
@brief Turn off the support for multiple fields in a single GRIB message.
@exception CodesInternalError
"""
lib.grib_multi_support_off(ffi.NULL)
@require(fileobj=file)
def grib_multi_support_reset_file(fileobj):
"""
@brief Reset file handle in multiple field support mode
"""
context = lib.grib_context_get_default()
lib.grib_multi_support_reset_file(context, fileobj)
@require(fileobj=file)
def grib_context_set_logging(fileobj):
"""
@brief Send logging messages to fileobj
"""
context = lib.grib_context_get_default()
lib.grib_context_set_logging_file(context, fileobj)
@require(msgid=int)
def grib_release(msgid):
"""
@brief Free the memory for the message referred to by msgid.
\b Examples: \ref grib_get_keys.py "grib_get_keys.py"
@param msgid id of the message loaded in memory
@exception CodesInternalError
"""
h = get_handle(msgid)
GRIB_CHECK(lib.grib_handle_delete(h))
@require(msgid=int, key=str)
def grib_get_string(msgid, key):
"""
@brief Get the string value of a key from a message.
@param msgid id of the message loaded in memory
@param key key name
@return string value of key
@exception CodesInternalError
"""
length = grib_get_string_length(msgid, key)
h = get_handle(msgid)
values = ffi.new("char[]", length)
length_p = ffi.new("size_t *", length)
err = lib.grib_get_string(h, key.encode(ENC), values, length_p)
GRIB_CHECK(err)
return _decode_bytes(values, length_p[0])
@require(msgid=int, key=str, value=str)
def grib_set_string(msgid, key, value):
"""
@brief Set the value for a string key in a message.
@param msgid id of the message loaded in memory
@param key key name
@param value string value
@exception CodesInternalError
"""
h = get_handle(msgid)
bvalue = value.encode(ENC)
length_p = ffi.new("size_t *", len(bvalue))
GRIB_CHECK(lib.grib_set_string(h, key.encode(ENC), bvalue, length_p))
def grib_gribex_mode_on():
"""
@brief Turn on the compatibility mode with GRIBEX.
@exception CodesInternalError
"""
lib.grib_gribex_mode_on(ffi.NULL)
def grib_gribex_mode_off():
"""
@brief Turn off the compatibility mode with GRIBEX.
@exception CodesInternalError
"""
lib.grib_gribex_mode_off(ffi.NULL)
@require(msgid=int, fileobj=file)
def grib_write(msgid, fileobj):
"""
@brief Write a message to a file.
\b Examples: \ref grib_set_keys.py "grib_set_keys.py"
@param msgid id of the message loaded in memory
@param fileobj python file object
@exception CodesInternalError
"""
msg_bytes = grib_get_message(msgid)
fileobj.write(msg_bytes)
fileobj.flush()
@require(multigribid=int, fileobj=file)
def grib_multi_write(multigribid, fileobj):
"""
@brief Write a multi-field GRIB message to a file.
\b Examples: \ref grib_multi_write.py "grib_multi_write.py"
@param multigribid id of the multi-field grib loaded in memory
@param fileobj python file object
@exception CodesInternalError
"""
mh = get_multi_handle(multigribid)
GRIB_CHECK(lib.grib_multi_handle_write(mh, fileobj))
@require(ingribid=int, startsection=int, multigribid=int)
def grib_multi_append(ingribid, startsection, multigribid):
"""
@brief Append a single-field GRIB message to a multi-field GRIB message.
Only the sections with section number greather or equal "startsection"
are copied from the input single message to the multi-field output grib.
\b Examples: \ref grib_multi_write.py "grib_multi_write.py"
@param ingribid id of the input single-field GRIB
@param startsection starting from startsection (included) all the sections are copied
from the input single grib to the output multi-field grib
@param multigribid id of the output multi-field GRIB
@exception CodesInternalError
"""
h = get_handle(ingribid)
mh = get_multi_handle(multigribid)
GRIB_CHECK(lib.grib_multi_handle_append(h, startsection, mh))
@require(msgid=int, key=str)
def grib_get_offset(msgid, key):
"""
@brief Get the byte offset of a key. If several keys of the same name
are present, the offset of the last one is returned
@param msgid id of the message loaded in memory
@param key name of the key
@exception CodesInternalError
"""
h = get_handle(msgid)
offset_p = ffi.new("size_t*")
err = lib.grib_get_offset(h, key.encode(ENC), offset_p)
GRIB_CHECK(err)
return offset_p[0]
@require(msgid=int, key=str)
def grib_get_size(msgid, key):
"""
@brief Get the size of a key. Return 1 for scalar keys and >1 for array keys
\b Examples: \ref grib_get_keys.py "grib_get_keys.py",\ref count_messages.py "count_messages.py"
@param msgid id of the message loaded in memory
@param key name of the key
@exception CodesInternalError
"""
h = get_handle(msgid)
size_p = ffi.new("size_t*")
err = lib.grib_get_size(h, key.encode(ENC), size_p)
GRIB_CHECK(err)
return size_p[0]
@require(msgid=int, key=str)
def grib_get_string_length(msgid, key):
"""
@brief Get the length of the string version of a key.
@param msgid id of the message loaded in memory
@param key name of the key
@exception CodesInternalError
"""
h = get_handle(msgid)
size = ffi.new("size_t *")
err = lib.grib_get_length(h, key.encode(ENC), size)
GRIB_CHECK(err)
return size[0]
@require(iterid=int)
def grib_skip_computed(iterid):
"""
@brief Skip the computed keys in a keys iterator.
The computed keys are not coded in the message, they are computed
from other keys.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_COMPUTED)
@require(iterid=int)
def grib_skip_coded(iterid):
"""
@brief Skip the coded keys in a keys iterator.
The coded keys are actually coded in the message.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_CODED)
@require(iterid=int)
def grib_skip_edition_specific(iterid):
"""
@brief Skip the edition specific keys in a keys iterator.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC)
@require(iterid=int)
def grib_skip_duplicates(iterid):
"""
@brief Skip the duplicate keys in a keys iterator.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_DUPLICATES)
@require(iterid=int)
def grib_skip_read_only(iterid):
"""
@brief Skip the read_only keys in a keys iterator.
Read only keys cannot be set.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_READ_ONLY)
@require(iterid=int)
def grib_skip_function(iterid):
"""
@brief Skip the function keys in a keys iterator.
@see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete
@param iterid keys iterator id
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_FUNCTION)
@require(gribid=int, mode=int)
def grib_iterator_new(gribid, mode):
"""
@brief Create a new geoiterator for the given GRIB message, using its geometry and values.
The geoiterator can be used to go through all the geopoints in a GRIB message and
retrieve the values corresponding to those geopoints.
\b Examples: \ref grib_iterator.py "grib_iterator.py"
@param gribid id of the GRIB loaded in memory
@param mode flags for future use
@return geoiterator id
"""
h = get_handle(gribid)
err, iterid = err_last(lib.grib_iterator_new)(h, mode)
GRIB_CHECK(err)
return put_iterator(iterid)
@require(iterid=int)
def grib_iterator_delete(iterid):
"""
@brief Delete a geoiterator and free memory.
\b Examples: \ref grib_iterator.py "grib_iterator.py"
@param iterid geoiterator id
@exception CodesInternalError
"""
ih = get_iterator(iterid)
GRIB_CHECK(lib.grib_iterator_delete(ih))
@require(iterid=int)
def grib_iterator_next(iterid):
"""
@brief Retrieve the next value from a geoiterator.
\b Examples: \ref grib_iterator.py "grib_iterator.py"
@param iterid geoiterator id
@return tuple with the latitude, longitude and value
@exception CodesInternalError
"""
iterh = get_iterator(iterid)
lat_p = ffi.new("double*")
lon_p = ffi.new("double*")
value_p = ffi.new("double*")
retval = lib.grib_iterator_next(iterh, lat_p, lon_p, value_p)
if retval == 0:
# No more data available. End of iteration
return []
else:
return (lat_p[0], lon_p[0], value_p[0])
@require(msgid=int)
def grib_keys_iterator_new(msgid, namespace=None):
"""
@brief Create a new iterator on the keys.
The keys iterator can be navigated to give all the key names which
can then be used to get or set the key values with \ref grib_get or
\ref grib_set.
The set of keys returned can be controlled with the input variable
namespace or using the functions
\ref grib_skip_read_only, \ref grib_skip_duplicates,
\ref grib_skip_coded,\ref grib_skip_computed.
If namespace is a non empty string only the keys belonging to
that namespace are returned. Example namespaces are "ls" (to get the same
default keys as the grib_ls) and "mars" to get the keys used by mars.
\b Examples: \ref grib_iterator.py "grib_iterator.py"
@param msgid id of the message loaded in memory
@param namespace the namespace of the keys to search for (all the keys if None)
@return keys iterator id to be used in the keys iterator functions
@exception CodesInternalError
"""
h = get_handle(msgid)
bnamespace = ffi.NULL if namespace is None else namespace.encode(ENC)
iterid = lib.grib_keys_iterator_new(h, 0, bnamespace)
return put_grib_keys_iterator(iterid)
@require(iterid=int)
def grib_keys_iterator_next(iterid):
"""
@brief Advance to the next keys iterator value.
\b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py"
@param iterid keys iterator id created with @ref grib_keys_iterator_new
@exception CodesInternalError
"""
kih = get_grib_keys_iterator(iterid)
res = lib.grib_keys_iterator_next(kih)
# res is 0 or 1
return res
@require(iterid=int)
def grib_keys_iterator_delete(iterid):
"""
@brief Delete a keys iterator and free memory.
\b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py"
@param iterid keys iterator id created with @ref grib_keys_iterator_new
@exception CodesInternalError
"""
kih = get_grib_keys_iterator(iterid)
lib.grib_keys_iterator_delete(kih)
@require(iterid=int)
def grib_keys_iterator_get_name(iterid):
"""
@brief Get the name of a key from a keys iterator.
\b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py"
@param iterid keys iterator id created with @ref grib_keys_iterator_new
@return key name to be retrieved
@exception CodesInternalError
"""
kih = get_grib_keys_iterator(iterid)
name = lib.grib_keys_iterator_get_name(kih)
return ffi.string(name).decode(ENC)
@require(iterid=int)
def grib_keys_iterator_rewind(iterid):
"""
@brief Rewind a keys iterator.
@param iterid keys iterator id created with @ref grib_keys_iterator_new
@exception CodesInternalError
"""
gki = get_grib_keys_iterator(iterid)
GRIB_CHECK(lib.grib_keys_iterator_rewind(gki))
# BUFR keys iterator
@require(bufrid=int)
def codes_bufr_keys_iterator_new(bufrid):
"""
@brief Create a new iterator on the BUFR keys.
The keys iterator can be navigated to give all the key names which
can then be used to get or set the key values with \ref codes_get or
\ref codes_set.
\b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py"
@param bufrid id of the BUFR message loaded in memory
@return keys iterator id to be used in the bufr keys iterator functions
@exception CodesInternalError
"""
h = get_handle(bufrid)
bki = lib.codes_bufr_keys_iterator_new(h, 0)
if bki == ffi.NULL:
raise errors.InvalidKeysIteratorError(
f"BUFR keys iterator failed bufrid={bufrid}"
)
return put_bufr_keys_iterator(bki)
@require(iterid=int)
def codes_bufr_keys_iterator_next(iterid):
"""
@brief Advance to the next BUFR keys iterator value.
\b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py"
@param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new
@exception CodesInternalError
"""
bki = get_bufr_keys_iterator(iterid)
res = lib.codes_bufr_keys_iterator_next(bki)
# res is 0 or 1
return res
@require(iterid=int)
def codes_bufr_keys_iterator_delete(iterid):
"""
@brief Delete a BUFR keys iterator and free memory.
\b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py"
@param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new
@exception CodesInternalError
"""
bki = get_bufr_keys_iterator(iterid)
GRIB_CHECK(lib.codes_bufr_keys_iterator_delete(bki))
@require(iterid=int)
def codes_bufr_keys_iterator_get_name(iterid):
"""
@brief Get the name of a key from a BUFR keys iterator.
\b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py"
@param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new
@return key name to be retrieved
@exception CodesInternalError
"""
bki = get_bufr_keys_iterator(iterid)
name = lib.codes_bufr_keys_iterator_get_name(bki)
return ffi.string(name).decode(ENC)
@require(iterid=int)
def codes_bufr_keys_iterator_rewind(iterid):
"""
@brief Rewind a BUFR keys iterator.
@param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new
@exception CodesInternalError
"""
bki = get_bufr_keys_iterator(iterid)
GRIB_CHECK(lib.codes_bufr_keys_iterator_rewind(bki))
@require(msgid=int, key=str)
def grib_get_long(msgid, key):
"""
@brief Get the value of a key in a message as an integer.
@param msgid id of the message loaded in memory
@param key key name
@return value of key as int
@exception CodesInternalError
"""
h = get_handle(msgid)
value_p = ffi.new("long*")
err = lib.grib_get_long(h, key.encode(ENC), value_p)
GRIB_CHECK(err)
return value_p[0]
@require(msgid=int, key=str)
def grib_get_double(msgid, key):
"""
@brief Get the value of a key in a message as a float.
@param msgid id of the message loaded in memory
@param key key name
@return value of key as float
@exception CodesInternalError
"""
h = get_handle(msgid)
value_p = ffi.new("double*")
err = lib.grib_get_double(h, key.encode(ENC), value_p)
GRIB_CHECK(err)
return value_p[0]
@require(msgid=int, key=str, value=(int, float, np.float16, np.float32, np.int64, str))
def grib_set_long(msgid, key, value):
"""
@brief Set the integer value for a key in a message.
A TypeError exception will be thrown if value cannot be represented
as an integer.
@param msgid id of the message loaded in memory
@param key key name
@param value value to set
@exception CodesInternalError,TypeError
"""
try:
value = int(value)
except (ValueError, TypeError):
raise TypeError("Invalid type")
if value > sys.maxsize:
raise ValueError("Value too large")
h = get_handle(msgid)
GRIB_CHECK(lib.grib_set_long(h, key.encode(ENC), value))
@require(msgid=int, key=str, value=(int, float, np.float16, np.float32, str))
def grib_set_double(msgid, key, value):
"""
@brief Set the double value for a key in a message.
A TypeError exception will be thrown if value cannot be represented
as a float.
@param msgid id of the message loaded in memory
@param key key name
@param value float value to set
@exception CodesInternalError,TypeError
"""
try:
value = float(value)
except (ValueError, TypeError):
raise TypeError("Invalid type")
h = get_handle(msgid)
GRIB_CHECK(lib.grib_set_double(h, key.encode(ENC), value))
@require(samplename=str, product_kind=int)
def codes_new_from_samples(samplename, product_kind):
"""
@brief Create a new valid message from a sample for a given product.
The available samples are picked up from the directory pointed to
by the environment variable ECCODES_SAMPLES_PATH.
To know where the samples directory is run the codes_info tool.\n
\b Examples: \ref grib_samples.py "grib_samples.py"
@param samplename name of the sample to be used
@param product_kind CODES_PRODUCT_GRIB or CODES_PRODUCT_BUFR
@return id of the message loaded in memory
@exception CodesInternalError
"""
if product_kind == CODES_PRODUCT_GRIB:
return grib_new_from_samples(samplename)
if product_kind == CODES_PRODUCT_BUFR:
return codes_bufr_new_from_samples(samplename)
if product_kind == CODES_PRODUCT_ANY:
return codes_any_new_from_samples(samplename)
raise ValueError("Invalid product kind %d" % product_kind)
@require(samplename=str)
def grib_new_from_samples(samplename):
"""
@brief Create a new valid GRIB message from a sample.
The available samples are picked up from the directory pointed to
by the environment variable ECCODES_SAMPLES_PATH.
To know where the samples directory is run the codes_info tool.\n
\b Examples: \ref grib_samples.py "grib_samples.py"
@param samplename name of the sample to be used
@return id of the message loaded in memory
@exception CodesInternalError
"""
h = lib.grib_handle_new_from_samples(ffi.NULL, samplename.encode(ENC))
if h == ffi.NULL:
raise errors.FileNotFoundError(f"grib_new_from_samples failed: {samplename}")
return put_handle(h)
@require(samplename=str)
def codes_bufr_new_from_samples(samplename):
"""
@brief Create a new valid BUFR message from a sample.
The available samples are picked up from the directory pointed to
by the environment variable ECCODES_SAMPLES_PATH.
To know where the samples directory is run the codes_info tool.\n
\b Examples: \ref bufr_copy_data.py "bufr_copy_data.py"
@param samplename name of the BUFR sample to be used
@return id of the message loaded in memory
@exception CodesInternalError
"""
h = lib.codes_bufr_handle_new_from_samples(ffi.NULL, samplename.encode(ENC))
if h == ffi.NULL:
raise errors.FileNotFoundError(f"bufr_new_from_samples failed: {samplename}")
return put_handle(h)
@require(samplename=str)
def codes_any_new_from_samples(samplename):
"""
@brief Create a new valid message from a sample.
The available samples are picked up from the directory pointed to
by the environment variable ECCODES_SAMPLES_PATH.
To know where the samples directory is run the codes_info tool.\n
@param samplename name of the sample to be used
@return id of the message loaded in memory
@exception CodesInternalError
"""
h = lib.codes_handle_new_from_samples(ffi.NULL, samplename.encode(ENC))
if h == ffi.NULL:
raise errors.FileNotFoundError(f"any_new_from_samples failed: {samplename}")
return put_handle(h)
@require(msgid_src=int, msgid_dst=int)
def codes_bufr_copy_data(msgid_src, msgid_dst):
"""
@brief Copy data values from a BUFR message msgid_src to another message msgid_dst
Copies all the values in the data section that are present in the same position
in the data tree and with the same number of values to the output handle.
@param msgid_src id of the message from which the data are copied
@param msgid_dst id of the message to which the data are copied
@return id of new message
@exception CodesInternalError
"""
h_src = get_handle(msgid_src)
h_dst = get_handle(msgid_dst)
err = lib.codes_bufr_copy_data(h_src, h_dst)
GRIB_CHECK(err)
return msgid_dst
@require(msgid_src=int)
def grib_clone(msgid_src, headers_only=False):
r"""
@brief Create a copy of a message.
Create a copy of a given message (\em msgid_src) resulting in a new
message in memory (\em msgid_dest) identical to the original one.
If the headers_only option is enabled, the clone will not contain
the Bitmap and Data sections
\b Examples: \ref grib_clone.py "grib_clone.py"
@param msgid_src id of message to be cloned
@param headers_only whether or not to clone the message with the headers only
@return id of clone
@exception CodesInternalError
"""
h_src = get_handle(msgid_src)
if headers_only:
h_dest = lib.grib_handle_clone_headers_only(h_src)
else:
h_dest = lib.grib_handle_clone(h_src)
if h_dest == ffi.NULL:
raise errors.MessageInvalidError("clone failed")
return put_handle(h_dest)
@require(msgid=int, key=str)
def grib_set_double_array(msgid, key, inarray):
"""
@brief Set the value of the key to a double array.
The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ...
The elements of the input sequence need to be convertible to a double.
@param msgid id of the message loaded in memory
@param key key name
@param inarray tuple,list,array,numpy.ndarray
@exception CodesInternalError
"""
h = get_handle(msgid)
length = len(inarray)
if isinstance(inarray, np.ndarray):
nd = inarray
# ECC-1555
length = inarray.size
if length > 0:
if not isinstance(nd[0], float):
# ECC-1042: input array of integers
nd = nd.astype(float)
# ECC-1007: Could also call numpy.ascontiguousarray
if not inarray.flags["C_CONTIGUOUS"]:
nd = nd.copy(order="C")
a = ffi.cast("double*", nd.ctypes.data)
else:
a = inarray
GRIB_CHECK(lib.grib_set_double_array(h, key.encode(ENC), a, length))
@require(msgid=int, key=str)
def grib_get_double_array(msgid, key):
"""
@brief Get the value of the key as a NumPy array of doubles.
@param msgid id of the message loaded in memory
@param key key name
@return numpy.ndarray
@exception CodesInternalError
"""
h = get_handle(msgid)
nval = grib_get_size(msgid, key)
length_p = ffi.new("size_t*", nval)
arr = np.empty((nval,), dtype="float64")
vals_p = ffi.cast("double *", arr.ctypes.data)
err = lib.grib_get_double_array(h, key.encode(ENC), vals_p, length_p)
GRIB_CHECK(err)
return arr
@require(msgid=int, key=str)
def grib_get_float_array(msgid, key):
"""
@brief Get the value of the key as a NumPy array of floats.
@param msgid id of the message loaded in memory
@param key key name
@return numpy.ndarray
@exception CodesInternalError
"""
h = get_handle(msgid)
nval = grib_get_size(msgid, key)
length_p = ffi.new("size_t*", nval)
arr = np.empty((nval,), dtype="float32")
vals_p = ffi.cast("float *", arr.ctypes.data)
err = lib.grib_get_float_array(h, key.encode(ENC), vals_p, length_p)
GRIB_CHECK(err)
return arr
# See ECC-1246
def _decode_bytes(binput, maxlen=None):
if maxlen:
a_str = ffi.string(binput, maxlen)
else:
a_str = ffi.string(binput)
# Check for a MISSING value i.e., each character has all its bits=1
if all(x == 255 for x in a_str):
return ""
# Replace with a suitable replacement character rather than throw an exception
return a_str.decode(ENC, "replace")
@require(msgid=int, key=str)
def grib_get_string_array(msgid, key):
"""
@brief Get the value of the key as a list of strings.
@param msgid id of the message loaded in memory
@param key key name
@return list
@exception CodesInternalError
"""
length = grib_get_string_length(msgid, key)
size = grib_get_size(msgid, key)
h = get_handle(msgid)
values_keepalive = [ffi.new("char[]", length) for _ in range(size)]
values = ffi.new("char*[]", values_keepalive)
size_p = ffi.new("size_t *", size)
err = lib.grib_get_string_array(h, key.encode(ENC), values, size_p)
GRIB_CHECK(err)
return [_decode_bytes(values[i]) for i in range(size_p[0])]
@require(msgid=int, key=str)
def grib_set_string_array(msgid, key, inarray):
"""
@brief Set the value of the key to a string array.
The input array can be a python sequence like tuple, list, array, ...
The elements of the input sequence need to be convertible to a string.
@param msgid id of the message loaded in memory
@param key key name
@param inarray tuple,list,array
@exception CodesInternalError
"""
h = get_handle(msgid)
size = len(inarray)
# See https://cffi.readthedocs.io/en/release-1.3/using.html
values_keepalive = [ffi.new("char[]", s.encode(ENC)) for s in inarray]
values_p = ffi.new("const char *[]", values_keepalive)
GRIB_CHECK(lib.grib_set_string_array(h, key.encode(ENC), values_p, size))
@require(msgid=int, key=str)
def grib_set_long_array(msgid, key, inarray):
"""
@brief Set the value of the key to an integer array.
The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ...
The elements of the input sequence need to be convertible to an int.
@param msgid id of the message loaded in memory
@param key key name
@param inarray tuple,list,python array,numpy.ndarray
@exception CodesInternalError
"""
h = get_handle(msgid)
if isinstance(inarray, np.ndarray):
inarray = inarray.tolist()
GRIB_CHECK(lib.grib_set_long_array(h, key.encode(ENC), inarray, len(inarray)))
@require(msgid=int, key=str)
def grib_get_long_array(msgid, key):
"""
@brief Get the integer array of values for a key from a message.
@param msgid id of the message loaded in memory
@param key key name
@return numpy.ndarray
@exception CodesInternalError
"""
# See ECC-1113
sizeof_long = ffi.sizeof("long")
dataType = "int64"
if sizeof_long == 4:
dataType = "int32"
h = get_handle(msgid)
nval = grib_get_size(msgid, key)
length_p = ffi.new("size_t*", nval)
arr = np.empty((nval,), dtype=dataType)
vals_p = ffi.cast("long *", arr.ctypes.data)
err = lib.grib_get_long_array(h, key.encode(ENC), vals_p, length_p)
GRIB_CHECK(err)
return arr
def grib_multi_new():
"""
@brief Create a new multi-field GRIB message and return its id.
\b Examples: \ref grib_multi_write.py "grib_multi_write.py"
@return id of the multi-field message
@exception CodesInternalError
"""
mgid = lib.grib_multi_handle_new(ffi.NULL)
if mgid == ffi.NULL:
raise errors.InvalidGribError("GRIB multi new failed")
return put_multi_handle(mgid)
@require(gribid=int)
def grib_multi_release(gribid):
"""
@brief Release a multi-field message from memory.
\b Examples: \ref grib_multi_write.py "grib_multi_write.py"
@param gribid id of the multi-field we want to release the memory for
@exception CodesInternalError
"""
mh = get_multi_handle(gribid)
GRIB_CHECK(lib.grib_multi_handle_delete(mh))
@require(gribid_src=int, namespace=str, gribid_dest=int)
def grib_copy_namespace(gribid_src, namespace, gribid_dest):
"""
@brief Copy the value of all the keys belonging to a namespace from the source message
to the destination message.
@param gribid_src id of source message
@param gribid_dest id of destination message
@param namespace namespace to be copied
@exception CodesInternalError
"""
h_src = get_handle(gribid_src)
h_dest = get_handle(gribid_dest)
GRIB_CHECK(lib.grib_copy_namespace(h_src, namespace.encode(ENC), h_dest))
@require(filename=str, keys=(tuple, list))
def grib_index_new_from_file(filename, keys):
"""
@brief Create a new index from a file.
\b Examples: \ref grib_index.py "grib_index.py"
@param filename path of the file to index on
@param keys sequence of keys to index on.
The type of the key can be explicitly declared appending
:l for long (or alternatively :i),
:d for double,
:s for string to the key name.
@return index id
@exception CodesInternalError
"""
ckeys = ",".join(keys)
err, iid = err_last(lib.grib_index_new_from_file)(
ffi.NULL, filename.encode(ENC), ckeys.encode(ENC)
)
GRIB_CHECK(err)
return put_index(iid)
@require(indexid=int, filename=str)
def grib_index_add_file(indexid, filename):
"""
@brief Add a file to an index.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of the index to add the file to
@param filename path of the file to be added to index
@exception CodesInternalError
"""
iid = get_index(indexid)
err = lib.grib_index_add_file(iid, filename.encode(ENC))
GRIB_CHECK(err)
@require(indexid=int)
def grib_index_release(indexid):
"""
@brief Delete an index.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file.
@exception CodesInternalError
"""
ih = get_index(indexid)
lib.grib_index_delete(ih)
@require(indexid=int, key=str)
def grib_index_get_size(indexid, key):
"""
@brief Get the number of distinct values for the index key.
The key must belong to the index.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created on the given key.
@param key key for which the number of values is computed
@return number of distinct values for key in index
@exception CodesInternalError
"""
ih = get_index(indexid)
size_p = ffi.new("size_t*")
err = lib.grib_index_get_size(ih, key.encode(ENC), size_p)
GRIB_CHECK(err)
return size_p[0]
@require(indexid=int, key=str)
def grib_index_get_long(indexid, key):
"""
@brief Get the distinct values of the key in argument contained in the index.
The key must belong to the index.
This function is used when the type of the key was explicitly defined as long or when the native type of
the key is long.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key for which the values are returned
@return tuple with values of key in index
@exception CodesInternalError
"""
nval = grib_index_get_size(indexid, key)
ih = get_index(indexid)
values_p = ffi.new("long[]", nval)
size_p = ffi.new("size_t *", nval)
err = lib.grib_index_get_long(ih, key.encode(ENC), values_p, size_p)
GRIB_CHECK(err)
return tuple(int(values_p[i]) for i in range(size_p[0]))
@require(indexid=int, key=str)
def grib_index_get_string(indexid, key):
"""
@brief Get the distinct values of the key in argument contained in the index.
The key must belong to the index.
This function is used when the type of the key was explicitly defined as string or when the native type of
the key is string.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key for which the values are returned
@return tuple with values of key in index
@exception CodesInternalError
"""
nval = grib_index_get_size(indexid, key)
ih = get_index(indexid)
max_val_size = 1024
values_keepalive = [ffi.new("char[]", max_val_size) for _ in range(nval)]
values_p = ffi.new("const char *[]", values_keepalive)
size_p = ffi.new("size_t *", max_val_size)
err = lib.grib_index_get_string(ih, key.encode(ENC), values_p, size_p)
GRIB_CHECK(err)
return tuple(ffi.string(values_p[i]).decode(ENC) for i in range(size_p[0]))
@require(indexid=int, key=str)
def grib_index_get_double(indexid, key):
"""
@brief Get the distinct values of the key in argument contained in the index.
The key must belong to the index.
This function is used when the type of the key was explicitly defined as double or when the native type
of the key is double.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key for which the values are returned
@return tuple with values of key in index
@exception CodesInternalError
"""
nval = grib_index_get_size(indexid, key)
ih = get_index(indexid)
values_p = ffi.new("double[]", nval)
size_p = ffi.new("size_t *", nval)
err = lib.grib_index_get_double(ih, key.encode(ENC), values_p, size_p)
GRIB_CHECK(err)
return tuple(values_p[i] for i in range(size_p[0]))
@require(indexid=int, key=str, value=int)
def grib_index_select_long(indexid, key, value):
"""
@brief Select the message subset with key==value.
The value is an integer.
The key must have been created with integer type or have integer as native type if the type
was not explicitly defined in the index creation.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key to be selected
@param value value of the key to select
@exception CodesInternalError
"""
iid = get_index(indexid)
GRIB_CHECK(lib.grib_index_select_long(iid, key.encode(ENC), value))
@require(indexid=int, key=str, value=float)
def grib_index_select_double(indexid, key, value):
"""
@brief Select the message subset with key==value.
The value is a double.
The key must have been created with integer type or have integer as native type if the type was
not explicitly defined in the index creation.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key to be selected
@param value value of the key to select
@exception CodesInternalError
"""
iid = get_index(indexid)
GRIB_CHECK(lib.grib_index_select_double(iid, key.encode(ENC), value))
@require(indexid=int, key=str, value=str)
def grib_index_select_string(indexid, key, value):
"""
@brief Select the message subset with key==value.
The value is an integer.
The key must have been created with string type or have string as native type if the type
was not explicitly defined in the index creation.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key to be selected
@param value value of the key to select
@exception CodesInternalError
"""
ih = get_index(indexid)
GRIB_CHECK(lib.grib_index_select_string(ih, key.encode(ENC), value.encode(ENC)))
@require(indexid=int)
def grib_new_from_index(indexid):
"""
@brief Create a new handle from an index after having selected the key values.
All the keys belonging to the index must be selected before calling this function.
Successive calls to this function will return all the handles compatible with the constraints
defined selecting the values of the index keys.
The message can be accessed through its gribid and will be available until @ref grib_release is called.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file.
@return id of the message loaded in memory or None if end of index
@exception CodesInternalError
"""
ih = get_index(indexid)
err, h = err_last(lib.grib_handle_new_from_index)(ih)
if h == ffi.NULL or err == lib.GRIB_END_OF_INDEX:
return None
elif err:
GRIB_CHECK(err)
return None
else:
return put_handle(h)
@require(msgid=int)
def grib_get_message_size(msgid):
"""
@brief Get the size of a coded message.
@param msgid id of the message loaded in memory
@return size in bytes of the message
@exception CodesInternalError
"""
h = get_handle(msgid)
size_p = ffi.new("size_t*")
err = lib.grib_get_message_size(h, size_p)
GRIB_CHECK(err)
return size_p[0]
@require(msgid=int)
def grib_get_message_offset(msgid):
"""
@brief Get the offset of a coded message.
@param msgid id of the message loaded in memory
@return offset in bytes of the message
@exception CodesInternalError
"""
h = get_handle(msgid)
offset_p = ffi.new("long int*")
err = lib.grib_get_message_offset(h, offset_p)
GRIB_CHECK(err)
return offset_p[0]
@require(msgid=int, key=str, index=int)
def grib_get_double_element(msgid, key, index):
"""
@brief Get as double the i-th element of the "key" array.
@param msgid id of the message loaded in memory
@param key the key to be searched
@param index zero based index of value to retrieve
@return value
@exception CodesInternalError
"""
h = get_handle(msgid)
value_p = ffi.new("double*")
err = lib.grib_get_double_element(h, key.encode(ENC), index, value_p)
GRIB_CHECK(err)
return value_p[0]
@require(msgid=int, key=str, indexes=(list, tuple))
def grib_get_double_elements(msgid, key, indexes):
"""
@brief Get as double array the elements of the "key" array whose indexes are listed in the input array.
@param msgid id of the message loaded in memory
@param key the key to be searched
@param indexes list or tuple of indexes
@return numpy.ndarray
@exception CodesInternalError
"""
nidx = len(indexes)
h = get_handle(msgid)
i_p = ffi.new("int[]", indexes)
value_p = ffi.new("double[]", nidx)
err = lib.grib_get_double_elements(h, key.encode(ENC), i_p, nidx, value_p)
GRIB_CHECK(err)
return [float(v) for v in value_p]
@require(msgid=int, key=str)
def grib_get_elements(msgid, key, indexes):
"""
@brief Retrieve the elements of the key array for the indexes specified in the input.
@param msgid id of the message loaded in memory
@param key the key to be searched
@param indexes single index or a list of indexes
@return numpy.ndarray containing the values of key for the given indexes
@exception CodesInternalError
"""
try:
iter(indexes)
except TypeError:
indexes = (indexes,)
return grib_get_double_elements(msgid, key, indexes)
@require(msgid=int, key=str)
def grib_set_missing(msgid, key):
"""
@brief Set as missing the value for a key in a GRIB message.
It can be used to set a missing value in the GRIB header but not in
the data values.
\b Examples: \ref grib_set_missing.py "grib_set_missing.py"
@param msgid id of the message loaded in memory
@param key key name
@exception CodesInternalError
"""
h = get_handle(msgid)
GRIB_CHECK(lib.grib_set_missing(h, key.encode(ENC)))
@require(gribid=int)
def grib_set_key_vals(gribid, key_vals):
"""
Set the values for several keys at once in a grib message.
@param gribid id of the grib loaded in memory
@param key_vals can be a string, list/tuple or dictionary.
If a string, format must be "key1=val1,key2=val2"
If a list, it must contain strings of the form "key1=val1"
@exception GribInternalError
"""
if len(key_vals) == 0:
raise errors.InvalidKeyValueError("Empty key/values argument")
key_vals_str = ""
if isinstance(key_vals, str):
# Plain string. We need to do a DEEP copy so as not to change the original
key_vals_str = "".join(key_vals)
elif isinstance(key_vals, (list, tuple)):
# A list of key=val strings
for kv in key_vals:
if not isinstance(kv, str):
raise TypeError("Invalid list/tuple element type '%s'" % kv)
if "=" not in str(kv):
raise GribInternalError("Invalid list/tuple element format '%s'" % kv)
if len(key_vals_str) > 0:
key_vals_str += ","
key_vals_str += kv
elif isinstance(key_vals, dict):
# A dictionary mapping keys to values
for key in key_vals.keys():
if len(key_vals_str) > 0:
key_vals_str += ","
key_vals_str += key + "=" + str(key_vals[key])
else:
raise TypeError("Invalid argument type")
h = get_handle(gribid)
values = ffi.new("grib_values[]", 1024)
count_p = ffi.new("int*", 1000)
err = lib.parse_keyval_string(
ffi.NULL, key_vals_str.encode(ENC), 1, lib.GRIB_TYPE_UNDEFINED, values, count_p
)
GRIB_CHECK(err)
err = lib.grib_set_values(h, values, count_p[0])
GRIB_CHECK(err)
@require(msgid=int, key=str)
def grib_is_missing(msgid, key):
"""
@brief Check if the value of a key is MISSING.
The value of a key is considered as MISSING when all the bits assigned to it
are set to 1. This is different from the actual key missing from the grib message.
The value of a key MISSING has a special significance and that can be read about
in the WMO documentation.
@param msgid id of the message loaded in memory
@param key key name
@return 0->not missing, 1->missing
@exception CodesInternalError
"""
h = get_handle(msgid)
err, value = err_last(lib.grib_is_missing)(h, key.encode(ENC))
GRIB_CHECK(err)
return value
@require(msgid=int, key=str)
def grib_is_defined(msgid, key):
"""
@brief Check if a key is defined (exists)
@param msgid id of the message loaded in memory
@param key key name
@return 0->not defined, 1->defined
@exception GribInternalError
"""
h = get_handle(msgid)
return lib.grib_is_defined(h, key.encode(ENC))
@require(gribid=int, inlat=(int, float), inlon=(int, float))
def grib_find_nearest(gribid, inlat, inlon, is_lsm=False, npoints=1):
"""
@brief Find the nearest grid point or the nearest four grid points to a given latitude/longitude.
The number of nearest points returned can be controled through the npoints function argument.
\b Examples: \ref grib_nearest.py "grib_nearest.py"
@param gribid id of the GRIB message loaded in memory
@param inlat latitude of the point
@param inlon longitude of the point
@param is_lsm True if the nearest land point is required otherwise False.
@param npoints 1 or 4 nearest grid points
@return (npoints*(outlat,outlon,value,dist,index))
@exception CodesInternalError
"""
h = get_handle(gribid)
inlats_p = ffi.new("double*", inlat)
inlons_p = ffi.new("double*", inlon)
if npoints == 1:
outlats_p = ffi.new("double[]", 1)
outlons_p = ffi.new("double[]", 1)
values_p = ffi.new("double[]", 1)
distances_p = ffi.new("double[]", 1)
indexes_p = ffi.new("int[]", 1)
num_input_points = 1
# grib_nearest_find_multiple always returns ONE nearest neighbour
err = lib.grib_nearest_find_multiple(
h,
is_lsm,
inlats_p,
inlons_p,
num_input_points,
outlats_p,
outlons_p,
values_p,
distances_p,
indexes_p,
)
GRIB_CHECK(err)
elif npoints == 4:
outlats_p = ffi.new("double[]", npoints)
outlons_p = ffi.new("double[]", npoints)
values_p = ffi.new("double[]", npoints)
distances_p = ffi.new("double[]", npoints)
indexes_p = ffi.new("int[]", npoints)
size = ffi.new("size_t *")
err, nid = err_last(lib.grib_nearest_new)(h)
GRIB_CHECK(err)
flags = 0
err = lib.grib_nearest_find(
nid,
h,
inlat,
inlon,
flags,
outlats_p,
outlons_p,
values_p,
distances_p,
indexes_p,
size,
)
GRIB_CHECK(err)
GRIB_CHECK(lib.grib_nearest_delete(nid))
else:
raise ValueError("Invalid value for npoints. Expecting 1 or 4.")
result = []
for i in range(npoints):
result.append(
Bunch(
lat=outlats_p[i],
lon=outlons_p[i],
value=values_p[i],
distance=distances_p[i],
index=indexes_p[i],
)
)
return tuple(result)
@require(gribid=int, is_lsm=bool)
def grib_find_nearest_multiple(gribid, is_lsm, inlats, inlons):
"""
@brief Find the nearest point of a set of points whose latitudes and longitudes are given in
the inlats, inlons arrays respectively
@param gribid id of the GRIB message loaded in memory
@param is_lsm True if the nearest land point is required otherwise False.
@param inlats latitudes of the points to search for
@param inlons longitudes of the points to search for
@return (npoints*(outlat,outlon,value,dist,index))
@exception CodesInternalError
"""
h = get_handle(gribid)
npoints = len(inlats)
if len(inlons) != npoints:
raise ValueError(
"grib_find_nearest_multiple: input arrays inlats and inlons must have the same length"
)
inlats_p = ffi.new("double[]", inlats)
inlons_p = ffi.new("double[]", inlons)
outlats_p = ffi.new("double[]", npoints)
outlons_p = ffi.new("double[]", npoints)
values_p = ffi.new("double[]", npoints)
distances_p = ffi.new("double[]", npoints)
indexes_p = ffi.new("int[]", npoints)
# Note: grib_nearest_find_multiple always returns ONE nearest neighbour
err = lib.grib_nearest_find_multiple(
h,
is_lsm,
inlats_p,
inlons_p,
npoints,
outlats_p,
outlons_p,
values_p,
distances_p,
indexes_p,
)
GRIB_CHECK(err)
result = []
for i in range(npoints):
result.append(
Bunch(
lat=outlats_p[i],
lon=outlons_p[i],
value=values_p[i],
distance=distances_p[i],
index=indexes_p[i],
)
)
return tuple(result)
@require(msgid=int, key=str)
def grib_get_native_type(msgid, key):
"""
@brief Retrieve the native type of a key.
Possible values can be int, float or str.
@param msgid id of the message loaded in memory
@param key key we want to find out the type for
@return type of key given as input or None if not determined
@exception CodesInternalError
"""
h = get_handle(msgid)
itype_p = ffi.new("int*")
err = lib.grib_get_native_type(h, key.encode(ENC), itype_p)
GRIB_CHECK(err)
if itype_p[0] in KEYTYPES:
return KEYTYPES[itype_p[0]]
else:
return None
@require(msgid=int, key=str)
def grib_get(msgid, key, ktype=None):
r"""
@brief Get the value of a key in a message.
The type of value returned depends on the native type of the requested key.
The type of value returned can be forced by using the type argument of the
function. The ktype argument can be int, float, str or bytes.
The \em msgid references a message loaded in memory.
\b Examples: \ref grib_get_keys.py "grib_get_keys.py", \ref grib_print_data.py "grib_print_data.py"
@see grib_new_from_file, grib_release, grib_set
@param msgid id of the message loaded in memory
@param key key name
@param ktype the type we want the output in, native type if not specified
@return scalar value of key as int, float or str
@exception CodesInternalError
"""
if not key:
raise ValueError("Invalid key name")
if ktype is None:
ktype = grib_get_native_type(msgid, key)
result = None
if ktype is int:
result = grib_get_long(msgid, key)
elif ktype is float:
result = grib_get_double(msgid, key)
elif ktype is str:
result = grib_get_string(msgid, key)
elif ktype is bytes:
result = grib_get_string(msgid, key)
return result
@require(msgid=int, key=str)
def grib_get_array(msgid, key, ktype=None):
"""
@brief Get the contents of an array key.
The type of the array returned depends on the native type of the requested key.
For numeric data, the output array will be stored in a NumPy ndarray.
The type of value returned can be forced by using the ktype argument of the function.
The ktype argument can be int, float, float32, float64, str or bytes.
@param msgid id of the message loaded in memory
@param key the key to get the value for
@param ktype the type we want the output in, native type if not specified
@return numpy.ndarray or None
@exception CodesInternalError
"""
if ktype is None:
ktype = grib_get_native_type(msgid, key)
# ECC-2086
if ktype is bytes and key == "bitmap":
return grib_get_long_array(msgid, key)
result = None
if ktype is int:
result = grib_get_long_array(msgid, key)
elif ktype is float or ktype is np.float64:
result = grib_get_double_array(msgid, key)
elif ktype is np.float32:
result = grib_get_float_array(msgid, key)
elif ktype is str:
result = grib_get_string_array(msgid, key)
elif ktype is bytes:
result = grib_get_string_array(msgid, key)
return result
@require(gribid=int)
def grib_get_values(gribid, ktype=float):
"""
@brief Retrieve the contents of the 'values' key for a GRIB message.
A NumPy ndarray containing the values in the GRIB message is returned.
\b Examples: \ref grib_print_data.py "grib_print_data.py", \ref grib_samples.py "grib_samples.py"
@param gribid id of the GRIB loaded in memory
@param ktype data type of the result: numpy.float32 or numpy.float64
@return numpy.ndarray
@exception CodesInternalError
"""
result = None
if ktype is np.float32:
result = grib_get_float_array(gribid, "values")
elif ktype is np.float64 or ktype is float:
result = grib_get_double_array(gribid, "values")
else:
raise TypeError(
f"Unsupported data type {ktype}. Supported data types are numpy.float32 and numpy.float64"
)
return result
@require(gribid=int)
def grib_get_data(gribid):
"""
@brief Get array containing latitude/longitude and data values.
@param gribid id of the GRIB loaded in memory
@return lat/lon/value list. Each list element is a dict
"""
npoints = grib_get(gribid, "numberOfDataPoints")
outlats_p = ffi.new("double[]", npoints)
outlons_p = ffi.new("double[]", npoints)
values_p = ffi.new("double[]", npoints)
h = get_handle(gribid)
err = lib.grib_get_data(h, outlats_p, outlons_p, values_p)
GRIB_CHECK(err)
result = []
for i in range(npoints):
result.append(Bunch(lat=outlats_p[i], lon=outlons_p[i], value=values_p[i]))
return tuple(result)
@require(gribid=int)
def grib_set_values(gribid, values):
"""
@brief Set the contents of the 'values' key for a GRIB message.
The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ...
The elements of the input sequence need to be convertible to a double.
\b Examples: \ref grib_clone.py "grib_clone.py", \ref grib_samples.py "grib_samples.py"
@param gribid id of the GRIB loaded in memory
@param values array of values to set as tuple, list, array or numpy.ndarray
"""
grib_set_double_array(gribid, "values", values)
@require(msgid=int, key=str)
def grib_set(msgid, key, value):
"""
@brief Set the value for a scalar key in a message.
The input value can be a python int, float or str.
\b Examples: \ref grib_set_keys.py "grib_set_keys.py"
@see grib_new_from_file, grib_release, grib_get
@param msgid id of the message loaded in memory
@param key key name
@param value scalar value to set for key
@exception CodesInternalError
"""
if isinstance(value, (int, np.int64)):
grib_set_long(msgid, key, value)
elif isinstance(value, (float, np.float16, np.float32, np.float64)):
grib_set_double(msgid, key, value)
elif isinstance(value, str):
grib_set_string(msgid, key, value)
# elif hasattr(value, "__iter__"):
# # The value passed in is iterable; i.e. a list or array etc
# grib_set_array(msgid, key, value)
else:
hint = ""
if hasattr(value, "__iter__"):
hint = " (Hint: for array keys use codes_set_array(msgid, key, value))"
raise GribInternalError(
"Invalid type of value when setting key '%s'%s." % (key, hint)
)
@require(msgid=int, key=str)
def grib_set_array(msgid, key, value):
"""
@brief Set the value for an array key in a message.
Examples of array keys:
"values" - data values
"pl" - list of number of points for each latitude in a reduced grid
"pv" - list of vertical levels
The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ...
@param msgid id of the message loaded in memory
@param key key name
@param value array to set for key
@exception CodesInternalError
"""
val0 = None
try:
val0 = value[0]
except TypeError:
pass
if isinstance(val0, (float, np.float16, np.float32, np.float64)):
grib_set_double_array(msgid, key, value)
elif isinstance(val0, str):
grib_set_string_array(msgid, key, value)
else:
try:
int(val0)
except (ValueError, TypeError):
raise GribInternalError(
"Invalid type of value when setting key '%s'." % key
)
grib_set_long_array(msgid, key, value)
@require(indexid=int, key=str)
def grib_index_get(indexid, key, ktype=str):
"""
@brief Get the distinct values of an index key.
The key must belong to the index.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created on the given key.
@param key key for which the values are returned
@param ktype the type we want the output in (int, float or str), str if not specified
@return array of values
@exception CodesInternalError
"""
# Cannot get the native type of a key from an index
# so right now the default is str. The user can overwrite
# the type but there is no way right now to do it automatically.
# if ktype is None:
# ktype = grib_get_native_type(indexid,key)
result = None
if ktype is int:
result = grib_index_get_long(indexid, key)
elif ktype is float:
result = grib_index_get_double(indexid, key)
elif ktype is str:
result = grib_index_get_string(indexid, key)
return result
@require(indexid=int, key=str)
def grib_index_select(indexid, key, value):
"""
@brief Select the message subset with key==value.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of an index created from a file. The index must have been created with the key in argument.
@param key key to be selected
@param value value of the key to select
@exception CodesInternalError
"""
if isinstance(value, int):
grib_index_select_long(indexid, key, value)
elif isinstance(value, float):
grib_index_select_double(indexid, key, value)
elif isinstance(value, str):
grib_index_select_string(indexid, key, value)
else:
raise GribInternalError("Invalid type of value when setting key '%s'." % key)
@require(indexid=int, filename=str)
def grib_index_write(indexid, filename):
"""
@brief Write an index to a file for later reuse.
An index can be loaded back from an index file with \ref grib_index_read.
\b Examples: \ref grib_index.py "grib_index.py"
@param indexid id of the index
@param filename path of file to save the index to
@exception CodesInternalError
"""
ih = get_index(indexid)
GRIB_CHECK(lib.grib_index_write(ih, filename.encode(ENC)))
@require(filename=str)
def grib_index_read(filename):
"""
@brief Loads an index previously saved with \ref grib_index_write to a file.
\b Examples: \ref grib_index.py "grib_index.py"
@param filename path of file to load the index from
@return id of the loaded index
@exception CodesInternalError
"""
err, ih = err_last(lib.grib_index_read)(ffi.NULL, filename.encode(ENC))
GRIB_CHECK(err)
return put_index(ih)
@require(flag=bool)
def grib_no_fail_on_wrong_length(flag):
"""
@brief Do not fail if the message has the wrong length.
@param flag True/False
"""
raise NotImplementedError("API not implemented in CFFI porting.")
@require(flag=bool)
def grib_gts_header(flag):
"""
@brief Set the GTS header on/off.
@param flag True/False
"""
context = lib.grib_context_get_default()
if flag:
lib.grib_gts_header_on(context)
else:
lib.grib_gts_header_off(context)
def grib_get_api_version(vformat=str):
"""
@brief Get the API version.
Returns the version of the API as a string in the format "major.minor.revision"
or as an integer (10000*major + 100*minor + revision)
"""
def div(v, d):
return (v / d, v % d)
if not lib:
raise RuntimeError("Could not load the ecCodes library!")
v = lib.grib_get_api_version()
if vformat is str:
v, revision = div(v, 100)
v, minor = div(v, 100)
major = v
return "%d.%d.%d" % (major, minor, revision)
else:
return v
__version__ = grib_get_api_version()
def codes_get_version_info():
"""
@brief Get version information.
Returns a dictionary containing the versions of the ecCodes API and the Python bindings
"""
vinfo = dict()
vinfo["eccodes"] = grib_get_api_version()
vinfo["bindings"] = bindings_version
return vinfo
@require(order=int)
def codes_get_gaussian_latitudes(order):
"""
@brief Return the Gaussian latitudes
@param order The Gaussian order/number (also called the truncation)
@return A list of latitudes with 2*order elements
"""
num_elems = 2 * order
outlats_p = ffi.new("double[]", num_elems)
err = lib.grib_get_gaussian_latitudes(order, outlats_p)
GRIB_CHECK(err)
return outlats_p
@require(msgid=int)
def grib_get_message(msgid):
"""
@brief Get the binary message.
Returns the binary string message associated with the message identified by msgid.
@see grib_new_from_message
@param msgid id of the message loaded in memory
@return binary string message associated with msgid
@exception CodesInternalError
"""
h = get_handle(msgid)
message_p = ffi.new("const void**")
message_length_p = ffi.new("size_t*")
err = lib.grib_get_message(h, message_p, message_length_p)
GRIB_CHECK(err)
# NOTE: ffi.string would stop on the first nul-character.
fixed_length_buffer = ffi.buffer(
ffi.cast("char*", message_p[0]), message_length_p[0]
)
# Convert to bytes
return fixed_length_buffer[:]
@require(message=(bytes, str, memoryview))
def grib_new_from_message(message):
"""
@brief Create a handle from a message in memory.
Create a new message from the input binary string and return its id.
@see grib_get_message
@param message binary string message
@return msgid of the newly created message
@exception CodesInternalError
"""
if isinstance(message, memoryview):
message = ffi.from_buffer(message)
if isinstance(message, str):
message = message.encode(ENC)
h = lib.grib_handle_new_from_message_copy(ffi.NULL, message, len(message))
if h == ffi.NULL:
raise errors.MessageInvalidError("new_from_message failed")
return put_handle(h)
def codes_definition_path():
"""
@brief Get the definition path
"""
context = lib.grib_context_get_default()
dpath = lib.codes_definition_path(context)
return ffi.string(dpath).decode(ENC)
def codes_samples_path():
"""
@brief Get the samples path
"""
context = lib.grib_context_get_default()
spath = lib.codes_samples_path(context)
return ffi.string(spath).decode(ENC)
def grib_set_debug(dmode):
"""
@brief Set the debug mode
@param dmode -1, 0 or 1
"""
context = lib.grib_context_get_default()
lib.grib_context_set_debug(context, dmode)
@require(val=int)
def grib_set_data_quality_checks(val):
"""
@brief Enable/Disable GRIB data quality checks
@param val 0, 1 or 2
0 -> disable data quality checks
1 -> failure results in an error
2 -> failure results in a warning
"""
assert val == 0 or val == 1 or val == 2
context = lib.grib_context_get_default()
lib.grib_context_set_data_quality_checks(context, val)
@require(basename=str)
def grib_full_defs_path(basename):
"""
@brief Get the full path of a definition file
"""
context = lib.grib_context_get_default()
path = lib.grib_context_full_defs_path(context, basename.encode(ENC))
if path:
return ffi.string(path).decode(ENC)
else:
return ""
@require(defs_path=str)
def grib_set_definitions_path(defs_path):
"""
@brief Set the definitions path
@param defs_path definitions path
"""
context = lib.grib_context_get_default()
lib.grib_context_set_definitions_path(context, defs_path.encode(ENC))
@require(samples_path=str)
def grib_set_samples_path(samples_path):
"""
@brief Set the samples path
@param samples_path samples path
"""
context = lib.grib_context_get_default()
lib.grib_context_set_samples_path(context, samples_path.encode(ENC))
def grib_context_delete():
"""
@brief Wipe all the cached data and definitions files in the context
"""
lib.grib_context_delete(ffi.NULL)
def codes_bufr_multi_element_constant_arrays_on():
"""
@brief BUFR: Turn on the mode where you get multiple elements
in constant arrays
@exception CodesInternalError
"""
context = lib.grib_context_get_default()
lib.codes_bufr_multi_element_constant_arrays_on(context)
def codes_bufr_multi_element_constant_arrays_off():
"""
@brief BUFR: Turn off the mode where you get multiple elements
in constant arrays i.e. you get a single element
@exception CodesInternalError
"""
context = lib.grib_context_get_default()
lib.codes_bufr_multi_element_constant_arrays_off(context)
@require(msgid=int)
def codes_dump(msgid, output_fileobj=sys.stdout, mode="wmo", flags=0):
"""
@brief Print all keys to an output file object, with the given dump mode and flags
@param msgid id of the message loaded in memory
@param output_fileobj output file object e.g., sys.stdout
@param mode dump mode e.g., "wmo", "debug", "json"
"""
h = get_handle(msgid)
lib.grib_dump_content(h, output_fileobj, mode.encode(ENC), flags, ffi.NULL)
# Convert the C codes_bufr_header struct to a Python dictionary
def _convert_struct_to_dict(s):
result = {}
ident_found = False
for a in dir(s):
value = getattr(s, a)
if not ident_found and a == "ident":
value = ffi.string(value).decode(ENC)
ident_found = True
result[a] = value
return result
def codes_bufr_extract_headers(filepath, is_strict=True):
"""
@brief BUFR header extraction
@param filepath path of input BUFR file
@param is_strict fail as soon as any invalid BUFR message is encountered
@return a generator that yields items (each item is a dictionary)
@exception CodesInternalError
"""
context = lib.grib_context_get_default()
headers_p = ffi.new("struct codes_bufr_header**")
num_message_p = ffi.new("int*")
err = lib.codes_bufr_extract_headers_malloc(
context, filepath.encode(ENC), headers_p, num_message_p, is_strict
)
GRIB_CHECK(err)
num_messages = num_message_p[0]
headers = headers_p[0]
# result = []
# for i in range(num_messages):
# d = _convert_struct_to_dict(headers[i])
# result.append(d)
# return result
i = 0
while i < num_messages:
yield _convert_struct_to_dict(headers[i])
i += 1
@require(msgid=int)
def codes_bufr_key_is_header(msgid, key):
"""
@brief Check if the BUFR key is in the header or in the data section.
If the data section has not been unpacked, then passing in a key from
the data section will throw KeyValueNotFoundError.
@param msgid id of the BUFR message loaded in memory
@param key key name
@return 1->header, 0->data section
@exception CodesInternalError
"""
h = get_handle(msgid)
err, value = err_last(lib.codes_bufr_key_is_header)(h, key.encode(ENC))
GRIB_CHECK(err)
return value
@require(msgid=int)
def codes_bufr_key_is_coordinate(msgid, key):
"""
@brief Check if the BUFR key corresponds to a coordinate descriptor.
If the data section has not been unpacked, then passing in a key from
the data section will throw KeyValueNotFoundError.
@param msgid id of the BUFR message loaded in memory
@param key key name
@return 1->coordinate, 0->not coordinate
@exception CodesInternalError
"""
h = get_handle(msgid)
err, value = err_last(lib.codes_bufr_key_is_coordinate)(h, key.encode(ENC))
GRIB_CHECK(err)
return value
def codes_extract_offsets(filepath, product_kind, is_strict=True):
"""
@brief Message offset extraction
@param filepath path of input file
@param product_kind one of CODES_PRODUCT_GRIB, CODES_PRODUCT_BUFR, CODES_PRODUCT_ANY or CODES_PRODUCT_GTS
@param is_strict if True, fail as soon as any invalid message is encountered
@return a generator that yields offsets (as integers)
@exception CodesInternalError
"""
context = lib.grib_context_get_default()
offsets_p = ffi.new("long int**")
num_message_p = ffi.new("int*")
err = lib.codes_extract_offsets_malloc(
context, filepath.encode(ENC), product_kind, offsets_p, num_message_p, is_strict
)
GRIB_CHECK(err)
num_messages = num_message_p[0]
offsets = offsets_p[0]
i = 0
while i < num_messages:
yield offsets[i]
i += 1
def codes_extract_offsets_sizes(filepath, product_kind, is_strict=True):
"""
@brief Message offset and size extraction
@param filepath path of input file
@param product_kind one of CODES_PRODUCT_GRIB, CODES_PRODUCT_BUFR, CODES_PRODUCT_ANY or CODES_PRODUCT_GTS
@param is_strict if True, fail as soon as any invalid message is encountered
@return a generator that yields lists of pairs of offsets and sizes (as integers)
@exception CodesInternalError
"""
context = lib.grib_context_get_default()
offsets_p = ffi.new("long int**")
sizes_p = ffi.new("size_t**")
num_message_p = ffi.new("int*")
err = lib.codes_extract_offsets_sizes_malloc(
context,
filepath.encode(ENC),
product_kind,
offsets_p,
sizes_p,
num_message_p,
is_strict,
)
GRIB_CHECK(err)
num_messages = num_message_p[0]
offsets = offsets_p[0]
sizes = sizes_p[0]
i = 0
while i < num_messages:
yield (offsets[i], sizes[i])
i += 1
@require(select=int)
def codes_get_features(select=CODES_FEATURES_ALL):
"""
@brief Get the list of library features.
@param select One of CODES_FEATURES_ALL, CODES_FEATURES_ENABLED or CODES_FEATURES_DISABLED
@return space-separated string of feature names
@exception CodesInternalError
"""
ssize = 1024
result = ffi.new("char[]", ssize)
size_p = ffi.new("size_t *", ssize)
err = lib.codes_get_features(result, size_p, select)
GRIB_CHECK(err)
return ffi.string(result).decode(ENC)
# -------------------------------
# EXPERIMENTAL FEATURES
# -------------------------------
@require(msgid=int)
def grib_nearest_new(msgid):
h = get_handle(msgid)
err, nid = err_last(lib.grib_nearest_new)(h)
GRIB_CHECK(err)
return put_grib_nearest(nid)
def put_grib_nearest(nid):
return int(ffi.cast("size_t", nid))
def get_grib_nearest(nid):
return ffi.cast("grib_nearest*", nid)
@require(nid=int)
def grib_nearest_delete(nid):
nh = get_grib_nearest(nid)
lib.grib_nearest_delete(nh)
@require(nid=int, gribid=int)
def grib_nearest_find(nid, gribid, inlat, inlon, flags, is_lsm=False, npoints=4):
# flags has to be one of:
# GRIB_NEAREST_SAME_GRID
# GRIB_NEAREST_SAME_DATA
# GRIB_NEAREST_SAME_POINT
if npoints != 4:
raise errors.FunctionNotImplementedError(
"grib_nearest_find npoints argument: Only 4 points supported"
)
if is_lsm:
raise errors.FunctionNotImplementedError(
"grib_nearest_find is_lsm argument: Land sea mask not supported"
)
h = get_handle(gribid)
outlats_p = ffi.new("double[]", npoints)
outlons_p = ffi.new("double[]", npoints)
values_p = ffi.new("double[]", npoints)
distances_p = ffi.new("double[]", npoints)
indexes_p = ffi.new("int[]", npoints)
size = ffi.new("size_t *")
nh = get_grib_nearest(nid)
err = lib.grib_nearest_find(
nh,
h,
inlat,
inlon,
flags,
outlats_p,
outlons_p,
values_p,
distances_p,
indexes_p,
size,
)
GRIB_CHECK(err)
result = []
for i in range(npoints):
result.append(
Bunch(
lat=outlats_p[i],
lon=outlons_p[i],
value=values_p[i],
distance=distances_p[i],
index=indexes_p[i],
)
)
return tuple(result)
def codes_get_library_path():
return library_path
ecmwf-eccodes-python-b43a0f2/gribapi/errors.py 0000664 0001750 0001750 00000021126 15172126764 021644 0 ustar alastair alastair #
# (C) Copyright 2017- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
#
"""
Exception class hierarchy
"""
from .bindings import ENC, ffi, lib
class GribInternalError(Exception):
"""
@brief Wrap errors coming from the C API in a Python exception object.
Base class for all exceptions
"""
def __init__(self, value):
# Call the base class constructor with the parameters it needs
Exception.__init__(self, value)
if isinstance(value, int):
self.msg = ffi.string(lib.grib_get_error_message(value)).decode(ENC)
else:
self.msg = value
def __str__(self):
return self.msg
class RuntimeError(GribInternalError):
"""Runtime error."""
class FunctionalityNotEnabledError(GribInternalError):
"""Functionality not enabled."""
class WrongBitmapSizeError(GribInternalError):
"""Size of bitmap is incorrect."""
class OutOfRangeError(GribInternalError):
"""Value out of coding range."""
class UnsupportedEditionError(GribInternalError):
"""Edition not supported.."""
class AttributeNotFoundError(GribInternalError):
"""Attribute not found.."""
class TooManyAttributesError(GribInternalError):
"""Too many attributes. Increase MAX_ACCESSOR_ATTRIBUTES."""
class AttributeClashError(GribInternalError):
"""Attribute is already present, cannot add."""
class NullPointerError(GribInternalError):
"""Null pointer."""
class MissingBufrEntryError(GribInternalError):
"""Missing BUFR table entry for descriptor."""
class WrongConversionError(GribInternalError):
"""Wrong type conversion."""
class StringTooSmallError(GribInternalError):
"""String is smaller than requested."""
class InvalidKeyValueError(GribInternalError):
"""Invalid key value."""
class ValueDifferentError(GribInternalError):
"""Value is different."""
class DifferentEditionError(GribInternalError):
"""Edition of two messages is different."""
class InvalidBitsPerValueError(GribInternalError):
"""Invalid number of bits per value."""
class CorruptedIndexError(GribInternalError):
"""Index is corrupted."""
class MessageMalformedError(GribInternalError):
"""Message malformed."""
class UnderflowError(GribInternalError):
"""Underflow."""
class SwitchNoMatchError(GribInternalError):
"""Switch unable to find a matching case."""
class ConstantFieldError(GribInternalError):
"""Constant field."""
class MessageTooLargeError(GribInternalError):
"""Message is too large for the current architecture."""
class InternalArrayTooSmallError(GribInternalError):
"""An internal array is too small."""
class PrematureEndOfFileError(GribInternalError):
"""End of resource reached when reading message."""
class NullIndexError(GribInternalError):
"""Null index."""
class EndOfIndexError(GribInternalError):
"""End of index reached."""
class WrongGridError(GribInternalError):
"""Grid description is wrong or inconsistent."""
class NoValuesError(GribInternalError):
"""Unable to code a field without values."""
class EndError(GribInternalError):
"""End of resource."""
class WrongTypeError(GribInternalError):
"""Wrong type while packing."""
class NoDefinitionsError(GribInternalError):
"""Definitions files not found."""
class HashArrayNoMatchError(GribInternalError):
"""Hash array no match."""
class ConceptNoMatchError(GribInternalError):
"""Concept no match."""
class OutOfAreaError(GribInternalError):
"""The point is out of the grid area."""
class MissingKeyError(GribInternalError):
"""Missing a key from the fieldset."""
class InvalidOrderByError(GribInternalError):
"""Invalid order by."""
class InvalidNearestError(GribInternalError):
"""Invalid nearest id."""
class InvalidKeysIteratorError(GribInternalError):
"""Invalid keys iterator id."""
class InvalidIteratorError(GribInternalError):
"""Invalid iterator id."""
class InvalidIndexError(GribInternalError):
"""Invalid index id."""
class InvalidGribError(GribInternalError):
"""Invalid GRIB id."""
class InvalidFileError(GribInternalError):
"""Invalid file id."""
class WrongStepUnitError(GribInternalError):
"""Wrong units for step (step must be integer)."""
class WrongStepError(GribInternalError):
"""Unable to set step."""
class InvalidTypeError(GribInternalError):
"""Invalid key type."""
class WrongLengthError(GribInternalError):
"""Wrong message length."""
class ValueCannotBeMissingError(GribInternalError):
"""Value cannot be missing."""
class InvalidSectionNumberError(GribInternalError):
"""Invalid section number."""
class NullHandleError(GribInternalError):
"""Null handle."""
class InvalidArgumentError(GribInternalError):
"""Invalid argument."""
class ReadOnlyError(GribInternalError):
"""Value is read only."""
class MemoryAllocationError(GribInternalError):
"""Memory allocation error."""
class GeocalculusError(GribInternalError):
"""Problem with calculation of geographic attributes."""
class NoMoreInSetError(GribInternalError):
"""Code cannot unpack because of string too small."""
class EncodingError(GribInternalError):
"""Encoding invalid."""
class DecodingError(GribInternalError):
"""Decoding invalid."""
class MessageInvalidError(GribInternalError):
"""Message invalid."""
class IOProblemError(GribInternalError):
"""Input output problem."""
class KeyValueNotFoundError(GribInternalError):
"""Key/value not found."""
class WrongArraySizeError(GribInternalError):
"""Array size mismatch."""
class CodeNotFoundInTableError(GribInternalError):
"""Code not found in code table."""
class FileNotFoundError(GribInternalError):
"""File not found."""
class ArrayTooSmallError(GribInternalError):
"""Passed array is too small."""
class MessageEndNotFoundError(GribInternalError):
"""Missing 7777 at end of message."""
class FunctionNotImplementedError(GribInternalError):
"""Function not yet implemented."""
class BufferTooSmallError(GribInternalError):
"""Passed buffer is too small."""
class InternalError(GribInternalError):
"""Internal error."""
class EndOfFileError(GribInternalError):
"""End of resource reached."""
ERROR_MAP = {
-80: RuntimeError,
-67: FunctionalityNotEnabledError,
-66: WrongBitmapSizeError,
-65: OutOfRangeError,
-64: UnsupportedEditionError,
-63: AttributeNotFoundError,
-62: TooManyAttributesError,
-61: AttributeClashError,
-60: NullPointerError,
-59: MissingBufrEntryError,
-58: WrongConversionError,
-57: StringTooSmallError,
-56: InvalidKeyValueError,
-55: ValueDifferentError,
-54: DifferentEditionError,
-53: InvalidBitsPerValueError,
-52: CorruptedIndexError,
-51: MessageMalformedError,
-50: UnderflowError,
-49: SwitchNoMatchError,
-48: ConstantFieldError,
-47: MessageTooLargeError,
-46: InternalArrayTooSmallError,
-45: PrematureEndOfFileError,
-44: NullIndexError,
-43: EndOfIndexError,
-42: WrongGridError,
-41: NoValuesError,
-40: EndError,
-39: WrongTypeError,
-38: NoDefinitionsError,
-37: HashArrayNoMatchError,
-36: ConceptNoMatchError,
-35: OutOfAreaError,
-34: MissingKeyError,
-33: InvalidOrderByError,
-32: InvalidNearestError,
-31: InvalidKeysIteratorError,
-30: InvalidIteratorError,
-29: InvalidIndexError,
-28: InvalidGribError,
-27: InvalidFileError,
-26: WrongStepUnitError,
-25: WrongStepError,
-24: InvalidTypeError,
-23: WrongLengthError,
-22: ValueCannotBeMissingError,
-21: InvalidSectionNumberError,
-20: NullHandleError,
-19: InvalidArgumentError,
-18: ReadOnlyError,
-17: MemoryAllocationError,
-16: GeocalculusError,
-15: NoMoreInSetError,
-14: EncodingError,
-13: DecodingError,
-12: MessageInvalidError,
-11: IOProblemError,
-10: KeyValueNotFoundError,
-9: WrongArraySizeError,
-8: CodeNotFoundInTableError,
-7: FileNotFoundError,
-6: ArrayTooSmallError,
-5: MessageEndNotFoundError,
-4: FunctionNotImplementedError,
-3: BufferTooSmallError,
-2: InternalError,
-1: EndOfFileError,
}
def raise_grib_error(errid):
"""
Raise the GribInternalError corresponding to ``errid``.
"""
raise ERROR_MAP[errid](errid)
ecmwf-eccodes-python-b43a0f2/eccodes-python.code-workspace 0000664 0001750 0001750 00000000075 15172126764 024115 0 ustar alastair alastair {
"folders": [
{
"path": "."
}
],
"settings": {}
}
ecmwf-eccodes-python-b43a0f2/tests/ 0000775 0001750 0001750 00000000000 15172126764 017501 5 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/tests/test_bufr_examples.py 0000664 0001750 0001750 00000004437 15172126764 023756 0 ustar alastair alastair # Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
import io
import sys
from pathlib import Path
import pytest
if sys.platform.startswith("win32"):
pytest.skip("Not applicable on Windows", allow_module_level=True)
@pytest.fixture(autouse=True)
def fixture(monkeypatch):
this_dir = Path(__file__).parent
monkeypatch.syspath_prepend(this_dir.parent)
# monkeypatch.chdir(this_dir.parent / 'data')
monkeypatch.chdir(this_dir / "sample-data")
def compare(output: str, reference: Path):
if not reference.exists():
reference.write_text(output)
try:
assert output == reference.read_text()
except AssertionError as error:
reference.with_suffix(reference.suffix + ".bad").write_text(output)
raise error
else:
reference.with_suffix(reference.suffix + ".bad").unlink(missing_ok=True)
inputs = [
"acars.bufr",
"ahi-himawari-8.bufr",
"amsu-a-noaa-19.bufr",
"amv-goes-9.bufr",
"amv-insat-3d.bufr",
"amv-meteosat-9.bufr",
"amv-noaa-20.bufr",
"aura-omi-ak.bufr",
"buoy-drifting.bufr",
"geos-abi-goes-16.bufr",
"geos-mviri-meteosat-7.bufr",
"hdob.bufr",
"rwp.bufr",
"rwp_jma.bufr",
"saral-altika.bufr",
"sral_sentinel_3a.bufr",
"synop.bufr",
"synop_multi_subset.bufr",
"temp.bufr",
"terra-modis-aerosol.bufr",
"wave.bufr",
]
@pytest.mark.parametrize("input", inputs)
def test_attributes_example(input):
from examples.attributes import run_example
input = Path(input)
stream = io.StringIO()
run_example(input, output=stream)
output = stream.getvalue()
reference = input.with_suffix(".attributes")
compare(output, reference)
@pytest.mark.parametrize("input", inputs)
def test_items_example(input):
from examples.items import run_example
input = Path(input)
stream = io.StringIO()
run_example(input, stream)
output = stream.getvalue()
reference = input.with_suffix(".items")
compare(output, reference)
ecmwf-eccodes-python-b43a0f2/tests/requirements.txt 0000664 0001750 0001750 00000000007 15172126764 022762 0 ustar alastair alastair pytest
ecmwf-eccodes-python-b43a0f2/tests/test_bufr_workarounds.py 0000664 0001750 0001750 00000006774 15172126764 024524 0 ustar alastair alastair # Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# flake8: noqa: F403
# flake8: noqa: F405
import sys
import numpy as np
import pytest as pt
import eccodes
from eccodes import *
from eccodes.highlevel._bufr import *
if sys.platform.startswith("win32"):
pt.skip("Not applicable on Windows", allow_module_level=True)
@pt.fixture(autouse=True)
def fixture(monkeypatch):
import os
this_dir = os.path.dirname(__file__)
monkeypatch.chdir(this_dir) # always run tests from the tests directory
@pt.mark.skipif(
eccodes.__version__ < "2.41.0",
reason="requires eccodes 2.41.0; otherwise segfaults (see ECC-2024)",
)
def test_workaround_for_ECC_2015():
old = BUFRMessage(open("./sample-data/hdob.bufr"))
old.copy(subsets=[0])
old.copy(subsets=[1]) # would fail without the workaround
def test_workaround_for_ECC_1624():
# Consider the following message with operator 222000, and thus the 'centre'
# key in the data section.
bufr = BUFRMessage("BUFR3")
bufr["numberOfSubsets"] = 1
bufr["compressedData"] = 0
bufr["unexpandedDescriptors"] = [
307011,
7006,
10004,
222000,
101023,
31031,
1031,
1032,
101023,
33007,
]
# There should be only one element with key 'centre', and its
# initial value should be missing.
assert bufr.get_count("centre") == 1
assert bufr["centre"] is np.ma.masked
# Test that setting 'centre' value does the right thing, and doesn't interfere
# with 'bufrHeaderCentre' from section 1.
bufr["centre"] = 42
bufr.pack()
assert bufr["centre"] == 42
assert bufr["bufrHeaderCentre"] == 98
# Test that setting 'centre' on header raises an exception.
with pt.raises(KeyValueNotFoundError):
bufr.header["centre"]
with pt.raises(KeyValueNotFoundError):
bufr.header["centre"] = 42
# Now consider a message *without* operator 222000, and thus *no* 'centre'
# key in the data section.
bufr = BUFRMessage("BUFR3")
bufr["numberOfSubsets"] = 1
bufr["compressedData"] = 0
bufr["unexpandedDescriptors"] = [307011]
# Test that setting 'centre' raises an exception.
with pt.raises(KeyValueNotFoundError):
bufr["centre"]
with pt.raises(KeyValueNotFoundError):
bufr["centre"] = 42
def test_workaround_for_ECC_2098():
bufr = BUFRMessage("BUFR3")
bufr["inputDelayedDescriptorReplicationFactor"] = 3
bufr["unexpandedDescriptors"] = [
104000, # Delayed replication of 4 elements
31001, # Delayed replication factor
204001, # Operator: Add associated field
31021, # Associated field significance
11001, # Wind speed
204000, # Operator: Close associated field
]
bufr["windDirection->associatedField"] = [0, 0, 0] # OK
bufr["windDirection->associatedField->associatedFieldSignificance"] = [
1,
1,
1,
] # Would raise ArrayTooSmallError when packing
bufr.pack()
def test_workaround_for_ECC_2022():
old = BUFRMessage(open("./sample-data/amv-goes-9.bufr", "rb"))
new = old.copy()
old.copy_to(new)
assert old.data == new.data # would fail without the workaround
ecmwf-eccodes-python-b43a0f2/tests/test_bufr_data.py 0000664 0001750 0001750 00000033006 15172126764 023043 0 ustar alastair alastair # Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# flake8: noqa: F403
# flake8: noqa: F405
import sys
import warnings
import numpy as np
import pytest as pt
from eccodes import *
from eccodes.highlevel._bufr.message import *
if sys.platform.startswith("win32"):
pt.skip("Not applicable on Windows", allow_module_level=True)
@pt.fixture(autouse=True)
def fixture(monkeypatch):
import os
this_dir = os.path.dirname(__file__)
monkeypatch.chdir(this_dir) # always run tests from the tests directory
def test_data_key():
assert Key.from_string("year") == Key(name="year")
assert Key.from_string("year->code") == Key(name="year", attribute="code")
assert Key.from_string("year->units") == Key(name="year", attribute="units")
assert Key.from_string("year->reference") == Key(name="year", attribute="reference")
assert Key.from_string("year->scale") == Key(name="year", attribute="scale")
assert Key.from_string("year->width") == Key(name="year", attribute="width")
assert Key.from_string("year->percentConfidence") == Key(
name="year->percentConfidence", primary="year", secondary="percentConfidence"
)
assert Key.from_string("year->percentConfidence->code") == Key(
name="year->percentConfidence",
primary="year",
secondary="percentConfidence",
attribute="code",
)
assert Key.from_string("#1#year") == Key(name="year", rank=1)
assert Key.from_string("#1#year->code") == Key(
name="year", rank=1, attribute="code"
)
assert Key.from_string("#1#year->percentConfidence") == Key(
name="year->percentConfidence",
rank=1,
primary="year",
secondary="percentConfidence",
)
assert Key.from_string("#1#year->percentConfidence->code") == Key(
name="year->percentConfidence",
rank=1,
primary="year",
secondary="percentConfidence",
attribute="code",
)
assert "year" == Key(name="year").string
assert "year->code" == Key(name="year", attribute="code").string
assert "year->units" == Key(name="year", attribute="units").string
assert "year->reference" == Key(name="year", attribute="reference").string
assert "year->scale" == Key(name="year", attribute="scale").string
assert "year->width" == Key(name="year", attribute="width").string
assert (
"year->percentConfidence"
== Key(
name="year->percentConfidence",
primary="year",
secondary="percentConfidence",
).string
)
assert (
"year->percentConfidence->code"
== Key(
name="year->percentConfidence",
primary="year",
secondary="percentConfidence",
attribute="code",
).string
)
assert "#1#year" == Key(name="year", rank=1).string
assert "#1#year->code" == Key(name="year", rank=1, attribute="code").string
assert (
"#1#year->percentConfidence"
== Key(
name="year->percentConfidence",
rank=1,
primary="year",
secondary="percentConfidence",
).string
)
assert (
"#1#year->percentConfidence->code"
== Key(
name="year->percentConfidence",
rank=1,
primary="year",
secondary="percentConfidence",
attribute="code",
).string
)
with pt.raises(NotFoundError):
Key.from_string("/pressure=85000/airTemperature")
def test_data_accessors():
bufr = BUFRMessage(open("./sample-data/synop.bufr"))
# Header keys (should not be accessible from the Data class)
assert "edition" not in bufr.data
with pt.raises(NotFoundError):
bufr.data["edition"]
with pt.raises(NotFoundError):
bufr.data["edition"] = 3
# Data keys: defined
assert "year" in bufr.data
assert "year->code" in bufr.data
assert "year->percentConfidence" in bufr.data
assert "year->percentConfidence->code" in bufr.data
assert "#1#year" in bufr.data
assert "#1#year->code" in bufr.data
assert "#1#year->percentConfidence" in bufr.data
assert "#1#year->percentConfidence->code" in bufr.data
assert bufr.data["year"] == 2009
assert bufr.data["year->code"] == 4001
assert bufr.data["year->percentConfidence"] == 70
assert np.all(
bufr.data["cloudAmount"] == np.ma.masked_values([8, -1, -1, -1, -1], -1)
)
# assert bufr.data['year->percentConfidence->code'] == TODO
assert bufr.data["#1#year"] == 2009
assert bufr.data["#1#year->code"] == 4001
assert bufr.data["#1#year->percentConfidence"] == 70
assert bufr.data["#1#cloudAmount"] == 8
# assert bufr.data['#1#year->percentConfidence->code'] == TODO
bufr.data["year"] = 2025
bufr.data["year->percentConfidence"] = 100
bufr.data["cloudAmount"] = [1, 2, 3, 4, 5]
bufr.data["#1#year"] = 2025
bufr.data["#1#year->percentConfidence"] = 100
bufr.data["#1#cloudAmount"] = 8
with pt.raises(ReadOnlyError):
bufr.data["year->code"] = 0
with pt.raises(ReadOnlyError):
bufr.data["#1#year->code"] = 0
# with pt.raises(ReadOnlyError): bufr.data['#1#year->percentConfidence->code'] = TODO
# Data keys: undefined
assert "year->code->code" not in bufr.data
assert "year->percentConfidence->code->code" not in bufr.data
assert "#2#year" not in bufr.data
assert "#2#year->percentConfidence" not in bufr.data
assert "#2#year->percentConfidence->code" not in bufr.data
# Negative subscripts
bufr = BUFRMessage(open("./sample-data/temp.bufr"))
assert "centre" in bufr.data[-1]
assert np.all(bufr.data[-1]["centre"] == bufr.data[2]["centre"])
assert "pressure" in bufr.data[-2]
assert np.all(bufr.data[-2]["pressure"] == bufr.data[1]["pressure"])
assert "blockNumber" in bufr.data[-3]
assert bufr.data[-3]["blockNumber"] == bufr.data[0]["blockNumber"]
with pt.raises(IndexError):
bufr.data[-4]
def test_data_get_count():
bufr = BUFRMessage(open("./sample-data/synop.bufr"))
with pt.raises(KeyValueNotFoundError):
bufr.data.get_count("edition")
assert bufr.get_count("year") == 1
assert bufr.get_count("year->code") == 1
assert bufr.get_count("year->percentConfidence") == 1
assert bufr.get_count("year->percentConfidence->code") == 1
assert bufr.get_count("#1#year") == 1
assert bufr.get_count("#1#year->code") == 1
assert bufr.get_count("#1#year->percentConfidence") == 1
assert bufr.get_count("#1#year->percentConfidence->code") == 1
with pt.raises(KeyValueNotFoundError):
bufr.get_count("#2#year")
assert bufr.get_count("cloudAmount") == 5
assert bufr.get_count("cloudAmount->code") == 5
assert bufr.get_count("cloudAmount->percentConfidence") == 5
assert bufr.get_count("cloudAmount->percentConfidence->code") == 5
assert bufr.get_count("#1#cloudAmount") == 1
assert bufr.get_count("#1#cloudAmount->code") == 1
assert bufr.get_count("#1#cloudAmount->percentConfidence") == 1
assert bufr.get_count("#1#cloudAmount->percentConfidence->code") == 1
with pt.raises(KeyValueNotFoundError):
bufr.get_count("#6#cloudAmount")
bufr = BUFRMessage(open("./sample-data/terra-modis-aerosol.bufr"))
assert bufr.get_count("opticalDepth") == 10
assert bufr.data[0].get_count("opticalDepth") == 2
assert bufr.data[1].get_count("opticalDepth") == 8
assert bufr.data.get_count("#1#opticalDepth") == 1
assert bufr.data[0].get_count("#1#opticalDepth") == 1
assert bufr.data[1].get_count("#1#opticalDepth") == 1
with pt.raises(KeyValueNotFoundError):
bufr.data.get_count("#11#opticalDepth")
with pt.raises(KeyValueNotFoundError):
bufr.data[0].get_count("#3#opticalDepth")
with pt.raises(KeyValueNotFoundError):
bufr.data[1].get_count("#9#opticalDepth")
def test_data_is_missing():
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
with pt.raises(KeyValueNotFoundError):
bufr.data.is_missing("edition")
assert bufr.is_missing("pressure")
assert bufr.is_missing("#1#pressure")
assert bufr.is_missing("#2#pressure")
array = bufr["pressure"]
assert np.all(array.mask)
assert array.shape[0] > 1
array[0, :] = 1013.25
assert not np.all(array.mask)
assert not bufr.is_missing("pressure")
assert not bufr.is_missing("#1#pressure")
assert bufr.is_missing("#2#pressure")
bufr.pack()
assert not bufr.is_missing("pressure")
assert not bufr.is_missing("#1#pressure")
assert bufr.is_missing("#2#pressure")
def test_data_set_missing():
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
with pt.raises(KeyValueNotFoundError):
bufr.data.set_missing("edition")
with pt.raises(ValueCannotBeMissingError):
bufr.set_missing("pressure->units")
bufr["pressure"] = 1013.25
bufr.pack()
bufr.set_missing("pressure")
array = bufr["pressure"]
assert np.all(array.mask)
bufr["pressure"] = 1013.25
bufr.pack()
bufr.set_missing("#1#pressure")
array = bufr["pressure"]
assert np.all(array[0, :].mask)
assert not np.any(array[1:, :].mask)
bufr.set_missing("#2#pressure")
assert np.all(array[1, :].mask)
assert not np.any(array[2:, :].mask)
bufr.set_missing("pressure")
assert np.all(array.mask)
def test_data_type_of_seconds():
# With default, zero scale -> int
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
bufr["unexpandedDescriptors"] = [4006]
assert bufr["second->scale"] == 0
assert bufr["second"].dtype == np.dtype(int)
assert bufr["second"].data[0] == CODES_MISSING_LONG
assert bufr["second"][0] is np.ma.masked
# With changed, non-zero scale -> float
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
bufr["unexpandedDescriptors"] = [202131, 4006, 202000]
assert bufr["second->scale"] == 3
assert bufr["second"].dtype == np.dtype(float)
assert bufr["second"].data[0] == CODES_MISSING_DOUBLE
assert bufr["second"][0] is np.ma.masked
def test_data_assumed_scalar_elements():
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
bufr["unexpandedDescriptors"] = [1007]
bufr.data["satelliteIdentifier"] = [1, 2, 3]
bytes = bufr.get_buffer()
bufr = BUFRMessage(bytes)
with change_behaviour() as behaviour:
behaviour.assumed_scalar_elements.add("satelliteIdentifier")
with pt.warns(UserWarning):
assert np.all(bufr.data["satelliteIdentifier"] == [1, 2, 3])
with change_behaviour() as behaviour:
behaviour.on_assumed_scalar_element_invalid_size = "raise"
bufr = BUFRMessage(bytes)
with pt.raises(ValueError):
assert np.all(bufr.data["satelliteIdentifier"] == [1, 2, 3])
with change_behaviour() as behaviour:
behaviour.on_assumed_scalar_element_invalid_size = "ignore"
bufr = BUFRMessage(bytes)
with warnings.catch_warnings(record=True) as ws:
assert np.all(bufr.data["satelliteIdentifier"] == [1, 2, 3])
assert not ws
with change_behaviour() as behaviour:
behaviour.assumed_scalar_elements.add("satelliteIdentifier")
# Single rank
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
bufr["unexpandedDescriptors"] = [1007]
assert bufr.data["satelliteIdentifier"] is np.ma.masked
assert bufr.data["#1#satelliteIdentifier"] is np.ma.masked
bufr.data["satelliteIdentifier"] = 1
bufr.data["#1#satelliteIdentifier"] = 1
bufr.pack()
assert bufr.data["satelliteIdentifier"] == 1
with pt.raises(ValueError):
bufr.data["satelliteIdentifier"] = [1, 2, 3]
# Multiple ranks
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["numberOfSubsets"] = 3
bufr["unexpandedDescriptors"] = [1007, 1007]
value = bufr.data["satelliteIdentifier"]
assert isinstance(value, np.ndarray)
assert value.shape == (2,)
assert value.all() is np.ma.masked
value1 = bufr.data["#1#satelliteIdentifier"]
assert value1 is np.ma.masked
bufr.data["satelliteIdentifier"] = [0, 0]
assert np.all(bufr.data["satelliteIdentifier"] == 0)
bufr.data["#1#satelliteIdentifier"] = 1
bufr.data["#2#satelliteIdentifier"] = 2
assert np.all(bufr.data["satelliteIdentifier"] == [1, 2])
def test_data_assignment_with_non_native_types():
bufr = BUFRMessage("BUFR3_local_satellite")
bufr["satelliteIdentifier"] = np.int8(0)
bufr["satelliteIdentifier"] = np.int16(0)
bufr["satelliteIdentifier"] = np.int32(0)
bufr["latitude"] = np.float32(0)
def test_data_set_string_uncompressed(): # ECC-2220
bufr = BUFRMessage("BUFR4")
bufr["unexpandedDescriptors"] = [1025]
bufr["stormIdentifier"] = "70A"
assert bufr["stormIdentifier"] == "70A"
bufr.pack()
assert bufr["stormIdentifier"] == "70A"
def test_data_set_string_compressed(): # ECC-2220
bufr = BUFRMessage("BUFR4")
bufr["compressedData"] = 1
bufr["unexpandedDescriptors"] = [1025]
bufr["stormIdentifier"] = "70A"
assert bufr["stormIdentifier"] == "70A"
bufr.pack()
assert bufr["stormIdentifier"] == "70A"
ecmwf-eccodes-python-b43a0f2/tests/test_highlevel.py 0000664 0001750 0001750 00000017653 15172126764 023075 0 ustar alastair alastair import collections
import io
import itertools
import pathlib
import sys
import numpy as np
import pytest
import eccodes
SAMPLE_DATA_FOLDER = pathlib.Path(__file__).parent / "sample-data"
TEST_GRIB_DATA = SAMPLE_DATA_FOLDER / "tiggelam_cnmc_sfc.grib2"
TEST_GRIB_DATA2 = SAMPLE_DATA_FOLDER / "era5-levels-members.grib"
def test_filereader():
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
count = len([None for _ in reader])
assert count == 7
def test_read_message():
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
message = next(reader)
assert isinstance(message, eccodes.GRIBMessage)
def test_message_get():
dummy_default = object()
known_missing = "scaleFactorOfSecondFixedSurface"
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
message = next(reader)
assert message.get("edition") == 2
assert message.get("nonexistent") is None
assert message.get("nonexistent", dummy_default) is dummy_default
assert message.get("centre", ktype=int) == 250
assert message.get("dataType:int") == 11
num_vals = message.get("numberOfValues")
assert message.get_size("values") == num_vals
vals = message.get("values")
assert len(vals) == num_vals
vals2 = message.data
assert np.all(vals == vals2)
assert message["Ni"] == 511
assert message["gridType:int"] == 0
with pytest.raises(KeyError):
message["invalid"]
# assert message.get("gridSpec", dummy_default) is dummy_default
# keys set as MISSING
assert message.is_missing(known_missing)
assert message.get(known_missing) is None
assert message.get(known_missing, dummy_default) is dummy_default
with pytest.raises(KeyError):
message[known_missing]
def test_message_set_plain():
missing_key = "scaleFactorOfFirstFixedSurface"
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
message = next(reader)
message.set("centre", "ecmf")
vals = np.arange(message.get("numberOfValues"), dtype=np.float32)
message.set_array("values", vals)
assert np.all(message.get("values") == vals)
message.set("values", vals)
message.set_missing(missing_key)
assert message.get("centre") == "ecmf"
assert np.all(message.get("values") == vals)
assert message.is_missing(missing_key)
def test_message_set_dict_with_checks():
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
message = next(reader)
message.set(
{
"centre": 98,
"numberOfValues": 10,
"shortName": "z",
}
)
with pytest.raises(TypeError):
message.set("centre", "ecmwf", 2)
with pytest.raises(ValueError):
message.set("stepRange", "0-12")
message.set({"stepType": "max", "stepRange": "0-12"})
message.set("iDirectionIncrementInDegrees", 1.5)
def test_message_set_dict_no_checks():
with eccodes.FileReader(TEST_GRIB_DATA) as reader:
message = next(reader)
assert message.get("longitudeOfFirstGridPoint") == 344250000
assert message.get("longitudeOfLastGridPoint") == 16125000
message.set("swapScanningX", 1, check_values=False)
assert message.get("longitudeOfFirstGridPoint") == 16125000
assert message.get("longitudeOfLastGridPoint") == 344250000
def test_message_iter():
with eccodes.FileReader(TEST_GRIB_DATA2) as reader:
message = next(reader)
keys = list(message)
assert len(keys) >= 192
assert keys[-1] == "7777"
assert "centre" in keys
assert "shortName" in keys
keys2 = list(message.keys())
assert keys == keys2
items = collections.OrderedDict(message.items())
assert list(items.keys()) == keys
assert items["shortName"] == "z"
assert items["centre"] == "ecmf"
values = list(message.values())
assert values[keys.index("shortName")] == "z"
assert values[keys.index("centre")] == "ecmf"
assert values[-1] == "7777"
def test_message_iter_missingvalues():
missing_key = "level"
with eccodes.FileReader(TEST_GRIB_DATA2) as reader:
message = next(reader)
message[missing_key] = 42
message.set_missing(missing_key)
assert missing_key not in set(message)
assert missing_key not in set(message.keys())
assert missing_key not in dict(message.items())
def test_message_copy():
with eccodes.FileReader(TEST_GRIB_DATA2) as reader:
message = next(reader)
message2 = message.copy()
assert list(message.keys()) == list(message2.keys())
def test_write_message(tmp_path):
with eccodes.FileReader(TEST_GRIB_DATA2) as reader1:
fname = tmp_path / "foo.grib"
written = []
with open(fname, "wb") as fout:
for message in itertools.islice(reader1, 15):
message.write_to(fout)
written.append(message)
with eccodes.FileReader(fname) as reader2:
for message1, message2 in itertools.zip_longest(written, reader2):
assert message1 is not None
assert message2 is not None
for key in [
"edition",
"centre",
"typeOfLevel",
"level",
"dataDate",
"stepRange",
"dataType",
"shortName",
"packingType",
"gridType",
"number",
]:
assert message1[key] == message2[key]
assert np.all(message1.data == message2.data)
def test_grib_message_from_samples():
message = eccodes.GRIBMessage.from_samples("regular_ll_sfc_grib2")
assert message["edition"] == 2
assert message["gridType"] == "regular_ll"
assert message["levtype"] == "sfc"
def test_bufr_message_from_samples():
message = eccodes.BUFRMessage.from_samples("BUFR4")
assert message["edition"] == 4
def test_read_memory():
buffer = io.BytesIO()
with eccodes.FileReader(TEST_GRIB_DATA2) as reader1:
written = []
for message in itertools.islice(reader1, 15):
message.write_to(buffer)
written.append(message)
with eccodes.MemoryReader(buffer.getvalue()) as reader2:
for message1, message2 in itertools.zip_longest(written, reader2):
assert message1 is not None
assert message2 is not None
for key in [
"edition",
"centre",
"typeOfLevel",
"level",
"dataDate",
"stepRange",
"dataType",
"shortName",
"packingType",
"gridType",
"number",
]:
assert message1[key] == message2[key]
assert np.all(message1.data == message2.data)
def test_read_stream():
if sys.platform.startswith("win"):
pytest.skip("Test disabled on Windows")
buffer = io.BytesIO()
with eccodes.FileReader(TEST_GRIB_DATA2) as reader1:
written = []
for message in itertools.islice(reader1, 15):
message.write_to(buffer)
written.append(message)
buffer.seek(0, 0)
with eccodes.StreamReader(buffer) as reader2:
for message1, message2 in itertools.zip_longest(written, reader2):
assert message1 is not None
assert message2 is not None
for key in [
"edition",
"centre",
"typeOfLevel",
"level",
"dataDate",
"stepRange",
"dataType",
"shortName",
"packingType",
"gridType",
"number",
]:
assert message1[key] == message2[key]
assert np.all(message1.data == message2.data)
ecmwf-eccodes-python-b43a0f2/tests/test_eccodes.py 0000664 0001750 0001750 00000116541 15172126764 022527 0 ustar alastair alastair #
# (C) Copyright 2017- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
#
"""
Tests of the ecCodes Python3 bindings
"""
import math
import os.path
import sys
import numpy as np
import pytest
import eccodes
SAMPLE_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "sample-data")
TEST_GRIB_TIGGE_DATA = os.path.join(SAMPLE_DATA_FOLDER, "tiggelam_cnmc_sfc.grib2")
TEST_GRIB_ERA5_DATA = os.path.join(SAMPLE_DATA_FOLDER, "era5-levels-members.grib")
def get_sample_fullpath(sample):
samples_path = eccodes.codes_samples_path()
if not os.path.isdir(samples_path):
return None
return os.path.join(samples_path, sample)
# ---------------------------------------------
# PRODUCT ANY
# ---------------------------------------------
def test_codes_definition_path():
df = eccodes.codes_definition_path()
print(f"\n\teccodes.codes_definition_path returned {df}")
assert df is not None
def test_codes_samples_path():
sp = eccodes.codes_samples_path()
print(f"\n\teccodes.codes_samples_path returned {sp}")
assert sp is not None
def test_codes_set_debug():
if eccodes.codes_get_api_version(int) < 23800:
pytest.skip("ecCodes version too old")
eccodes.codes_set_debug(-1)
eccodes.codes_set_debug(0)
def test_codes_set_data_quality_checks():
if eccodes.codes_get_api_version(int) < 24100:
pytest.skip("ecCodes version too old")
eccodes.codes_set_data_quality_checks(2)
eccodes.codes_set_data_quality_checks(1)
eccodes.codes_set_data_quality_checks(0)
def test_codes_set_samples_path():
eccodes.codes_set_samples_path(eccodes.codes_samples_path())
def test_api_version():
vs = eccodes.codes_get_api_version()
assert isinstance(vs, str)
assert len(vs) > 0
assert vs == eccodes.codes_get_api_version(str)
vi = eccodes.codes_get_api_version(int)
assert isinstance(vi, int)
assert vi > 20000
print(vi)
def test_version_info():
vinfo = eccodes.codes_get_version_info()
print("ecCodes version information: ", vinfo)
assert len(vinfo) == 2
def test_codes_get_features():
if eccodes.codes_get_api_version(int) < 23800:
pytest.skip("ecCodes version too old")
features = eccodes.codes_get_features(eccodes.CODES_FEATURES_ALL)
print(f"\n\tAll features = {features}")
features = eccodes.codes_get_features(eccodes.CODES_FEATURES_DISABLED)
print(f"\tDisabled features = {features}")
features = eccodes.codes_get_features(eccodes.CODES_FEATURES_ENABLED)
print(f"\tEnabled features = {features}")
def test_codes_is_defined():
gid = eccodes.codes_grib_new_from_samples("sh_sfc_grib1")
assert eccodes.codes_is_defined(gid, "JS")
def test_codes_get_native_type():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
assert eccodes.codes_get_native_type(gid, "date") is int
assert eccodes.codes_get_native_type(gid, "referenceValue") is float
assert eccodes.codes_get_native_type(gid, "stepType") is str
assert eccodes.codes_get_native_type(gid, "section_1") is None
with pytest.raises(eccodes.NullHandleError):
eccodes.codes_get_native_type(0, "aKey") # NULL handle
def test_set_logging_file():
if eccodes.codes_get_api_version(int) < 24400:
pytest.skip("ecCodes version too old")
nullDeviceFile = "/dev/null"
if os.path.exists(nullDeviceFile):
with open(nullDeviceFile, "w") as fnull:
eccodes.codes_context_set_logging(fnull)
with pytest.raises(eccodes.FileNotFoundError):
eccodes.codes_grib_new_from_samples("Silenced")
eccodes.codes_context_set_logging(sys.stderr)
with pytest.raises(eccodes.FileNotFoundError):
eccodes.codes_grib_new_from_samples("Restored")
def test_new_from_file():
fpath = get_sample_fullpath("GRIB2.tmpl")
if fpath is None:
return
with open(fpath, "rb") as f:
msgid = eccodes.codes_new_from_file(f, eccodes.CODES_PRODUCT_GRIB)
assert msgid
fpath = get_sample_fullpath("BUFR4.tmpl")
with open(fpath, "rb") as f:
msgid = eccodes.codes_new_from_file(f, eccodes.CODES_PRODUCT_BUFR)
assert msgid
fpath = get_sample_fullpath("clusters_grib1.tmpl")
with open(fpath, "rb") as f:
msgid = eccodes.codes_new_from_file(f, eccodes.CODES_PRODUCT_ANY)
assert msgid
with open(fpath, "rb") as f:
msgid = eccodes.codes_new_from_file(f, eccodes.CODES_PRODUCT_GTS)
assert msgid is None
with pytest.raises(ValueError):
with open(fpath, "rb") as f:
eccodes.codes_new_from_file(f, 1024)
def test_any_read():
fpath = get_sample_fullpath("GRIB1.tmpl")
if fpath is None:
return
with open(fpath, "rb") as f:
msgid = eccodes.codes_any_new_from_file(f)
assert eccodes.codes_get(msgid, "edition") == 1
assert eccodes.codes_get(msgid, "identifier") == "GRIB"
assert eccodes.codes_get(msgid, "identifier", str) == "GRIB"
eccodes.codes_release(msgid)
fpath = get_sample_fullpath("BUFR3.tmpl")
with open(fpath, "rb") as f:
msgid = eccodes.codes_any_new_from_file(f)
assert eccodes.codes_get(msgid, "edition") == 3
assert eccodes.codes_get(msgid, "identifier") == "BUFR"
eccodes.codes_release(msgid)
def test_count_in_file():
with open(TEST_GRIB_TIGGE_DATA, "rb") as f:
assert eccodes.codes_count_in_file(f) == 7
with open(TEST_GRIB_ERA5_DATA, "rb") as f:
assert eccodes.codes_count_in_file(f) == 160
fpath = get_sample_fullpath("GRIB1.tmpl")
if fpath is None:
return
with open(fpath, "rb") as f:
assert eccodes.codes_count_in_file(f) == 1
def test_new_from_message():
gid = eccodes.codes_grib_new_from_samples("sh_sfc_grib1")
message = eccodes.codes_get_message(gid)
eccodes.codes_release(gid)
assert len(message) == 9358
newgid = eccodes.codes_new_from_message(message)
assert eccodes.codes_get(newgid, "packingType") == "spectral_complex"
assert eccodes.codes_get(newgid, "gridType") == "sh"
eccodes.codes_release(newgid)
# This time read from a string rather than a file
# metar_str = "METAR LQMO 022350Z 09003KT 6000 FEW010 SCT035 BKN060 08/08 Q1003="
# newgid = eccodes.codes_new_from_message(metar_str)
# cccc = eccodes.codes_get(newgid, "CCCC")
# assert cccc == "LQMO"
# eccodes.codes_release(newgid)
def test_new_from_message_memoryview():
# ECC-2081
fpath = get_sample_fullpath("gg_sfc_grib1.tmpl")
if fpath is None:
return
with open(fpath, "rb") as f:
data = f.read()
mv = memoryview(data)
assert len(mv) == 26860
newgid = eccodes.codes_new_from_message(mv)
assert eccodes.codes_get(newgid, "packingType") == "grid_simple"
assert eccodes.codes_get(newgid, "gridType") == "reduced_gg"
assert eccodes.codes_get(newgid, "totalLength") == 26860
eccodes.codes_release(newgid)
def test_gts_header():
eccodes.codes_gts_header(True)
eccodes.codes_gts_header(False)
def test_extract_offsets():
offsets = eccodes.codes_extract_offsets(
TEST_GRIB_TIGGE_DATA, eccodes.CODES_PRODUCT_ANY, is_strict=True
)
offsets_list = list(offsets)
expected = [0, 432, 864, 1296, 1728, 2160, 2616]
assert offsets_list == expected
def test_extract_offsets_sizes():
if eccodes.codes_get_api_version(int) < 23400:
pytest.skip("ecCodes version too old")
offsets_sizes = eccodes.codes_extract_offsets_sizes(
TEST_GRIB_TIGGE_DATA, eccodes.CODES_PRODUCT_GRIB, is_strict=True
)
result = list(offsets_sizes)
expected = [
(0, 432),
(432, 432),
(864, 432),
(1296, 432),
(1728, 432),
(2160, 456),
(2616, 456),
]
assert result == expected
def test_any_new_from_samples():
msgid = eccodes.codes_new_from_samples(
"reduced_gg_ml_grib2", eccodes.CODES_PRODUCT_ANY
)
assert eccodes.codes_get(msgid, "identifier") == "GRIB"
eccodes.codes_release(msgid)
msgid = eccodes.codes_new_from_samples("BUFR4", eccodes.CODES_PRODUCT_ANY)
assert eccodes.codes_get(msgid, "identifier") == "BUFR"
eccodes.codes_release(msgid)
msgid = eccodes.codes_any_new_from_samples("diag.tmpl")
assert eccodes.codes_get(msgid, "identifier") == "DIAG"
eccodes.codes_release(msgid)
# ---------------------------------------------
# PRODUCT GRIB
# ---------------------------------------------
def test_grib_read():
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib1")
assert eccodes.codes_get(gid, "Ni") == 16
assert eccodes.codes_get(gid, "Nj") == 31
assert eccodes.codes_get(gid, "const") == 1
assert eccodes.codes_get(gid, "centre", str) == "ecmf"
assert eccodes.codes_get(gid, "packingType", str) == "grid_simple"
assert eccodes.codes_get(gid, "gridType", str) == "regular_ll"
eccodes.codes_release(gid)
gid = eccodes.codes_grib_new_from_samples("sh_ml_grib2")
assert eccodes.codes_get(gid, "const") == 0
assert eccodes.codes_get(gid, "gridType", str) == "sh"
assert eccodes.codes_get(gid, "typeOfLevel", str) == "hybrid"
assert eccodes.codes_get_long(gid, "avg") == 185
eccodes.codes_release(gid)
def test_grib_set_string():
gid = eccodes.codes_grib_new_from_samples("regular_gg_sfc_grib2")
eccodes.codes_set(gid, "gridType", "reduced_gg")
eccodes.codes_release(gid)
def test_grib_set_error():
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib1")
with pytest.raises(TypeError):
eccodes.codes_set_long(gid, "centre", "kwbc")
with pytest.raises(TypeError):
eccodes.codes_set_double(gid, "centre", "kwbc")
with pytest.raises(eccodes.CodesInternalError):
eccodes.codes_set(gid, "centre", [])
def test_grib_write(tmpdir):
gid = eccodes.codes_grib_new_from_samples("GRIB2")
eccodes.codes_set(gid, "backgroundProcess", 44)
output = tmpdir.join("test_grib_write.grib")
with open(str(output), "wb") as fout:
eccodes.codes_write(gid, fout)
eccodes.codes_release(gid)
def test_grib_codes_set_missing():
gid = eccodes.codes_grib_new_from_samples("reduced_rotated_gg_ml_grib2")
eccodes.codes_set(gid, "typeOfFirstFixedSurface", "sfc")
eccodes.codes_set_missing(gid, "scaleFactorOfFirstFixedSurface")
eccodes.codes_set_missing(gid, "scaledValueOfFirstFixedSurface")
assert eccodes.codes_is_missing(gid, "scaleFactorOfFirstFixedSurface")
def test_grib_set_key_vals():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
# String
eccodes.codes_set_key_vals(gid, "shortName=z,dataDate=20201112")
assert eccodes.codes_get(gid, "shortName", str) == "z"
assert eccodes.codes_get(gid, "date", int) == 20201112
# List
eccodes.codes_set_key_vals(gid, ["shortName=2t", "dataDate=20191010"])
assert eccodes.codes_get(gid, "shortName", str) == "2t"
assert eccodes.codes_get(gid, "date", int) == 20191010
# Dictionary
eccodes.codes_set_key_vals(gid, {"shortName": "msl", "dataDate": 20181010})
assert eccodes.codes_get(gid, "shortName", str) == "msl"
assert eccodes.codes_get(gid, "date", int) == 20181010
eccodes.codes_release(gid)
def test_grib_get_error():
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib2")
# Note: AssertionError can be raised when type checks are enabled
with pytest.raises((ValueError, AssertionError)):
eccodes.codes_get(gid, None)
def test_grib_get_array():
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_160_grib2")
pl = eccodes.codes_get_array(gid, "pl")
assert pl[0] == 18
pli = eccodes.codes_get_array(gid, "pl", int)
assert np.array_equal(pl, pli)
pls = eccodes.codes_get_array(gid, "centre", str)
assert pls == ["ecmf"]
dvals = eccodes.codes_get_array(gid, "values")
assert len(dvals) == 138346
assert type(dvals[0]) is np.float64
eccodes.codes_release(gid)
def test_grib_get_array_single_precision():
if eccodes.codes_get_api_version(int) < 23100:
pytest.skip("ecCodes version too old")
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_160_grib2")
dvals = eccodes.codes_get_array(gid, "values", ktype=float)
assert type(dvals[0]) is np.float64
fvals = eccodes.codes_get_array(gid, "values", ktype=np.float32)
assert type(fvals[0]) is np.float32
fvals = eccodes.codes_get_float_array(gid, "values")
assert type(fvals[0]) is np.float32
dvals = eccodes.codes_get_values(gid)
assert type(dvals[0]) is np.float64
fvals = eccodes.codes_get_values(gid, np.float32)
assert type(fvals[0]) is np.float32
eccodes.codes_release(gid)
def test_grib_gaussian_latitudes():
orders = [256, 1280]
# Latitude of the first element (nearest the north pole)
expected_lats = [89.731148, 89.946187]
for _order, _lat in zip(orders, expected_lats):
lats = eccodes.codes_get_gaussian_latitudes(_order)
assert len(lats) == 2 * _order
assert math.isclose(lats[0], _lat, abs_tol=0.00001)
def test_grib_latitudes_longitudes():
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_160_grib2")
lats = eccodes.codes_get_array(gid, "latitudes")
assert len(lats) == 138346
assert math.isclose(lats[0], 89.570089, abs_tol=0.00001)
lats = eccodes.codes_get_array(gid, "distinctLatitudes")
assert len(lats) == 320
assert math.isclose(lats[0], 89.570089, abs_tol=0.00001)
assert math.isclose(lats[319], -89.570089, abs_tol=0.00001)
lons = eccodes.codes_get_array(gid, "longitudes")
assert len(lons) == 138346
assert math.isclose(lons[138345], 340.00, abs_tol=0.00001)
lons = eccodes.codes_get_array(gid, "distinctLongitudes")
assert len(lons) == 4762
assert math.isclose(lons[4761], 359.4375, abs_tol=0.00001)
def test_grib_get_message_size():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
assert eccodes.codes_get_message_size(gid) == 179
def test_grib_get_message_offset():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
assert eccodes.codes_get_message_offset(gid) == 0
def test_grib_get_key_offset():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
assert eccodes.codes_get_offset(gid, "identifier") == 0
assert eccodes.codes_get_offset(gid, "discipline") == 6
assert eccodes.codes_get_offset(gid, "offsetSection1") == 16
assert eccodes.codes_get_offset(gid, "7777") == 175
def test_grib_clone():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
clone = eccodes.codes_clone(gid)
assert gid
assert clone
assert eccodes.codes_get(clone, "identifier") == "GRIB"
assert eccodes.codes_get(clone, "totalLength") == 179
eccodes.codes_release(gid)
eccodes.codes_release(clone)
def test_grib_clone_headers_only1():
if eccodes.codes_get_api_version(int) < 24400:
pytest.skip("ecCodes version too old")
meta = dict(
Ni=72,
Nj=37,
Nx=72,
Ny=37,
gridType="regular_ll",
iDirectionIncrementInDegrees=5.0,
iScansNegatively=0,
jDirectionIncrementInDegrees=5.0,
jPointsAreConsecutive=0,
jScansPositively=0,
latitudeOfFirstGridPointInDegrees=90.0,
latitudeOfLastGridPointInDegrees=-90.0,
longitudeOfFirstGridPointInDegrees=0.0,
longitudeOfLastGridPointInDegrees=355.0,
iDirectionIncrementGiven=1,
jDirectionIncrementGiven=1,
numberOfGridPoints=2664, # =72*37 (for both editions)
)
for sample in ("reduced_gg_pl_160_grib2", "reduced_gg_pl_160_grib1"):
handle = eccodes.codes_grib_new_from_samples(sample)
c = eccodes.codes_clone(handle, headers_only=True)
# set keys to change grid
eccodes.codes_set_key_vals(c, meta)
lon = eccodes.codes_get_array(c, "longitudes")
print(f"GRIB sample {sample}: len lon={len(lon)}")
assert len(lon) == 72 * 37
eccodes.codes_set(c, "messageValidityChecks", "grid")
assert eccodes.codes_get(c, "isMessageValid") == 1
eccodes.codes_release(handle)
eccodes.codes_release(c)
def test_grib_clone_headers_only2():
if eccodes.codes_get_api_version(int) < 23400:
pytest.skip("ecCodes version too old")
with open(TEST_GRIB_ERA5_DATA, "rb") as f:
msgid1 = eccodes.codes_grib_new_from_file(f)
msgid2 = eccodes.codes_clone(msgid1, headers_only=True)
msg1_size = eccodes.codes_get_message_size(msgid1)
msg2_size = eccodes.codes_get_message_size(msgid2)
assert msg1_size > msg2_size
assert eccodes.codes_get(msgid1, "totalLength") == 14752
assert eccodes.codes_get(msgid2, "totalLength") == 112
assert eccodes.codes_get(msgid1, "bitsPerValue") == 16
assert eccodes.codes_get(msgid2, "bitsPerValue") == 0
eccodes.codes_release(msgid1)
eccodes.codes_release(msgid2)
def test_grib_keys_iterator():
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_1280_grib1")
iterid = eccodes.codes_keys_iterator_new(gid, "ls")
count = 0
while eccodes.codes_keys_iterator_next(iterid):
keyname = eccodes.codes_keys_iterator_get_name(iterid)
keyval = eccodes.codes_get_string(gid, keyname)
assert len(keyval) > 0
count += 1
assert count == 10
eccodes.codes_keys_iterator_rewind(iterid)
eccodes.codes_keys_iterator_delete(iterid)
eccodes.codes_release(gid)
def test_grib_keys_iterator_skip():
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_1280_grib1")
iterid = eccodes.codes_keys_iterator_new(gid, "ls")
count = 0
eccodes.codes_skip_computed(iterid)
# codes_skip_coded(iterid)
eccodes.codes_skip_edition_specific(iterid)
eccodes.codes_skip_duplicates(iterid)
eccodes.codes_skip_read_only(iterid)
eccodes.codes_skip_function(iterid)
while eccodes.codes_keys_iterator_next(iterid):
keyname = eccodes.codes_keys_iterator_get_name(iterid)
keyval = eccodes.codes_get_string(gid, keyname)
assert len(keyval) > 0
count += 1
# centre, level and dataType
assert count == 3
eccodes.codes_keys_iterator_delete(iterid)
iterid = eccodes.codes_keys_iterator_new(gid)
count = 0
eccodes.codes_skip_coded(iterid)
while eccodes.codes_keys_iterator_next(iterid):
count += 1
assert count > 140
eccodes.codes_keys_iterator_delete(iterid)
eccodes.codes_release(gid)
def test_grib_get_data():
gid = eccodes.codes_grib_new_from_samples("GRIB1")
ggd = eccodes.codes_grib_get_data(gid)
assert len(ggd) == 65160
eccodes.codes_release(gid)
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_32_grib2")
ggd = eccodes.codes_grib_get_data(gid)
assert len(ggd) == 6114
elem1 = ggd[0] # This is a 'Bunch' derived off dict
assert "lat" in elem1.keys()
assert "lon" in elem1.keys()
assert "value" in elem1.keys()
eccodes.codes_release(gid)
def test_grib_get_double_element():
gid = eccodes.codes_grib_new_from_samples("gg_sfc_grib2")
elem = eccodes.codes_get_double_element(gid, "values", 1)
assert math.isclose(elem, 259.9865, abs_tol=0.001)
eccodes.codes_release(gid)
def test_grib_get_double_elements():
gid = eccodes.codes_grib_new_from_samples("gg_sfc_grib1")
values = eccodes.codes_get_values(gid)
num_vals = len(values)
indexes = [0, int(num_vals / 2), num_vals - 1]
elems = eccodes.codes_get_double_elements(gid, "values", indexes)
assert math.isclose(elems[0], 259.6935, abs_tol=0.001)
assert math.isclose(elems[1], 299.9064, abs_tol=0.001)
assert math.isclose(elems[2], 218.8146, abs_tol=0.001)
elems2 = eccodes.codes_get_elements(gid, "values", indexes)
assert elems == elems2
def test_grib_get_values():
gid = eccodes.codes_grib_new_from_samples("gg_sfc_grib2")
with pytest.raises(TypeError):
eccodes.codes_get_values(gid, str)
eccodes.codes_release(gid)
def test_grib_geoiterator():
# version 2.44 has different sample values. See ECC-2110
if eccodes.codes_get_api_version(int) < 24400:
pytest.skip("ecCodes version too old")
gid = eccodes.codes_grib_new_from_samples("reduced_gg_pl_256_grib2")
iterid = eccodes.codes_grib_iterator_new(gid, 0)
i = 0
while 1:
result = eccodes.codes_grib_iterator_next(iterid)
if not result:
break
[lat, lon, value] = result
assert -90.0 < lat < 90.00
assert 0.0 <= lon < 360.0
assert math.isclose(value, 273.15, abs_tol=0.001)
i += 1
assert i == 348528
eccodes.codes_grib_iterator_delete(iterid)
eccodes.codes_release(gid)
def test_grib_nearest():
gid = eccodes.codes_grib_new_from_samples("reduced_gg_ml_grib2")
lat, lon = 30, -20
nearest = eccodes.codes_grib_find_nearest(gid, lat, lon)[0]
assert nearest.index == 1770
lat, lon = 10, 0
nearest = eccodes.codes_grib_find_nearest(gid, lat, lon)[0]
assert nearest.index == 2545
lat, lon = 10, 20
nearest = eccodes.codes_grib_find_nearest(gid, lat, lon, False, 4)
expected_indexes = (2553, 2552, 2425, 2424)
returned_indexes = (
nearest[0].index,
nearest[1].index,
nearest[2].index,
nearest[3].index,
)
assert sorted(expected_indexes) == sorted(returned_indexes)
# Cannot do more than 4 nearest neighbours
with pytest.raises(ValueError):
eccodes.codes_grib_find_nearest(gid, lat, lon, False, 5)
eccodes.codes_release(gid)
def test_grib_nearest_multiple():
gid = eccodes.codes_new_from_samples(
"reduced_gg_ml_grib2", eccodes.CODES_PRODUCT_GRIB
)
inlats = (30, 13)
inlons = (-20, 234)
is_lsm = False
nearest = eccodes.codes_grib_find_nearest_multiple(gid, is_lsm, inlats, inlons)
eccodes.codes_release(gid)
assert nearest[0].index == 1770
assert nearest[1].index == 2500
# Error condition
with pytest.raises(ValueError):
eccodes.codes_grib_find_nearest_multiple(gid, is_lsm, (1, 2), (1, 2, 3))
def test_grib_ecc_1042():
# Issue ECC-1042: Python3 interface writes integer arrays incorrectly
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib2")
# Trying write with inferred dtype
write_vals = np.array([1, 2, 3])
eccodes.codes_set_values(gid, write_vals)
read_vals = eccodes.codes_get_values(gid)
length = len(read_vals)
assert read_vals[0] == 1
assert read_vals[length - 1] == 3
# Trying write with explicit dtype
write_vals = np.array(
[
1,
2,
3,
],
dtype=float,
)
eccodes.codes_set_values(gid, write_vals)
read_vals = eccodes.codes_get_values(gid)
assert read_vals[0] == 1
assert read_vals[length - 1] == 3
eccodes.codes_release(gid)
def test_grib_ecc_1007():
# Issue ECC-1007: Python3 interface cannot write large arrays
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib2")
numvals = 1501 * 1501
values = np.zeros((numvals,))
values[0] = 12 # Make sure it's not a constant field
eccodes.codes_set_values(gid, values)
maxv = eccodes.codes_get(gid, "max")
minv = eccodes.codes_get(gid, "min")
assert minv == 0
assert maxv == 12
eccodes.codes_release(gid)
def test_grib_set_bitmap():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
# Note: np.Infinity was removed in the NumPy 2.0 release. Use np.inf instead
missing = np.inf
eccodes.codes_set(gid, "bitmapPresent", 1)
eccodes.codes_set(gid, "missingValue", missing)
# Grid with 100 points 2 of which are missing
values = np.ones((100,))
values[2] = missing
values[4] = missing
eccodes.codes_set_values(gid, values)
assert eccodes.codes_get(gid, "numberOfDataPoints") == 100
assert eccodes.codes_get(gid, "numberOfCodedValues") == 98
assert eccodes.codes_get(gid, "numberOfMissing") == 2
eccodes.codes_get_array(gid, "bitmap")
eccodes.codes_release(gid)
def test_grib_set_float_array():
gid = eccodes.codes_grib_new_from_samples("regular_ll_sfc_grib2")
for ftype in (float, np.float16, np.float32, np.float64):
values = np.ones((100000,), ftype)
eccodes.codes_set_array(gid, "values", values)
assert (eccodes.codes_get_values(gid) == 1.0).all()
eccodes.codes_set_values(gid, values)
assert (eccodes.codes_get_values(gid) == 1.0).all()
def test_grib_set_2d_array():
gid = eccodes.codes_grib_new_from_samples("GRIB2")
num_vals = eccodes.codes_get(gid, "numberOfValues")
assert num_vals == 496
vals2d = np.array([[11, 2, 3], [4, 15, -6]], np.float64)
eccodes.codes_set_double_array(gid, "values", vals2d)
num_vals = eccodes.codes_get(gid, "numberOfValues")
assert num_vals == 6
vals = eccodes.codes_get_double_array(gid, "values")
assert vals[0] == 11.0
assert vals[1] == 2.0
assert vals[4] == 15.0
assert vals[5] == -6.0
def test_grib_set_np_int64():
gid = eccodes.codes_grib_new_from_samples("regular_gg_sfc_grib2")
eccodes.codes_set(gid, "productDefinitionTemplateNumber", 1)
eccodes.codes_set(gid, "number", np.int64(17))
assert eccodes.codes_get_long(gid, "number") == 17
eccodes.codes_set_long(gid, "number", np.int64(16))
assert eccodes.codes_get_long(gid, "number") == 16
eccodes.codes_release(gid)
def test_gribex_mode():
eccodes.codes_gribex_mode_on()
eccodes.codes_gribex_mode_off()
def test_grib_new_from_samples_error():
with pytest.raises(eccodes.FileNotFoundError):
eccodes.codes_new_from_samples("nonExistentSample", eccodes.CODES_PRODUCT_GRIB)
def test_grib_new_from_file_error(tmp_path):
# Note: AssertionError can be raised when type checks are enabled
with pytest.raises((TypeError, AssertionError)):
eccodes.codes_grib_new_from_file(None)
p = tmp_path / "afile.txt"
p.write_text("GRIBxxxx")
with open(p, "rb") as f:
with pytest.raises(eccodes.UnsupportedEditionError):
eccodes.codes_grib_new_from_file(f)
def test_grib_index_new_from_file(tmpdir):
fpath = get_sample_fullpath("GRIB1.tmpl")
if fpath is None:
return
index_keys = ["shortName", "level", "number", "step", "referenceValue"]
iid = eccodes.codes_index_new_from_file(fpath, index_keys)
index_file = str(tmpdir.join("temp.grib.index"))
eccodes.codes_index_write(iid, index_file)
key = "level"
assert eccodes.codes_index_get_size(iid, key) == 1
# Cannot get the native type of a key from an index
# so right now the default is str.
assert eccodes.codes_index_get(iid, key) == ("500",)
assert eccodes.codes_index_get(iid, key, int) == (500,)
assert eccodes.codes_index_get_long(iid, key) == (500,)
key = "referenceValue"
refVal = 47485.4
assert eccodes.codes_index_get_double(iid, key) == (refVal,)
assert eccodes.codes_index_get(iid, key, float) == (refVal,)
eccodes.codes_index_select(iid, "level", 500)
eccodes.codes_index_select(iid, "shortName", "z")
eccodes.codes_index_select(iid, "number", 0)
eccodes.codes_index_select(iid, "step", 0)
eccodes.codes_index_select(iid, "referenceValue", refVal)
gid = eccodes.codes_new_from_index(iid)
assert eccodes.codes_get(gid, "edition") == 1
assert eccodes.codes_get(gid, "totalLength") == 107
eccodes.codes_release(gid)
eccodes.codes_index_release(iid)
iid2 = eccodes.codes_index_read(index_file)
assert eccodes.codes_index_get(iid2, "shortName") == ("z",)
eccodes.codes_index_release(iid2)
def test_grib_multi_support_reset_file():
try:
# TODO: read an actual multi-field GRIB here
fpath = get_sample_fullpath("GRIB2.tmpl")
if fpath is None:
return
eccodes.codes_grib_multi_support_on()
with open(fpath, "rb") as f:
eccodes.codes_grib_multi_support_reset_file(f)
finally:
eccodes.codes_grib_multi_support_off()
def test_grib_multi_field_write(tmpdir):
# Note: eccodes.codes_grib_multi_new() calls codes_grib_multi_support_on()
# hence the 'finally' block
try:
gid = eccodes.codes_grib_new_from_samples("GRIB2")
mgid = eccodes.codes_grib_multi_new()
section_num = 4
for step in range(12, 132, 12):
eccodes.codes_set(gid, "step", step)
eccodes.codes_grib_multi_append(gid, section_num, mgid)
output = tmpdir.join("test_grib_multi_field_write.grib2")
with open(str(output), "wb") as fout:
eccodes.codes_grib_multi_write(mgid, fout)
eccodes.codes_grib_multi_release(mgid)
eccodes.codes_release(gid)
finally:
eccodes.codes_grib_multi_support_off()
def test_grib_uuid_get_set():
# ECC-1167
gid = eccodes.codes_grib_new_from_samples("GRIB2")
eccodes.codes_set(gid, "gridType", "unstructured_grid")
key = "uuidOfHGrid"
ntype = eccodes.codes_get_native_type(gid, key)
assert ntype is bytes
uuid = eccodes.codes_get_string(gid, key)
assert uuid == "00000000000000000000000000000000"
eccodes.codes_set_string(gid, key, "DEfdBEef10203040b00b1e50001100FF")
uuid = eccodes.codes_get(gid, key, str)
assert uuid == "defdbeef10203040b00b1e50001100ff"
uuid = eccodes.codes_get(gid, key)
assert uuid == "defdbeef10203040b00b1e50001100ff"
uuid_arr = eccodes.codes_get_array(gid, key)
assert uuid_arr == ["defdbeef10203040b00b1e50001100ff"]
eccodes.codes_release(gid)
def test_grib_dump(tmp_path):
gid = eccodes.codes_grib_new_from_samples("GRIB2")
p = tmp_path / "dump_grib.txt"
with open(p, "w") as fout:
eccodes.codes_dump(gid, fout)
eccodes.codes_dump(gid, fout, "debug")
eccodes.codes_release(gid)
def test_grib_copy_namespace():
gid1 = eccodes.codes_grib_new_from_samples("GRIB2")
gid2 = eccodes.codes_grib_new_from_samples("reduced_gg_pl_32_grib2")
eccodes.codes_copy_namespace(gid1, "ls", gid2)
# ---------------------------------------------
# PRODUCT BUFR
# ---------------------------------------------
def test_bufr_read_write(tmpdir):
bid = eccodes.codes_new_from_samples("BUFR4", eccodes.CODES_PRODUCT_BUFR)
eccodes.codes_set(bid, "unpack", 1)
assert eccodes.codes_get(bid, "typicalYear") == 2012
assert eccodes.codes_get(bid, "centre", str) == "ecmf"
eccodes.codes_set(bid, "totalSunshine", 13)
eccodes.codes_set(bid, "pack", 1)
output = tmpdir.join("test_bufr_write.bufr")
with open(str(output), "wb") as fout:
eccodes.codes_write(bid, fout)
assert eccodes.codes_get(bid, "totalSunshine") == 13
eccodes.codes_release(bid)
def test_bufr_encode(tmpdir):
ibufr = eccodes.codes_bufr_new_from_samples("BUFR3_local_satellite")
eccodes.codes_set_array(ibufr, "inputDelayedDescriptorReplicationFactor", (4,))
eccodes.codes_set(ibufr, "masterTableNumber", 0)
eccodes.codes_set(ibufr, "bufrHeaderSubCentre", 0)
eccodes.codes_set(ibufr, "bufrHeaderCentre", 98)
eccodes.codes_set(ibufr, "updateSequenceNumber", 0)
eccodes.codes_set(ibufr, "dataCategory", 12)
eccodes.codes_set(ibufr, "dataSubCategory", 139)
eccodes.codes_set(ibufr, "masterTablesVersionNumber", 13)
eccodes.codes_set(ibufr, "localTablesVersionNumber", 1)
eccodes.codes_set(ibufr, "numberOfSubsets", 492)
eccodes.codes_set(ibufr, "localNumberOfObservations", 492)
eccodes.codes_set(ibufr, "satelliteID", 4)
eccodes.codes_set(ibufr, "observedData", 1)
eccodes.codes_set(ibufr, "compressedData", 1)
eccodes.codes_set(ibufr, "unexpandedDescriptors", 312061)
eccodes.codes_set(ibufr, "pixelSizeOnHorizontal1", 1.25e04)
eccodes.codes_set(ibufr, "orbitNumber", 31330)
eccodes.codes_set(ibufr, "#1#beamIdentifier", 1)
eccodes.codes_set(
ibufr, "#4#likelihoodComputedForSolution", eccodes.CODES_MISSING_DOUBLE
)
eccodes.codes_set(ibufr, "pack", 1)
output = tmpdir.join("test_bufr_encode.bufr")
with open(str(output), "wb") as fout:
eccodes.codes_write(ibufr, fout)
eccodes.codes_release(ibufr)
def test_bufr_set_float():
ibufr = eccodes.codes_bufr_new_from_samples("BUFR4")
eccodes.codes_set(ibufr, "unpack", 1)
eccodes.codes_set(ibufr, "totalPrecipitationPast24Hours", np.float32(1.26e04))
eccodes.codes_set(ibufr, "totalPrecipitationPast24Hours", np.float16(1.27e04))
eccodes.codes_release(ibufr)
def test_bufr_set_string_array():
ibufr = eccodes.codes_bufr_new_from_samples("BUFR3_local_satellite")
eccodes.codes_set(ibufr, "numberOfSubsets", 3)
eccodes.codes_set(ibufr, "unexpandedDescriptors", 307022)
inputVals = ("ARD2-LPTR", "EPFL-LPTR", "BOU2-LPTR")
eccodes.codes_set_array(ibufr, "stationOrSiteName", inputVals)
eccodes.codes_set(ibufr, "pack", 1)
outputVals = eccodes.codes_get_string_array(ibufr, "stationOrSiteName")
assert len(outputVals) == 3
assert outputVals[0] == "ARD2-LPTR"
assert outputVals[1] == "EPFL-LPTR"
assert outputVals[2] == "BOU2-LPTR"
eccodes.codes_release(ibufr)
def test_bufr_keys_iterator():
bid = eccodes.codes_bufr_new_from_samples("BUFR3_local_satellite")
# Header keys only
iterid = eccodes.codes_bufr_keys_iterator_new(bid)
count = 0
while eccodes.codes_bufr_keys_iterator_next(iterid):
keyname = eccodes.codes_bufr_keys_iterator_get_name(iterid)
assert "#" not in keyname
count += 1
# assert count == 54
eccodes.codes_set(bid, "unpack", 1)
eccodes.codes_bufr_keys_iterator_rewind(iterid)
count = 0
while eccodes.codes_bufr_keys_iterator_next(iterid):
keyname = eccodes.codes_bufr_keys_iterator_get_name(iterid)
count += 1
# assert count == 157
eccodes.codes_bufr_keys_iterator_rewind(iterid)
eccodes.codes_bufr_keys_iterator_delete(iterid)
eccodes.codes_release(bid)
def test_bufr_codes_is_missing():
bid = eccodes.codes_bufr_new_from_samples("BUFR4_local")
eccodes.codes_set(bid, "unpack", 1)
assert eccodes.codes_is_missing(bid, "heightOfBarometerAboveMeanSeaLevel") == 1
assert eccodes.codes_is_missing(bid, "blockNumber") == 1
assert eccodes.codes_is_missing(bid, "stationOrSiteName") == 1
assert eccodes.codes_is_missing(bid, "unexpandedDescriptors") == 0
assert eccodes.codes_is_missing(bid, "ident") == 0
eccodes.codes_set(bid, "stationOrSiteName", "Barca")
eccodes.codes_set(bid, "pack", 1)
assert eccodes.codes_is_missing(bid, "stationOrSiteName") == 0
eccodes.codes_release(bid)
def test_bufr_multi_element_constant_arrays():
eccodes.codes_bufr_multi_element_constant_arrays_off()
bid = eccodes.codes_bufr_new_from_samples("BUFR3_local_satellite")
eccodes.codes_set(bid, "unpack", 1)
assert eccodes.codes_get_size(bid, "satelliteIdentifier") == 1
eccodes.codes_release(bid)
eccodes.codes_bufr_multi_element_constant_arrays_on()
bid = eccodes.codes_bufr_new_from_samples("BUFR3_local_satellite")
eccodes.codes_set(bid, "unpack", 1)
numSubsets = eccodes.codes_get(bid, "numberOfSubsets")
assert eccodes.codes_get_size(bid, "satelliteIdentifier") == numSubsets
eccodes.codes_release(bid)
eccodes.codes_bufr_multi_element_constant_arrays_off()
def test_bufr_new_from_samples_error():
with pytest.raises(eccodes.FileNotFoundError):
eccodes.codes_new_from_samples("nonExistentSample", eccodes.CODES_PRODUCT_BUFR)
with pytest.raises(ValueError):
eccodes.codes_new_from_samples("BUFR3_local", 1024)
def test_bufr_get_message_size():
gid = eccodes.codes_bufr_new_from_samples("BUFR3_local")
assert eccodes.codes_get_message_size(gid) == 279
def test_bufr_get_message_offset():
gid = eccodes.codes_bufr_new_from_samples("BUFR3_local")
assert eccodes.codes_get_message_offset(gid) == 0
def test_codes_bufr_key_is_header():
bid = eccodes.codes_bufr_new_from_samples("BUFR4_local_satellite")
assert eccodes.codes_bufr_key_is_header(bid, "edition")
assert eccodes.codes_bufr_key_is_header(bid, "satelliteID")
assert eccodes.codes_bufr_key_is_header(bid, "unexpandedDescriptors")
with pytest.raises(eccodes.KeyValueNotFoundError):
eccodes.codes_bufr_key_is_header(bid, "satelliteSensorIndicator")
eccodes.codes_set(bid, "unpack", 1)
assert not eccodes.codes_bufr_key_is_header(bid, "satelliteSensorIndicator")
assert not eccodes.codes_bufr_key_is_header(bid, "#6#brightnessTemperature")
def test_codes_bufr_key_is_coordinate():
if eccodes.codes_get_api_version(int) < 23100:
pytest.skip("ecCodes version too old")
bid = eccodes.codes_bufr_new_from_samples("BUFR4")
assert not eccodes.codes_bufr_key_is_coordinate(bid, "edition")
with pytest.raises(eccodes.KeyValueNotFoundError):
eccodes.codes_bufr_key_is_coordinate(bid, "latitude")
eccodes.codes_set(bid, "unpack", 1)
assert eccodes.codes_bufr_key_is_coordinate(bid, "latitude")
assert eccodes.codes_bufr_key_is_coordinate(bid, "#14#timePeriod")
assert not eccodes.codes_bufr_key_is_coordinate(bid, "dewpointTemperature")
eccodes.codes_release(bid)
def test_bufr_extract_headers():
fpath = get_sample_fullpath("BUFR4_local.tmpl")
if fpath is None:
return
headers = list(eccodes.codes_bufr_extract_headers(fpath))
# Sample file contains just one message
assert len(headers) == 1
header = headers[0]
assert header["edition"] == 4
assert header["internationalDataSubCategory"] == 255
assert header["masterTablesVersionNumber"] == 24
assert header["ident"].strip() == "91334"
assert header["rdbtimeSecond"] == 19
assert math.isclose(header["localLongitude"], 151.83)
@pytest.mark.skipif(
sys.platform.startswith("win32"), reason="Not applicable on Windows"
)
def test_bufr_dump(tmp_path):
bid = eccodes.codes_bufr_new_from_samples("BUFR4")
eccodes.codes_set(bid, "unpack", 1)
p = tmp_path / "dump_bufr.txt"
with open(p, "w") as fout:
eccodes.codes_dump(bid, fout, "json")
eccodes.codes_release(bid)
def test_bufr_copy_data():
bid1 = eccodes.codes_bufr_new_from_samples("BUFR4_local")
bid2 = eccodes.codes_bufr_new_from_samples("BUFR4")
bid3 = eccodes.codes_bufr_copy_data(bid1, bid2)
assert bid3
# ---------------------------------------------
# Experimental features
# ---------------------------------------------
def test_grib_nearest2():
if "codes_grib_nearest_new" not in dir(eccodes):
pytest.skip("codes_grib_nearest_new absent")
gid = eccodes.codes_grib_new_from_samples("gg_sfc_grib2")
lat, lon = 40, 20
flags = eccodes.CODES_GRIB_NEAREST_SAME_GRID | eccodes.CODES_GRIB_NEAREST_SAME_POINT
nid = eccodes.codes_grib_nearest_new(gid)
assert nid > 0
nearest = eccodes.codes_grib_nearest_find(nid, gid, lat, lon, flags)
assert len(nearest) == 4
expected_indexes = (2679, 2678, 2517, 2516)
returned_indexes = (
nearest[0].index,
nearest[1].index,
nearest[2].index,
nearest[3].index,
)
assert sorted(expected_indexes) == sorted(returned_indexes)
assert math.isclose(nearest[0].value, 295.22085, abs_tol=0.0001)
assert math.isclose(nearest[2].distance, 24.16520, abs_tol=0.0001)
# Error conditions
with pytest.raises(eccodes.FunctionNotImplementedError):
eccodes.codes_grib_nearest_find(nid, gid, lat, lon, flags, is_lsm=True)
with pytest.raises(eccodes.FunctionNotImplementedError):
eccodes.codes_grib_nearest_find(nid, gid, lat, lon, flags, False, npoints=5)
eccodes.codes_release(gid)
eccodes.codes_grib_nearest_delete(nid)
ecmwf-eccodes-python-b43a0f2/tests/test_bufr_helpers.py 0000664 0001750 0001750 00000016641 15172126764 023602 0 ustar alastair alastair # Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# flake8: noqa: F403
# flake8: noqa: F405
import numpy as np
import pytest as pt
from eccodes.highlevel._bufr.helpers import *
# Test RaggedArray
def test_ragged_array_getset():
a0 = RaggedArray(0)
assert a0.ndim == 0
assert a0[None] == 0
assert a0[()] == 0
a1 = RaggedArray([0, 1])
assert a1.ndim == 1
assert a1[0] == 0
assert a1[(1,)] == 1
a1[0] = -0
a1[(1,)] = -1
assert a1[0] == -0
assert a1[(1,)] == -1
a2 = RaggedArray([[0], [1, 2]])
assert a2.ndim == 2
assert a2[(0, 0)] == 0
assert a2[(1, 0)] == 1
assert a2[(1, 1)] == 2
a2[(0, 0)] = -0
a2[(1, 0)] = -1
a2[(1, 1)] = -2
assert a2[(0, 0)] == -0
assert a2[(1, 0)] == -1
assert a2[(1, 1)] == -2
def test_ragged_array_insert():
a0 = RaggedArray.empty(ndim=0)
assert a0.data is None
assert a0.ndim == 0
a0.insert((), 0)
assert a0[()] == 0
a1 = RaggedArray.empty(ndim=1)
assert a1.data == []
assert a1.ndim == 1
a1.insert(0, 0)
a1.insert((1,), 1)
assert a1.data == [0, 1]
a2 = RaggedArray.empty(ndim=2)
assert a2.data == [[]]
assert a2.ndim == 2
a2.insert((0, 0), 0)
assert a2.data == [[0]]
a2.insert((1, 0), 1)
a2.insert((1, 1), 2)
assert a2.data == [[0], [1, 2]]
def test_ragged_array_bool():
assert not bool(RaggedArray(0))
assert not bool(RaggedArray([]))
assert not bool(RaggedArray([[]]))
assert bool(RaggedArray(1))
assert bool(RaggedArray([0]))
assert bool(RaggedArray([[0]]))
# Test get_datetime()
def test_get_datetime_with_no_seconds():
data = dict(year=2000, month=1, day=2, hour=3, minute=4)
datetime = get_datetime(data)
assert not isinstance(datetime, np.ndarray)
assert datetime == np.datetime64("2000-01-02T03:04")
def test_get_datetime_with_missing_seconds():
data = dict(year=2000, month=1, day=2, hour=3, minute=4)
data.update(second=CODES_MISSING_LONG)
datetime = get_datetime(data)
assert datetime is np.ma.masked
data.update(second=np.ma.masked)
datetime = get_datetime(data)
assert datetime is np.ma.masked
data.update(second=[5, CODES_MISSING_LONG])
datetime = get_datetime(data)
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert datetime[0] == np.datetime64("2000-01-02T03:04:05")
assert datetime[1] is np.ma.masked
assert np.ma.all(
datetime == np.array(["2000-01-02T03:04:05", "NaT"], dtype="M8[s]")
)
def test_get_datetime_with_decimal_seconds():
data = dict(year=2000, month=1, day=2, hour=3, minute=4)
data.update(second=[5.6, CODES_MISSING_DOUBLE])
datetime = get_datetime(data)
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert datetime[0] == np.datetime64("2000-01-02T03:04:05.6")
assert datetime[1] is np.ma.masked
assert np.ma.all(
datetime == np.array(["2000-01-02T03:04:05.6", "NaT"], dtype="M8[ms]")
)
def test_get_datetime_with_out_of_range_seconds():
data = dict(year=2000, month=1, day=2, hour=3, minute=4)
data.update(second=[5, 60])
datetime = get_datetime(data)
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert datetime[0] == np.datetime64("2000-01-02T03:04:05")
assert datetime[1] == np.datetime64("2000-01-02T03:04:59")
def test_get_datetime_with_int32_seconds():
data = dict(year=2000, month=1, day=2, hour=3, minute=4)
data.update(second=np.array([5, 6], dtype="int32"))
datetime = get_datetime(data)
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert datetime[0] == np.datetime64("2000-01-02T03:04:05")
assert datetime[1] == np.datetime64("2000-01-02T03:04:06")
def test_get_datetime_with_microseconds():
data = dict(
year=2000,
month=1,
day=2,
hour=3,
minute=4,
secondsWithinAMinuteMicrosecond=5.6789,
)
datetime = get_datetime(data)
assert not isinstance(datetime, np.ndarray)
assert datetime == numpy.datetime64("2000-01-02T03:04:05.6789")
def test_get_datetime_with_seconds_and_microseconds():
data = dict(
year=2000,
month=1,
day=2,
hour=3,
minute=4,
second=5,
secondsWithinAMinuteMicrosecond=5.6789,
)
datetime = get_datetime(data)
assert not isinstance(datetime, np.ndarray)
assert datetime == numpy.datetime64("2000-01-02T03:04:05.6789")
def test_get_datetime_with_prefix():
header = dict(rdbtimeDay=2, rdbtimeHour=3, rdbtimeMinute=4, rdbtimeSecond=5)
with pt.raises(KeyError):
datetime = get_datetime(header, prefix="rdbtime")
datetime = get_datetime(header, prefix="rdbtime", year=2000, month=1)
assert not isinstance(datetime, np.ndarray)
assert datetime == numpy.datetime64("2000-01-02T03:04:05")
def test_get_datetime_with_rank():
data = dict(year=[2000, 2001, 2002], month=[1, 2, 3], day=[2, 3, 4])
data.update(hour=[3, 4, 5], minute=[4, 5, 6], second=[5, 6, 7])
for name, values in list(data.items()):
# data.pop(name)
for rank, value in enumerate(values, 1):
data[f"#{rank}#{name}"] = value
# Number
datetime = get_datetime(data, rank=2)
assert not isinstance(datetime, np.ndarray)
assert datetime == np.datetime64("2001-02-03T04:05:06")
# Slice
datetime = get_datetime(data, rank=slice(2, None))
assert isinstance(datetime, np.ndarray)
assert np.all(
datetime
== np.array(["2001-02-03T04:05:06", "2002-03-04T05:06:07"], dtype="M8[s]")
)
# Test missing_of()
def test_missing_of():
with pt.raises(ValueError):
missing_of(np.int8)
with pt.raises(ValueError):
missing_of(np.int16)
assert CODES_MISSING_LONG == missing_of(np.int32)
assert CODES_MISSING_LONG == missing_of(np.int64)
with pt.raises(ValueError):
missing_of(np.dtype("i2"))
with pt.raises(ValueError):
missing_of(np.dtype("f4"))
assert CODES_MISSING_LONG == missing_of(np.dtype("i4"))
assert CODES_MISSING_LONG == missing_of(np.dtype("i8"))
assert CODES_MISSING_LONG == missing_of(np.array([1]))
assert CODES_MISSING_LONG == missing_of([1])
assert CODES_MISSING_LONG == missing_of(1)
with pt.raises(ValueError):
missing_of(np.float16)
with pt.raises(ValueError):
missing_of(np.float32)
assert CODES_MISSING_DOUBLE == missing_of(np.float64)
assert CODES_MISSING_DOUBLE == missing_of(np.dtype("f8"))
assert CODES_MISSING_DOUBLE == missing_of(np.array([1.0]))
assert CODES_MISSING_DOUBLE == missing_of([1.0])
assert CODES_MISSING_DOUBLE == missing_of(1.0)
assert "" == missing_of(np.dtype("U"))
assert "" == missing_of(np.array(["one"]))
assert "" == missing_of(["one"])
assert "" == missing_of("one")
assert np.array(CODES_MISSING_LONG, "M8[D]") == missing_of(np.dtype("M8[D]"))
assert np.array(CODES_MISSING_LONG, "M8[D]") == missing_of(np.array(1, "M8[D]"))
assert np.array(CODES_MISSING_LONG, "m8[h]") == missing_of(np.dtype("m8[h]"))
assert np.array(CODES_MISSING_LONG, "m8[h]") == missing_of(np.array(1, "m8[h]"))
ecmwf-eccodes-python-b43a0f2/tests/test_20_main.py 0000664 0001750 0001750 00000000431 15172126764 022335 0 ustar alastair alastair import pytest
from eccodes import __main__
def test_main(capsys):
__main__.main(argv=["selfcheck"])
stdout, _ = capsys.readouterr()
assert "Your system is ready." in stdout
with pytest.raises(RuntimeError):
__main__.main(argv=["non-existent-command"])
ecmwf-eccodes-python-b43a0f2/tests/test_bufr_message.py 0000664 0001750 0001750 00000042243 15172126764 023561 0 ustar alastair alastair # Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# flake8: noqa: F403
# flake8: noqa: F405
import datetime as dt
import sys
import numpy as np
import pytest as pt
from eccodes import *
from eccodes.highlevel._bufr import *
if sys.platform.startswith("win32"):
pt.skip("Not applicable on Windows", allow_module_level=True)
@pt.fixture(autouse=True)
def fixture(monkeypatch):
import os
this_dir = os.path.dirname(__file__)
monkeypatch.chdir(this_dir) # always run tests from the tests directory
def unpacked(bufr):
defined = codes_is_defined(bufr._handle, "year")
return bool(defined)
def compare_items(m1, m2, keys=None):
if keys:
keys = list(keys)
i1 = [(k, m1[k]) for k in keys]
i2 = [(k, m2[k]) for k in keys]
else:
i1 = list(m1.items())
i2 = list(m2.items())
for (k1, v1), (k2, v2) in zip(i1, i2):
assert k1 == k2
if isinstance(v1, np.ndarray):
if v1.dtype.type == np.str_:
equal = np.char.compare_chararrays(v1, v2, "==", rstrip=True)
else:
equal = v1.data == v2.data
assert k1 and np.all(equal)
else:
assert k1 and v1 == v2
def test_message_uncompressed():
bufr = BUFRMessage(open("./sample-data/synop_multi_subset.bufr"))
assert bufr["edition"] == 4
assert np.all(
bufr["unexpandedDescriptors"] == [307079, 4025, 11042]
) # is always array
assert "dataCategory" in bufr.header
assert bufr.get_count("dataCategory") == 1
bufr["dataCategory"] = 11
assert not unpacked(bufr)
assert "rubbish_key" not in bufr # triggers unpacking
assert unpacked(bufr)
# Accessing undefined elements raises an exception
with pt.raises(KeyValueNotFoundError):
bufr["undefined"]
with pt.raises(KeyValueNotFoundError):
bufr["undefined"] = 0
assert "blockNumber" in bufr
assert bufr.get_count("blockNumber") == 12
assert len(bufr.data) == 12 # number of subsets
assert len(bufr.data[0]) == 19 # number of data tree nodes per subset
assert np.all(
bufr["stationNumber"].data
== np.array([27, 84, 270, 272, 308, 371, 381, 382, 387, 413, 464, 485])
)
assert bufr["#1#stationNumber"] == 27
assert bufr.data[0]["stationNumber"] == 27 # of the 1st subset
bufr["stationNumber"][0] = missing_of(int)
assert bufr["stationNumber"].data[0] == missing_of(int)
bufr["stationNumber"] = missing_of(int)
assert np.all(bufr["stationNumber"].data == missing_of(int))
# Keys can have element attribute as a suffix (Ã la ecCodes)
assert bufr["stationNumber->units"] == "Numeric"
# Attempting to modify element attributes raises an exception
with pt.raises(ReadOnlyError):
bufr["stationNumber->units"] = "Turmeric"
def test_message_compressed():
bufr = BUFRMessage(open("./sample-data/sral_sentinel_3a.bufr"))
assert (
len(bufr.data) == 1
) # this message doesn't have any replications, so the structure is flat
year = bufr["year"]
assert year.shape == (22, 5)
assert np.all(year[0].data == 2017)
assert np.all(year[-1].data == missing_of(int))
# Keys can have rank prefix (Ã la ecCodes)
bufr["#1#year"] = 2018
assert np.all(bufr["#1#year"].data == 2018)
# Packing the message by setting the 'pack' key is disallowed
with pt.raises(KeyValueNotFoundError):
bufr["pack"] = 1
# The same goes for unpacking
with pt.raises(KeyValueNotFoundError):
bufr["unpack"] = 1
def test_message_sample_defaults():
bufr = BUFRMessage("BUFR3")
assert bufr["edition"] == 3
assert bufr["masterTableNumber"] == 0
assert bufr["masterTablesVersionNumber"] == 24
assert bufr["localTablesVersionNumber"] == 0
assert bufr["bufrHeaderCentre"] == 98
assert bufr["bufrHeaderSubCentre"] == 0
assert bufr["numberOfSubsets"] == 1
assert not bufr["compressedData"]
assert not bufr["section2Present"]
assert np.all(bufr["unexpandedDescriptors"] == 307080)
bufr = BUFRMessage("BUFR3_local")
assert bufr["edition"] == 3
assert bufr["masterTableNumber"] == 0
assert bufr["masterTablesVersionNumber"] == 24
assert bufr["localTablesVersionNumber"] == 0
assert bufr["bufrHeaderCentre"] == 98
assert bufr["bufrHeaderSubCentre"] == 0
assert bufr["numberOfSubsets"] == 1
assert not bufr["compressedData"]
assert bufr["section2Present"]
assert np.all(bufr["unexpandedDescriptors"] == 307080)
bufr = BUFRMessage("BUFR4_local")
assert bufr["edition"] == 4
assert bufr["masterTableNumber"] == 0
assert bufr["masterTablesVersionNumber"] == 24
assert bufr["localTablesVersionNumber"] == 0
assert bufr["bufrHeaderCentre"] == 98
assert bufr["bufrHeaderSubCentre"] == 0
assert bufr["numberOfSubsets"] == 1
assert not bufr["compressedData"]
assert np.all(bufr["unexpandedDescriptors"] == 307080)
bufr = BUFRMessage("BUFR3_local_satellite")
assert bufr["edition"] == 3
assert bufr["masterTableNumber"] == 0
assert bufr["masterTablesVersionNumber"] == 13
assert bufr["localTablesVersionNumber"] == 1
assert bufr["bufrHeaderCentre"] == 98
assert bufr["bufrHeaderSubCentre"] == 0
assert bufr["numberOfSubsets"] == 128
assert bufr["compressedData"]
assert np.all(bufr["unexpandedDescriptors"] == 310014)
bufr = BUFRMessage("BUFR4_local_satellite")
assert bufr["edition"] == 4
assert bufr["masterTableNumber"] == 0
assert bufr["masterTablesVersionNumber"] == 13
assert bufr["localTablesVersionNumber"] == 1
assert bufr["bufrHeaderCentre"] == 98
assert bufr["bufrHeaderSubCentre"] == 0
assert bufr["numberOfSubsets"] == 128
assert bufr["compressedData"]
assert np.all(bufr["unexpandedDescriptors"] == 310008)
def test_message_new_from_sample():
bufr = BUFRMessage("BUFR3_local_satellite")
# If the tables version is not set properly, eccodes fails with
# "HashArrayNoMatchError: Hash array no match" when setting
# unexpandedDescriptors.
bufr["masterTablesVersionNumber"] = 18
bufr["localTablesVersionNumber"] = 0
bufr["numberOfSubsets"] = 2
bufr["compressedData"]
# Input replication factors are optional; will default to 0 if not set.
bufr["inputDelayedDescriptorReplicationFactor"] = [0, 0]
bufr["inputShortDelayedDescriptorReplicationFactor"] = [0, 0, 0, 0, 0, 0]
bufr["unexpandedDescriptors"] = [311011]
bufr["year"] = 2022
# Template-related items can't be changed once the header is "baked" in
for key in ("numberOfSubsets", "compressedData", "unexpandedDescriptors"):
with pt.raises(ReadOnlyError):
bufr[key] = 1
# Test that common Section 2 keys are set properly
bufr.pack()
assert bufr["localNumberOfObservations"] == bufr["numberOfSubsets"]
def test_message_new_from_bytes():
old = BUFRMessage("BUFR3_local")
bytes = old.get_buffer()
new = BUFRMessage(bytes)
compare_items(old.header, new.header, keys=old.header.keys(skip="read_only"))
compare_items(old.data, new.data)
def test_message_context_manager():
bufr = BUFRMessage("BUFR3_local")
assert bufr._handle > 0
with bufr:
pass
assert bufr._handle == 0
def test_message_copy():
# Copy is the same as the original (note that copying doesn't require unpacking)
old = BUFRMessage(open("./sample-data/synop.bufr"))
assert unpacked(old) is False
new = old.copy()
assert unpacked(old) is False
assert old["md5Data"] == new["md5Data"]
assert old._handle != new._handle
compare_items(old, new)
# FIXME: The following code doesnt't work because eccodes apparently doesn't
# update typicalDate (which is computed key) when we modify typicalYearOfCentury .
# After chainging one of the header section keys
# old['typicalYearOfCentury'] = 25
# new = old.copy()
# assert old['md5Data'] == new['md5Data']
# compare_items(old, new)
# After changing one of the data section keys
old["blockNumber"] = 99
new = old.copy()
assert old["md5Data"] == new["md5Data"]
compare_items(old, new)
def test_message_copy_subsets():
old = BUFRMessage(open("./sample-data/ahi-himawari-8.bufr"))
old_count = old["numberOfSubsets"]
old["second"] = range(0, old_count)
# Copy adjacent subsets where mask is True (uses extractSubsetInterval)
mask = np.repeat(False, old_count)
mask[0:3] = True
with old.copy(subsets=mask) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 1, 2])
# # Copy adjacent subsets where mask is True (uses extractSubsetList)
mask = np.repeat(False, old_count)
mask[[0, 2, 4]] = True
with old.copy(subsets=mask) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 2, 4])
# Copy a range of adjacent subsets (uses extractSubsetInterval)
with old.copy(subsets=range(3)) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 1, 2])
# Copy a range of non-adjacent subsets (uses extractSubsetList)
with old.copy(subsets=range(0, 5, 2)) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 2, 4])
# Copy a list of adjacent subsets (uses extractSubsetInterval)
with old.copy(subsets=[0, 1, 2]) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 1, 2])
# Copy a list of non-adjacent subsets (uses extractSubsetList)
with old.copy(subsets=[0, 2, 4]) as new:
assert new["numberOfSubsets"] == 3
assert np.all(new["second"] == [0, 2, 4])
# Copy a full slice of all subsets
# (uses extractSubsetInterval, or no extraction if there were no previous ones)
with old.copy(subsets=slice(None, None, None)) as new: # [1]
assert new["numberOfSubsets"] == old_count
assert np.all(new["second"] == range(0, old_count))
# Copy every 4th subset (uses extractSubsetInterval)
with old.copy(subsets=slice(None, None, 4)) as new:
assert new["numberOfSubsets"] == divmod(old_count, 4)[0]
assert np.all(new["second"] == range(0, old_count, 4))
# Copy every 4th subset within a range (uses extractSubsetList)
with old.copy(subsets=slice(16, 32, 4)) as new:
assert new["numberOfSubsets"] == divmod(32 - 16, 4)[0]
assert np.all(new["second"] == old["second"][16:32:4])
# Copy datetime slice (uses extractSubsetInterval)
old_datetime = old.get_datetime()
start = old_datetime[0].item()
end = start + dt.timedelta(seconds=2)
with old.copy(subsets=slice(None, end)) as new:
assert new["numberOfSubsets"] == 3
new_datetime = new.get_datetime()
assert np.all(new_datetime == old_datetime[0:3])
# [1] Note that this prints a lot of 'ECCODES ERROR' messages due to ECC-2025,
# but otherwise the code works OK because we work around the issue internally.
def test_message_copy_to():
old = BUFRMessage(open("./sample-data/aura-omi-ak.bufr"))
new = BUFRMessage("BUFR3_local_satellite")
old.copy_to(new)
compare_items(old.header, new.header, keys=old.header.keys(skip="read_only"))
compare_items(old.data, new.data)
def test_message_eq():
bufr1 = BUFRMessage(open("./sample-data/synop.bufr"))
bufr2 = BUFRMessage(open("./sample-data/synop.bufr"))
assert bufr1 == bufr2
bufr1["typicalDay"] = 1
bufr2["typicalDay"] = 2
assert bufr1.header != bufr2.header
assert bufr1.data == bufr2.data
def test_message_update():
bufr = BUFRMessage("BUFR3")
bufr.update(year=2000)
bufr.update({"month": 2})
bufr.update([("day", 4)])
bufr.update(dict(hour=8, minute=16).items())
assert bufr["year"] == 2000
assert bufr["month"] == 2
assert bufr["day"] == 4
assert bufr["hour"] == 8
assert bufr["minute"] == 16
def test_message_get_datetime_compressed():
bufr = BUFRMessage("BUFR4_local_satellite")
bufr.update(numberOfSubsets=2)
datetime = bufr.get_datetime()
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert datetime.all() is np.ma.masked
bufr.update(year=2000, month=1, day=2, hour=3, minute=4)
bufr.update(second=[5, CODES_MISSING_LONG])
datetime = bufr.get_datetime()
assert isinstance(datetime, np.ndarray)
assert datetime.size == 2
assert np.ma.all(
datetime == np.array(["2000-01-02T03:04:05", "NaT"], dtype="M8[s]")
)
def test_message_get_datetime_uncompressed():
bufr = BUFRMessage("BUFR3")
datetime = bufr.get_datetime()
assert datetime is np.ma.masked
bufr.update(year=2000, month=1, day=2, hour=3, minute=4)
datetime = bufr.get_datetime()
assert not isinstance(datetime, np.ndarray)
assert datetime == np.datetime64("2000-01-02T03:04:00")
datetime = bufr.get_datetime(prefix="typical")
assert datetime == np.datetime64("2012-10-31T00:02:00")
def test_message_set_datetime_compressed():
bufr = BUFRMessage("BUFR4_local_satellite")
# bufr.update(numberOfSubsets=3, unexpandedDescriptors=310008)
bufr.update(numberOfSubsets=3)
# Built-in datetime object
datetime = np.datetime64("2000-01-02T03:04:05", "s").item()
bufr.set_datetime(datetime)
assert np.all(bufr["year"] == 2000)
assert np.all(bufr["month"] == 1)
assert np.all(bufr["day"] == 2)
assert np.all(bufr["hour"] == 3)
assert np.all(bufr["minute"] == 4)
assert np.all(bufr["second"] == 5)
# Numpy datetime64 array
values = ["2000-01-02T03:04:05", "2000-01-02T06:07:08", "NaT"]
array = np.ma.array(values, dtype="M8[s]", mask=[False, False, True])
bufr.set_datetime(array)
assert np.all(bufr["year"] == 2000)
assert np.all(bufr["month"] == 1)
assert np.all(bufr["day"] == 2)
assert np.all(bufr["hour"] == np.array([3, 6, -1]))
assert np.all(bufr["minute"] == np.array([4, 7, -1]))
assert np.all(bufr["second"] == np.array([5, 8, -1]))
# Unless explicitly set by the user, all datetime-related keys from the header
# section (i.e., typicalYear, typicalMonth, etc.) get updated automatically,
# whilst packing, based on values from the data section.
bufr["typicalYear"]
bufr["localYear"]
bufr.pack()
assert bufr["typicalYear"] == 2000
assert bufr["typicalMonth"] == 1
assert bufr["typicalDay"] == 2
assert bufr["typicalHour"] == 3
assert bufr["typicalMinute"] == 4
assert bufr["typicalSecond"] == 5
assert bufr["localYear"] == 2000
assert bufr["localMonth"] == 1
assert bufr["localDay"] == 2
assert bufr["localHour"] == 3
assert bufr["localMinute"] == 4
assert bufr["localSecond"] == 5
# But respect values of section 1 keys if explicitly set by the user.
bufr.update(typicalMinute=0, typicalSecond=0)
bufr.update(localMinute=0, localSecond=0)
bufr.pack()
assert bufr["typicalMinute"] == 0
assert bufr["typicalSecond"] == 0
assert bufr["localMinute"] == 0
assert bufr["localSecond"] == 0
def test_message_set_datetime_compressed_irregular():
bufr = BUFRMessage("BUFR3_local_satellite")
bufr.update(numberOfSubsets=3)
assert bufr.get_count("year") == 2
assert bufr.get_count("month") == 2
assert bufr.get_count("day") == 2
assert bufr.get_count("hour") == 10
assert bufr.get_count("minute") == 9
assert bufr.get_count("second") == 9
values = ["2000-01-02T03:04:05", "2000-01-02T06:07:08", "NaT"]
array = np.ma.array(values, dtype="M8[s]", mask=[False, False, True])
bufr.set_datetime(array, rank=1)
assert np.all(bufr["#1#year"] == 2000)
assert np.all(bufr["#1#month"] == 1)
assert np.all(bufr["#1#day"] == 2)
assert np.all(bufr["#1#hour"] == np.array([3, 6, -1]))
assert np.all(bufr["#1#minute"] == np.array([4, 7, -1]))
assert np.all(bufr["#1#second"] == np.array([5, 8, -1]))
assert np.all(bufr["year"][1:]) is np.ma.masked
assert np.all(bufr["month"][1:]) is np.ma.masked
assert np.all(bufr["day"][1:]) is np.ma.masked
assert np.all(bufr["hour"][1:]) is np.ma.masked
assert np.all(bufr["minute"][1:]) is np.ma.masked
assert np.all(bufr["second"][1:]) is np.ma.masked
# Section 1 keys updated whilst packing
bufr["typicalYearOfCentury"]
bufr.pack()
assert bufr.get_datetime(prefix="typical") == np.datetime64(values[0])
assert bufr.get_datetime(prefix="local") == np.datetime64(values[0])
def test_message_set_datetime_uncompressed():
bufr = BUFRMessage("BUFR3_local")
datetime = np.datetime64("2000-01-02T03:04:05", "s").item()
bufr.set_datetime(datetime)
assert bufr["year"] == 2000
assert bufr["month"] == 1
assert bufr["day"] == 2
assert bufr["hour"] == 3
assert bufr["minute"] == 4
# Section 1 keys updated whilst packing
bufr["typicalYearOfCentury"]
bufr.pack()
assert bufr["typicalYearOfCentury"] == 0
assert bufr["typicalMonth"] == 1
assert bufr["typicalDay"] == 2
assert bufr["typicalHour"] == 3
assert bufr["typicalMinute"] == 4
assert bufr["typicalSecond"] == 0
ecmwf-eccodes-python-b43a0f2/tests/test_20_messages.py 0000664 0001750 0001750 00000015247 15172126764 023233 0 ustar alastair alastair import os.path
import numpy as np
import pytest
# flake8: noqa
# from eccodes import messages
SAMPLE_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "sample-data")
TEST_DATA = os.path.join(SAMPLE_DATA_FOLDER, "era5-levels-members.grib")
def _test_Message_read():
with open(TEST_DATA) as file:
res1 = messages.Message.from_file(file)
assert res1.message_get("paramId") == 129
assert res1["paramId"] == 129
assert list(res1)[0] == "globalDomain"
assert list(res1.message_grib_keys("time"))[0] == "dataDate"
assert "paramId" in res1
assert len(res1) > 100
with pytest.raises(KeyError):
res1["non-existent-key"]
assert res1.message_get("non-existent-key", default=1) == 1
res2 = messages.Message.from_message(res1)
for (k2, v2), (k1, v1) in zip(res2.items(), res1.items()):
assert k2 == k1
if isinstance(v2, np.ndarray) or isinstance(v1, np.ndarray):
assert np.allclose(v2, v1)
else:
assert v2 == v1
with open(TEST_DATA) as file:
with pytest.raises(EOFError):
while True:
messages.Message.from_file(file)
def _test_Message_write(tmpdir):
res = messages.Message.from_sample_name("regular_ll_pl_grib2")
assert res["gridType"] == "regular_ll"
res.message_set("Ni", 20)
assert res["Ni"] == 20
res["iDirectionIncrementInDegrees"] = 1.0
assert res["iDirectionIncrementInDegrees"] == 1.0
res.message_set("gridType", "reduced_gg")
assert res["gridType"] == "reduced_gg"
res["pl"] = [2.0, 3.0]
assert np.allclose(res["pl"], [2.0, 3.0])
# warn on errors
res["centreDescription"] = "DUMMY"
assert res["centreDescription"] != "DUMMY"
res["edition"] = -1
assert res["edition"] != -1
# ignore errors
res.errors = "ignore"
res["centreDescription"] = "DUMMY"
assert res["centreDescription"] != "DUMMY"
# raise errors
res.errors = "raise"
with pytest.raises(KeyError):
res["centreDescription"] = "DUMMY"
with pytest.raises(NotImplementedError):
del res["gridType"]
out = tmpdir.join("test.grib")
with open(str(out), "wb") as file:
res.write(file)
def _test_ComputedKeysMessage_read():
computed_keys = {
"ref_time": (lambda m: str(m["dataDate"]) + str(m["dataTime"]), None),
"error_key": (lambda m: 1 / 0, None),
"centre": (lambda m: -1, lambda m, v: None),
}
with open(TEST_DATA) as file:
res = messages.ComputedKeysMessage.from_file(file, computed_keys=computed_keys)
assert res["paramId"] == 129
assert res["ref_time"] == "201701010"
assert len(res) > 100
assert res["centre"] == -1
with pytest.raises(ZeroDivisionError):
res["error_key"]
def _test_ComputedKeysMessage_write():
computed_keys = {
"ref_time": (lambda m: "%s%04d" % (m["dataDate"], m["dataTime"]), None),
"error_key": (lambda m: 1 / 0, None),
"centre": (lambda m: -1, lambda m, v: None),
}
res = messages.ComputedKeysMessage.from_sample_name(
"regular_ll_pl_grib2", computed_keys=computed_keys
)
res["dataDate"] = 20180101
res["dataTime"] = 0
assert res["ref_time"] == "201801010000"
res["centre"] = 1
def _test_compat_create_exclusive(tmpdir):
test_file = tmpdir.join("file.grib.idx")
try:
with messages.compat_create_exclusive(str(test_file)):
raise RuntimeError("Test remove")
except RuntimeError:
pass
with messages.compat_create_exclusive(str(test_file)) as file:
file.write(b"Hi!")
with pytest.raises(OSError):
with messages.compat_create_exclusive(str(test_file)) as file:
file.write(b"Hi!")
def _test_FileIndex():
res = messages.FileIndex.from_filestream(
messages.FileStream(TEST_DATA), ["paramId"]
)
assert res["paramId"] == [129, 130]
assert len(res) == 1
assert list(res) == ["paramId"]
assert res.first()
with pytest.raises(ValueError):
res.getone("paramId")
with pytest.raises(KeyError):
res["non-existent-key"]
subres = res.subindex(paramId=130)
assert subres.get("paramId") == [130]
assert subres.getone("paramId") == 130
assert len(subres) == 1
def _test_FileIndex_from_indexpath_or_filestream(tmpdir):
grib_file = tmpdir.join("file.grib")
with open(TEST_DATA, "rb") as file:
grib_file.write_binary(file.read())
# create index file
res = messages.FileIndex.from_indexpath_or_filestream(
messages.FileStream(str(grib_file)), ["paramId"]
)
assert isinstance(res, messages.FileIndex)
# read index file
res = messages.FileIndex.from_indexpath_or_filestream(
messages.FileStream(str(grib_file)), ["paramId"]
)
assert isinstance(res, messages.FileIndex)
# do not read nor create the index file
res = messages.FileIndex.from_indexpath_or_filestream(
messages.FileStream(str(grib_file)), ["paramId"], indexpath=""
)
assert isinstance(res, messages.FileIndex)
# can't create nor read index file
res = messages.FileIndex.from_indexpath_or_filestream(
messages.FileStream(str(grib_file)),
["paramId"],
indexpath=str(tmpdir.join("non-existent-folder").join("non-existent-file")),
)
assert isinstance(res, messages.FileIndex)
# trigger mtime check
grib_file.remove()
with open(TEST_DATA, "rb") as file:
grib_file.write_binary(file.read())
res = messages.FileIndex.from_indexpath_or_filestream(
messages.FileStream(str(grib_file)), ["paramId"]
)
assert isinstance(res, messages.FileIndex)
def _test_FileIndex_errors():
class MyMessage(messages.ComputedKeysMessage):
computed_keys = {"error_key": lambda m: 1 / 0}
stream = messages.FileStream(TEST_DATA, message_class=MyMessage)
res = messages.FileIndex.from_filestream(stream, ["paramId", "error_key"])
assert res["paramId"] == [129, 130]
assert len(res) == 2
assert list(res) == ["paramId", "error_key"]
assert res["error_key"] == ["undef"]
def _test_FileStream():
res = messages.FileStream(TEST_DATA)
leader = res.first()
assert len(leader) > 100
assert sum(1 for _ in res) == leader["count"]
assert len(res.index(["paramId"])) == 1
# __file__ is not a GRIB, but contains the "GRIB" string, so it is a very tricky corner case
res = messages.FileStream(str(__file__))
with pytest.raises(EOFError):
res.first()
res = messages.FileStream(str(__file__), errors="ignore")
with pytest.raises(EOFError):
res.first()
# res = messages.FileStream(str(__file__), errors='raise')
# with pytest.raises(bindings.EcCodesError):
# res.first()
ecmwf-eccodes-python-b43a0f2/tests/examples/ 0000775 0001750 0001750 00000000000 15172126764 021317 5 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/tests/examples/__init__.py 0000664 0001750 0001750 00000000000 15172126764 023416 0 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/tests/examples/items.py 0000775 0001750 0001750 00000003160 15172126764 023015 0 ustar alastair alastair #!/usr/bin/env python3
# Copyright 2005- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# This example shows how to list all items in a BUFR message.
#
# Usage: ./items.py input.bufr
# flake8: noqa: F403
# flake8: noqa: F405
import sys
from eccodes import *
from eccodes.highlevel import *
def run_example(input, output=sys.stdout):
reader = FileReader(input, CODES_PRODUCT_BUFR)
for i, message in enumerate(reader, start=1):
print("messageNumber", i, file=output)
if message["compressedData"] or message["numberOfSubsets"] == 1:
for k, v in message.items():
print(k, v, file=output)
else:
for k, v in message.header.items():
print(k, v, file=output)
# For uncompressed multi-subset messages, print items one subset at a time. [1]
for j, subset in enumerate(message.data, start=1):
print("subsetNumber", j, file=output)
for k, v in subset.items():
print(k, v, file=output)
if __name__ == "__main__":
run_example(sys.argv[1])
# Footnotes:
#
# [1] This is not stricly necessary but, generally, it's easier to interpret
# contents of ucompressed messages by looking at individual subsets, especially
# so when subsets have different replication factors.
ecmwf-eccodes-python-b43a0f2/tests/examples/attributes.py 0000775 0001750 0001750 00000002574 15172126764 024072 0 ustar alastair alastair #!/usr/bin/env python3
# Copyright 2022- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# This example shows how to read key attributes.
#
# Usage: attributes.py input.bufr
# flake8: noqa: F403
# flake8: noqa: F405
import sys
from eccodes import *
from eccodes.highlevel import *
ATTRIBUTES = ["code", "units", "scale", "reference", "width"] # See BUFR Code Table B
def run_example(input, output=sys.stdout):
# Loop over the messages in the file
for number, bufr in enumerate(FileReader(input, CODES_PRODUCT_BUFR), start=1):
print(f"messageNumber: {number}", file=output)
# Loop over all keys in the message
for name in bufr.keys():
# Print element's attributes. Attributes themselves are keys.
for attribute in ATTRIBUTES:
try:
key = f"{name}->{attribute}"
value = bufr[key]
print(f"{key}: {value}", file=output)
except eccodes.KeyValueNotFoundError:
pass
if __name__ == "__main__":
run_example(sys.argv[1])
ecmwf-eccodes-python-b43a0f2/tests/sample-data/ 0000775 0001750 0001750 00000000000 15172126764 021671 5 ustar alastair alastair ecmwf-eccodes-python-b43a0f2/tests/sample-data/saral-altika.bufr 0000664 0001750 0001750 00000020737 15172126764 025127 0 ustar alastair alastair BUFR !ß b €Ú ( 4 Ú~I9t kÂ@z#í uG Ei €¹ F+¶F+¶ F €Àè !„ n@.