Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
atsyplenkov opened this issue Nov 13, 2024 · 10 comments
Open
Labels
enhancement New feature or request

Comments

@atsyplenkov
Copy link

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
Copy link

e-kotov commented Nov 17, 2024

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.

@walkerke walkerke added the enhancement New feature or request label Jan 9, 2025
@tim-salabim
Copy link

FWIW, I am just starting to look into mapgl as a possible platform for mapview, mainly for 2 reasons:

  1. maplibre globe view - which I currently cannot find in mapgl, am I correct @walkerke to assume that globe view is not yet available?
  2. geoarrow/deck.gl-layers integration. This is ridiculously fast and scales for very very large data (RAM limited really). See https://fosstodon.org/@tim_salabim/113239194257725947 for a peek of rendering 1M+ building footprints rendered onto a leaflet map. For the leaflet integration, we need to go through a lot of hoops, but for maplibre this should be much easier.

I am just starting to get an idea of the API of mapgl, so this may still be a while out, but if someone is keen on helping out, feel free to open a PR in mapview, which I think is the correct place for such a quick preview function, as we already have a lot of helper functions needed for preparing arbitrary data to be put on a web map.

@walkerke
Copy link
Owner

walkerke commented Jan 20, 2025

Excellent @tim-salabim! A couple things:

-- Globe view is supported in mapgl / MapLibre with maplibre() |> set_projection("globe"). This is in the latest CRAN release. As opposed to Mapbox, it doesn't appear that there is a top-level projection parameter so the set_projection() function is required.

-- Others have brought up the idea of supporting deck.gl layers in mapgl (e.g. #37) but I don't know how much development time that would take.

-- There are a couple limitations of mapgl worth considering that I may end up resolving. As opposed to other R mapping packages, mapgl doesn't include color palette functions, and relies on users setting up colors similar to how they'd do it in Mapbox / MapLibre themselves. It also doesn't include automated legend support (which I suppose Leaflet doesn't really do either).

@tim-salabim
Copy link

tim-salabim commented Feb 1, 2025

@walkerke here's a first impression of what geoarrow/deck.gl-layers is capable of (rendering 1+M building footprints in Utah):

Image

There's quite a lot left to implement, but this does look promising IMHO :-)

Note, the standard geojson layer takes about 30 secs to load! somehow the gif won't render properly, so you'd have to take my word for it

@kmcd39
Copy link
Contributor

kmcd39 commented Mar 17, 2025

Hm, i was thinking of adding a PR either here or in mapview to support something like this because I've also been craving a quick-map function for maplibre/mapgl.

But the adding to mapview felt less approachable -- just learning the existing code and structure of the package enough to add to it -- felt like a bigger lift than writing a quick function similar to what @atsyplenkov already has above (but with more flexibility to support something like the zcol parameter from mapview and to work as rendering a new map with basetiles vs adding layers). But, i can potentially plug into some of this

@tim-salabim
Copy link

Hi @kmcd39
thanks for the comment. I feel similar about the code base of mapview, even though I am the maintainer 🤕 .

My current plan is to finish https://github.com/tim-salabim/geoarrowDeckgl before moving on to implementing anything in mapview (see also the discussion in #71).
However, if you have the time and would like to get started on preparing a PR, I could set up a branch and add the necessary scaffolding for you to start implementing functionality for maplibre/mapbox. In the beginning, a simple function should be enough. We can then iterate to make everything more flexible as needed (most flexible stuff such as addFeatures actually lives in package leafem). I also feel like mapview could benefit from a core rewrite to slim down the code base to a more maintainable level, so this could be a good starting point.

In any case, let me know if you would like to contribute a maplibre/mapbox based mapview method while I still finish up geoarrowDeckgl.

@kmcd39
Copy link
Contributor

kmcd39 commented Mar 17, 2025

Okay!

@tim-salabim I have a generic function in my own repo for now -- mapglview

I used code from @atsyplenkov above to make the tooltips and existing code i had to allow a several options for color interpolation. I think it's pretty smooth and flexible as a starting place!

I implemented the "start a new map or add a layer" very differently from what is currently done in mapview. Instead of adding the + sign as a method to add layers, the function adapts based on the class of the first argument.

Some examples:

Image

Image

@atsyplenkov
Copy link
Author

However, if you have the time and would like to get started on preparing a PR, I could set up a branch and add the necessary scaffolding for you to start implementing functionality for maplibre/mapbox. In the beginning, a simple function should be enough.

@tim-salabim @kmcd39 I can help you with that too. If you can create a special branch for it — would be terrific.

@tim-salabim
Copy link

@kmcd39 @atsyplenkov I've created an issue and a corresponding branch in the mapview repo for development of mapgl support.

Feel free to fork and start hacking away :-)

And thanks for your support on this!!
Any questions, don't hesitate to ask (probably in the above mentioned issue rather than here)

@tim-salabim
Copy link

@kmcd39 just FYI, mapview generally uses the + sign to enable direct supply of data and that was then expanded to mapview objects as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants