Leaflet - how to properly pass several layerIds to removeShape() or removeMarker()

1,527 views
Skip to first unread message

Bastien Tran

unread,
Jun 25, 2015, 10:49:50 PM6/25/15
to shiny-...@googlegroups.com
Hi All,

I have a data frame named "polygon" that looks like this:

         lng          lat         layerId     pointId     user
  2.5048828 48.60386       4              1       user 1
  3.8452148 47.81315       4              2       user 1
  0.9667969 47.84266       4              3       user 1
  2.5048828 48.60386       4              1       user 1
         NA       NA          <NA>       <NA>    <NA>
  2.3291016 47.26432       8              5       user 1
  3.6254883 46.61926       8              6       user 1
  0.3735352 46.63435       8              7       user 1
  2.3291016 47.26432       8              5       user 1
         NA       NA           <NA>      <NA>   <NA>
  2.5048828 46.14939      12             9       user 1
  3.5375977 45.01142      12            10      user 1
  0.1098633 44.74673      12            11      user 1
  2.5048828 46.14939      12             9       user 1
         NA       NA          <NA>       <NA>   <NA>

'data.frame':    15 obs. of  5 variables:
 $ lng    : num  2.505 3.845 0.967 2.505 NA ...
 $ lat    : num  48.6 47.8 47.8 48.6 NA ...
 $ layerId: chr  "4" "4" "4" "4" ...
 $ pointId: Factor w/ 9 levels "1","2","3","5",..: 1 2 3 1 NA 4 5 6 4 NA ...
 $ user   : Factor w/ 1 level "user 1": 1 1 1 1 NA 1 1 1 1 NA ...

On the ui.R side I have two actionLinks meant to trigger the following functions in server.R, respectively in order to hide and show the polygons stored in the data frame:

#Hide the polygons
  bindEvent(input$hidePolygons, function() {
    leafletProxy("map") %>% removeShape(unique(polygons[,3]))
  })

#Show the polygons
  bindEvent(input$showPolygons, function() {unique(polygons[,3])
    leafletProxy("map") %>% addPolygons(lng=polygons[,1],lat=polygons[,2],layerId=unique(polygons[,3]))
  })

My problem is that:
1/ when I try to "hide" the polygons this way nothing occurs. I am probably passing the layerId argument to removeShape the wrong way but I can't see how. clearShapes() indeed works but I would like to pick some specific shapes through their layerIds.

2/ when I "show" these shapes they are drawn on top of the former ones. I assume they should be replaced since I add the same type of shape with the same layerIds

3/ depending on the number of rows of NA's and their positions in the polygons data frame, I can get strange behaviours when "showing" the polygons. For example only the first or the second one is (re)drawn. So I may not have fully understood how multiple polygons must be passed.

I have a similar issue with the markers:
#Hide the markers
  bindEvent(input$hideMarkers, function() {
    leafletProxy("map") %>% removeMarker(unique(points[,3]))
  })

doesn't hide anything whilie

#Hide the markers
  bindEvent(input$hideMarkers, function() {
    leafletProxy("map") %>% removeMarker(unique(points[x,3]))
  })

will hide the xth point in my points list...

My main problem is to get several markers or shapes to be removed at the same time.
If possible I would like not to use the groups for that, I mean to use them for a different purpose in this project.

Any idea of where I got wrong?

I have attached the whole scripts if it helps.

Best regards,

Bastien


styles.css
server.R
ui.R

Joe Cheng

unread,
Jun 26, 2015, 10:47:33 AM6/26/15
to Bastien Tran, shiny-...@googlegroups.com
Try reinstalling github from master, the CRAN release has an unfortunate bug with vectorizes removes.

devtools::install_github("rstudio/leaflet")
--
You received this message because you are subscribed to the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/shiny-discuss/f4e5c64b-e5b4-4594-86b3-3ca0ba9f8de3%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bastien Tran

unread,
Jun 27, 2015, 10:12:50 AM6/27/15
to Joe Cheng, shiny-...@googlegroups.com
Thanks Joe,

I reinstalled lealfet from master with no better results. I discovered in the process that R was using four library paths. I made sure that leaflet was removed from each path before reinstalling from master, but maybe I missed something here...

I have also tried to pass a single layerId to removeShape(),  which was successful. It seems that my issue is related to vectorized removes as you point out.

Thinking about it, I believe I have had the same issue before I switched to the CRAN release this week... I then assumed I was doing it the wrong way, maybe it is still the case. If it is a vectorized removeShape() issue I could probably get away with a loop for the scale I mean to reach.

Yihui Xie

unread,
Jun 27, 2015, 3:33:13 PM6/27/15
to Bastien Tran, Joe Cheng, shiny-discuss
It sounds like an installation issue. I just tried the following
minimal example, and I can remove two polygons at the same time by
clicking the button:

