diff --git a/NAMESPACE b/NAMESPACE index 13929861..65a338d7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -64,6 +64,7 @@ export(add_mesh) export(add_path) export(add_pointcloud) export(add_polygon) +export(add_s2) export(add_scatterplot) export(add_screengrid) export(add_sf) @@ -88,6 +89,7 @@ export(clear_mesh) export(clear_path) export(clear_pointcloud) export(clear_polygon) +export(clear_s2) export(clear_scatterplot) export(clear_screengrid) export(clear_terrain) diff --git a/NEWS.md b/NEWS.md index c0ca1489..9e479f55 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,6 @@ +# v0.3.7 +* `add_s2` and `clear_s2` for adding S2 layers to the map [issue 395](https://github.com/SymbolixAU/mapdeck/issues/395) + # v0.3.6 * `add_legend` and `clear_legend()` for adding custom legends to the map [issue 390](https://github.com/SymbolixAU/mapdeck/issues/390) diff --git a/R/map_layer_s2.R b/R/map_layer_s2.R new file mode 100644 index 00000000..f97fafb4 --- /dev/null +++ b/R/map_layer_s2.R @@ -0,0 +1,173 @@ +mapdeckS2Dependency <- function() { + list( + createHtmlDependency( + name = "s2", + version = "1.0.0", + src = system.file("htmlwidgets/lib/s2", package = "mapdeck"), + script = c("s2.js"), + all_files = FALSE + ) + ) +} + +#' Add s2 +#' +#' The S2 layer renders filled and/or stroked polygons based on the S2 +#' geospatial indexing system. +#' +#' @inheritParams add_polygon +#' @param token column of \code{data} containing the s2 tokens +#' +#' @section transitions: +#' +#' The transitions argument lets you specify the time it will take for the shapes to transition +#' from one state to the next. Only works in an interactive environment (Shiny) +#' and on WebGL-2 supported browsers and hardware. +#' +#' The time is in milliseconds +#' +#' Available transitions for s2 +#' +#' list( +#' elevation = 0 +#' colour = 0 +#' ) +#' +#' @examples +#' \dontrun{ +#' +#' ## You need a valid access token from Mapbox +#' key <- 'abc' +#' set_token( key ) +#' +#' mapdeck( +#' style = mapdeck_style("dark") +#' , location = c(-122.4, 37.8) +#' , zoom = 10 +#' , pitch = 60 +#' ) %>% +#' add_s2( +#' data = s2 +#' , token = "token" +#' , fill_colour = "value" +#' , auto_highlight = TRUE +#' , legend = TRUE +#' , elevation = "value" +#' , elevation_scale = 1000 +#' , palette = colourvalues::get_palette("inferno") +#' ) +#' +#' } +#' +#' @details +#' +#' \code{add_s2} supports a data.frame with a column of s2 tokens +#' +#' +#' @export +add_s2 <- function( + map, + data = get_map_data(map), + token = NULL, + stroke_colour = NULL, + stroke_width = NULL, + stroke_opacity = NULL, + fill_colour = NULL, + fill_opacity = NULL, + elevation = NULL, + tooltip = NULL, + auto_highlight = FALSE, + elevation_scale = 1, + highlight_colour = "#AAFFFFFF", + light_settings = list(), + layer_id = NULL, + id = NULL, + palette = "viridis", + na_colour = "#808080FF", + legend = FALSE, + legend_options = NULL, + legend_format = NULL, + update_view = TRUE, + focus_layer = FALSE, + transitions = NULL +) { + + l <- list() + l[["token"]] <- force( token ) + l[["stroke_colour"]] <- force( stroke_colour ) + l[["stroke_width"]] <- force( stroke_width ) + l[["stroke_opacity"]] <- resolve_opacity( stroke_opacity ) + l[["fill_colour"]] <- force( fill_colour ) + l[["fill_opacity"]] <- resolve_opacity( fill_opacity ) + l[["elevation"]] <- force( elevation ) + l[["tooltip"]] <- force( tooltip ) + l[["id"]] <- force( id ) + l[["na_colour"]] <- force( na_colour ) + + l <- resolve_palette( l, palette ) + l <- resolve_legend( l, legend ) + l <- resolve_legend_options( l, legend_options ) + + # l <- resolve_data( data, l, c("POINT","MULTIPOINT") ) + l[["data_type"]] <- "df" + l[["data"]] <- data + + bbox <- init_bbox() + update_view <- force( update_view ) + focus_layer <- force( focus_layer ) + + is_extruded <- TRUE + if( !is.null( l[["stroke_width"]] ) | !is.null( l[["stroke_colour"]] ) ) { + is_extruded <- FALSE + if( !is.null( elevation ) ) { + message("stroke provided, ignoring elevation") + } + if( is.null( l[["stroke_width"]] ) ) { + l[["stroke_width"]] <- 1L + } + } + + if ( !is.null(l[["data"]]) ) { + data <- l[["data"]] + l[["data"]] <- NULL + } + + checkHexAlpha(highlight_colour) + layer_id <- layerId(layer_id, "s2") + + map <- addDependency(map, mapdeckS2Dependency()) + + tp <- l[["data_type"]] + l[["data_type"]] <- NULL + + geometry_column <- "token" + + ## use 'polyline' method because we have strings (tokens), not lat/lon coordinates + shape <- rcpp_point_polyline( data, l, geometry_column, "s2") + + jsfunc <- "add_s2" + + light_settings <- jsonify::to_json(light_settings, unbox = T) + js_transitions <- resolve_transitions(transitions, "polygon") + + if( inherits( legend, "json" ) ) { + shape[["legend"]] <- legend + legend_format <- "hex" + } else { + shape[["legend"]] <- resolve_legend_format( shape[["legend"]], legend_format ) + legend_format <- "rgb" + } + + invoke_method( + map, jsfunc, map_type( map ), shape[["data"]], layer_id, light_settings, + elevation_scale, auto_highlight, highlight_colour, shape[["legend"]], legend_format, + js_transitions, is_extruded + ) +} + +#' @rdname clear +#' @export +clear_s2 <- function(map, layer_id = NULL, update_view = TRUE, clear_legend = TRUE) { + layer_id <- layerId(layer_id, "s2") + invoke_method(map, "md_layer_clear", map_type( map ), layer_id, "s2", update_view, clear_legend ) +} \ No newline at end of file diff --git a/R/mapdeck_map_utilities.R b/R/mapdeck_map_utilities.R index 2caca973..f6cadada 100644 --- a/R/mapdeck_map_utilities.R +++ b/R/mapdeck_map_utilities.R @@ -141,6 +141,7 @@ layerId <- function( , "path" , "pointcloud" , "polygon" + , "s2" , "scatterplot" , "screengrid" , "terrain" diff --git a/data-raw/s2.R b/data-raw/s2.R new file mode 100644 index 00000000..d732a0aa --- /dev/null +++ b/data-raw/s2.R @@ -0,0 +1,8 @@ + + +df <- jsonify::from_json("https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf.s2cells.json") + +s2 <- as.data.frame(df) + +usethis::use_data(s2, overwrite = TRUE) + diff --git a/data/s2.rda b/data/s2.rda new file mode 100644 index 00000000..3050f899 Binary files /dev/null and b/data/s2.rda differ diff --git a/docs/articles/img/articles/s2.png b/docs/articles/img/articles/s2.png new file mode 100644 index 00000000..553e6a0e Binary files /dev/null and b/docs/articles/img/articles/s2.png differ diff --git a/inst/htmlwidgets/lib/s2/s2.js b/inst/htmlwidgets/lib/s2/s2.js new file mode 100644 index 00000000..e351311b --- /dev/null +++ b/inst/htmlwidgets/lib/s2/s2.js @@ -0,0 +1,41 @@ +function add_s2( map_id, map_type, s2_data, layer_id, light_settings, elevation_scale, auto_highlight, highlight_colour, legend, legend_format, js_transition, is_extruded ) { +//bbox, update_view, focus_layer, + + console.log( legend ); + + const s2Layer = new deck.S2Layer({ + map_id: map_id, + id: 's2-'+layer_id, + data: s2_data, + pickable: true, + stroked: true, + filled: true, + wireframe: false, + extruded: is_extruded, + lineWidthMinPixels: 0, + getS2Token: d => d.token, + getLineColor: d => d.stroke_colour, + getFillColor: d => d.fill_colour, + getLineWidth: d => d.stroke_width, + getElevation: d => d.elevation, + elevationScale: elevation_scale, + lightSettings: light_settings, + autoHighlight: auto_highlight, + highlightColor: md_hexToRGBA( highlight_colour ), + onHover: md_update_tooltip, + onClick: info => md_layer_click( map_id, "s2", info ), + transitions: js_transition || {} + }); + + if( map_type == "google_map") { + md_update_overlay( map_id, 's2-'+layer_id, s2Layer ); + } else { + md_update_layer( map_id, 's2-'+layer_id, s2Layer ); + } + + if (legend !== false) { + md_add_legend(map_id, map_type, layer_id, legend, legend_format); + } + + // md_layer_view( map_id, map_type, layer_id, focus_layer, bbox, update_view ); +} diff --git a/man/add_s2.Rd b/man/add_s2.Rd new file mode 100644 index 00000000..bc8ad12c --- /dev/null +++ b/man/add_s2.Rd @@ -0,0 +1,151 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/map_layer_s2.R +\name{add_s2} +\alias{add_s2} +\title{Add s2} +\usage{ +add_s2( + map, + data = get_map_data(map), + token = NULL, + stroke_colour = NULL, + stroke_width = NULL, + stroke_opacity = NULL, + fill_colour = NULL, + fill_opacity = NULL, + elevation = NULL, + tooltip = NULL, + auto_highlight = FALSE, + elevation_scale = 1, + highlight_colour = "#AAFFFFFF", + light_settings = list(), + layer_id = NULL, + id = NULL, + palette = "viridis", + na_colour = "#808080FF", + legend = FALSE, + legend_options = NULL, + legend_format = NULL, + update_view = TRUE, + focus_layer = FALSE, + transitions = NULL +) +} +\arguments{ +\item{map}{a mapdeck map object} + +\item{data}{data to be used in the layer. All coordinates are expected to be +EPSG:4326 (WGS 84) coordinate system} + +\item{token}{column of \code{data} containing the s2 tokens} + +\item{stroke_colour}{variable of \code{data} or hex colour for the stroke. If used, +\code{elevation} is ignored. +If using a hex colour, use either a single value, or a column of hex colours on \code{data}} + +\item{stroke_width}{width of the stroke in meters. If used, \code{elevation} is ignored. Default 1.} + +\item{stroke_opacity}{Either a string specifying the column of \code{data} +containing the opacity of each shape, or a single value in [0,255], or [0, 1), +to be applied to all the shapes. Default 255. If a hex-string is used as the +colour, this argument is ignored and you should include the alpha on the hex string} + +\item{fill_colour}{column of \code{data} or hex colour for the fill colour. +If using a hex colour, use either a single value, or a column of hex colours on \code{data}} + +\item{fill_opacity}{Either a string specifying the column of \code{data} +containing the opacity of each shape, or a single value in [0,255], or [0, 1), +to be applied to all the shapes. Default 255. If a hex-string is used as the +colour, this argument is ignored and you should include the alpha on the hex string} + +\item{elevation}{the height the polygon extrudes from the map. Only available if neither +\code{stroke_colour} or \code{stroke_width} are supplied. Default 0} + +\item{tooltip}{variable of \code{data} containing text or HTML to render as a tooltip} + +\item{auto_highlight}{logical indicating if the shape under the mouse should auto-highlight} + +\item{elevation_scale}{elevation multiplier.} + +\item{highlight_colour}{hex string colour to use for highlighting. Must contain the alpha component.} + +\item{light_settings}{list of light setting parameters. See \link{light_settings}} + +\item{layer_id}{single value specifying an id for the layer. Use this value to +distinguish between shape layers of the same type. Layers with the same id are likely +to conflict and not plot correctly} + +\item{id}{an id value in \code{data} to identify layers when interacting in Shiny apps.} + +\item{palette}{string or matrix. String will be one of \code{colourvalues::colour_palettes()}. +A matrix must have at least 5 rows, and 3 or 4 columns of values between [0, 255], +where the 4th column represents the alpha. You can use a named list to specify a different +palette for different colour options (where available), + e.g. list(fill_colour = "viridis", stroke_colour = "inferno")} + +\item{na_colour}{hex string colour to use for NA values} + +\item{legend}{either a logical indiciating if the legend(s) should be displayed, or +a named list indicating which colour attributes should be included in the legend.} + +\item{legend_options}{A list of options for controlling the legend.} + +\item{legend_format}{A list containing functions to apply to legend values. See section legend} + +\item{update_view}{logical indicating if the map should update the bounds to include this layer} + +\item{focus_layer}{logical indicating if the map should update the bounds to only include this layer} + +\item{transitions}{list specifying the duration of transitions.} +} +\description{ +The S2 layer renders filled and/or stroked polygons based on the S2 +geospatial indexing system. +} +\details{ +\code{add_s2} supports a data.frame with a column of s2 tokens +} +\section{transitions}{ + + +The transitions argument lets you specify the time it will take for the shapes to transition +from one state to the next. Only works in an interactive environment (Shiny) +and on WebGL-2 supported browsers and hardware. + +The time is in milliseconds + +Available transitions for s2 + +list( +elevation = 0 +colour = 0 +) +} + +\examples{ +\dontrun{ + +## You need a valid access token from Mapbox +key <- 'abc' +set_token( key ) + +mapdeck( + style = mapdeck_style("dark") + , location = c(-122.4, 37.8) + , zoom = 10 + , pitch = 60 + ) \%>\% + add_s2( + data = s2 + , token = "token" + , fill_colour = "value" + , auto_highlight = TRUE + , legend = TRUE + , elevation = "value" + , elevation_scale = 1000 + , palette = colourvalues::get_palette("inferno") + ) + +} + +} diff --git a/man/clear.Rd b/man/clear.Rd index 4a964fb5..4733031d 100644 --- a/man/clear.Rd +++ b/man/clear.Rd @@ -5,8 +5,9 @@ % R/map_layer_grid.R, R/map_layer_h3.R, R/map_layer_heatmap.R, % R/map_layer_hexagon.R, R/map_layer_line.R, R/map_layer_mesh.R, % R/map_layer_path.R, R/map_layer_pointcloud.R, R/map_layer_polygon.R, -% R/map_layer_scatterplot.R, R/map_layer_screengrid.R, R/map_layer_terrain.R, -% R/map_layer_text.R, R/map_layer_title.R, R/map_layer_trips.R +% R/map_layer_s2.R, R/map_layer_scatterplot.R, R/map_layer_screengrid.R, +% R/map_layer_terrain.R, R/map_layer_text.R, R/map_layer_title.R, +% R/map_layer_trips.R \name{clear_animated_arc} \alias{clear_animated_arc} \alias{clear_line} @@ -24,6 +25,7 @@ \alias{clear_path} \alias{clear_pointcloud} \alias{clear_polygon} +\alias{clear_s2} \alias{clear_scatterplot} \alias{clear_screengrid} \alias{clear_terrain} @@ -71,6 +73,8 @@ clear_pointcloud(map, layer_id = NULL, update_view = TRUE, clear_legend = TRUE) clear_polygon(map, layer_id = NULL, update_view = TRUE, clear_legend = TRUE) +clear_s2(map, layer_id = NULL, update_view = TRUE, clear_legend = TRUE) + clear_scatterplot( map, layer_id = NULL, diff --git a/src/point.cpp b/src/point.cpp index 659e0418..10d461d9 100644 --- a/src/point.cpp +++ b/src/point.cpp @@ -56,7 +56,8 @@ Rcpp::StringVector get_point_legend_colours( std::string layer_name ) { Rcpp::StringVector point_legend; - if( layer_name == "column" || layer_name == "scatterplot" || layer_name == "h3") { + if( layer_name == "column" || layer_name == "scatterplot" || + layer_name == "h3" || layer_name == "s2" ) { point_legend = mapdeck::layer_colours::fill_stroke_legend; } else if ( layer_name == "pointcloud" ) { point_legend = mapdeck::layer_colours::fill_legend; @@ -70,7 +71,8 @@ std::unordered_map< std::string, std::string > get_point_colours( std::string la std::unordered_map< std::string, std::string > point_colours; - if( layer_name == "column" || layer_name == "scatterplot" || layer_name == "h3" ) { + if( layer_name == "column" || layer_name == "scatterplot" || + layer_name == "h3" || layer_name == "s2" ) { point_colours = mapdeck::layer_colours::fill_stroke_colours; } else if ( layer_name == "pointcloud" ) { point_colours = mapdeck::layer_colours::fill_colours; diff --git a/tests/testthat/test-map_layers.R b/tests/testthat/test-map_layers.R index 4fbc795b..70d2ccfd 100644 --- a/tests/testthat/test-map_layers.R +++ b/tests/testthat/test-map_layers.R @@ -26,6 +26,7 @@ test_that("layerId includes all layers", { , "polygon" , "scatterplot" , "screengrid" + , "s2" , "terrain" , "text" , "tile3d" diff --git a/vignettes/img/articles/s2.png b/vignettes/img/articles/s2.png new file mode 100644 index 00000000..553e6a0e Binary files /dev/null and b/vignettes/img/articles/s2.png differ diff --git a/vignettes/layers.Rmd b/vignettes/layers.Rmd index b9f1d1fe..ed6a52e2 100644 --- a/vignettes/layers.Rmd +++ b/vignettes/layers.Rmd @@ -170,9 +170,6 @@ mapdeck( style = mapdeck_style('dark'), pitch = 45 ) %>% ```{r} -library(mapdeck) - -set_token( read.dcf("~/Documents/.googleAPI", fields = "MAPBOX")) df <- read.csv(paste0( 'https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/' , '3d-heatmap/heatmap-data.csv' @@ -274,6 +271,33 @@ mapdeck(token = key, style = mapdeck_style("dark")) %>% ![Polygons](./img/articles/polygons.gif) +## S2 + +```{r} +df <- jsonify::from_json("https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf.s2cells.json") + +mapdeck( + style = mapdeck_style("dark") + , location = c(-122.4, 37.8) + , zoom = 10 + , pitch = 60 + ) %>% + add_s2( + data = df + , token = "token" + , fill_colour = "value" + , auto_highlight = TRUE + , legend = FALSE + , elevation = "value" + , elevation_scale = 1000 + , palette = colourvalues::get_palette("inferno") + ) + + +``` + +![S2](./img/articles/s2.png) + ## Scatter ```{r}