Skip to content

Commit 5d13177

Browse files
authored
Merge pull request #807 from rhijmans/main
Support for SpatVector and SpatRaster
2 parents 0016c07 + 73ff8a3 commit 5d13177

File tree

12 files changed

+434
-71
lines changed

12 files changed

+434
-71
lines changed

.github/workflows/R-CMD-check.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ jobs:
2424
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
2525
with:
2626
minimum-r-version: "3.5.0"
27+
ubuntu: "ubuntu-20.04"

DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Suggests:
6161
RJSONIO,
6262
purrr,
6363
testthat (>= 3.0.0),
64-
s2
65-
RoxygenNote: 7.1.2
64+
s2,
65+
terra
66+
RoxygenNote: 7.2.0
6667
Encoding: UTF-8
6768
LazyData: true

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
S3method("[",leaflet_awesome_icon_set)
44
S3method("[",leaflet_icon_set)
55
S3method(metaData,SharedData)
6+
S3method(metaData,SpatVector)
67
S3method(metaData,SpatialLinesDataFrame)
78
S3method(metaData,SpatialPointsDataFrame)
89
S3method(metaData,SpatialPolygonsDataFrame)
@@ -12,6 +13,7 @@ S3method(metaData,map)
1213
S3method(metaData,sf)
1314
S3method(pointData,POINT)
1415
S3method(pointData,SharedData)
16+
S3method(pointData,SpatVector)
1517
S3method(pointData,SpatialPoints)
1618
S3method(pointData,SpatialPointsDataFrame)
1719
S3method(pointData,data.frame)
@@ -25,6 +27,7 @@ S3method(polygonData,Lines)
2527
S3method(polygonData,Polygon)
2628
S3method(polygonData,Polygons)
2729
S3method(polygonData,SharedData)
30+
S3method(polygonData,SpatVector)
2831
S3method(polygonData,SpatialLines)
2932
S3method(polygonData,SpatialLinesDataFrame)
3033
S3method(polygonData,SpatialPolygons)

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ BUG FIXES and IMPROVEMENTS
66