library(leaflet)
library(shiny)
shinyApp(
ui = fluidPage(leafletOutput('map'), actionButton('remove', 'Remove')),
server = function(input, output) {
output$map = renderLeaflet({
leaflet() %>%
addPolygons(c(1, 2, 3, NA, 1, 2, 3), c(1, 2, 1, NA, 3, 4, 3),
layerId = c('x1', 'x2'))
})
observeEvent(input$remove, {
leafletProxy('map') %>% removeShape(c('x1', 'x2'))
})
}
)

You may print out sapply(.libPaths(), list.files) to see where exactly
leaflet was installed (or if multiple versions were installed in
different library paths). devtools::session_info() may also be helpful
to tell you if you really installed leaflet from Github successfully.

Regards,
Yihui

Bastien Tran

unread,
Jun 27, 2015, 4:58:31 PM6/27/15
to Yihui Xie, Joe Cheng, shiny-discuss
Thanks Yihui,

Your example works just fine in my environment (and mine still doesn't, I am a bit jealous :)

sapply(.libPaths(), list.files) confirmed I have one single copy of leaflet in `/usr/local/lib/R/site-library`

devtools::session_info() returned  "leaflet     * 1.0.0.9999 2015-06-27 Github (rstudio/leaflet@5a056e3)"

That would suggest I am doing something wrong, it seems it has to do with the way my dataframe is organized, possibly the rows of NAs.

I tried to use your example with my polygon dataframe from earlier and I now have some funny behaviours (that I have had already seen in the past).

If I do this:

library(leaflet)
library(shiny)
x<-readRDS("polygons.Rds")
#x<-rbind(NA,x)


shinyApp(
  ui = fluidPage(leafletOutput('map'), actionButton('remove', 'Remove')),
  server = function(input, output) {
    output$map = renderLeaflet({
      leaflet() %>%
        addPolygons(lng=x$lng, x$lat,
                    layerId = unique(x$layerId))
    })
    observeEvent(input$remove, {
      leafletProxy('map') %>% removeShape(unique(x$layerId))
    })
  }
)

Only the first and third (or last?) polygons are removed. If I uncomment the fourth line and add a row of NAs on top only the second and third polygon are removed. My guess is that my dataframe's structure is the issue, I'll look into that.

Thanks a lot for your help.

All the best,
Bastien

Yihui Xie

unread,
Jun 27, 2015, 6:08:06 PM6/27/15
to Bastien Tran, Joe Cheng, shiny-discuss
Okay, I just tested your data, and the culprit was NA in x$layerId:

> unique(x$layerId)
[1] "4" NA "8" "12"

It works if you remove the NA, e.g.

library(leaflet)
library(shiny)
x<-readRDS("polygons.Rds")

shinyApp(
ui = fluidPage(leafletOutput('map'), actionButton('remove', 'Remove')),
server = function(input, output) {
output$map = renderLeaflet({
leaflet() %>%
addPolygons(lng=x$lng, x$lat,
layerId = unique(na.omit(x$layerId)))
})
observeEvent(input$remove, {
leafletProxy('map') %>% removeShape(unique(na.omit(x$layerId)))
})
}
)

Regards,
Yihui

Bastien Tran

unread,
Jun 28, 2015, 3:56:38 PM6/28/15
to Yihui Xie, Joe Cheng, shiny-discuss
That's great Yihui, thanks a lot.

I had actually given a quick try to complete.cases() while I was struggling on my own but didn't check thoroughly if it actually returned what I was expecting... And obviously I had totally forgotten about na.omit().

Sorry for the late feedback. I had not much trouble fixing my app but was a bit confused as to why I had troubles removing the shapes even though they had the correct layerIds in the first place. It is now obvious it was because I wasn't adding them properly...

I therefore have a couple of questions if I may?

At first, while drawing the polygons on clicks, they seemed properly added (with the correct layerId) even though I was feeding NA's along with the polygon's nodes to addShape(). Through this process I was drawing one polygon at the time, storing their nodes in such fashions...

polygon<-data.frame(lng=c(1, 2, 3, NA),lat=c(1, 2, 1, NA),
                    layerId = c(('x1','x1','x1',NA))

or

polygon<-data.frame(lng=c(1, 2, 3, NA),lat=c(1, 2, 1, NA),
                    layerId = c('x1','x1','x1','x1'))

...which I did because the polygons are to be stored in a single dataframe in the end, and I had read that rows of NA's where needed for R to distinguish the polygons when there are several stored in a dataframe.
If they are necessary it wasn't probably the best place in my code for me to add them since I was using the same 'polygon' object as the data source for addPolygon(), hence my NA issue.

Questions:
Does that mean the trouble was somehow due to each "blank node" (nodes with (NA,NA) coordinates) within each polygon object?

If so, is there something possibly worth knowing about how shapes are internally handled? (sets of nodes and edges that have their 'internal IDs' so that R can't match created shapes with shapes to be removed if one of tjeir element exists but is NA)

What would be a better way to store shapes data, meaning it would be simple to addPolygon() and removeShape from the same R object as well as writing to said object?

I hope I am not asking too much, I am very grateful for the help you already provided.

Best regards,
Bastien

Yihui Xie

unread,
Jun 28, 2015, 4:40:35 PM6/28/15
to Bastien Tran, Joe Cheng, shiny-discuss
These are actually very good questions, but I guess I do not really
know much more than you. Polygon data is different with other shapes
data, since one polygon has multiple vertices. NA-separated vectors is
one way to specify polygon data, and I think the other common spec is
in the sp package. In the former case, NA is only used for the
separation purpose. It is not carried over to the final polygon data,
e.g. c(1, 2, 3, NA, 4, 5, 6) will eventually become c(1, 2, 3) and
c(4, 5, 6).

One polygon only needs one layerId. It is not necessary to assign a id
to each of its vertex, so your use of unique() is absolutely
appropriate. It is just that you forgot to remove NA from the id's,
whereas we removed NA's from the polygon data internally. That led to
the mismatch (three polygons, but four layerId's).

I'm not a spatial expert, so I cannot comment on the "best practice"
of storing polygon data. In fact, personally I'm also interested to
know the answer. In particular, I'm looking for a data structure for
polygons so that I can easily assign/store ID's in it, get the ID's,
and query the polygon data by ID. sp::Polygons() has an ID argument,
but at the moment I do not see a helper function to extract the ID's
from the data object it creates.

Regards,
Yihui

Joe Cheng

unread,
Jun 29, 2015, 6:43:59 PM6/29/15
to Yihui Xie, Bastien Tran, shiny-discuss
I'm planning to attend a tutorial on sp at the useR conference tomorrow, maybe I can give you an authoritative answer after that :)

Bastien Tran

unread,
Jun 30, 2015, 9:05:40 AM6/30/15
to Joe Cheng, Yihui Xie, shiny-discuss
Enjoy Joe!

Maybe this is getting off topic and it needs a new one. I'll share my thoughts here in the mean time:

Before I got started I thought there would be a way in R to store practically anything in a data.frame (including data.frames).
As I eventually found out (or assumed?) this was in fact not possible so I changed my mind for lists. Probably not the best way to store my points, lines and shapes' data but that kept me rolling while I focused on other aspects of the app.

Now reading Hadley Whickam's Advanced R, I see it is possible to define list-matrices and list-arrays. He provides this as an example:
l <- list(1:3, "a", TRUE, 1.0)
dim(l) <- c(2, 2)
l
#>      [,1]      [,2]
#> [1,] Integer,3 TRUE
#> [2,] "a"       1
I feel like this could be an interesting basis for keeping thinks square enough with spatial data which can be quite heterogenous (points, lines, polygons....groups). I am going to give it a try though this probably has downsides I don't see right now. If someone has some experience with such objects (whether or not in a spatial context) that would sure be interesting.

All the best,
Bastien


Chris Holcomb

unread,
Jun 30, 2015, 12:51:24 PM6/30/15
to shiny-...@googlegroups.com, j...@rstudio.com, yi...@rstudio.com
Has anyone tested performance of Polygon vs GeoJSON? I find it most convenient to avoid these issues altogether and use GeoJSON which handles points, lines, polygons, multipolygons, with holes, etc. Plus GeoJSON has some easy to use simplification methods via PostGIS and GDAL. You could just use sp's SpatialPolygonsDataFrame but I've found that to be slow because we have a ton of attributes. Because of this, I typically process a shapefile or PostGIS table into two parts: an attributes data.frame (with ids) and a separate GeoJSON (with ids) for the geometries. I read in the GeoJSON as a list into R which lets me easily move back attributes as needed. Only if I need to manipulate the actual geometry dynamically in R will I have an intermediary step using an sp class (SpatialPolygons, SpatialPoints, or if I'm using gstat, that may require a sp class with attributes, e.g. SpatialLinesDataFrame). I actually have the lat lng's preloaded in the attributes data.frame so I can go straight into SpatialPointsDataFrame from there if needed.

-

P.S., We are finally transitioning to the new Leaflet and we're thrilled with the new inclusions. We can do pretty much everything we need to do. I so wish GeoJSON had a zIndex or leaflet.draw was included. I'll likely post more thoughts once we've finished migrating.

Chris
Reply all
Reply to author
Forward
0 new messages