Skip to content

Commit aa41930

Browse files
committed
Swapping out PCICt for CFtime
1 parent 93bcbc4 commit aa41930

22 files changed

Lines changed: 259 additions & 296 deletions

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ Imports:
4444
units
4545
Suggests:
4646
Cairo,
47+
CFtime,
4748
OpenStreetMap,
48-
PCICt,
4949
RNetCDF (>= 1.8-2),
5050
clue,
5151
covr,

R/aggregate.R

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#' spatially or temporally aggregate stars object, returning a data cube with lower spatial or temporal resolution
44
#'
55
#' @param x object of class \code{stars} with information to be aggregated
6-
#' @param by object of class \code{sf} or \code{sfc} for spatial aggregation, for temporal aggregation a vector with time values (\code{Date}, \code{POSIXct}, or \code{PCICt}) that is interpreted as a sequence of left-closed, right-open time intervals or a string like "months", "5 days" or the like (see \link{cut.POSIXt}), or a function that cuts time into intervals; if by is an object of class \code{stars}, it is converted to sfc by \code{st_as_sfc(by, as_points = FALSE)} thus ignoring its time component. Note: each pixel is assigned to only a single group (in the order the groups occur) so non-overlapping spatial features and temporal windows are recommended.
6+
#' @param by object of class \code{sf} or \code{sfc} for spatial aggregation, for temporal aggregation a vector with time values (\code{Date}, \code{POSIXct}, or \code{CFtime}) that is interpreted as a sequence of left-closed, right-open time intervals or a string like "months", "5 days" or the like (see \link{cut.POSIXt}), or a function that cuts time into intervals; if by is an object of class \code{stars}, it is converted to sfc by \code{st_as_sfc(by, as_points = FALSE)} thus ignoring its time component. Note: each pixel is assigned to only a single group (in the order the groups occur) so non-overlapping spatial features and temporal windows are recommended.
77
#' @param FUN aggregation function, such as \code{mean}
88
#' @param ... arguments passed on to \code{FUN}, such as \code{na.rm=TRUE}
99
#' @param drop logical; ignored
@@ -72,7 +72,7 @@ aggregate.stars = function(x, by, FUN, ..., drop = FALSE, join = st_intersects,
7272
left.open = FALSE, exact = FALSE) {
7373

7474
fn_name = substr(deparse1(substitute(FUN)), 1, 20)
75-
classes = c("sf", "sfc", "POSIXct", "Date", "PCICt", "character", "function", "stars")
75+
classes = c("sf", "sfc", "POSIXct", "Date", "character", "function", "stars")
7676
if (!is.function(by) && !inherits(by, classes))
7777
stop(paste("currently, only `by' arguments of class",
7878
paste(classes, collapse= ", "), "supported"))
@@ -119,6 +119,7 @@ aggregate.stars = function(x, by, FUN, ..., drop = FALSE, join = st_intersects,
119119
return(st_as_stars(agg, dimensions = d))
120120
}
121121

122+
values = NULL
122123
drop_y = FALSE
123124
grps = if (inherits(by, c("sf", "sfc"))) {
124125
x = if (has_raster(x)) {
@@ -153,11 +154,17 @@ aggregate.stars = function(x, by, FUN, ..., drop = FALSE, join = st_intersects,
153154
i = as.factor(i)
154155
by = levels(i)
155156
} else if (inherits(by, "character")) {
156-
i = cut(values, by, right = left.open)
157-
by = if (inherits(values, "Date"))
158-
as.Date(levels(i))
159-
else
160-
as.POSIXct(levels(i))
157+
if (inherits(values, "CFTime")) {
158+
i = values$cut(by)
159+
by = levels(i)
160+
new_time = attr(i, "CFTime")
161+
} else {
162+
i = cut(values, by, right = left.open)
163+
by = if (inherits(values, "Date"))
164+
as.Date(levels(i))
165+
else
166+
as.POSIXct(levels(i))
167+
}
161168
} else {
162169
if (!inherits(values, class(by)))
163170
warning(paste0('argument "by" is of a different class (', class(by)[1],
@@ -206,11 +213,16 @@ aggregate.stars = function(x, by, FUN, ..., drop = FALSE, join = st_intersects,
206213
}
207214

208215
# reconstruct dimensions table:
209-
d[[1]] = create_dimension(values = by)
210-
names(d)[1] = if (is.function(by) || inherits(by, c("POSIXct", "Date", "PCICt", "function")))
211-
"time"
212-
else
213-
geom
216+
if (!is.null(values) && inherits(values, "CFTime")) {
217+
d[[1]] = create_dimension(refsys = "CFtime", values = new_time)
218+
names(d)[1] <- "time"
219+
} else {
220+
d[[1]] = create_dimension(values = by)
221+
names(d)[1] = if (is.function(by) || inherits(by, c("POSIXct", "Date", "function")))
222+
"time"
223+
else
224+
geom
225+
}
214226
if (drop_y)
215227
d = d[-2] # y
216228

R/dimensions.R

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ regular_intervals = function(x, epsilon = 1e-10) {
243243
if (length(x) <= 1)
244244
FALSE
245245
else {
246-
ud = if (is.atomic(x) && (is.numeric(x) || inherits(x, c("POSIXt", "Date", "PCICt"))))
246+
ud = if (is.atomic(x) && (is.numeric(x) || inherits(x, c("POSIXt", "Date"))))
247247
unique(diff(x))
248248
else {
249249
if (inherits(x, "intervals") && identical(tail(x$end, -1), head(x$start, -1)))
@@ -290,8 +290,8 @@ create_dimension = function(from = 1, to, offset = NA_real_, delta = NA_real_,
290290
refsys = "POSIXct"
291291
else if (inherits(example, "Date"))
292292
refsys = "Date"
293-
else if (inherits(example, "PCICt"))
294-
refsys = paste0("PCICt_", attr(example, "cal"))
293+
else if (inherits(example, "CFTime"))
294+
refsys = "CFtime"
295295
else if (inherits(example, "units"))
296296
refsys = "udunits"
297297

@@ -322,6 +322,7 @@ create_dimensions = function(lst, raster = NULL) {
322322
sel = which(names(lst) == "")
323323
names(lst)[sel] = make.names(seq_along(sel))
324324
}
325+
325326
if (is.null(raster))
326327
structure(lst, raster = get_raster(dimensions = c(NA_character_, NA_character_)), class = "dimensions")
327328
else {
@@ -378,19 +379,21 @@ create_dimensions_from_gdal_meta = function(dims, pr) {
378379
}
379380
if (! is.null(pr$dim_extra)) { # netcdf...
380381
for (d in names(pr$dim_extra)) {
381-
refsys = if (inherits(pr$dim_extra[[d]], "POSIXct"))
382+
de = pr$dim_extra[[d]]
383+
if (inherits(de, "CFTime"))
384+
lst[[d]] = create_dimension(from = 1, to = length(de), values = de, refsys = "CFtime", point = TRUE)
385+
else {
386+
refsys = if (inherits(pr$dim_extra[[d]], "POSIXct"))
382387
"POSIXct"
383-
else if (inherits(pr$dim_extra[[d]], "PCICt"))
384-
"PCICt"
385388
else
386389
NA_character_
387-
de = pr$dim_extra[[d]]
388-
diff.de = diff(de)
389-
lst[[d]] = if (length(unique(diff.de)) <= 1) {
390-
delta = if (length(diff.de)) diff.de[1] else NA_real_
391-
create_dimension(from = 1, to = length(de), offset = de[1], delta = delta, refsys = refsys)
392-
} else
393-
create_dimension(from = 1, to = length(de), values = de, refsys = refsys)
390+
diff.de = diff(de)
391+
lst[[d]] = if (length(unique(diff.de)) <= 1) {
392+
delta = if (length(diff.de)) diff.de[1] else NA_real_
393+
create_dimension(from = 1, to = length(de), offset = de[1], delta = delta, refsys = refsys)
394+
} else
395+
create_dimension(from = 1, to = length(de), values = de, refsys = refsys)
396+
}
394397
}
395398
lst[["band"]] = NULL
396399
}
@@ -443,23 +446,6 @@ get_val = function(pattern, meta) {
443446
NA_character_
444447
}
445448

446-
get_pcict = function(values, unts, calendar) {
447-
stopifnot(calendar %in% c("360_day", "365_day", "noleap"))
448-
if (!requireNamespace("PCICt", quietly = TRUE))
449-
stop("package PCICt required, please install it first") # nocov
450-
origin = 0:1
451-
units(origin) = try_as_units(unts)
452-
delta = if (grepl("months", unts)) { # for these calendars, length of months are different from udunits representation
453-
if (calendar == "360_day")
454-
set_units(30 * 24 * 3600, "s", mode = "standard")
455-
else
456-
set_units((365./12) * 24 * 3600, "s", mode = "standard")
457-
} else
458-
set_units(as_units(diff(as.POSIXct(origin))), "s", mode = "standard")
459-
origin_txt = as.character(as.POSIXct(origin[1]))
460-
PCICt::as.PCICt(values * as.numeric(delta), calendar, origin_txt)
461-
}
462-
463449
parse_netcdf_meta = function(pr, name) {
464450
meta = pr$meta
465451
spl = strsplit(name, ":")[[1]] # "C:\..." results in length 2:
@@ -488,16 +474,18 @@ parse_netcdf_meta = function(pr, name) {
488474
rhs = get_val(paste0("NETCDF_DIM_", v), meta) # nocov # FIXME: find example?
489475
pr$dim_extra[[v]] = as.numeric(rhs) # nocov
490476
}
491-
cal = get_val(paste0(v, "#calendar"), meta)
492477
u = get_val(paste0(v, "#units"), meta)
493478
if (! is.na(u)) {
494-
if (v %in% c("t", "time") && !is.na(cal) && cal %in% c("360_day", "365_day", "noleap"))
495-
pr$dim_extra[[v]] = get_pcict(pr$dim_extra[[v]], u, cal)
496-
else {
497-
units(pr$dim_extra[[v]]) = try_as_units(u)
498-
if (v %in% c("t", "time") && !inherits(try(as.POSIXct(pr$dim_extra[[v]]), silent = TRUE),
499-
"try-error"))
500-
pr$dim_extra[[v]] = as.POSIXct(pr$dim_extra[[v]])
479+
cal = get_val(paste0(v, "#calendar"), meta)
480+
if (is.null(cal) || is.na(cal))
481+
cal = "standard"
482+
time = try(CFtime::CFtime(u, cal), silent = TRUE)
483+
if (inherits(time, "CFTime")) {
484+
time = time + pr$dim_extra[[v]] # add the time offsets
485+
if (cal %in% CF_calendar_regular)
486+
pr$dim_extra[[v]] = time$as_timestamp(asPOSIX = TRUE)
487+
else
488+
pr$dim_extra[[v]] = time
501489
}
502490
}
503491
}
@@ -579,19 +567,27 @@ expand_dimensions.dimensions = function(x, ..., max = FALSE, center = NA) {
579567
xy = attr(x, "raster")$dimensions
580568
gt = st_geotransform(x)
581569
lst = vector("list", length(dimensions))
582-
names(lst) = names(dimensions)
570+
names = names(dimensions)
571+
names(lst) = names
583572
if (! is.null(xy) && all(!is.na(xy))) { # we have raster: where defaulting to 0.5
584573
where[xy] = ifelse(!max[xy] & (is.na(center[xy]) | center[xy]), 0.5, where[xy])
585-
if (xy[1] %in% names(lst)) # x
574+
if (xy[1] %in% names) # x
586575
lst[[ xy[1] ]] = get_dimension_values(dimensions[[ xy[1] ]], where[[ xy[1] ]], gt, "x")
587-
if (xy[2] %in% names(lst)) # y
576+
if (xy[2] %in% names) # y
588577
lst[[ xy[2] ]] = get_dimension_values(dimensions[[ xy[2] ]], where[[ xy[2] ]], gt, "y")
589578
}
590579

591-
if ("crs" %in% names(lst))
580+
if ("crs" %in% names)
592581
lst[[ "crs" ]] = dimensions[[ "crs" ]]$values
582+
583+
is_time = sapply(dimensions, function(d) inherits(d$values, "CFTime"))
584+
if (all(is.logical(is_time)) && length(time_dim <- which(is_time))) {
585+
lst[[ time_dim ]] = dimensions[[ time_dim ]]$values$offsets
586+
time_name = names[time_dim]
587+
} else
588+
time_name = ""
593589

594-
for (nm in setdiff(names(lst), c(xy, "crs"))) # non-xy, non-crs dimensions
590+
for (nm in setdiff(names, c(xy, "crs", time_name))) # non-xy, non-crs, non-time dimensions
595591
lst[[ nm ]] = get_dimension_values(dimensions[[ nm ]], where[[nm]], NA, NA)
596592

597593
lst
@@ -608,19 +604,22 @@ dim.dimensions = function(x) {
608604

609605
#' @name print_stars
610606
#' @param digits number of digits to print numbers
611-
#' @param usetz logical; used to format \code{PCICt} or \code{POSIXct} values
607+
#' @param usetz logical; used to format \code{POSIXct} values
612608
#' @param stars_crs maximum width of string for CRS objects
613609
#' @param all logical; if \code{TRUE} print also fields entirely filled with \code{NA} or \code{NULL}
614610
#' @export
615611
as.data.frame.dimensions = function(x, ..., digits = max(3, getOption("digits")-3), usetz = TRUE, stars_crs = getOption("stars.crs") %||% 28, all = FALSE) {
616612
mformat = function(x, ..., digits) {
617-
if (inherits(x, c("PCICt", "POSIXct")))
613+
if (inherits(x, "POSIXct"))
618614
format(x, ..., usetz = usetz)
619615
else
620616
format(x, digits = digits, ...)
621617
}
622618
abbrev_dim = function(y) {
623-
if (length(y$values) > 3 || (inherits(y$values, "sfc") && length(y$values) > 2)) {
619+
if (inherits(y$values, "CFTime")) {
620+
rng = range(y$values, ...)
621+
y$values = paste0(rng[1], ",...,", rng[2])
622+
} else if (length(y$values) > 3 || (inherits(y$values, "sfc") && length(y$values) > 2)) {
624623
y$values = if (is.array(y$values))
625624
paste0("[", paste(dim(y$values), collapse = "x"), "] ",
626625
mformat(min(y$values), digits = digits), ",...,",
@@ -737,6 +736,14 @@ seq.dimension = function(from, ..., center = FALSE) { # does what expand_dimensi
737736
#' @export
738737
`[.dimension` = function(x, i, ...) {
739738
if (!missing(i)) {
739+
if (!is.na(x$refsys) && x$refsys == "CFtime") {
740+
ind = x$values$indexOf(i)
741+
newcf = attr(ind, "CFTime")
742+
x$to = length(newcf)
743+
x$values = newcf
744+
return(x)
745+
}
746+
740747
if (!is.null(x$values) && !is.matrix(x$values))
741748
x$values = x$values[i]
742749
if (!is.na(x$from)) {
@@ -790,7 +797,8 @@ as.POSIXct.stars = function(x, ...) {
790797
d = st_dimensions(x)
791798
e = expand_dimensions(d)
792799
for (i in seq_along(d)) {
793-
if (inherits(e[[i]], c("Date", "POSIXt", "udunits", "PCICt"))) {
800+
# No need to check for refsys == "CFtime" as these will not convert to POSIXct
801+
if (inherits(e[[i]], c("Date", "POSIXt", "udunits"))) {
794802
p = try(as.POSIXct(e[[i]]), silent = TRUE)
795803
if (!inherits(p, "try-error"))
796804
d[[i]] = create_dimension(values = p)

R/extract.R

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ st_extract = function(x, ...) UseMethod("st_extract")
99
#' @param x object of class \code{stars} or \code{stars_proxy}
1010
#' @param at object of class \code{sf} or \code{sfc} with geometries, or two-column matrix with coordinate points in rows, indicating where to extract values of \code{x}, or a \code{stars} object with geometry and temporal dimensions (vector data cube)
1111
#' @param bilinear logical; use bilinear interpolation rather than nearest neighbour?
12-
#' @param time_column character or integer; name or index of a column with time or date values that will be matched to values of the first temporal dimension (matching classes \code{POSIXct}, \code{POSIXt}, \code{Date}, or \code{PCICt}), in \code{x}, after which this dimension is reduced. This is useful to extract data cube values along a trajectory; see https://github.com/r-spatial/stars/issues/352 .
12+
#' @param time_column character or integer; name or index of a column with time or date values that will be matched to values of the first temporal dimension (matching classes \code{POSIXct}, \code{POSIXt}, \code{Date}, or \code{CFTime}), in \code{x}, after which this dimension is reduced. This is useful to extract data cube values along a trajectory; see https://github.com/r-spatial/stars/issues/352 .
1313
#' @param interpolate_time logical; should time be interpolated? if FALSE, time instances are matched using the coinciding or the last preceding time in the data cube.
1414
#' @param FUN function used to aggregate pixel values when geometries of \code{at} intersect with more than one pixel
1515
#' @param resampling character; resampling method; for method cubic or cubicspline,
@@ -159,7 +159,7 @@ st_extract.stars = function(x, at, ..., bilinear = FALSE, time_column =
159159
}
160160
# match times:
161161
if (!is.null(time_column)) {
162-
refsys_time = c("POSIXct", "POSIXt", "Date", "PCICt")
162+
refsys_time = c("POSIXct", "POSIXt", "Date", "CFtime")
163163
## If there are more than two temporal dimensions, the first one is taken
164164
tm = names(which(sapply(
165165
st_dimensions(x),
@@ -233,6 +233,13 @@ st_extract.stars = function(x, at, ..., bilinear = FALSE, time_column =
233233
# if interpolate = FALSE, returns an integer in 1...length(b) or NA if outside
234234
# if interpolate = TRUE, returns a continuous index in 1...length(b) or NA if outside
235235
match_time = function(a, b, intervals = FALSE, interpolate = FALSE) {
236+
if (inherits(b, "CFTime")) {
237+
if (interpolate && isFALSE(intervals) && is.null(b$bounds))
238+
return(b$indexOf(a, method = "linear"))
239+
else
240+
return(b$indexOf(a))
241+
}
242+
236243
if (inherits(a, "POSIXct") && inherits(b, "Date"))
237244
a = as.Date(a)
238245
if (inherits(b, "POSIXct") && inherits(a, "Date"))

R/intervals.R

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ c.intervals = function(...) {
4545
#' @export
4646
format.intervals = function(x, ...) {
4747
mformat = function(x, ..., digits = getOption("digits")) {
48-
if (inherits(x, "PCICt"))
49-
format(x, ...)
50-
else
48+
if (inherits(x, "CFTime")) {
49+
rng = x$range()
50+
paste0("[", rng[1], ",", rng[2], ")")
51+
} else
5152
format(x, digits = digits, ...)
5253
}
5354
if (inherits(x$start, "units") && inherits(x$end, "units")) {

0 commit comments

Comments
 (0)