diff --git a/.Rbuildignore b/.Rbuildignore index 947ace5..debbe42 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,3 +7,4 @@ ^_pkgdown\.yml$ ^docs$ ^pkgdown$ +^private$ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 234f028..366c77c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .RData .Ruserdata docs +private diff --git a/NAMESPACE b/NAMESPACE index c8f0044..c5c3fa8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,17 @@ # Generated by roxygen2: do not edit by hand +S3method(leafem::addLineFeatures,leaflet) +S3method(leafem::addLineFeatures,leaflet_proxy) +S3method(leafem::addLineFeatures,mapdeck) +S3method(leafem::addLineFeatures,mapview) +S3method(leafem::addPointFeatures,leaflet) +S3method(leafem::addPointFeatures,leaflet_proxy) +S3method(leafem::addPointFeatures,mapdeck) +S3method(leafem::addPointFeatures,mapview) +S3method(leafem::addPolygonFeatures,leaflet) +S3method(leafem::addPolygonFeatures,leaflet_proxy) +S3method(leafem::addPolygonFeatures,mapdeck) +S3method(leafem::addPolygonFeatures,mapview) export(addCOG) export(addCopyExtent) export(addExtent) @@ -23,6 +35,7 @@ export(addStaticLabels) export(addTileFolder) export(clip2sfc) export(colorOptions) +export(extendLayerControl) export(garnishMap) export(hideLogo) export(imagequeryOptions) diff --git a/R/features.R b/R/features.R index 0e90bdf..4fdc3f4 100644 --- a/R/features.R +++ b/R/features.R @@ -95,6 +95,7 @@ addPointFeatures = function(map, ...) UseMethod("addPointFeatures") ### Point Features leaflet +#' @exportS3Method leafem::addPointFeatures addPointFeatures.leaflet <- function(map, data, pane, @@ -125,12 +126,15 @@ addPointFeatures.leaflet <- function(map, } ### Point Features leaflet_proxy +#' @exportS3Method leafem::addPointFeatures addPointFeatures.leaflet_proxy <- addPointFeatures.leaflet ### Point Features mapview +#' @exportS3Method leafem::addPointFeatures addPointFeatures.mapview = addPointFeatures.leaflet ### Point Features mapdeck +#' @exportS3Method leafem::addPointFeatures addPointFeatures.mapdeck <- function(map, data, ...) { @@ -151,6 +155,7 @@ addPointFeatures.mapdeck <- function(map, addLineFeatures = function(map, ...) UseMethod("addLineFeatures") ### Line Features leaflet +#' @exportS3Method leafem::addLineFeatures addLineFeatures.leaflet <- function(map, data, pane, @@ -177,12 +182,15 @@ addLineFeatures.leaflet <- function(map, } ### Line Features leaflet_proxy +#' @exportS3Method leafem::addLineFeatures addLineFeatures.leaflet_proxy <- addLineFeatures.leaflet ### Line Features mapview +#' @exportS3Method leafem::addLineFeatures addLineFeatures.mapview = addLineFeatures.leaflet ### Line Features mapdeck +#' @exportS3Method leafem::addLineFeatures addLineFeatures.mapdeck <- function(map, data, ...) { @@ -202,6 +210,7 @@ addLineFeatures.mapdeck <- function(map, addPolygonFeatures = function(map, ...) UseMethod("addPolygonFeatures") ### Polygon Features leaflet +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.leaflet <- function(map, data, pane, @@ -228,12 +237,15 @@ addPolygonFeatures.leaflet <- function(map, } ### Polygon Features leaflet_proxy +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.leaflet_proxy <- addPolygonFeatures.leaflet ### Polygon Features mapview +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.mapview = addPolygonFeatures.leaflet ### Polygon Features mapdeck +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.mapdeck <- function(map, data, ...) { diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R new file mode 100644 index 0000000..d6e4089 --- /dev/null +++ b/R/layerviewcontrol.R @@ -0,0 +1,195 @@ +#' Extend Layers Control in Leaflet Map +#' +#' This function extends an existing layers control in a `leaflet` map by adding custom views, home buttons, +#' opacity controls, and legends. It enhances the functionality of a layers control created with `leaflet` +#' or `leaflet.extras`. +#' +#' @param map A `leaflet` or `mapview` object to which the extended layers control will be added. +#' @param view_settings A list specifying the view settings for each layer. Each list element should contain +#' either: +#' \itemize{ +#' \item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 +#' (bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. +#' \item \code{zoom}: The zoom level (used for `setView`). +#' \item \code{fly} (optional): A logical indicating whether to use `flyTo` or `flyToBounds` instead of `setView` or `fitBounds`. +#' \item \code{options} (optional): Additional options to pass to `setView`, `fitBounds`, or `flyTo`. +#' } +#' @param home_btns Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. +#' Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}. +#' @param setviewonselect Logical. If `TRUE` (default) sets the view when the layer is selected. +#' @param home_btn_options A list of options to customize the home button appearance and behavior. +#' Possible options include: +#' - `text`: The text or emoji to display on the button (default is '🏠'). +#' - `cursor`: CSS cursor style for the button (default is 'pointer'). +#' - `class`: CSS class name for the button (default is 'leaflet-home-btn'). +#' - `styles`: Semicolon separated CSS-string (default is 'float: inline-end;'). +#' +#' @param opacityControl A list specifying the opacity control settings for each layer. Each list element should contain: +#' \itemize{ +#' \item \code{min}: Minimum opacity value (default is 0). +#' \item \code{max}: Maximum opacity value (default is 1). +#' \item \code{step}: Step size for the opacity slider (default is 0.1). +#' \item \code{default}: Default opacity value (default is 1). +#' \item \code{width}: Width of the opacity slider (default is '100%'). +#' \item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). +#' } +#' +#' @param includelegends Logical. If `TRUE` (default), appends legends to the layer control. Legends are matched +#' to layers by their group name. The legends need to be added with corresponding layer IDs. +#' +#' @return A modified `leaflet` map object with extended layers control including view controls, home buttons, opacity controls, and legends. +#' +#' @details +#' This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events +#' and automatically sets the view or zoom level according to the specified \code{view_settings}. +#' If `home_btns` is enabled, a home button is added next to each layer in the layer control. +#' When clicked, it zooms the map to the predefined view of that layer. +#' The opacity control slider allows users to adjust the opacity of layers. The legend will be appended +#' to the corresponding layer control, matched by the layer's group name. +#' +#' @examples +#' library(sf) +#' library(leaflet) +#' library(leafem) +#' +#' # Example data ########## +#' breweries91 <- st_as_sf(breweries91) +#' lines <- st_as_sf(atlStorms2005) +#' polys <- st_as_sf(leaflet::gadmCHE) +#' +#' # View settings ########## +#' view_settings <- list( +#' "Base_tiles1" = list( +#' coords = c(20, 50), +#' zoom = 3 +#' ), +#' "Base_tiles2" = list( +#' coords = c(-110, 50), +#' zoom = 5 +#' ), +#' "breweries91" = list( +#' coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))), +#' zoom = 8 +#' ), +#' "atlStorms2005" = list( +#' coords = as.numeric(st_bbox(lines)), +#' options = list(padding = c(110, 110)) +#' ), +#' "gadmCHE" = list( +#' coords = as.numeric(st_bbox(polys)), +#' options = list(padding = c(2, 2)), +#' fly = TRUE +#' ) +#' ) +#' +#' # Opacity control settings ########## +#' opacityControl <- list( +#' "breweries91" = list( +#' min = 0, +#' max = 1, +#' step = 0.1, +#' default = 1, +#' width = '100%', +#' class = 'opacity-slider' +#' ) +#' ) +#' +#' # Legends ########## +#' legends <- list( +#' "breweries91" = "
Legend for breweries
" +#' ) +#' +#' leaflet() %>% +#' ## Baselayer +#' addTiles(group = "Base_tiles1") %>% +#' addProviderTiles("CartoDB", group = "Base_tiles2") %>% +#' +#' ## Overlays +#' addCircleMarkers(data = breweries91, group = "breweries91") %>% +#' addPolylines(data = lines, group = "atlStorms2005") %>% +#' addPolygons(data = polys, group = "gadmCHE") %>% +#' +#' ## Extend Layers Control +#' extendLayerControl( +#' view_settings, home_btns = TRUE, +#' home_btn_options = list( +#' "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), +#' "Base_tiles2" = list(text = '❤️', cursor = 'pointer'), +#' "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), +#' "breweries91" = list(text = '🌎', styles = 'background-color: red'), +#' "gadmCHE" = list(text = '🌎', styles = 'float: none;') +#' ), +#' opacityControl = opacityControl, +#' includelegends = TRUE +#' ) %>% +#' +#' ## LayersControl +#' addLayersControl( +#' baseGroups = c("Base_tiles1", "Base_tiles2"), +#' overlayGroups = c("breweries91", "atlStorms2005", "gadmCHE"), +#' options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) +#' ) +#' +#' @export +extendLayerControl <- function(map, view_settings, home_btns = FALSE, + home_btn_options = list(), setviewonselect = TRUE, + opacityControl = list(), + includelegends = TRUE) { + + # Initialize data structures for view settings and home buttons + view_data <- list() + home_data <- list() + + # Loop over each layer to populate view_data and home_data + for (layer in names(view_settings)) { + setting <- view_settings[[layer]] + + # Store coordinates and zoom options for setView or fitBounds + if (length(setting$coords) == 2) { + view_data[[layer]] <- list( + coords = setting$coords, + zoom = setting$zoom, + fly = ifelse(is.null(setting[["fly"]]), FALSE, setting[["fly"]]), + options = setting$options + ) + } else if (length(setting$coords) == 4) { + view_data[[layer]] <- list( + bounds = setting$coords, + fly = ifelse(is.null(setting[["fly"]]), FALSE, setting[["fly"]]), + options = setting$options + ) + } + + # Store home button data if enabled + if (isTRUE(home_btns)) { + home_data[[layer]] <- as.list(c( + layer = layer, home_btn_options[[layer]] + )) + } + } + + # Add deps & Pass view and home button data using invokeMethod + map$dependencies <- c(map$dependencies, layerViewControlDependencies()) + leaflet::invokeMethod( + map, + NULL, + 'extendLayerControl', + view_data, + home_data, + setviewonselect, + opacityControl, + includelegends + ) +} + + +layerViewControlDependencies <- function() { + list( + htmltools::htmlDependency( + "layerViewControl", + '0.0.1', + system.file("htmlwidgets/lib/layerviewcontrol", package = "leafem"), + script = "layerviewcontrol.js", + stylesheet = "layerviewcontrol.css" + )) +} diff --git a/R/logo.R b/R/logo.R index 750354b..ab714a2 100644 --- a/R/logo.R +++ b/R/logo.R @@ -123,7 +123,6 @@ addLogo <- function(map, } #' updateLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export updateLogo <- function(map, img, layerId) { @@ -137,7 +136,6 @@ updateLogo <- function(map, img, layerId) { } #' removeLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export removeLogo <- function(map, layerId) { @@ -149,7 +147,6 @@ removeLogo <- function(map, layerId) { } #' hideLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export hideLogo <- function(map, layerId) { @@ -161,7 +158,6 @@ hideLogo <- function(map, layerId) { } #' showLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export showLogo <- function(map, layerId) { diff --git a/inst/.gitignore b/inst/.gitignore new file mode 100644 index 0000000..038d718 --- /dev/null +++ b/inst/.gitignore @@ -0,0 +1 @@ +testing diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css new file mode 100644 index 0000000..3aec75f --- /dev/null +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css @@ -0,0 +1,8 @@ +.leaflet-control-layers-overlays .legend { + width: 100% !important; + padding: 0 !important; + box-shadow: unset !important; + margin: 0; + left: 13px; +} + diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js new file mode 100644 index 0000000..e7d615d --- /dev/null +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -0,0 +1,160 @@ +LeafletWidget.methods.extendLayerControl = function(viewSettings, homeSettings, setviewonselect, opacityControl, includelegends) { + const map = this; + + // Handle view settings for each layer on 'overlayadd' or 'baselayerchange' + map.on('overlayadd baselayerchange', function(e) { + let setting = viewSettings[e.name]; + if (setting && setviewonselect) handleView(map, setting); + }); + + // Handle home buttons + if (homeSettings) { + setTimeout(() => { + Object.entries(homeSettings).forEach(([layer, options]) => { + let homeButton = document.createElement('span'); + Object.assign(homeButton.style, { + cursor: options.cursor || 'pointer', + cssText: options.styles || 'float: inline-end;' + }); + homeButton.className = options.class || 'leaflet-home-btn'; + homeButton.dataset.layer = layer; + homeButton.innerHTML = options.text || '🏠'; + + appendToLabelHome(layer, homeButton); + + homeButton.addEventListener('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + let setting = viewSettings[this.dataset.layer]; + if (setting) handleView(map, setting); + }); + }); + }, 20); + } + + // Handle opacity control + if (opacityControl) { + // Helper function to check if the layer is active + function isLayerActive(layerName) { + return Object.keys(map.layerManager._groupContainers).some(function(layer) { + return layer === layerName; + }); + } + + setTimeout(() => { + Object.entries(opacityControl).forEach(([layer, options]) => { + let sliderContainer = document.createElement('div'); + let slider = document.createElement('input'); + Object.assign(slider, { + type: 'range', + min: options.min || 0, + max: options.max || 1, + step: options.step || 0.1, + value: options.default || 1, + style: `width: ${options.width || '100%'};` + }); + slider.className = options.class || 'leaflet-opacity-slider'; + sliderContainer.style.display = 'none'; + sliderContainer.appendChild(slider); + + appendToLabel(layer, sliderContainer); + + slider.addEventListener('input', function() { + let opacityVal = parseFloat(this.value); + Object.values(map.layerManager._byGroup[layer]).forEach(layer => { + if (layer.setOpacity) { + layer.setOpacity(opacityVal); + } else if (layer.setStyle) { + layer.setStyle({ opacity: opacityVal, fillOpacity: opacityVal }); + } + }); + }); + + // Initialize slider visibility based on the current state of the layer + if (isLayerActive(layer)) { + sliderContainer.style.display = 'block'; + } + + // Handle layer visibility + map.on('overlayadd overlayremove baselayerchange', function(e) { + if (e.name === layer) { + sliderContainer.style.display = (e.type === 'overlayadd' || e.type === 'baselayerchange') ? 'block' : 'none'; + } + }); + + }); + }, 30); + } + + // Handle legends + if (includelegends) { + function moveLegends() { + Object.entries(map.controls._controlsById).forEach(([controlId, control]) => { + let legendContainer = control._container; + if (legendContainer) { + appendToLabel(controlId, legendContainer) + } + }); + + // Fix for leaflegend package + let elements = document.querySelectorAll('[class*="leaflegend-group"]'); + elements.forEach(function(element) { + // Find the class that starts with 'leaflegend-group-' + let groupClass = Array.from(element.classList).find(cls => cls.startsWith('leaflegend-group-')); + + if (groupClass) { + // Extract everything after 'leaflegend-group-' + let groupName = groupClass.split('leaflegend-group-')[1]; + appendToLabel(groupName, element) + } + }); + + } + setTimeout(moveLegends, 40); + map.on('overlayadd baselayerchange', () => setTimeout(moveLegends, 20)); + } +}; + + + +// function to handle setting view or bounds +function handleView(map, setting) { + if (setting.coords) { + const method = setting.fly ? 'flyTo' : 'setView'; + map[method]([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } else if (setting.bounds) { + const method = setting.fly ? 'flyToBounds' : 'fitBounds'; + const bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; + map[method](bounds, setting.options); + } +} + +// function to find the correct label element +function findLabel(layerName) { + return Array.from(document.querySelectorAll('.leaflet-control-layers label:not([class])')).find(label => + $(label).find("span")[0].textContent.trim() === layerName + ); +} + +// function to append a child to the label element +function appendToLabel(layer, childElement) { + const label = findLabel(layer); + if (label) { + let labelDiv = label.querySelector('div') || document.createElement('div'); + labelDiv.appendChild(childElement); + label.appendChild(labelDiv); + } +} + +// function to append a child to the label element +function appendToLabelHome(layer, childElement) { + const label = findLabel(layer); + if (label) { + let labelDiv = label.querySelector('div') + if (labelDiv) { + labelDiv.appendChild(childElement); + } else { + label.appendChild(childElement); + } + } +} diff --git a/inst/test_layerviewcontrol.R b/inst/test_layerviewcontrol.R new file mode 100644 index 0000000..d80ea89 --- /dev/null +++ b/inst/test_layerviewcontrol.R @@ -0,0 +1,190 @@ +library(sf) +library(shiny) +library(leaflet) +library(leaflet.extras) +library(leaflegend) +library(leafem) +options("shiny.autoreload" = TRUE) + +# Example data ########## +breweries91 <- st_as_sf(breweries91) +lines <- st_as_sf(atlStorms2005) +data("gadmCHE") +gadmCHE@data$x <- sample(c('A', 'B', 'C'), nrow(gadmCHE@data), replace = TRUE) +polys <- st_as_sf(gadmCHE) +overlay1 <- "Overlay with Legend (orange)" +overlay2 <- "Overlay with Legend (blue)" + +n = 300 +df1 = data.frame(id = 1:n, + x = rnorm(n, 20, 3), + y = rnorm(n, -49, 1.8)) +pts = st_as_sf(df1, coords = c("x", "y"), crs = 4326) +dfnew <- local({ + n <- 300; x <- rnorm(n, mean = 30); y <- rnorm(n, 50) + z <- sqrt(x ^ 2 + y ^ 2); z[sample(n, 10)] <- NA + data.frame(x, y, z) +}) +palnew <- colorNumeric("OrRd", dfnew$z) +palnew2 <- colorNumeric("Blues", dfnew$z) + +# View settings: Each entry is a list with 'coords', 'zoom', and optional 'options' (e.g., padding) ########## +view_settings <- list( + "Base_tiles1" = list( + coords = c(20, 50) + , zoom = 3 + ), + "Base_tiles2" = list( + coords = c(-110, 50) + , zoom = 5 + ), + "breweries91" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) + , zoom = 8 + , options = NULL + ), + "atlStorms2005" = list( + coords = as.numeric(st_bbox(lines)) + # , options = list(padding = c(10, 10), maxZoom = 6) + ), + "gadmCHE" = list( + coords = as.numeric(st_bbox(polys)) + , options = list(padding = c(10, 10)) + , fly = TRUE + ), + "random_points" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(pts)))) + , zoom = 7 + , fly = TRUE + ), + overlay1 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) , + overlay2 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) +) +names(view_settings)[names(view_settings)=="overlay1"] <- overlay1 +names(view_settings)[names(view_settings)=="overlay2"] <- overlay2 + +# Create leaflet map and apply the layer control function ######### +ui <- fluidPage( + ## Custom CSS ########### + tags$head(tags$style(" + .home-btn-layer3 { + background-color: gray; + padding; 4px + } + .home-btn-layer3 { + background-image: url(https://png.pngtree.com/png-clipart/20190904/original/pngtree-zoom-in-icon-png-image_4490537.jpg); + content: ''; + color: transparent; + width: 22px; + height: 22px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + ")), + ## Input + Map ########### + tags$div(style="display: inline-flex", + selectInput("layercontrol", "Layer Control", + choices = c("layercontrol", "groupedlayercontrol")), + shiny::checkboxInput("includelegends", "Inlcude Legends", value = TRUE), + shiny::checkboxInput("homebtns", "Home Buttons", value = TRUE), + shiny::checkboxInput("setviewonselect", "Set View on select", value = TRUE), + ), + leafletOutput("map", height = 800) +) + +## Server ################## +server <- function(input, output, session) { + output$map <- renderLeaflet({ + + factorPal <- colorFactor(c('#1f77b4', '#ff7f0e' , '#2ca02c'), gadmCHE@data$x) + binPal <- colorBin('Set1', lines$MaxWind, bins = 4) + quantPal <- colorQuantile('Reds', lines$MaxWind, n = 3) + + m <- leaflet() %>% + ## Baselayer ########## + addTiles(group = "Base_tiles1") %>% + addProviderTiles("CartoDB", group = "Base_tiles2") %>% + + ## Overlays ########## + addCircleMarkers(data = breweries91, group = "breweries91") %>% + addCircleMarkers(data = pts, opacity = 1, fillOpacity = .4, + group = "random_points", color = "red", weight = 1) %>% + # addLegendSize(values = 1, color = 'red', shape = 'circle', breaks = 1, group = "random_points", layerId="random_points") %>% + addLegendImage(images = makeSymbol(shape="circle", color = "red", opacity = 1, fillOpacity = .4, width = 10), + labels = "", group = "random_points", layerId="random_points", + orientation = 'horizontal') %>% + addPolylines(data = lines, color = ~quantPal(MaxWind), label=~MaxWind, group = "atlStorms2005") %>% + addLegendQuantile(data = lines, pal = quantPal, values = ~MaxWind, numberFormat = NULL, + group = "atlStorms2005", position = 'topright') %>% + addPolygons(data = polys, color = ~factorPal(x), label=~x, group = "gadmCHE") %>% + addLegendFactor(pal = factorPal, shape = 'polygon', fillOpacity = .5, + opacity = 0, values = ~x, + position = 'topright', data = gadmCHE, group = 'gadmCHE') %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), label=~z, group = overlay1) %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), label=~z, group = overlay2) %>% + addLegendNumeric(orientation = "horizontal", width = 180, height = 20, + data = dfnew, pal = palnew, layerId = overlay1, + values = ~z, group = overlay1, position = "bottomleft") %>% + addLegend(data = dfnew, pal = palnew2, layerId = overlay2, values = ~z, group = overlay2, position = "bottomleft") %>% + + ## extendLayerControl ########## + extendLayerControl(view_settings + , includelegends = input$includelegends + , home_btns = input$homebtns + , setviewonselect = input$setviewonselect + , home_btn_options = list( + "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn home-btn-layer1'), + "Base_tiles2" = list(text = '❤️', cursor = 'pointer', class = 'homebtn home-btn-layer2'), + "random_points" = list(text = '🌎', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (orange)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (blue)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3') + ) + , opacityControl = list( + "random_points" = list(min= 0, max= 1, step= 0.01, default= 0.7, width= '140px'), + "Overlay with Legend (orange)" = list(min= 0.1, max= 0.8, step= 0.1, default= 1), + "Overlay with Legend (blue)" = list(default= 0.5) + ) + ) + + ## LayersControls ########## + if (input$layercontrol == "layercontrol") { + m %>% + addLayersControl( + baseGroups = c("Base_tiles1", "Base_tiles2" + ), + overlayGroups = c("breweries91", "random_points", + overlay1, overlay2, + "atlStorms2005", "gadmCHE"), + options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) + ) + } else { + m %>% + addGroupedLayersControl( + baseGroups = c("Base_tiles1","Base_tiles2"), + overlayGroups = list( + "Group1" = c("breweries91","random_points", + overlay1, overlay2), + "Group2" = c("atlStorms2005", "gadmCHE")), + position = "topright", + options = groupedLayersControlOptions(groupCheckboxes = TRUE, + collapsed = FALSE, + groupsCollapsable = TRUE, + groupsExpandedClass = "glyphicon glyphicon-chevron-down", + groupsCollapsedClass = "glyphicon glyphicon-chevron-right", + sortLayers = FALSE, + sortGroups = FALSE, + sortBaseLayers = FALSE, + exclusiveGroups = "Group2") + ) + } + + }) +} +shinyApp(ui, server) diff --git a/man/extendLayerControl.Rd b/man/extendLayerControl.Rd new file mode 100644 index 0000000..17faca9 --- /dev/null +++ b/man/extendLayerControl.Rd @@ -0,0 +1,154 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/layerviewcontrol.R +\name{extendLayerControl} +\alias{extendLayerControl} +\title{Extend Layers Control in Leaflet Map} +\usage{ +extendLayerControl( + map, + view_settings, + home_btns = FALSE, + home_btn_options = list(), + setviewonselect = TRUE, + opacityControl = list(), + includelegends = TRUE +) +} +\arguments{ +\item{map}{A `leaflet` or `mapview` object to which the extended layers control will be added.} + +\item{view_settings}{A list specifying the view settings for each layer. Each list element should contain +either: +\itemize{ + \item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 + (bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. + \item \code{zoom}: The zoom level (used for `setView`). + \item \code{fly} (optional): A logical indicating whether to use `flyTo` or `flyToBounds` instead of `setView` or `fitBounds`. + \item \code{options} (optional): Additional options to pass to `setView`, `fitBounds`, or `flyTo`. +}} + +\item{home_btns}{Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. +Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}.} + +\item{home_btn_options}{A list of options to customize the home button appearance and behavior. +Possible options include: +- `text`: The text or emoji to display on the button (default is '🏠'). +- `cursor`: CSS cursor style for the button (default is 'pointer'). +- `class`: CSS class name for the button (default is 'leaflet-home-btn'). +- `styles`: Semicolon separated CSS-string (default is 'float: inline-end;').} + +\item{setviewonselect}{Logical. If `TRUE` (default) sets the view when the layer is selected.} + +\item{opacityControl}{A list specifying the opacity control settings for each layer. Each list element should contain: +\itemize{ + \item \code{min}: Minimum opacity value (default is 0). + \item \code{max}: Maximum opacity value (default is 1). + \item \code{step}: Step size for the opacity slider (default is 0.1). + \item \code{default}: Default opacity value (default is 1). + \item \code{width}: Width of the opacity slider (default is '100%'). + \item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). +}} + +\item{includelegends}{Logical. If `TRUE` (default), appends legends to the layer control. Legends are matched +to layers by their group name. The legends need to be added with corresponding layer IDs.} +} +\value{ +A modified `leaflet` map object with extended layers control including view controls, home buttons, opacity controls, and legends. +} +\description{ +This function extends an existing layers control in a `leaflet` map by adding custom views, home buttons, +opacity controls, and legends. It enhances the functionality of a layers control created with `leaflet` +or `leaflet.extras`. +} +\details{ +This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events +and automatically sets the view or zoom level according to the specified \code{view_settings}. +If `home_btns` is enabled, a home button is added next to each layer in the layer control. +When clicked, it zooms the map to the predefined view of that layer. +The opacity control slider allows users to adjust the opacity of layers. The legend will be appended +to the corresponding layer control, matched by the layer's group name. +} +\examples{ +library(sf) +library(leaflet) +library(leafem) + +# Example data ########## +breweries91 <- st_as_sf(breweries91) +lines <- st_as_sf(atlStorms2005) +polys <- st_as_sf(leaflet::gadmCHE) + +# View settings ########## +view_settings <- list( + "Base_tiles1" = list( + coords = c(20, 50), + zoom = 3 + ), + "Base_tiles2" = list( + coords = c(-110, 50), + zoom = 5 + ), + "breweries91" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))), + zoom = 8 + ), + "atlStorms2005" = list( + coords = as.numeric(st_bbox(lines)), + options = list(padding = c(110, 110)) + ), + "gadmCHE" = list( + coords = as.numeric(st_bbox(polys)), + options = list(padding = c(2, 2)), + fly = TRUE + ) +) + +# Opacity control settings ########## +opacityControl <- list( + "breweries91" = list( + min = 0, + max = 1, + step = 0.1, + default = 1, + width = '100\%', + class = 'opacity-slider' + ) +) + +# Legends ########## +legends <- list( + "breweries91" = "
Legend for breweries
" +) + +leaflet() \%>\% + ## Baselayer + addTiles(group = "Base_tiles1") \%>\% + addProviderTiles("CartoDB", group = "Base_tiles2") \%>\% + + ## Overlays + addCircleMarkers(data = breweries91, group = "breweries91") \%>\% + addPolylines(data = lines, group = "atlStorms2005") \%>\% + addPolygons(data = polys, group = "gadmCHE") \%>\% + + ## Extend Layers Control + extendLayerControl( + view_settings, home_btns = TRUE, + home_btn_options = list( + "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), + "Base_tiles2" = list(text = '❤️', cursor = 'pointer'), + "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), + "breweries91" = list(text = '🌎', styles = 'background-color: red'), + "gadmCHE" = list(text = '🌎', styles = 'float: none;') + ), + opacityControl = opacityControl, + includelegends = TRUE + ) \%>\% + + ## LayersControl + addLayersControl( + baseGroups = c("Base_tiles1", "Base_tiles2"), + overlayGroups = c("breweries91", "atlStorms2005", "gadmCHE"), + options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) + ) + +}