/
tile.R
263 lines (254 loc) · 10.8 KB
/
tile.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
tmprst <- function() {
tempfile(pattern = "tmp_raster_", fileext = ".tif")
}
#' Create map tiles
#'
#' Create geographic and non-geographic map tiles from a file.
#'
#' @details
#' This function supports both geographic and non-geographic tile generation.
#' When `file` is a simple image file such as `png`, `tile()` generates
#' non-geographic, simple CRS tiles. Files that can be loaded by the `raster`
#' package yield geographic tiles as long as `file` has projection information.
#' If the raster object's Proj4 string is `NA`, it falls back on non-geographic
#' tile generation and a warning is thrown.
#'
#' Choice of appropriate zoom levels for non-geographic image files may depend
#' on the size of the image. A `zoom` value may be partially ignored for image
#' files under certain conditions. For instance using the example `map.png`
#' below, when passing strictly `zoom = n` where `n` is less than 3, this still
#' generates tiles for zoom `n` up through 3.
#'
#' \subsection{Supported file types}{
#' Supported simple CRS/non-geographic image file types include `png`, `jpg` and
#' `bmp`. For projected map data, supported file types include three types
#' readable by the `raster` package: `grd`, `tif`, and `nc` (requires `ncdf4`).
#' Other currently unsupported file types passed to `file` throw an error.
#' }
#' \subsection{Raster file inputs}{
#' If a map file loadable by `raster` is a single-layer raster object, tile
#' coloring is applied. To override default coloring of data and `noData`
#' pixels, pass the additional arguments `col` and `colNA` to `...`.
#' Multi-layer raster objects are rejected with an error message. The only
#' exception is a three- or four-band raster, which is assumed to represent
#' red, green, blue and alpha channels, respectively.
#' In this case, processing will continue but coloring arguments are ignored as
#' unnecessary.
#' \cr\cr
#' Prior to tiling, a geographically-projected raster layer is reprojected to
#' EPSG:4326 only if it has some other projection. The only reprojection
#' argument available through `...` is `method`, which can be `"bilinear"`
#' (default) or`"ngb"`. If complete control over reprojection is required, this
#' should be done prior to passing the rasterized file to the `tile` function.
#' Then no reprojection is performed by `tile()`. When `file` consists of RGB or
#' RGBA bands, `method` is ignored if provided and reprojection uses nearest
#' neighbor.
#' \cr\cr
#' It is recommended to avoid using a projected 4-band RGBA raster file.
#' However, the alpha channel appears to be ignored anyway. `gdal2tiles` gives
#' an internal warning. Instead, create your RGBA raster file in unprojected
#' form and it should pass through to `gdal2tiles` without any issues.
#' Three-band RGB raster files are unaffected by reprojection.
#' }
#' \subsection{Tiles and Leaflet}{
#' `gdal2tiles` generates TMS tiles. If expecting XYZ, for example when using
#' with Leaflet, you can change the end of the URL to your hosted tiles from
#' `{z}/{x}/{y}.png` to `{z}/{x}/{-y}.png`.
#' \cr\cr
#' This function is supported by two different versions of `gdal2tiles`. There
#' is the standard version, which generates geospatial tiles in TMS format. The
#' other version of `gdal2tiles} handles basic image files like a matrix of rows
#' and columns, using a simple Cartesian coordinate system based on pixel
#' dimensions of the image file. See the Leaflet JS library and `leaflet`
#' package documentation for working with custom tiles in Leaflet.
#' }
#'
#' @param file character, input file.
#' @param tiles character, output directory for generated tiles.
#' @param zoom character, zoom levels. Example format: `"3-7"`.
#' See details.
#' @param crs character, Proj4 string. Use this to force set the CRS of a loaded
#' raster object from `file` in cases where the CRS is missing but known, to
#' avoid defaulting to non-geographic tiling.
#' @param resume logical, only generate missing tiles.
#' @param viewer logical, also create `preview.html` adjacent to `tiles`
#' directory for previewing tiles in the browser using Leaflet.
#' @param georef logical, for non-geographic tiles only. If `viewer = TRUE`,
#' then the Leaflet widget in `preview.html` will add map markers with
#' coordinate labels on mouse click to assist with georeferencing of
#' non-geographic tiles.
#' @param ... additional arguments for projected maps: reprojection method or
#' any arguments to `raster::RGB()`, e.g. `col` and `colNA`. See details. Other
#' additional arguments `lng` and `lat` can also be passed to the tile
#' previewer. See [tile_viewer()] for details.
#'
#' @return nothing is returned but tiles are written to disk.
#' @export
#' @seealso [view_tiles()], [tile_viewer()]
#'
#' @examples
#' \dontshow{tmpfiles <- list.files(tempdir(), full.names = TRUE)}
#' # non-geographic/simple CRS
#' x <- system.file("maps/map.png", package = "tiler")
#' tiles <- file.path(tempdir(), "tiles")
#' tile(x, tiles, "2-3")
#'
#' \dontrun{
#' # unprojected map
#' x <- system.file("maps/map_wgs84.tif", package = "tiler")
#' tile(x, tiles, 0)
#'
#' # projected map
#' x <- system.file("maps/map_albers.tif", package = "tiler")
#' tile(x, tiles, 0)
#' }
#' \dontshow{
#' unlink(c(tiles, file.path(tempdir(), "preview.html")), recursive = TRUE,
#' force = TRUE)
#' extrafiles <- setdiff(list.files(tempdir(), full.names = TRUE), tmpfiles)
#' if(length(extrafiles)) unlink(extrafiles, recursive = TRUE, force = TRUE)
#' }
tile <- function(file, tiles, zoom, crs = NULL, resume = FALSE, viewer = TRUE,
georef = TRUE, ...){
ext <- .get_ext(file)
if(ext == "jpg" && !requireNamespace("jpeg", quietly = TRUE)){
message(paste("jpg files are optionally supported (png recommended).",
"Install `jpeg` package to use jpg images."))
return(invisible())
}
if(ext == "bmp" && !requireNamespace("bmp", quietly = TRUE)){
message(paste("bmp files are optionally supported (png recommended).",
"Install `bmp` package to use bmp images."))
return(invisible())
}
if(!ext %in% unlist(.supported_filetypes))
stop(paste0("File type .", ext, " not supported."))
ex <- tiler_options()$python
if(.Platform$OS.type == "windows"){
if(ex != "python") ex <- paste0("\"", ex, "\"")
bat <- tiler_options()$osgeo4w
if(bat != "OSGeo4W.bat"){
ex <- paste0("\"", bat, "\" ", ex)
} else {
ex <- paste(bat, ex)
}
}
dir.create(tiles, showWarnings = FALSE, recursive = TRUE)
tf <- tmprst()
projected <- .proj_check(file, crs, tmpf = tf, ...)
if(ext %in% .supported_filetypes$ras)
file <- tf
dir.create(g2t_tmp_dir <- tempfile("g2ttmp_"),
showWarnings = FALSE, recursive = TRUE)
if(projected){
g2t <- system.file("python/gdal2tiles.py", package = "tiler")
ex <- paste0(ex, " \"", g2t, "\" -z ", zoom, " -w none ", "--tmpdir \"",
normalizePath(g2t_tmp_dir), "\" ",
ifelse(resume, "-e ", ""), "\"",
normalizePath(file), "\" \"", normalizePath(tiles), "\"")
} else {
g2t <- system.file("python/gdal2tilesIMG.py", package = "tiler")
ex <- paste0(ex, " \"", g2t, "\" --leaflet -p raster -z ", zoom,
" -w none ", "--tmpdir \"", normalizePath(g2t_tmp_dir), "\" ",
ifelse(resume, "-e ", ""), "\"", normalizePath(file), "\" \"",
normalizePath(tiles), "\"")
}
cat("Creating tiles. Please wait...\n")
system(ex)
unlink(g2t_tmp_dir, recursive = TRUE, force = TRUE)
if(viewer){
cat("Creating tile viewer...\n")
w <- h <- NULL
if(!projected){
if(file == tf){
x <- raster::raster(file)
w <- ncol(x)
h <- nrow(x)
} else {
if(ext == "png") f <- png::readPNG else
if(ext == "jpg") f <- jpeg::readJPEG else f <- bmp::read.bmp
x <- f(file)
w <- dim(x)[2]
h <- dim(x)[1]
}
}
viewer_args <- c(
list(tiles = tiles, zoom = zoom, width = w, height = h, georef = georef),
list(...)
)
do.call(tile_viewer, viewer_args)
cat("Complete.\n")
}
if(ext %in% .supported_filetypes$ras) unlink(file)
invisible()
}
.proj_check <- function(file, crs = NULL, tmpf = tmprst(), ...){
ext <- .get_ext(file)
if(ext %in% .supported_filetypes$img) return(FALSE)
dots <- list(...)
r <- raster::readAll(raster::stack(file))
bands <- raster::nlayers(r)
if(!bands %in% c(1, 3, 4))
stop("`file` is multi-band but does not appear to be RGB or RGBA layers.")
method <- ifelse(bands != 1, "ngb", ifelse(is.null(dots$method), "bilinear",
dots$method))
if(bands == 1) r <- raster::raster(r, layer = 1)
wgs84 <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
proj4 <- raster::projection(r)
req_reproj <- !is.na(proj4) && !.is_wgs84(proj4)
projected <- req_reproj || !is.null(crs)
if(!projected && !.is_wgs84(proj4)){
warning(paste("Projection expected but is missing.",
"Continuing as non-geographic image."))
} else if(!is.null(crs)){
proj4 <- raster::projection(r) <- crs
}
if(projected){
cat("Reprojecting raster...\n")
e <- raster::projectExtent(r, crs = sp::CRS(wgs84))
r <- raster::projectRaster(r, e, method = method)
}
if(bands == 1){
cat("Coloring raster...\n")
if(!is.null(dots$col)){
col <- .fix_colors(dots$col, no_white = TRUE)
} else if(method == "ngb"){
col <- c("#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854",
"#FFD92F", "#E5C494", "#B3B3B3")
} else {
col <- grDevices::colorRampPalette(
c("#7F3B08", "#B35806", "#E08214", "#FDB863", "#FEE0B6",
"#F7F7F7", "#D8DAEB", "#B2ABD2", "#8073AC", "#542788", "#2D004B"))(30)
}
nacol <- if(!is.null(dots$colNA)) .fix_colors(dots$colNA) else "#FFFFFF"
alpha <- if(!is.null(dots$alpha)) dots$alpha else FALSE
r <- raster::RGB(r, col = col, breaks = dots$breaks, alpha = alpha,
colNA = nacol, zlim = dots$zlim, zlimcol = dots$zcol,
ext = dots$ext)
}
cat("Preparing for tiling...\n")
raster::writeRaster(r, filename = tmpf, overwrite = TRUE, datatype = "INT1U")
projected
}
.get_ext <- function(file) utils::tail(strsplit(file, "\\.")[[1]], 1)
.supported_filetypes <- list(img = c("bmp", "jpg", "png"),
ras = c("grd", "tif", "nc"))
.is_wgs84 <- function(x){
grepl("+proj=longlat", x) & grepl("+datum=WGS84", x) &
grepl("+no_defs|+towgs84=0,0,0", x) # & grepl("+ellps=WGS84", x) # last bit unnecessary?
}
.fix_colors <- function(x, no_white = FALSE){
x <- tolower(x)
if(no_white) x <- gsub("#ffffff|white", "#feffff", x)
hex <- grepl("^#", x)
if(any(hex)) x[hex] <- gsub("^#ff", "#fe", x[hex])
if(!all(hex)){
f <- function(x){
x <- format(as.hexmode(x), width = 2)
x[1] <- gsub("ff", "fe", x[1])
paste0("#", paste(x, collapse = ""))
}
x[!hex] <- apply(grDevices::col2rgb(x[!hex]), 2, f)
}
x
}