Skip to content

Commit 92c5279

Browse files
committed
More improvements
1 parent a52e50d commit 92c5279

1 file changed

Lines changed: 87 additions & 44 deletions

File tree

docs/src/user_manual/explanation/netcdf_io.rst

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,25 @@ Iris' optimisation all together, and will take its chunksizes from Dask's behavi
135135

136136
Character and String datatypes
137137
------------------------------
138+
Summary
139+
^^^^^^^
138140

139-
In NetCDF
140-
^^^^^^^^^
141+
* Iris currently *only* fully supports fixed-width 'char' type data in netCDF variables
142+
* the 'string' type variable-width unicode strings will be added in a future release
143+
* 'char' variable data is represented as numpy string arrays in Iris objects
144+
* the numpy dtype is of the type "U<xx>", where <xx> is a character width.
145+
* the character width relates to the string *dimension* of the netCDF variable,
146+
which does not appear as a cube or coordinate dimension.
147+
* the dtype 'width' controls the length of string dimensions created when saving
148+
* Iris also uses a variable ``_Encoding`` attribute to enable storage of non-ascii
149+
characters in 'char' type arrays.
150+
* it appears as a regular attribute of the Iris object
151+
* it is not needed for ascii-only data
152+
* it is not needed to *read* 'utf-8' encoded data correctly
153+
* it **is** required to *save* any non-ascii characters
154+
155+
In the NetCDF file format
156+
^^^^^^^^^^^^^^^^^^^^^^^^^
141157
In the NetCDF v4 implementation, there are three specific areas where the datatype and
142158
storage characteristics of character data are relevant:
143159

@@ -155,43 +171,52 @@ storage characteristics of character data are relevant:
155171
"string dimension". If they contain *only* ascii character values, this is
156172
uncomplicated, but they may also be used to contain non-ascii data (i.e.
157173
including unicode characters). There is no universally defined agreement on how
158-
to indicate a variable containing non-ascii data, but many datasets have used a
159-
suitable ``_Encoding`` attribute.
174+
to indicate that bytes are encoded non-ascii data, but many older datasets have
175+
used a variable attribute ``_Encoding`` indicating the encoding name.
160176

161177
The NetCDF documentation also mentions that an ``_Encoding`` attribute may be used to
162-
represent non-ascii strings in a 'char' type array. However this is described as
163-
"reserved for future use", and its valid values and effects are not explicitly defined.
178+
represent non-ascii strings. However this is described as "reserved for future use",
179+
and its valid values and effects are not explicitly defined.
164180

165181
However, it is also notable that the standard ``ncgen`` and ``ncdump`` tools *do*
166182
correctly interpret an ``_Encoding`` attribute in most cases, despite this not being an
167183
"official" solution.
168184

169185

170-
In CF
171-
^^^^^
172-
The CF Conventions define a subset of "allowed" datatypes, and describe various data
173-
elements stored as variables, such as data variables, auxiliary coordinates, cell
174-
methods, etc.
186+
In the netCDF CF Conventions
187+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
188+
The `CF Conventions <https://https://cfconventions.org/>`_ define a subset of
189+
"allowed" datatypes, and various types of data elements represented by variables
190+
-- such as data variables, auxiliary coordinates, cell methods, etc.
175191

176192
CF currently supports the use of either netcdf 'string' or 'char' arrays for any
177193
variables.
194+
However, *historically*, CF has had more limited support, and also "unofficial
195+
conventions" have been used for string data encoded as bytes, which may be encountered
196+
in older datasets.
178197

179-
However, historically CF has had more limited support, and also 'unofficial'
180-
conventions for the use of 'char' arrays have been used which may be encountered in
181-
older datasets.
198+
**Prior to v1.8**, CF required to use 'char' type only, without providing
199+
*any official means* of representing non-ascii data.
182200

183-
Since v1.8, CF has allowed the use of 'string' data in all variables, but prior to that
184-
it was required to use 'char' type only, without providing any official means of storing
185-
non-ascii data.
201+
**Since v1.8**, CF has allowed the use of 'string' data in all variables.
202+
However, up to v1.12 there was still no official way of encoding non-ascii data in
203+
'char' arrays.
186204

187-
Since v1.12, CF also mandates a *default* assumption of utf-8 encoding to store non-ascii
188-
data in 'char' form, but it also notes that some data in the past has used an
189-
``_Encoding`` attribute -- though was never an official CF usage.
205+
**Since v1.12**, CF now mandates a *default* assumption of utf-8 encoding to store
206+
non-ascii data in 'char' form, but it also notes that some data in the past has used an
207+
``_Encoding`` attribute -- though this was never an official CF usage.
190208

191-
Where strings are stored as 'char' type, the array must have a "string dimension",
192-
which is a normal file dimension. Thus, these strings always have a *fixed byte width*.
193-
(However, that is not the same as a fixed *character* width, since in most encodings
194-
non-ascii characters require more bytes to store.)
209+
Characteristics
210+
~~~~~~~~~~~~~~~
211+
Where strings are stored as 'char' type, which is the more common traditional approach,
212+
the array must have a "string dimension", which is a normal file dimension. Thus, these
213+
strings always have a *fixed byte width*. (However, that is not the same as a fixed
214+
*character* width, since in most encodings non-ascii characters require more bytes to
215+
store).
216+
217+
Although the variable-length 'string' data is now supported in CF, the use of
218+
fixed-width 'char' arrays is obviously more efficient for storage and access, and it is
219+
still the most common approach in practice.
195220

