Skip to content

Commit b9cf692

Browse files
committed
Fixed saving CFArray when the original netCDF file was packed as well.
1 parent 2fd49db commit b9cf692

15 files changed

+125
-34
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# ncdfCF (development version)
22

33
- Fixed `summarise()` when temporal result yields scalar time axis.
4+
- Fixed saving a packed `CFArray` when the original netCDF file was packed as
5+
well.
46

57
# ncdfCF 0.4.0
68

R/CFArray.R

+30-17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
CFArray <- R6::R6Class("CFArray",
3535
inherit = CFVariableBase,
3636
private = list(
37+
# The range of the values.
38+
actual_range = c(NA_real_, NA_real_),
39+
3740
# Orient self$values in such a way that it conforms to regular R arrays: axis
3841
# order will be Y-X-Z-T-others and Y values will go from the top to the bottom.
3942
# Returns a new array.
@@ -87,27 +90,25 @@ CFArray <- R6::R6Class("CFArray",
8790
#' prepared for writing into a new netCDF file.
8891
#' @param values The data of this object. The structure of the data depends
8992
#' on the method that produced it.
93+
#' @param values_type The unpacked netCDF data type for this object.
9094
#' @param axes A `list` of [CFAxis] descendant instances that describe the
9195
#' axes of the argument `value`.
9296
#' @param crs The [CFGridMapping] instance of this data object, or `NULL`
9397
#' when no grid mapping is available.
9498
#' @param attributes A `data.frame` with the attributes associated with the
9599
#' data in argument `value`.
96100
#' @return An instance of this class.
97-
initialize = function(name, group, values, axes, crs, attributes) {
98-
# FIXME: Various other data types
99-
first <- typeof(as.vector(values)[1L])
100-
dt <- if (first == "double") "NC_DOUBLE"
101-
else if (first == "integer") "NC_INT"
102-
else stop("Unsupported data type for the values", call. = FALSE)
103-
101+
initialize = function(name, group, values, values_type, axes, crs, attributes) {
104102
var <- NCVariable$new(-1L, name, group, dt, 0L, NULL)
105103
var$attributes <- attributes
106104
super$initialize(var, group, axes, crs)
107105

108106
self$values <- values
109-
if (!all(is.na(self$values)))
110-
self$set_attribute("valid_range", dt, range(values, na.rm = TRUE))
107+
private$values_type <- values_type
108+
if (!all(is.na(self$values))) {
109+
private$actual_range <- round(range(values, na.rm = TRUE), 8)
110+
self$set_attribute("actual_range", values_type, private$actual_range)
111+
}
111112
},
112113

113114
#' @description Print a summary of the data object to the console.
@@ -117,14 +118,13 @@ CFArray <- R6::R6Class("CFArray",
117118
if (!is.na(longname) && longname != self$name)
118119
cat("Long name:", longname, "\n")
119120

120-
if (all(is.na(self$values))) {
121+
if (is.na(private$actual_range[1L])) {
121122
cat("\nValues: -\n")
122123
cat(sprintf(" NA: %d (100%%)\n", length(self$values)))
123124
} else {
124-
rng <- range(self$values, na.rm = TRUE)
125125
units <- self$attribute("units")
126126
if (is.na(units)) units <- ""
127-
cat("\nValues: [", rng[1L], " ... ", rng[2L], "] ", units, "\n", sep = "")
127+
cat("\nValues: [", private$actual_range[1L], " ... ", private$actual_range[2L], "] ", units, "\n", sep = "")
128128
NAs <- sum(is.na(self$values))
129129
cat(sprintf(" NA: %d (%.1f%%)\n", NAs, NAs * 100 / length(self$values)))
130130
}
@@ -240,8 +240,11 @@ CFArray <- R6::R6Class("CFArray",
240240

241241
#' @description Save the data object to a netCDF file.
242242
#' @param fn The name of the netCDF file to create.
243+
#' @param pack Logical to indicate if the data should be packed. Packing is
244+
#' only useful for numeric data; packing is not performed on integer values.
245+
#' Packing is always to the "NC_SHORT" data type, i.e. 16-bits per value.
243246
#' @return Self, invisibly.
244-
save = function(fn) {
247+
save = function(fn, pack = FALSE) {
245248
nc <- RNetCDF::create.nc(fn, prefill = FALSE, format = "netcdf4")
246249
if (!inherits(nc, "NetCDF"))
247250
stop("Could not create the netCDF file. Please check that the location of the supplied file name is writable.", call. = FALSE)
@@ -259,17 +262,27 @@ CFArray <- R6::R6Class("CFArray",
259262
self$set_attribute("grid_mapping", "NC_CHAR", self$crs$name)
260263
}
261264

265+
# Packing
266+
pack <- pack && !is.na(private$actual_range[1L]) && private$values_type %in% c("NC_FLOAT", "NC_DOUBLE")
267+
if (pack) {
268+
self$set_attribute("add_offset", private$values_type, (private$actual_range[1L] + private$actual_range[2L]) * 0.5)
269+
self$set_attribute("scale_factor", private$values_type, (private$actual_range[2L] - private$actual_range[1L]) / 65534)
270+
self$set_attribute("missing_value", "NC_SHORT", -32767)
271+
}
272+
262273
# Data variable
263-
# FIXME: Pack data
264274
dim_axes <- length(dim(self$values))
265275
axis_names <- sapply(self$axes, function(ax) ax$name)
266-
RNetCDF::var.def.nc(nc, self$name, self$NCvar$vtype, axis_names[1L:dim_axes])
276+
RNetCDF::var.def.nc(nc, self$name, if (pack) "NC_SHORT" else private$values_type, axis_names[1L:dim_axes])
267277
if (length(self$axes) > dim_axes)
268278
self$set_attribute("coordinates", "NC_CHAR", paste(axis_names[-(1L:dim_axes)]))
269279
self$write_attributes(nc, self$name)
270-
RNetCDF::var.put.nc(nc, self$name, self$values)
271-
280+
RNetCDF::var.put.nc(nc, self$name, self$values, pack = pack, na.mode = 2)
272281
RNetCDF::close.nc(nc)
282+
283+
if (pack)
284+
self$delete_attribute(c("scale_factor", "add_offset", "missing_value"))
285+
273286
invisible(self)
274287
}
275288
),

R/CFAxis.R

+4
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,11 @@ CFAxis <- R6::R6Class("CFAxis",
192192
self$NCdim$write(h)
193193
RNetCDF::var.def.nc(h, self$name, self$NCvar$vtype, self$name)
194194
}
195+
196+
if (self$orientation %in% c("X", "Y", "Z", "T"))
197+
self$set_attribute("axis", "NC_CHAR", self$orientation)
195198
self$write_attributes(h, self$name)
199+
196200
RNetCDF::var.put.nc(h, self$name, private$get_values())
197201

198202
if (!is.null(self$bounds))

R/CFAxisTime.R

+14
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ CFAxisTime <- R6::R6Class("CFAxisTime",
162162
CFAxisTime$new(group, var, dim, attr(idx, "CFTime"))
163163
}
164164
}
165+
},
166+
167+
#' @description Write the axis to a netCDF file, including its attributes.
168+
#' If the calendar name is "gregorian", it will be set to the functionally
169+
#' identical calendar "standard" as the former is deprecated.
170+
#' @param nc The handle of the netCDF file opened for writing or a group in
171+
#' the netCDF file. If `NULL`, write to the file or group where the axis
172+
#' was read from (the file must have been opened for writing). If not
173+
#' `NULL`, the handle to a netCDF file or a group therein.
174+
#' @return Self, invisibly.
175+
write = function(nc = NULL) {
176+
if (self$values$cal$name == "gregorian")
177+
self$set_attribute("calendar", "NC_CHAR", "standard")
178+
super$write(nc)
165179
}
166180
),
167181
active = list(

R/CFVariable.R

+15-2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ CFVariable <- R6::R6Class("CFVariable",
169169
initialize = function(grp, nc_var, axes) {
170170
super$initialize(nc_var, grp, axes, NULL)
171171
nc_var$CF <- self
172+
173+
# Sanitize attributes for valid range, missing values and packing
174+
private$values_type <- self$attribute("scale_factor", "type")
175+
if (is.na(private$values_type))
176+
private$values_type <- self$attribute("add_offset", "type")
177+
if (!is.na(private$values_type))
178+
# Data is packed in the netCDF file, throw away the attributes and let
179+
# RNetCDF deal with unpacking when reading the data.
180+
self$delete_attribute(c("_FillValue", "scale_factor", "add_offset",
181+
"valid_range", "valid_min", "valid_max",
182+
"missing_value"))
183+
else
184+
private$values_type <- nc_var$vtype
172185
},
173186

174187
#' @description Print a summary of the data variable to the console.
@@ -255,7 +268,7 @@ CFVariable <- R6::R6Class("CFVariable",
255268
atts <- self$attributes
256269
atts <- atts[!(atts$name == "coordinates"), ]
257270

258-
CFArray$new(self$name, out_group, d, axes, self$crs, atts)
271+
CFArray$new(self$name, out_group, d, private$values_type, axes, self$crs, atts)
259272
},
260273

261274
#' @description This method extracts a subset of values from the array of
@@ -454,7 +467,7 @@ CFVariable <- R6::R6Class("CFVariable",
454467
# Assemble the CFArray instance
455468
axes <- c(out_axes_dim, out_axes_other)
456469
names(axes) <- sapply(axes, function(a) a$name)
457-
CFArray$new(self$name, out_group, d, axes, crs, atts)
470+
CFArray$new(self$name, out_group, d, private$values_type, axes, crs, atts)
458471
}
459472
),
460473
active = list(

R/CFVariableBase.R

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
CFVariableBase <- R6::R6Class("CFVariableBase",
1111
inherit = CFObject,
1212
private = list(
13+
# The netCDF type of the unpacked data of the variable.
14+
values_type = NA_character_,
15+
1316
# Return the R order of dimensional axes that "receive special treatment".
1417
# Scalar axes are not considered here.
1518
YXZT = function() {
@@ -168,11 +171,11 @@ CFVariableBase <- R6::R6Class("CFVariableBase",
168171
# Create the output
169172
len <- length(dt)
170173
if (len == 1L)
171-
CFArray$new(name[1L], self$group, dt[[1L]], ax, self$crs, atts)
174+
CFArray$new(name[1L], self$group, dt[[1L]], private$values_type, ax, self$crs, atts)
172175
else {
173176
if (length(name) < len)
174177
name <- c(name, paste0("result_", (length(name)+1L):len))
175-
out <- lapply(1:len, function(i) CFArray$new(name[i], self$group, dt[[i]], ax, self$crs, atts))
178+
out <- lapply(1:len, function(i) CFArray$new(name[i], self$group, dt[[i]], private$values_type, ax, self$crs, atts))
176179
names(out) <- name
177180
out
178181
}

R/CFVariableL3b.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ CFVariableL3b <- R6::R6Class("CFVariableL3b",
163163

164164
axes <- lapply(self$axes, function(ax) ax$clone())
165165

166-
CFArray$new(self$name, out_group, self$as_matrix(), axes, self$crs, self$attributes)
166+
CFArray$new(self$name, out_group, self$as_matrix(), private$values_type, axes, self$crs, self$attributes)
167167
},
168168

169169
#' @description This method extracts a subset of values from the data of the
@@ -264,7 +264,7 @@ CFVariableL3b <- R6::R6Class("CFVariableL3b",
264264
# Assemble the CFArray instance
265265
axes <- c(out_axes_dim, out_axes_other)
266266
names(axes) <- sapply(axes, function(a) a$name)
267-
CFArray$new(self$name, out_group, d, axes, self$crs, self$attributes)
267+
CFArray$new(self$name, out_group, d, private$values_type, axes, self$crs, self$attributes)
268268
}
269269
)
270270
)

R/NCObject.R

+3-3
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,12 @@ NCObject <- R6::R6Class("NCObject",
160160
invisible(self)
161161
},
162162

163-
#' @description Delete an attribute. If an attribute `name` is not present
163+
#' @description Delete attributes. If an attribute `name` is not present
164164
#' this method simply returns.
165-
#' @param name The name of the attribute to delete.
165+
#' @param name Vector of names of the attributes to delete.
166166
#' @return Self, invisibly.
167167
delete_attribute = function(name) {
168-
self$attributes <- self$attributes[!self$attributes$name == name, ]
168+
self$attributes <- self$attributes[!self$attributes$name %in% name, ]
169169
invisible(self)
170170
},
171171

R/NCVariable.R

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ NCVariable <- R6::R6Class("NCVariable",
2222
#' @field group NetCDF group where this variable is located.
2323
group = NULL,
2424

25-
#' @field vtype The netCDF data type of this variable.
25+
#' @field vtype The netCDF data type of this variable. This could be the
26+
#' packed type. Don't check this field but use the appropriate method in the
27+
#' class of the object whose data type you are looking for.
2628
vtype = NULL,
2729

2830
#' @field ndims Number of dimensions that this variable uses.

man/CFArray.Rd

+8-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/CFAxisTime.Rd

+26-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/CFVariableBase.Rd

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/NCObject.Rd

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/NCVariable.Rd

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)