walkerke / mapgl

R interface to Mapbox GL JS v3 and Maplibre GL JS
https://walker-data.com/mapgl
Other
91 stars 5 forks source link

Feature request: add a generic function that mimics `mapview::mapview()` #57

Open atsyplenkov opened 6 days ago

atsyplenkov commented 6 days ago

Hi Kyle 👋

Could you please consider adding a function similar to what mapview::mapview() does? I.e., a generic one that takes an sf/sfc object and plots it depending on the geometry. For example, I foresee it to be something like the following.

I am interested because sometimes I just want to visually inspect the geometry, but I found mapview a bit slow. And mapgl is so responsive!

If you like the approach, I am more than happy to create a PR. It can be scaled for terra support.

library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(mapgl)

mapglview <- function(
    x,
    fill_color = "#1f77b4",
    fill_opacity = 0.7,
    line_color = "#000000",
    line_width = 1,
    circle_radius = 6,
    circle_color = "#1f77b4",
    circle_opacity = 0.7,
    popup = NULL,
    ...) {
  # Get all attribute names if popup is NULL
  if (is.null(popup)) {
    popup <- names(x)[!names(x) %in% attr(x, "sf_column")]
  }

  # Create formatted popup content
  popup_template <- paste(
    sprintf("<strong>%s: </strong>{%s}", popup, popup),
    collapse = "<br>"
  )

  # Create popup for each feature
  x$popup_info <- apply(sf::st_drop_geometry(x[popup]), 1, function(row) {
    glue::glue(popup_template, .envir = as.list(row))
  })

  # Initialize map centered on data
  m <- maplibre(bounds = x, ...)

  # Add source
  m <- add_source(m, "data", x)

  # Determine geometry type
  geom_type <- sf::st_geometry_type(x, by_geometry = FALSE)

  # Add appropriate layers based on geometry type
  if (grepl("POLYGON", geom_type)) {
    m <- m %>%
      add_fill_layer(
        id = "data_fill",
        source = "data",
        fill_color = fill_color,
        fill_opacity = fill_opacity,
        popup = "popup_info"
      ) %>%
      add_line_layer(
        id = "data_line",
        source = "data",
        line_color = line_color,
        line_width = line_width
      )
  } else if (grepl("LINE", geom_type)) {
    m <- m %>%
      add_line_layer(
        id = "data_line",
        source = "data",
        line_color = line_color,
        line_width = line_width,
        popup = "popup_info"
      )
  } else if (grepl("POINT", geom_type)) {
    m <- m %>%
      add_circle_layer(
        id = "data_point",
        source = "data",
        circle_color = circle_color,
        circle_opacity = circle_opacity,
        circle_radius = circle_radius,
        popup = "popup_info"
      )
  }

  # Add navigation control
  m <- add_navigation_control(m)

  return(m)
}

# Set seed for reproducibility
set.seed(1234)

# Define the bounding box for Washington DC (approximately)
bbox <- st_bbox(
  c(
    xmin = -77.119759,
    ymin = 38.791645,
    xmax = -76.909393,
    ymax = 38.995548
  ),
  crs = st_crs(4326)
)

# Generate 30 random points within the bounding box
random_points <- st_as_sf(
  data.frame(
    id = 1:30,
    lon = runif(30, bbox["xmin"], bbox["xmax"]),
    lat = runif(30, bbox["ymin"], bbox["ymax"])
  ),
  coords = c("lon", "lat"),
  crs = 4326
)

# Assign random categories
categories <- c("music", "bar", "theatre", "bicycle")
random_points <- random_points %>%
  mutate(category = sample(categories, n(), replace = TRUE))

polygons <- sf::read_sf(system.file("shape/nc.shp", package = "sf"))

# mapglview(polygons)
# mapglview(random_points)

Positron_kjfaDlQTaP

Created on 2024-11-13 with reprex v2.1.0

e-kotov commented 1 day ago

I was thinking the same in the past few weeks. It's a bit annoying having to type out so much code to have a quick preview of the dataset with popups of all variables upon clicking a feature. For full customization I would of course go for the full syntax of {mapgl} functions, but fore quick previews I still use mapview() with mapviewOptions(platform='leafgl'), as it is just faster to type right now.