Skip to content

Commit 49437cb

Browse files
authored
Merge pull request #168 from pascal-sauer/master
more memory efficient writeNC, allow specifying whole grid (not just resolution)
2 parents c8ad8a6 + b2b9050 commit 49437cb

File tree

10 files changed

+131
-55
lines changed

10 files changed

+131
-55
lines changed

.buildlibrary

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ValidationKey: '121449200'
1+
ValidationKey: '121782300'
22
AcceptedWarnings:
33
- 'Warning: package ''.*'' was built under R version'
44
- 'Warning: namespace ''.*'' is not available and has been replaced'

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ cff-version: 1.2.0
22
message: If you use this software, please cite it using the metadata from this file.
33
type: software
44
title: 'magclass: Data Class and Tools for Handling Spatial-Temporal Data'
5-
version: 6.14.0
6-
date-released: '2024-02-27'
5+
version: 6.15.0
6+
date-released: '2024-03-20'
77
abstract: Data class for increased interoperability working with spatial-temporal
88
data together with corresponding functions and methods (conversions, basic calculations
99
and basic data manipulation). The class distinguishes between spatial, temporal

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Type: Package
22
Package: magclass
33
Title: Data Class and Tools for Handling Spatial-Temporal Data
4-
Version: 6.14.0
5-
Date: 2024-02-27
4+
Version: 6.15.0
5+
Date: 2024-03-20
66
Authors@R: c(
77
person("Jan Philipp", "Dietrich", , "dietrich@pik-potsdam.de", role = c("aut", "cre")),
88
person("Benjamin Leon", "Bodirsky", , "bodirsky@pik-potsdam.de", role = "aut"),

R/extend.R

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,54 @@
55
#' requires much more memory, so use with caution.
66
#'
77
#' @param x A magpie object
8-
#' @param xRange Range of x coordinates, in ascending order when writing to netCDF
9-
#' @param yRange Range of y coordinates, in descending order when writing to netCDF
10-
#' @param res Resolution of the data, if not provided it will be guessed
11-
#' @return An extended magpie object with the same data as x and gaps filled with NA
8+
#' @param gridDefinition A vector of 5 numeric values: c(xMin, xMax, yMin, yMax, resolution).
9+
#' Use c(-179.75, 179.75, -89.75, 89.75, 0.5) to extend to a standard 0.5-degree-resolution
10+
#' lon/lat grid. If NULL, use min/max of coordinates in x and guessResolution.
11+
#' @param crop If TRUE, discard cells from x which are not in the gridDefinition grid. If FALSE,
12+
#' throw an error if the coordinates of x are not a subset of the extended coordinates.
13+
#' @return Magpie object x with dense grid according to gridDefinition, gaps filled with NA.
1214
#' @author Pascal Sauer
1315
#' @export
14-
extend <- function(x,
15-
xRange = c(-179.75, 179.75),
16-
yRange = c(89.75, -89.75),
17-
res = NULL) {
18-
stopifnot(length(xRange) == 2, length(yRange) == 2)
19-
if (is.null(res)) {
20-
res <- guessResolution(x)
16+
extend <- function(x, gridDefinition = NULL, crop = FALSE) {
17+
if (is.null(gridDefinition)) {
18+
coords <- getCoords(x)
19+
firstX <- min(coords$x)
20+
lastX <- max(coords$x)
21+
firstY <- max(coords$y)
22+
lastY <- min(coords$y)
23+
res <- guessResolution(coords)
24+
} else {
25+
stopifnot(length(gridDefinition) == 5)
26+
firstX <- gridDefinition[1]
27+
lastX <- gridDefinition[2]
28+
firstY <- gridDefinition[3]
29+
lastY <- gridDefinition[4]
30+
res <- gridDefinition[5]
2131
}
22-
coords <- expand.grid(x = seq(xRange[1], xRange[2], if (xRange[1] < xRange[2]) res else -res),
23-
y = seq(yRange[1], yRange[2], if (yRange[1] < yRange[2]) res else -res))
32+
33+
coords <- expand.grid(x = seq(firstX, lastX, if (firstX < lastX) res else -res),
34+
y = seq(firstY, lastY, if (firstY < lastY) res else -res))
2435
coords <- paste0(coords$x, "|", coords$y)
2536
coords <- gsub("\\.", "p", coords)
2637
coords <- sub("\\|", ".", coords)
2738

2839
sparseCoords <- paste0(getItems(x, "x", full = TRUE),
2940
".",
3041
getItems(x, "y", full = TRUE))
31-
if (!all(sparseCoords %in% coords)) {
32-
stop("The coordinates of the input object are not a subset of the extended coordinates. ",
33-
"Try changing res, xRange or yRange.")
42+
if (crop) {
43+
x <- x[sparseCoords %in% coords, , ]
44+
if (length(x) > 0) {
45+
sparseCoords <- paste0(getItems(x, "x", full = TRUE),
46+
".",
47+
getItems(x, "y", full = TRUE))
48+
} else {
49+
sparseCoords <- character(0)
50+
}
51+
} else {
52+
if (!all(sparseCoords %in% coords)) {
53+
stop("The coordinates of the input object are not a subset of the extended coordinates. ",
54+
"Try changing res, xRange or yRange.")
55+
}
3456
}
3557

3658
subdims1 <- getSets(x)[grep("^d1\\.", names(getSets(x)))]

R/writeNC.R

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
#' @param x A magpie object
44
#' @param filename Name of the netCDF file to write
55
#' @param unit Unit of the data, to omit pass "" (empty string)
6-
#' @param ... For future expansion.
6+
#' @param ... For future expansion
77
#' @param compression Level of compression to use (1-9), NA for no compression
88
#' @param missval The value that encodes NA in the resulting netCDF file
9-
#' @param res Resolution of the data, if not provided it will be guessed
9+
#' @param gridDefinition A vector of 5 numeric values: c(xMin, xMax, yMin, yMax, resolution).
10+
#' Use c(-179.75, 179.75, -89.75, 89.75, 0.5) to write a standard 0.5-degree-resolution
11+
#' lon/lat grid. If NULL, use min/max of coordinates in x and guessResolution
1012
#' @param zname Name of the z dimension in the netCDF file
13+
#' @param progress If TRUE, print progress messages
1114
#' @author Pascal Sauer
1215
writeNC <- function(x, filename, unit, ..., compression = 2, missval = NA,
13-
res = NULL, zname = "time") {
16+
gridDefinition = NULL, zname = "time", progress = FALSE) {
1417
if (!requireNamespace("ncdf4", quietly = TRUE)) {
1518
stop("The ncdf4 package is required to write netCDF files, please install it.")
1619
}
@@ -20,10 +23,25 @@ writeNC <- function(x, filename, unit, ..., compression = 2, missval = NA,
2023
stop("Unknown argument passed to writeNC: ", paste(...names(), collapse = ", "))
2124
}
2225

23-
# magclass objects are sparse, fill gaps with NA
24-
coords <- getCoords(x)
25-
x <- extend(x, xRange = c(min(coords$x), max(coords$x)), yRange = c(max(coords$y), min(coords$y)), res = res)
26-
coords <- getCoords(x)
26+
if (is.null(gridDefinition)) {
27+
coords <- getCoords(x)
28+
firstX <- min(coords$x)
29+
lastX <- max(coords$x)
30+
firstY <- max(coords$y)
31+
lastY <- min(coords$y)
32+
res <- guessResolution(coords)
33+
} else {
34+
stopifnot(length(gridDefinition) == 5,
35+
gridDefinition[1] < gridDefinition[2],
36+
gridDefinition[3] < gridDefinition[4])
37+
firstX <- gridDefinition[1]
38+
lastX <- gridDefinition[2]
39+
firstY <- gridDefinition[4]
40+
lastY <- gridDefinition[3]
41+
res <- gridDefinition[5]
42+
}
43+
xCoords <- seq(firstX, lastX, res)
44+
yCoords <- seq(firstY, lastY, -res)
2745

2846
if (zname != "time") {
2947
message("terra will not recognize zname != 'time' as time dimension")
@@ -34,10 +52,12 @@ writeNC <- function(x, filename, unit, ..., compression = 2, missval = NA,
3452
}
3553

3654
# create netCDF file
37-
dimVars <- list(ncdf4::ncdim_def("lon", "degrees_east", unique(coords$x)),
38-
ncdf4::ncdim_def("lat", "degrees_north", unique(coords$y)))
39-
if (!is.null(getItems(x, 2))) {
40-
dimVars <- c(dimVars, list(ncdf4::ncdim_def(zname, "years since 0", getYears(x, as.integer = TRUE), unlim = TRUE)))
55+
dimVars <- list(ncdf4::ncdim_def("lon", "degrees_east", xCoords),
56+
ncdf4::ncdim_def("lat", "degrees_north", yCoords))
57+
hasTime <- !is.null(getItems(x, 2))
58+
if (hasTime) {
59+
dimVars <- c(dimVars, list(ncdf4::ncdim_def(zname, "years since 0",
60+
getYears(x, as.integer = TRUE), unlim = TRUE)))
4161
}
4262
vars <- lapply(getItems(x, 3), function(vname) {
4363
return(ncdf4::ncvar_def(vname, units = unit, dim = dimVars,
@@ -47,11 +67,23 @@ writeNC <- function(x, filename, unit, ..., compression = 2, missval = NA,
4767
nc <- ncdf4::nc_create(filename, vars = vars)
4868
withr::defer(ncdf4::nc_close(nc))
4969

50-
if (!is.null(getItems(x, 2)) && zname == "time") {
70+
if (hasTime && zname == "time") {
5171
ncdf4::ncatt_put(nc, "time", "axis", "T")
5272
}
5373

54-
for (vname in getItems(x, 3)) {
55-
ncdf4::ncvar_put(nc, vname, x[, , vname])
74+
messageIf(progress, "Writing ", filename)
75+
76+
for (i in seq_along(getItems(x, 3))) {
77+
vname <- getItems(x, 3)[i]
78+
messageIf(progress, i, "/", length(getItems(x, 3)), " Writing ", vname)
79+
# memory problems due to the extend happening here? checkout commit
80+
# 9e780b5fe00f38b82d20edc245e34605c6eb9c46 to get a chunk-based solution
81+
ncdf4::ncvar_put(nc, vname, extend(x[, , vname], gridDefinition = gridDefinition))
82+
}
83+
}
84+
85+
messageIf <- function(condition, ...) {
86+
if (condition) {
87+
message(...)
5688
}
5789
}

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Data Class and Tools for Handling Spatial-Temporal Data
22

3-
R package **magclass**, version **6.14.0**
3+
R package **magclass**, version **6.15.0**
44

55
[![CRAN status](https://www.r-pkg.org/badges/version/magclass)](https://cran.r-project.org/package=magclass) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1158580.svg)](https://doi.org/10.5281/zenodo.1158580) [![R build status](https://github.com/pik-piam/magclass/workflows/check/badge.svg)](https://github.com/pik-piam/magclass/actions) [![codecov](https://codecov.io/gh/pik-piam/magclass/branch/master/graph/badge.svg)](https://app.codecov.io/gh/pik-piam/magclass) [![r-universe](https://pik-piam.r-universe.dev/badges/magclass)](https://pik-piam.r-universe.dev/builds)
66

@@ -56,7 +56,7 @@ In case of questions / problems please contact Jan Philipp Dietrich <dietrich@pi
5656

5757
To cite package **magclass** in publications use:
5858

59-
Dietrich J, Bodirsky B, Bonsch M, Humpenoeder F, Bi S, Karstens K, Leip D, Sauer P (2024). _magclass: Data Class and Tools for Handling Spatial-Temporal Data_. doi:10.5281/zenodo.1158580 <https://doi.org/10.5281/zenodo.1158580>, R package version 6.14.0, <https://github.com/pik-piam/magclass>.
59+
Dietrich J, Bodirsky B, Bonsch M, Humpenoeder F, Bi S, Karstens K, Leip D, Sauer P (2024). _magclass: Data Class and Tools for Handling Spatial-Temporal Data_. doi:10.5281/zenodo.1158580 <https://doi.org/10.5281/zenodo.1158580>, R package version 6.15.0, <https://github.com/pik-piam/magclass>.
6060

6161
A BibTeX entry for LaTeX users is
6262

@@ -65,7 +65,7 @@ A BibTeX entry for LaTeX users is
6565
title = {magclass: Data Class and Tools for Handling Spatial-Temporal Data},
6666
author = {Jan Philipp Dietrich and Benjamin Leon Bodirsky and Markus Bonsch and Florian Humpenoeder and Stephen Bi and Kristine Karstens and Debbora Leip and Pascal Sauer},
6767
year = {2024},
68-
note = {R package version 6.14.0},
68+
note = {R package version 6.15.0},
6969
url = {https://github.com/pik-piam/magclass},
7070
doi = {10.5281/zenodo.1158580},
7171
}

man/extend.Rd

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/writeNC.Rd

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-extend.R

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@ test_that("extend works", {
22
animal <- maxample("animal")
33
# shuffle first dim to ensure order does not matter
44
animal <- animal[sample(seq_along(getItems(animal, 1))), , ]
5+
animalCoords <- getCoords(animal)
56
expect_identical(guessResolution(animal), 0.5)
67

78
x <- extend(animal)
8-
expectedCoords <- expand.grid(x = seq(-179.75, 179.75, 0.5),
9-
y = seq(89.75, -89.75, -0.5),
9+
expectedCoords <- expand.grid(x = seq(min(animalCoords$x), max(animalCoords$x), 0.5),
10+
y = seq(max(animalCoords$y), min(animalCoords$y), -0.5),
1011
KEEP.OUT.ATTRS = FALSE)
1112
expect_identical(getCoords(x), expectedCoords)
1213
expect_identical(getSets(x), getSets(animal))
1314
expect_identical(getItems(x[getItems(animal, 1), , ], 1), getItems(animal, 1))
1415

15-
expect_error(extend(animal, res = 1),
16+
expect_error(extend(animal, gridDefinition = c(3.25, 6.75, 53.25, 49.75, 1)),
1617
"The coordinates of the input object are not a subset of the extended coordinates")
1718

18-
x <- extend(animal, xRange = c(-3.25, 16.75), yRange = c(63.25, -59.75), res = 0.25)
19+
x <- extend(animal, gridDefinition = c(3.25, 6.75, 53.25, 49.75, 1), crop = TRUE)
20+
expectedCoords <- expand.grid(x = seq(3.25, 6.75, 1),
21+
y = seq(53.25, 49.75, -1),
22+
KEEP.OUT.ATTRS = FALSE)
23+
expect_identical(getCoords(x), expectedCoords)
24+
25+
x <- extend(animal, gridDefinition = c(-3.25, 16.75, 63.25, -59.75, 0.25))
1926

2027
expectedCoords <- expand.grid(x = seq(-3.25, 16.75, 0.25),
2128
y = seq(63.25, -59.75, -0.25),
@@ -26,8 +33,8 @@ test_that("extend works", {
2633
animal2 <- dimOrder(animal, c(3, 4, 2, 1), dim = 1)
2734
expect_identical(names(dimnames(animal2))[1], "country.cell.y.x")
2835
x <- extend(animal2)
29-
expectedCoords <- expand.grid(x = seq(-179.75, 179.75, 0.5),
30-
y = seq(89.75, -89.75, -0.5),
36+
expectedCoords <- expand.grid(x = seq(min(animalCoords$x), max(animalCoords$x), 0.5),
37+
y = seq(max(animalCoords$y), min(animalCoords$y), -0.5),
3138
KEEP.OUT.ATTRS = FALSE)
3239
expect_identical(getCoords(x), expectedCoords)
3340
})

tests/testthat/test-readwritemagpie.R

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ test_that("read supports older formats", {
7878
})
7979

8080
test_that("handling of spatial data works", {
81+
skip_if_not_installed("ncdf4")
8182
td <- withr::local_tempdir()
8283

8384
md <- magclass:::magclassdata$half_deg
@@ -117,6 +118,12 @@ test_that("handling of spatial data works", {
117118
getItems(anc, dim = 3) <- NULL
118119
getSets(anc) <- c("x", "y", "d2", "d3")
119120
expect_identical(anc, a)
121+
122+
write.magpie(a, file.path(td, "animal2.nc"), gridDefinition = c(3, 7, 49, 54, 0.25))
123+
animalRaster <- ncdf4::nc_open(file.path(td, "animal2.nc"))
124+
withr::defer(ncdf4::nc_close(animalRaster))
125+
expect_identical(as.vector(animalRaster$dim$lon$vals), seq(3, 7, 0.25))
126+
expect_identical(as.vector(animalRaster$dim$lat$vals), seq(54, 49, -0.25))
120127
})
121128

122129

@@ -162,9 +169,11 @@ test_that("read/write triggers errors and warnings correctly", {
162169
expect_error(read.magpie(tmpfile), "does not contain a magpie object")
163170
unlink(tmpfile)
164171

165-
expect_error(write.magpie(as.magpie(1), "bla.nc"), "No coordinates")
166172
expect_error(write.magpie(a, "blub.grd"), "no support for multiple variables")
167173
expect_error(write.magpie(a[, , 1], "blub.asc"), "choose just one")
174+
175+
skip_if_not_installed("ncdf4")
176+
expect_error(write.magpie(as.magpie(1), "bla.nc"), "No coordinates")
168177
})
169178

170179
test_that("copy.magpie works", {

0 commit comments

Comments
 (0)