196221

197222
In the netCDF4 Python module
@@ -213,47 +238,65 @@ In the netCDF4 Python module
213238

214239
In Iris
215240
^^^^^^^
216-
In Iris, the 'string' data type is supported at present, though this is planned for
217-
future releases. See the following section `Variable-length datatypes`_ for
241+
In Iris, **the 'string' data type is not supported at present**, though this is planned
242+
for future releases. See the following section `Variable-length datatypes`_ for
218243
an interim solution enabling you at least to *load* variable-length string data.
219244

220245
Iris stores string data in arrays of dtype "U<xx>", where <xx> is a maximum character
221246
width. However, this data is currently **only** read and written in netCDF files as
222-
'char' type variables.
247+
'char' type variables (i.e. byte arrays).
223248

224-
Iris provides a set of valid encodings for non-ascii data :
225-
"ascii", "utf8", "utf16" and "utf32". These (or valid aliases) will appear in the
249+
Iris supports a specific set of valid encodings for non-ascii data :
250+
"ascii", "utf8", "utf16" and "utf32". These (or aliases) will appear in the
226251
``_Encoding`` attribute of a file variable, and likewise as an attribute of the
227252
corresponding Iris component object (e.g. cube or coordinate).
228253

229-
**On loading**, if there is a valid ``_Encoding`` attribute this is used to decode the data,
230-
otherwise a default encoding of "utf8" is applied: This works transparently if only
231-
ascii bytes are present, and also allows the ``_Encoding`` attribute to be omitted as
232-
long as utf8 was used.
233-
234-
**On saving**, any string data with only ascii characters does not require an
235-
``_Encoding`` attribute. However if there are non-ascii bytes, and no ``_Encoding``
236-
attribute, then an error will be raised.
254+
When loading
255+
~~~~~~~~~~~~
256+
If there is a valid ``_Encoding`` attribute this is used to decode the
257+
data, otherwise a default encoding of "utf8" is applied: This works transparently when
258+
only ascii characters are present, and also allows the ``_Encoding`` attribute to be
259+
omitted as long as utf8 was used. An invalid or unsupported encoding name will be
260+
ignored, with a warning, but the attribute will still be added to the Iris component
261+
object.
262+
263+
When saving
264+
~~~~~~~~~~~
265+
Any string data with only ascii characters does not require an ``_Encoding`` attribute.
266+
However if there are any non-ascii characters, and no ``_Encoding``
267+
attribute, then an error will be raised. An invalid or unsupported encoding name will
268+
be ignored, with a warning, but the attribute will still be stored to the file.
237269

238270
So effectively, the **"default" encodings are 'utf8' for load and 'ascii' for save**.
239271

272+
String width dimensions
273+
~~~~~~~~~~~~~~~~~~~~~~~
240274
For each valid encoding there is a definite relation between the string dimension length
241-
in the file (actually, the number of *bytes*), and the maximum character length in the
242-
array dtype, i.e. the "<xx>" in the "U<xx>" dtype.
275+
in the file (actually, the number of *bytes*), and the maximum character length, aka
276+
string width, in the array dtype : i.e. the "<xx>" in the "U<xx>" dtype.
243277

244-
The lengths of string dimensions created on write are calculated as follows:
278+
The **lengths of string dimensions created on write** are calculated as follows:
245279

246280
* ascii : n-bytes = n-characters
247281
* utf8 : n-bytes = n-characters
248282
* utf16 : n-bytes = 2 * (n-characters + 1)
249283
* utf32 : n-bytes = 4 * (n-characters + 1)
250284

251285
For reading, the inverse relations are applied to determine the '"U<xx>"' dtype in which
252-
the data is presented. This will round-trip correctly, i.e. is unchanged if written and
253-
then read back.
286+
the data is presented. This will always round-trip correctly, i.e. the dimension length
287+
is unchanged if data is read and then written back.
288+
289+
.. note::
290+
291+
When processing string arrays, Numpy does not routinely preserve the "<xx>" width part
292+
of "U<xx>" type data, but some operations will reduce it to the maximum width
293+
occurring. Thus, it may be necessary to explicitly re-assert the desired "string
294+
width" before saving with computed string arrays.
295+
E.G. ``coord.points = coord.core_points().astype("U40")``
296+
254297

255-
For 'ascii' and 'utf32' this relationship is simple + fixed, but for 'utf8' and
256-
'utf16', the number of encoded bytes depends on the actual characters present
298+
For 'ascii' and 'utf32' this character-to-byte relationship is simple + fixed, but for
299+
'utf8' and 'utf16', the number of encoded bytes depends on the actual characters present
257300
**and can exceed the numbers given above**. If any string in the *actual* data encodes
258301
to more bytes than the above-calculated string dimension, then Iris will raise an
259302
:class:`iris.exceptions.TranslationError`. In this case, the user must **explicitly

0 commit comments

Comments
 (0)