@@ -135,9 +135,45 @@ Iris' optimisation all together, and will take its chunksizes from Dask's behavi
135135
136136Character and String datatypes
137137------------------------------
138+ Summary
139+ ^^^^^^^
140+
141+ * Iris currently *only * fully supports fixed-width 'char' type data in netCDF variables
142+
143+ * the 'string' type (variable-width unicode strings) will be added in a future release
144+
145+ * 'char' variable data is represented as numpy string arrays in Iris objects, such as
146+ cubes and coordinates.
147+
148+ * the numpy dtype is of the type "U<xx>", where <xx> is a character width.
149+ * the character width relates to a string *dimension * of the netCDF variable,
150+ which is not in the dimensions of the Iris object or its data array.
151+ * the dtype 'width' controls the length of string dimensions created when saving
152+
153+ * Iris also uses a variable ``_Encoding `` attribute to enable storage of non-ascii
154+ characters in 'char' type arrays.
155+
156+ * it appears as a regular attribute of the Iris object
157+ * it is not needed for ascii-only data
158+ * it is not needed to *read * 'utf-8' encoded data correctly
159+ * it **is ** required to *save * any non-ascii characters
160+
161+ The following describes the nature of character and string data handling in :
162+ netCDF itself; the CF conventions; the netCDF4 Python module and the Iris implementation.
163+ In practice all these are connected.
164+
165+ The details are generally much simpler when strings may contain only ASCII characters.
166+ When strings may include non-ascii characters, this requires a specific encoding to be
167+ adopted when translating to and from bytes, and rules for determining what the encoding
168+ is or was.
169+
170+ Another significant factor is the way in which all the relevant projects have changed
171+ their features and methods over time, so that historic datasets may often use
172+ non-standard approaches to record string data.
173+
138174
139- In NetCDF
140- ^^^^^^^^^
175+ In the NetCDF file format
176+ ^^^^^^^^^^^^^^^^^^^^^^^^^
141177In the NetCDF v4 implementation, there are three specific areas where the datatype and
142178storage characteristics of character data are relevant:
143179
@@ -154,44 +190,53 @@ storage characteristics of character data are relevant:
154190 * 'char' type variables contain one-byte characters, and generally have a fixed-length
155191 "string dimension". If they contain *only * ascii character values, this is
156192 uncomplicated, but they may also be used to contain non-ascii data (i.e.
157- 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 .
193+ including unicode characters). There is no universally defined agreement for
194+ how to indicate that bytes are encoded non-ascii data, but many older datasets
195+ have used a variable attribute ``_Encoding `` indicating the encoding name .
160196
161197The 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.
198+ represent non-ascii strings. However this is described as "reserved for future use",
199+ and its valid values and effects are not explicitly defined.
164200
165201However, it is also notable that the standard ``ncgen `` and ``ncdump `` tools *do *
166202correctly interpret an ``_Encoding `` attribute in most cases, despite this not being an
167203"official" solution.
168204
169205
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.
206+ In the netCDF CF Conventions
207+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
208+ The ` CF Conventions < https://https://cfconventions.org/ >`_ define a subset of
209+ "allowed" datatypes, and various types of data elements represented by variables
210+ -- such as data variables, auxiliary coordinates, cell methods, etc.
175211
176212CF currently supports the use of either netcdf 'string' or 'char' arrays for any
177213variables.
214+ However, *historically *, CF has had more limited support, and also "unofficial
215+ conventions" have been used for string data encoded as bytes, which may be encountered
216+ in older datasets.
217+
218+ **Prior to v1.8 **, CF required to use 'char' type only, and provided
219+ **no official means ** of representing non-ascii data.
178220
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 .
221+ ** Since v1.8 **, CF has allowed the use of 'string' data in all variables.
222+ However, up to v1.12 there was still no official way of encoding non-ascii data in
223+ 'char' arrays .
182224
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 .
225+ ** Since v1.12 ** , CF now mandates a * default * assumption of utf-8 encoding to store
226+ non-ascii data in 'char' form. It does also note that some data in the past has used an
227+ `` _Encoding `` attribute -- though this was never an official CF usage .
186228
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.
229+ Characteristics
230+ ~~~~~~~~~~~~~~~
231+ Where strings are stored as 'char' type, which is the more common traditional approach,
232+ the array must have a "string dimension", which is a normal file dimension. Thus, these
233+ strings always have a *fixed byte width *. (However, that is not the same as a fixed
234+ *character * width, since in most encodings non-ascii characters require more bytes to
235+ store).
190236
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.)
237+ Although the variable-length 'string' data is now supported in CF, the use of
238+ fixed-width 'char' arrays is obviously more efficient for storage and access, and it is
239+ still the most common approach in practice.
195240
196241
197242In the netCDF4 Python module
@@ -206,60 +251,82 @@ In the netCDF4 Python module
206251
207252* variable data of type 'char' is presented (read and written) as numpy arrays of
208253 dtype "S1" -- that is, an array of length-1 Python "bytes" objects.
209- The netCDF4 package can also automatically translate this to string arrays of dtype
210- "U<xx>", if the variable has an ``_Encoding `` attribute, but Iris turns this feature
211- *off * in order to implement its own wider-ranging encoding support (see below).
254+
255+ .. note ::
256+
257+ The netCDF4 package can also automatically translate this to string arrays of
258+ dtype "U<xx>", if the variable has an ``_Encoding `` attribute. **However, **
259+ Iris turns this feature *off *, in order to implement its own wider-ranging
260+ encoding support (described below).
212261
213262
214263In Iris
215264^^^^^^^
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
218- an interim solution enabling you at least to *load * variable-length string data.
265+ .. note ::
266+
267+ In Iris, **the 'string' data type is not supported at present **, though this is
268+ planned for future releases. See the following section `Variable-length datatypes `_
269+ for an interim solution enabling you at least to *load * variable-length string data.
219270
220271Iris stores string data in arrays of dtype "U<xx>", where <xx> is a maximum character
221272width. However, this data is currently **only ** read and written in netCDF files as
222- 'char' type variables.
273+ 'char' type variables (i.e. byte arrays) .
223274
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
275+ Iris supports a specific set of valid encodings for non-ascii data :
276+ "ascii", "utf8", "utf16" and "utf32". These (or aliases) will appear in the
226277``_Encoding `` attribute of a file variable, and likewise as an attribute of the
227278corresponding Iris component object (e.g. cube or coordinate).
228279
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.
237-
238- So effectively, the **"default" encodings are 'utf8' for load and 'ascii' for save **.
239-
280+ When loading
281+ ~~~~~~~~~~~~
282+ If there is a valid ``_Encoding `` attribute this is used to decode the
283+ data, otherwise a default encoding of "utf8" is applied: This works transparently when
284+ only ascii characters are present, and also allows the ``_Encoding `` attribute to be
285+ omitted as long as utf8 was used. An invalid or unsupported encoding name will be
286+ ignored, with a warning, but the attribute will still be added to the Iris component
287+ object.
288+
289+ When saving
290+ ~~~~~~~~~~~
291+ Any string data with only ascii characters does not require an ``_Encoding `` attribute.
292+ However if there are any non-ascii characters, and no ``_Encoding ``
293+ attribute, then an error will be raised. An invalid or unsupported encoding name will
294+ be ignored, with a warning, but the attribute will still be stored to the file.
295+
296+ So effectively, the **default encoding is 'utf8' for load and 'ascii' for save **.
297+
298+ String width dimensions
299+ ~~~~~~~~~~~~~~~~~~~~~~~
240300For 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.
301+ in the file (actually, the number of *bytes *), and the maximum character length, aka
302+ string width, in the array dtype : i.e. the "<xx>" in the "U<xx>" dtype.
243303
244- The lengths of string dimensions created on write are calculated as follows:
304+ The ** lengths of string dimensions created on write ** are calculated as follows:
245305
246306* ascii : n-bytes = n-characters
247307* utf8 : n-bytes = n-characters
248308* utf16 : n-bytes = 2 * (n-characters + 1)
249309* utf32 : n-bytes = 4 * (n-characters + 1)
250310
251311For 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.
312+ the data is presented. This will always round-trip correctly, i.e. the dimension length
313+ is unchanged if data is read and then written back.
254314
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
315+ For 'ascii' and 'utf32' this character-to-byte relationship is simple + fixed, but for
316+ 'utf8' and ' utf16', the number of encoded bytes depends on the actual characters present
257317**and can exceed the numbers given above **. If any string in the *actual * data encodes
258318to more bytes than the above-calculated string dimension, then Iris will raise an
259319:class: `iris.exceptions.TranslationError `. In this case, the user must **explicitly
260320specify ** a longer string dimension, by converting the data to a longer "U<xx>" dtype :
261321for example, ``cube.data = cube.core_data().astype("U20") ``.
262322
323+ .. warning ::
324+
325+ When processing string arrays, Numpy does not routinely preserve the "<xx>" width part
326+ of "U<xx>" type data : instead, some operations will reduce it to the maximum width
327+ occurring. So in these cases also, it may be necessary to explicitly re-assert the
328+ desired "string width" before saving -- use ``.astype() ``, as above.
329+
263330
264331Variable-length datatypes
265332-------------------------
0 commit comments