Search code examples
javascriptrshinyr-leaflet

How to add markers in a Leaflet minimap?


I have created an application in Shiny which allows for selecting a place and then displays a datatable with information and a marker at the corresponding position in the map. The marker is set in the map but not in the minimap. I would like to extend my app such that the marker is also added in the minimap. How to do this?

This is the code of the application and its libraries.

library(shiny)
library(leaflet)
library(leaflet.extras)
library(shinyjs)
library(tidyverse)

leafletMiniMapDependencies <- function() {
  list(
    htmltools::htmlDependency(
      "leaflet-minimap",
      "3.3.1",
      "htmlwidgets/plugins/Leaflet-MiniMap",
      package = "leaflet",
      script = c("Control.MiniMap.min.js", "Minimap-binding.js"),
      stylesheet = c("Control.MiniMap.min.css")
    )
  )
}

addMiniMap <- function(
  map,
  position = "bottomleft",
  width = 300,
  height = 225,
  collapsedWidth = 19,
  collapsedHeight = 19,
  zoomLevelOffset = -5,
  zoomLevelFixed = 6,
  centerFixed = c(28, -15.5),
  zoomAnimation = FALSE,
  toggleDisplay = T,
  autoToggleDisplay = FALSE,
  minimized = FALSE,
  aimingRectOptions = list(color = "rgba(0,0,0,0)", weight = 1, clickable = FALSE),
  shadowRectOptions = list(color = "#000000", weight = 1, clickable = FALSE,
                           opacity = 0, fillOpacity = 0),
  strings = list(hideText = "Hide MiniMap", showText = "Show MiniMap"),
  tiles = NULL,
  mapOptions = list(minZoom = 6, maxZoom = 6)
) {
  
  # determin tiles to use
  tilesURL <- NULL
  tilesProvider <- NULL
  if (!is.null(tiles)) {
    if (tiles %in% providers) {
      map$dependencies <- c(map$dependencies, leafletProviderDependencies())
      tilesProvider <- tiles
    } else {
      tilesURL <- tiles
    }
  }
  
  map$dependencies <- c(map$dependencies, leafletMiniMapDependencies())
  invokeMethod(
    map
    , getMapData(map)
    , "addMiniMap"
    , tilesURL
    , tilesProvider
    , position
    , width
    , height
    , collapsedWidth
    , collapsedHeight
    , zoomLevelOffset
    , zoomLevelFixed
    , centerFixed
    , zoomAnimation
    , toggleDisplay
    , autoToggleDisplay
    , minimized
    , aimingRectOptions
    , shadowRectOptions
    , strings
    , mapOptions
  )
}

topologia <- data.frame(
  etiqueta = c("El Hierro", "Fuerteventura", "Gran Canaria",  "La Palma", "Lanzarote", "Tenerife"),
  longitud = c(-18.038165, -14.004885, -15.606239, -17.857377, -13.636218, -16.621481),
  latitud = c(27.743606, 28.400363, 27.958157, 28.655223, 29.039468, 28.293378),
  geocode = c("ES703", "ES704", "ES705", "ES707", "ES708", "ES709"),
  valor = 1:6
)

topologia <-  topologia %>% 
  mutate(
    longitud = as.numeric(gsub("\\.", "", longitud)) / 10^6,
    latitud = as.numeric(gsub("\\.", "", latitud)) / 10^6
  )