77
* New behavior from tile.openstreetmap.org caused `addTiles` default tileset to break when viewed under non-https protocol on recent versions of Chrome. Fixed by always using the https protocol to connect to openstreetmap. (#786)
88

9+
* Added support for SpatRaster and SpatVector objects from the terra package. (#728)
10+
911
leaflet 2.1.0
1012
--------------------------------------------------------------------------------
1113

R/layers.R

Lines changed: 172 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,13 @@ addTiles <- function(
172172
epsg4326 <- "+proj=longlat +datum=WGS84 +no_defs"
173173
epsg3857 <- "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs" # nolint
174174

175+
175176
#' Add a raster image as a layer
176177
#'
177-
#' Create an image overlay from a \code{RasterLayer} object. \emph{This is only
178-
#' suitable for small to medium sized rasters}, as the entire image will be
179-
#' embedded into the HTML page (or passed over the websocket in a Shiny
180-
#' context).
178+
#' Create an image overlay from a \code{RasterLayer} or a \code{SpatRaster}
179+
#' object. \emph{This is only suitable for small to medium sized rasters},
180+
#' as the entire image will be embedded into the HTML page (or passed over
181+
#' the websocket in a Shiny context).
181182
#'
182183
#' The \code{maxBytes} parameter serves to prevent you from accidentally
183184
#' embedding an excessively large amount of data into your htmlwidget. This
@@ -187,18 +188,22 @@ epsg3857 <- "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y
187188
#' aware that very large rasters may not only make your map a large download but
188189
#' also may cause the browser to become slow or unresponsive.
189190
#'
190-
#' By default, the \code{addRasterImage} function will project the RasterLayer
191-
#' \code{x} to EPSG:3857 using the \code{raster} package's
192-
#' \code{\link[raster]{projectRaster}} function. This can be a time-consuming
193-
#' operation for even moderately sized rasters. Upgrading the \code{raster}
194-
#' package to 2.4 or later will provide a large speedup versus previous
195-
#' versions. If you are repeatedly adding a particular raster to your Leaflet
191+
#' To reduce the size of a SpatRaster, you can use \code{\link[terra]{spatSample}}
192+
#' as in \code{x = spatSample(x, 100000, method="regular", as.raster=TRUE)}. With
193+
#' a \code{RasterLayer} you can use \code{\link[raster]{sampleRegular}} as in
194+
#' \code{sampleRegular(x, 100000, asRaster=TRUE)}.
195+
#'
196+
#' By default, the \code{addRasterImage} function will project the raster data
197+
#' \code{x} to the Pseudo-Mercator projection (EPSG:3857). This can be a
198+
#' time-consuming operation for even moderately sized rasters; although it is much
199+
#' faster for SpatRasters than for RasterLayers.
200+
#' If you are repeatedly adding a particular raster to your Leaflet
196201
#' maps, you can perform the projection ahead of time using
197202
#' \code{projectRasterForLeaflet()}, and call \code{addRasterImage} with
198203
#' \code{project = FALSE}.
199204
#'
200205
#' @param map a map widget object
201-
#' @param x a \code{RasterLayer} object--see \code{\link[raster]{raster}}
206+
#' @param x a \code{\link[terra]{SpatRaster}} or a \code{RasterLayer} object--see \code{\link[raster]{raster}}
202207
#' @param colors the color palette (see \code{\link{colorNumeric}}) or function
203208
#' to use to color the raster values (hint: if providing a function, set
204209
#' \code{na.color} to \code{"#00000000"} to make \code{NA} areas transparent)
@@ -235,7 +240,7 @@ epsg3857 <- "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y
235240
addRasterImage <- function(
236241
map,
237242
x,
238-
colors = if (raster::is.factor(x)) "Set1" else "Spectral",
243+
colors = if (is.factor(x)[1]) "Set1" else "Spectral",
239244
opacity = 1,
240245
attribution = NULL,
241246
layerId = NULL,
@@ -245,7 +250,54 @@ addRasterImage <- function(
245250
maxBytes = 4 * 1024 * 1024,
246251
data = getMapData(map)
247252
) {
248-
stopifnot(inherits(x, "RasterLayer"))
253+
if (inherits(x, "SpatRaster")) {
254+
addRasterImage_SpatRaster(
255+
map=map,
256+
x=x,
257+
colors = colors,
258+
opacity = opacity,
259+
attribution = attribution,
260+
layerId = layerId,
261+
group = group,
262+
project = project,
263+
method = method,
264+
maxBytes = maxBytes,
265+
data = data
266+
)
267+
} else if (inherits(x, "RasterLayer")) {
268+
addRasterImage_RasterLayer(
269+
map=map,
270+
x=x,
271+
colors = colors,
272+
opacity = opacity,
273+
attribution = attribution,
274+
layerId = layerId,
275+
group = group,
276+
project = project,
277+
method = method,
278+
maxBytes = maxBytes,
279+
data = data
280+
)
281+
} else {
282+
stop("Don't know how to get path data from object of class ", class(x)[[1]])
283+
}
284+
}
285+
286+
287+
addRasterImage_RasterLayer <- function(
288+
map,
289+
x,
290+
colors = if (is.factor(x)[1]) "Set1" else "Spectral",
291+
opacity = 1,
292+
attribution = NULL,
293+
layerId = NULL,
294+
group = NULL,
295+
project = TRUE,
296+
method = c("auto", "bilinear", "ngb"),
297+
maxBytes = 4 * 1024 * 1024,
298+
data = getMapData(map)
299+
) {
300+
249301

250302
raster_is_factor <- raster::is.factor(x)
251303
method <- match.arg(method)
@@ -260,11 +312,6 @@ addRasterImage <- function(
260312
if (project) {
261313
# if we should project the data
262314
projected <- projectRasterForLeaflet(x, method)
263-
264-
# if data is factor data, make the result factors as well.
265-
if (raster_is_factor) {
266-
projected <- raster::as.factor(projected)
267-
}
268315
} else {
269316
# do not project data
270317
projected <- x
@@ -311,14 +358,116 @@ addRasterImage <- function(
311358
)
312359
}
313360

361+
addRasterImage_SpatRaster <- function(
362+
map,
363+
x,
364+
colors = if (terra::is.factor(x)[1]) "Set1" else "Spectral",
365+
opacity = 1,
366+
attribution = NULL,
367+
layerId = NULL,
368+
group = NULL,
369+
project = TRUE,
370+
method = c("auto", "bilinear", "ngb"),
371+
maxBytes = 4 * 1024 * 1024,
372+
data = getMapData(map)
373+
) {
374+
375+
if (terra::nlyr(x) > 1) {
376+
x <- x[[1]]
377+
warning("using the first layer in 'x'", call. = FALSE)
378+
}
379+
380+
raster_is_factor <- terra::is.factor(x)
381+
method <- match.arg(method)
382+
if (method == "ngb") method = "near"
383+
if (method == "auto") {
384+
if (raster_is_factor) {
385+
method <- "near"
386+
} else {
387+
method <- "bilinear"
388+
}
389+
}
390+
391+
if (project) {
392+
# if we should project the data
393+
projected <- projectRasterForLeaflet(x, method)
394+
} else {
395+
# do not project data
396+
projected <- x
397+
}
398+
399+
bounds <- terra::ext(
400+
terra::project(
401+
terra::project(
402+
terra::as.points(terra::ext(x), crs=terra::crs(x)),
403+
epsg3857),
404+
epsg4326)
405+
)
406+
407+
if (!is.function(colors)) {
408+
if (method == "near") {
409+
# 'factors'
410+
colors <- colorFactor(colors, domain = NULL, na.color = "#00000000", alpha = TRUE)
411+
} else {
412+
# 'numeric'
413+
colors <- colorNumeric(colors, domain = NULL, na.color = "#00000000", alpha = TRUE)
414+
}
415+
}
416+
417+
tileData <- terra::values(projected) %>% as.vector() %>% colors() %>% col2rgb(alpha = TRUE) %>% as.raw()
418+
dim(tileData) <- c(4, ncol(projected), nrow(projected))
419+
pngData <- png::writePNG(tileData)
420+
if (length(pngData) > maxBytes) {
421+
stop(
422+
"Raster image too large; ", length(pngData), " bytes is greater than maximum ",
423+
maxBytes, " bytes"
424+
)
425+
}
426+
encoded <- base64enc::base64encode(pngData)
427+
uri <- paste0("data:image/png;base64,", encoded)
428+
429+
latlng <- list(
430+
list(terra::ymax(bounds), terra::xmin(bounds)),
431+
list(terra::ymin(bounds), terra::xmax(bounds))
432+
)
433+
434+
invokeMethod(map, data, "addRasterImage", uri, latlng, opacity, attribution, layerId, group) %>%
435+
expandLimits(
436+
c(terra::ymin(bounds), terra::ymax(bounds)),
437+
c(terra::xmin(bounds), terra::xmax(bounds))
438+
)
439+
}
440+
441+
442+
314443
#' @rdname addRasterImage
315444
#' @export
316445
projectRasterForLeaflet <- function(x, method) {
317-
raster::projectRaster(
318-
x,
319-
raster::projectExtent(x, crs = sp::CRS(epsg3857)),
320-
method = method
321-
)
446+
if (inherits(x, "SpatRaster")) {
447+
if (method=="ngb") {
448+
method = "near"
449+
}
450+
terra::project(
451+
x,
452+
y=epsg3857,
453+
method=method
454+
)
455+
} else {
456+
raster_is_factor <- raster::is.factor(x);
457+
projected <- raster::projectRaster(
458+
x,
459+
raster::projectExtent(x, crs = sp::CRS(epsg3857)),
460+
method = method
461+
)
462+
# if data is factor data, make the result factors as well.
463+
# only meaningful if ngb was used
464+
if ((raster_is_factor) && (method == "ngb")) {
465+
raster::as.factor(projected)
466+
} else {
467+
projected
468+
}
469+
470+
}
322471
}
323472

324473
#' @rdname remove

R/leaflet.R

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,11 @@ leafletSizingPolicy <- function(
3939
#' we may add a circle layer to the map by \code{leaflet(data) \%>\%
4040
#' addCircles(lat = ~latitude, lng = ~longtitude)}, where the variables in the
4141
#' formulae will be evaluated in the \code{data}.
42-
#' @param data a data object. Currently supported objects are matrices, data
43-
#' frames, spatial objects from the \pkg{sp} package
44-
#' (\code{SpatialPoints}, \code{SpatialPointsDataFrame}, \code{Polygon},
45-
#' \code{Polygons}, \code{SpatialPolygons}, \code{SpatialPolygonsDataFrame},
46-
#' \code{Line}, \code{Lines}, \code{SpatialLines}, and
47-
#' \code{SpatialLinesDataFrame}), and
48-
#' spatial data frames from the \pkg{sf} package.
42+
#' @param data a data object. Currently supported objects are matrix, data
43+
#' frame, spatial data from the \pkg{sf} package,
44+
#' \code{SpatVector} from the \pkg{terra} package, and the Spatial*
45+
#' objects from the \pkg{sp} package that represent points, lines, or polygons.
46+
#'
4947
#' @param width the width of the map
5048
#' @param height the height of the map
5149
#' @param padding the padding of the map
@@ -66,7 +64,7 @@ leaflet <- function(data = NULL, width = NULL, height = NULL,
6664
!inherits(options[["crs"]], "leaflet_crs")) {
6765
stop("CRS in mapOptions should be a return value of leafletCRS() function")
6866
}
69-
67+
7068
map <- htmlwidgets::createWidget(
7169
"leaflet",
7270
structure(

0 commit comments

Comments
 (0)