mapgl icon indicating copy to clipboard operation
mapgl copied to clipboard

New Globe Minimap is instead of updating the existing one in Shiny app

Open ar-puuk opened this issue 7 months ago • 4 comments

As discussed in #97.

Hi, posting this here because I am not sure if this is a bug or (my) skill issue.

I am trying to create a shiny app using mapgl, and I have this issue where a new globe minimap is added when the geographic location on the map changes. This results in the following: (I changed the states from Colorado, California, Arizona, and Arkansas)

image

I was wondering if there is a way for the globe minimap to update rather than it being added every time a different state is selected?

# Minimal reprex for mapgl add_globe_minimap bug(?)

library(shiny)
library(mapgl)
library(sf)
library(tigris)
library(dplyr)

# Cache option for tigris
options(tigris_use_cache = TRUE)

# Prepare simple state geometries
states_sf <- tigris::states() |>
  sf::st_transform(crs = 3857) |>
  sf::st_zm(drop = TRUE, what = "ZM")

ui <- shiny::fluidPage(
  mapgl::story_maplibre(
    map_id      = "map",
    sections    = list(
      "intro" = mapgl::story_section(
        title   = "US States Explorer",
        content = list(
          shiny::selectInput(
            inputId = "state",
            label   = "Select a state:",
            choices = state.name,
            selected = "Colorado"
          )
        )
      )
    )
  )
)

server <- function(input, output, session) {

  # Reactive: geometry for selected state
  sel_state <- reactive({
    states_sf |> dplyr::filter(NAME == input$state)
  })

  # Initial map render
  output$map <- mapgl::renderMaplibre({
    mapgl::maplibre(
      style      = mapgl::carto_style("voyager"),
      bounds     = sel_state(),
      scrollZoom = FALSE
    ) |>
      mapgl::set_projection("globe") |>
      mapgl::add_globe_minimap(position = "bottom-right") |>
      mapgl::add_layer(
        id      = "state_outline",
        type = "fill",
        source  = sel_state(),
        paint = list(
          "fill-color" = "rgba(0,0,0,0)",
          "fill-outline-color" = "red"
        )
      )
  })


  # On entering the intro section, zoom to the selected state via proxy
  mapgl::on_section("map", "intro", {
    req(input$state)
    mapgl::maplibre_proxy("map") |>
      mapgl:: set_filter(
        layer_id = "state_outline",
        filter   = list("==", "NAME", input$state)
      ) |>
      mapgl::fit_bounds(
        sel_state(),
        animate = TRUE
      )
  })
}

shinyApp(ui, server)

ar-puuk avatar May 12 '25 23:05 ar-puuk

So, I think this is still a bug but there are ways you can get what you want without getting the bug fixed.

Typically with mapgl, you want to keep any reactive elements out of the initial call to renderMaplibre() or renderMapboxgl() if you can. This will avoid unnecessary map re-draws. Though it may relate to https://github.com/walkerke/mapgl/issues/17 which I still haven't solved.

Here's an app that gets your desired behavior. If you were to want to start on Colorado, you'd need a separate hard-coded Colorado object that you'd use when you initialize the map.

library(shiny)
library(mapgl)
library(sf)
library(tigris)
library(dplyr)

# Cache option for tigris
options(tigris_use_cache = TRUE)

# Prepare simple state geometries
states_sf <- states()

ui <- fluidPage(story_maplibre(
  map_id = "map",
  sections = list(
    "intro" = story_section(
      title = "US States Explorer",
      content = list(
        shiny::selectInput(
          inputId = "state",
          label = "Select a state:",
          choices = state.name,
          selected = "Colorado"
        ),
        shiny::tags$p("Scroll down to see the state boundary")
      )
    ),
    "state_detail" = story_section(
      title = NULL,
      content = list(
        uiOutput("state_info"),
        p("Scroll up to return to the overview")
      )
    )
  )
))

server <- function(input, output, session) {
  # Reactive: geometry for selected state
  sel_state <- reactive({
    states_sf |> dplyr::filter(NAME == input$state)
  })

  # Initial map render with ALL states
  output$map <- renderMaplibre({
    maplibre(
      style = carto_style("voyager"),
      center = c(-98.5, 39.5),
      # Center of US
      zoom = 3,
      scrollZoom = FALSE
    ) |>
      set_projection("globe") |>
      add_globe_minimap(position = "top-right") |>
      add_line_layer(
        id = "states",
        source = states_sf,
        line_color = "red",
        line_width = 2
      )
  })

  output$state_info <- renderUI({
    h3(paste("Welcome to", input$state))
  })

  # On intro section - show overview
  on_section("map", "intro", {
    maplibre_proxy("map") |>
      set_filter(
        layer_id = "states",
        filter = NULL
      ) |>
      fly_to(
        center = c(-98.5, 39.5),
        zoom = 3,
        bearing = 0,
        pitch = 0,
        duration = 1500
      )
  })

  # On state detail section - zoom to selected state
  on_section("map", "state_detail", {
    req(input$state)

    maplibre_proxy("map") |>
      set_filter(
        layer_id = "states",
        filter = list("==", "NAME", input$state)
      ) |>
      fit_bounds(sel_state(), animate = TRUE)
  })
}


shinyApp(ui, server)

walkerke avatar May 18 '25 19:05 walkerke

Ahh, thanks @walkerke. I now understand what the issue is. When the bound is assigned in the "intro" page map, the minimap is added instead of being updated. It seems like the user input and the reactive map need to be on different pages.

I am basically trying to practice mapgl by combining two of your examples to make a shiny app to create a population pyramid for a county/parish user can select.

Therefore, I wanted to provide a state input on page 1 and a county input on page 2. Let me see if I can make it work.

Also, possibly a minor issue. Selecting Alaska gives weird map extents. Not sure if it's an issue with shiny or mapgl projections.

Image

ar-puuk avatar May 18 '25 19:05 ar-puuk

You're observing an issue with fit_bounds() in Globe view for features that cross the antimeridian. Perhaps you could code in some Alaska-specific fly-to logic, where you use fly_to() to fly to a view focused on Alaska rather than the bounds.

walkerke avatar May 18 '25 19:05 walkerke

Thanks a lot for the help. I will first fix the other issues and then work on it.

ar-puuk avatar May 18 '25 19:05 ar-puuk