# Define UI for application
ui <- fluidPage(
  tags$head(
    tags$style(HTML("
      body{
      .leaflet-control-search{
        position: absolute !important;
        left: 50% !important;
        transform: translateX(-50%) !important;
        margin-left: 0 !important;
      }

      .leaflet-top.leaflet-left{
        width: 100%;
      }
      "))
    
  ),
  
  useShinyjs(),
  titlePanel("Mapa"),
  sidebarPanel(
    selectInput("provincia_seleccionada", "Provincia", choices = unique(topologia$etiqueta)), 
    actionButton("consultar", "Consulta")
  ),
  column(width = 12, leafletOutput("map", height = "600px")),
  column(width = 12, dataTableOutput("data"))
)

# Define server logic
server <- function(input, output, session) {
  
  # processing with click of consultar data
  topologia_consulta <- eventReactive(input$consultar, {
    topologia %>% filter(etiqueta %in% input$provincia_seleccionada) %>% 
      filter(!is.na(latitud) & !is.na(longitud))
  })
  
  # show data in datatable
  output$data <- renderDataTable(topologia_consulta())
  
  
  # Render the map
  output$map <- renderLeaflet({
    leaflet() %>%
      leaflet(options = leafletOptions(minZoom = 5)) %>%
      addTiles(options = tileOptions(updateWhenIdle = TRUE, updateWhenZooming = FALSE)) %>%
      addFullscreenControl() %>%
      addProviderTiles(providers$OpenStreetMap) %>%
      setView(lat = 40.416775, lng = -3.703790, zoom = 6) %>%
      addMiniMap() %>%
      addEasyButton(easyButton(
        icon = "fa-crosshairs", title = "Locate Me",
        onClick = JS("function(btn, map){ map.locate({setView: true}); }")
      )) %>%
      # addSearchOSM(options = searchOptions(collapsed = FALSE, autoCollapse = TRUE, minLength = 2)) %>%
      leaflet.extras::addSearchOSM(options = searchOptions(collapsed = F)) %>%
      addEasyButton(easyButton(states = list(
        easyButtonState(
          stateName = "Canarias",
          icon = '<strong>Ir a Islas Canarias</strong>',
          title = "Ir a Islas Canarias",
          onClick = JS("
          function(btn, map) {
            map.flyTo(L.latLng(28, -15.5), 7);
            btn.state('Peninsula');
          }")
        ),
        easyButtonState(
          stateName = "Peninsula",
          icon = '<strong>Ir a Península</strong>',
          title = "Ir a Península",
          onClick = JS("
          function(btn, map) {
            map.flyTo(L.latLng(40.416775, -3.703790), 6);
            btn.state('Canarias');
          }")
        )), id = 'botonCanarias', position = 'bottomleft')) %>% 
      
      htmlwidgets::onRender(paste0("
        function(el, t) {
          var myMap = this;
          
       $('#searchtext25').attr('placeholder', 'Buscar localización...');

          $('#searchtext25').on('input', function(){
            if($(this).val() === '')
              $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").hide();
          });

          $('#map ul.search-tooltip').on('mouseup', 'li', function(){
            $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").show();
          });

          $('#map a.search-cancel').on('click', function(){
            $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").hide();
          });
          
        }")) %>% 
      addLayersControl(
        overlayGroups = c("Pintar"),
        options = layersControlOptions(collapsed = TRUE)
      )
    
    
  })
  
 # When click in consultar bottom show the map 
  observeEvent(input$consultar, {
    mapProxy <- leafletProxy("map")
    mapProxy %>%
      clearGroup("Pintar") %>% 
    addAwesomeMarkers(data = topologia_consulta(),
                      lng = ~topologia_consulta()$longitud,
                      lat = ~topologia_consulta()$latitud,
                      group = "Pintar",
                      popup =  ~paste0(
                        "<b>Isla:</b> ", topologia_consulta()$etiqueta, "</b><br/>",
                        "<b>GeoCode:</b> ", topologia_consulta()$geocode, "</b><br/>",
                        "<b>Valor:</b> ", topologia_consulta()$valor, "<br/>"
                        
                      )
    )
  })
 
  
 
  
  }

# Run the application
shinyApp(ui = ui, server = server)

Solution

  • You can use the following event handler inside the htmlwidgets::onRender():

    myMap.on('layeradd', // if we add a layer on the original map
            function(e) {
                if (e.layer._eventParents != null) {
                    var layers = new L.LayerGroup(
                        // set a new layer group consisting out of the initial layer
                        // and a marker at the position of the one from the map
                        [initialLayer, L.marker(e.layer._latlng)]
                    );
                    // assign the layergroup to the minimap
                    myMap.minimap.changeLayer(layers);
                })
    

    enter image description here

    library(shiny)
    library(leaflet)
    library(leaflet.extras)
    library(shinyjs)
    library(tidyverse)
    
    leafletMiniMapDependencies <- function() {
      list(
        htmltools::htmlDependency(
          "leaflet-minimap",
          "3.3.1",
          "htmlwidgets/plugins/Leaflet-MiniMap",
          package = "leaflet",
          script = c("Control.MiniMap.min.js", "Minimap-binding.js"),
          stylesheet = c("Control.MiniMap.min.css")
        )
      )
    }
    
    addMiniMap <- function(
        map,
        position = "bottomleft",
        width = 300,
        height = 225,
        collapsedWidth = 19,
        collapsedHeight = 19,
        zoomLevelOffset = -5,
        zoomLevelFixed = 6,
        centerFixed = c(28, -15.5),
        zoomAnimation = FALSE,
        toggleDisplay = T,
        autoToggleDisplay = FALSE,
        minimized = FALSE,
        aimingRectOptions = list(color = "rgba(0,0,0,0)", weight = 1, clickable = FALSE),
        shadowRectOptions = list(color = "#000000", weight = 1, clickable = FALSE,
                                 opacity = 0, fillOpacity = 0),
        strings = list(hideText = "Hide MiniMap", showText = "Show MiniMap"),
        tiles = NULL,
        mapOptions = list(minZoom = 6, maxZoom = 6)
    ) {
      
      # determin tiles to use
      tilesURL <- NULL
      tilesProvider <- NULL
      if (!is.null(tiles)) {
        if (tiles %in% providers) {
          map$dependencies <- c(map$dependencies, leafletProviderDependencies())
          tilesProvider <- tiles
        } else {
          tilesURL <- tiles
        }
      }
      
      map$dependencies <- c(map$dependencies, leafletMiniMapDependencies())
      invokeMethod(
        map
        , getMapData(map)
        , "addMiniMap"
        , tilesURL
        , tilesProvider
        , position
        , width
        , height
        , collapsedWidth
        , collapsedHeight
        , zoomLevelOffset
        , zoomLevelFixed
        , centerFixed
        , zoomAnimation
        , toggleDisplay
        , autoToggleDisplay
        , minimized
        , aimingRectOptions
        , shadowRectOptions
        , strings
        , mapOptions
      )
    }
    
    topologia <- data.frame(
      etiqueta = c("El Hierro", "Fuerteventura", "Gran Canaria",  "La Palma", "Lanzarote", "Tenerife"),
      longitud = c(-18.038165, -14.004885, -15.606239, -17.857377, -13.636218, -16.621481),
      latitud = c(27.743606, 28.400363, 27.958157, 28.655223, 29.039468, 28.293378),
      geocode = c("ES703", "ES704", "ES705", "ES707", "ES708", "ES709"),
      valor = 1:6
    )
    
    topologia <-  topologia %>% 
      mutate(
        longitud = as.numeric(gsub("\\.", "", longitud)) / 10^6,
        latitud = as.numeric(gsub("\\.", "", latitud)) / 10^6
      )
    
    # Define UI for application
    ui <- fluidPage(
      tags$head(
        tags$style(HTML("
          body{
          .leaflet-control-search{
            position: absolute !important;
            left: 50% !important;
            transform: translateX(-50%) !important;
            margin-left: 0 !important;
          }
    
          .leaflet-top.leaflet-left{
            width: 100%;
          }
          "))
        
      ),
      
      useShinyjs(),
      titlePanel("Mapa"),
      sidebarPanel(
        selectInput("provincia_seleccionada", "Provincia", choices = unique(topologia$etiqueta)), 
        actionButton("consultar", "Consulta")
      ),
      column(width = 12, leafletOutput("map", height = "600px")),
      column(width = 12, dataTableOutput("data"))
    )
    
    # Define server logic
    server <- function(input, output, session) {
      
      # processing with click of consultar data
      topologia_consulta <- eventReactive(input$consultar, {
        topologia %>% filter(etiqueta %in% input$provincia_seleccionada) %>% 
          filter(!is.na(latitud) & !is.na(longitud))
      })
      
      # show data in datatable
      output$data <- renderDataTable(topologia_consulta())
      
      
      # Render the map
      output$map <- renderLeaflet({
        leaflet() %>%
          leaflet(options = leafletOptions(minZoom = 5)) %>%
          addTiles(options = tileOptions(updateWhenIdle = TRUE, updateWhenZooming = FALSE)) %>%
          addFullscreenControl() %>%
          addProviderTiles(providers$OpenStreetMap) %>%
          setView(lat = 40.416775, lng = -3.703790, zoom = 6) %>%
          addMiniMap() %>%
          addEasyButton(easyButton(
            icon = "fa-crosshairs", title = "Locate Me",
            onClick = JS("function(btn, map){ map.locate({setView: true}); }")
          )) %>%
          # addSearchOSM(options = searchOptions(collapsed = FALSE, autoCollapse = TRUE, minLength = 2)) %>%
          leaflet.extras::addSearchOSM(options = searchOptions(collapsed = F)) %>%
          addEasyButton(easyButton(states = list(
            easyButtonState(
              stateName = "Canarias",
              icon = '<strong>Ir a Islas Canarias</strong>',
              title = "Ir a Islas Canarias",
              onClick = JS("
              function(btn, map) {
                map.flyTo(L.latLng(28, -15.5), 7);
                btn.state('Peninsula');
              }")
            ),
            easyButtonState(
              stateName = "Peninsula",
              icon = '<strong>Ir a Península</strong>',
              title = "Ir a Península",
              onClick = JS("
              function(btn, map) {
                map.flyTo(L.latLng(40.416775, -3.703790), 6);
                btn.state('Canarias');
              }")
            )), id = 'botonCanarias', position = 'bottomleft')) %>% 
          
          htmlwidgets::onRender(paste0("
            function(el, t) {
              var myMap = this;
              
              $('#searchtext25').attr('placeholder', 'Buscar localización...');
    
              $('#searchtext25').on('input', function(){
                if($(this).val() === '')
                  $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").hide();
              });
    
              $('#map ul.search-tooltip').on('mouseup', 'li', function(){
                $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").show();
              });
    
              $('#map a.search-cancel').on('click', function(){
                $(\"path.leaflet-interactive[stroke='#3388ff'][stroke-linecap='round']\").hide();
              });
              
              // save initial layer (so that we can remove older markers)
              var initialLayer = myMap.minimap._layer;
              
              myMap.on('layeradd',  // if we add a layer on the original map
                function (e) {
                  if (e.layer._eventParents != null) {
                    var layers = new L.LayerGroup(
                      // set a new layer group consisting out of the initial layer
                      // and a marker at the position of the one from the map
                      [initialLayer, L.marker(e.layer._latlng)]
                    );
                    // assign the layergroup to the minimap
                    myMap.minimap.changeLayer(layers);
                  }
                })
              
            }")) %>% 
          addLayersControl(
            overlayGroups = c("Pintar"),
            options = layersControlOptions(collapsed = TRUE)
          )
        
        
      })
      
      # When click in consultar bottom show the map 
      observeEvent(input$consultar, {
        mapProxy <- leafletProxy("map")
        mapProxy %>%
          clearGroup("Pintar") %>% 
          addAwesomeMarkers(data = topologia_consulta(),
                            lng = ~topologia_consulta()$longitud,
                            lat = ~topologia_consulta()$latitud,
                            group = "Pintar",
                            popup =  ~paste0(
                              "<b>Isla:</b> ", topologia_consulta()$etiqueta, "</b><br/>",
                              "<b>GeoCode:</b> ", topologia_consulta()$geocode, "</b><br/>",
                              "<b>Valor:</b> ", topologia_consulta()$valor, "<br/>"
                              
                            )
          )
      })
      
      
      
      
    }
    
    # Run the application
    shinyApp(ui = ui, server = server)