2

I have some large geoTIFFs, now I want to convert them to ASCII files, after doing some searches, I write these codes:

library(raster)

f <- list.files("inputFolder", pattern = "*.tif", full.names = TRUE)
r <- lapply(f, raster)
a <- lapply(r, writeRaster, filename = "output", format = "ascii")

What confused me is that how can I name the output files respectively, according to its original names?

I tried:

a <- lapply(r, writeRaster, filename = "outputFolder" + f, format = "ascii")

But I received error:

non-numeric argument to binary operator

Then I tried:

a <- lapply(r, writeRaster, filename = paste0(f, ".asc"), format = "ascii")

But I received:

Error in file(filename, "w") : invalid 'description' argument In addition: Warning messages: 1: In if (filename == "") { : the condition has length > 1 and only the first element will be used 2: In if (!file.exists(dirname(filename))) { : the condition has length > 1 and only the first element will be used 3: In if (toupper(x@file@name) == toupper(filename)) { : the condition has length > 1 and only the first element will be used 4: In if (trim(filename) == "") { : the condition has length > 1 and only the first element will be used 5: In if (!file.exists(dirname(filename))) { : the condition has length > 1 and only the first element will be used 6: In if (filename == "") { : the condition has length > 1 and only the first element will be used 7: In if (!overwrite & file.exists(filename)) { : the condition has length > 1 and only the first element will be used

Tianjian Qin
  • 525
  • 4
  • 14

2 Answers2

5

I think you were basically nearly there, with two corrections:

First, you're calling writeRaster for its side effects (i.e. its ability to write a file to your filesystem) so you don't need to assign the output of your lapply() loop to an object. So, removing a <- we have:

lapply(r, writeRaster, filename = paste0(f, ".asc"), format = "ascii")

Next, the filename argument won't loop through f in this way. You have two options, of which the simplest is probably to pass the @file@name slot of r to the filename argument using an anonymous function:

lapply(r, function(x) {
  writeRaster(x, filename = x@file@name, format = "ascii", overwrite = TRUE)
})

Your other option would be to loop through r and f in parallel like you can in python with for r, f in..., which can be done with purrr:

library("purrr")
walk2(r, f, function(x, y) {
  writeRaster(x = x, filename = y, format = "ascii")
})

Here we're using walk2() rather than map2() because we need to call the function for side effects. This loops through r and f together so you can pass one to be the object to write, and one to be the filename.


Edit: here's the code I use to reproduce the problem

library("raster")

tmp_dir = tempdir()
tmp     = tempfile(tmpdir = tmp_dir, fileext = ".zip")

download.file(
  "http://biogeo.ucdavis.edu/data/climate/cmip5/10m/cc26bi50.zip",
  destfile = tmp
)
unzip(tmp, exdir = tmp_dir)

f = list.files(tmp_dir, pattern = ".tif$", full.names = TRUE)
r = lapply(f, raster)

# Solution one
lapply(r, function(x) {
  writeRaster(x, filename = x@file@name, format = "ascii", overwrite = TRUE)
})

# solution two
library("purrr")
walk2(r, f, function(x, y) {
  writeRaster(x = x, filename = y, format = "ascii")
})
Robert Hijmans
  • 40,301
  • 4
  • 55
  • 63
Phil
  • 4,344
  • 2
  • 23
  • 33
  • It is better to use `filename(x)` instead of x@file@name – Robert Hijmans Dec 01 '17 at 20:56
  • It seems that purrr is an essential package to learn, I'll look for some documents, thanks – Tianjian Qin Dec 03 '17 at 07:29
  • @RobertH I've never really come across `filename()` before. Why is it preferred over just accessing a slot? Thanks – Phil Dec 03 '17 at 09:52
  • 1
    It is best to consider slots as 'private' to the object and use methods to get or set their values. This is because if a value in a slot is changed, values in other slots may need to change too. This is generally not an issue with reading slot values, but it can be (e.g. when reading values from a Raster object). It is better to use methods as these will do sanity checking of the values as needed and this will lead to more reliable code. I consider it a 'contract' that values returned by methods like 'filename' wont change over time, but internal workings and slotnames of a class may change. – Robert Hijmans Dec 03 '17 at 19:52
  • 1
    Of course there are some cases where you legitimately need to use direct access to a S4 slot. For example if the package developers have not provided methods to get or set values. But even then you should consider whether that is because they did not think it was necessary to write such methods (and that is common enough), or because they thought you should not change the values. I believe the raster package has methods from all user-level relevant slots. – Robert Hijmans Dec 03 '17 at 19:57
  • @RobertH Thanks, very interesting food for thought. Always worth trying to make things more robust. – Phil Dec 03 '17 at 21:44
1

To test how to do this with small files:

library(raster)
s <- stack(system.file("external/rlogo.grd", package="raster")) 
writeRaster(s, file='testtif', format='GTiff', bylayer=T, overwrite=T)
f <- list.files(pattern="testtif_..tif")

Now you can use f with Phil's nice examples. You can also combine all in one step lapply:

f <- list.files("inputFolder", pattern = "*.tif", full.names = TRUE)
r <- lapply(f, function(i) { writeRaster(raster(i), filename=extension(i, '.asc'), overwrite=TRUE)} )

But if you have trouble with lapply, write a loop (it is fine!):

for (i in 1:length(f)) {
   r <- raster(f[i])
   ff <- extension(f[i], '.asc')
   writeRaster(r, ff)
}

Or like this

for (file in f) {
   r <- raster(file)
   ff <- extension(file, '.asc')
   writeRaster(r, ff)
}
Robert Hijmans
  • 40,301
  • 4
  • 55
  • 63