tmap icon indicating copy to clipboard operation
tmap copied to clipboard

tmap_mode("view") not rendering polygons correctly with >500 features in an sfc

Open marine-ecologist opened this issue 7 months ago • 2 comments

tmap 4.1: I'm trying to plot an sf object with >500 polygons through packcircles with tmap "view". After trying to debug why the sf object is generating incorrect interactive plots, it seems that 1:499 polygons works fine, but >=500 results in an interactive view issue. Apologies in advance if this is related to my use of packcircles, but I can't see an alternative explanation with the sf objects other than nrow. Reproducible example below:

Working with 499 polygons:

library(dplyr)
library(sf)
library(packcircles)
library(tibble)
library(tmap)

tmap_mode("view")

n_individuals <- 499

species_n <- sample(letters[1:11],  n_individuals, replace = TRUE)

# Generate random radii
radii <- runif(n_individuals, min = 0.1, max = 0.4)

# Generate circle centers within bounding box
x <- runif(n_individuals, min = -0.25, max = 20.5)
y <- runif(n_individuals, min = -0.25, max = 10.5)

# Create circle layout data
layout <- tibble(x = x, y = y, radius = radii, id = seq_along(x))

# Create circle vertices
circle_vertices <- packcircles::circleLayoutVertices(layout)

# Convert to sf polygons
circles_sf <- circle_vertices |>
  sf::st_as_sf(coords = c("x", "y"), crs = 3857) |>
  group_by(id) |>
  summarise(geometry = sf::st_combine(geometry), .groups = "drop") |>
  sf::st_cast("POLYGON") |>
  mutate(species = species_n)


tm_shape(circles_sf) +
  tm_polygons(fill = "species",
                    col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

Image

not working with 502 polygons:


library(dplyr)
library(sf)
library(packcircles)
library(tibble)

n_individuals <- 251

generate_circles <- function(seed_offset = 0) {
  set.seed(100 + seed_offset)
  
  species_n <- sample(letters[1:11], n_individuals, replace = TRUE)
  radii <- runif(n_individuals, min = 0.1, max = 0.4)
  x <- runif(n_individuals, min = -0.25, max = 20.5)
  y <- runif(n_individuals, min = -0.25, max = 10.5)
  
  layout <- tibble(x = x, y = y, radius = radii, id = seq_along(x))
  vertices <- packcircles::circleLayoutVertices(layout)
  
  sf::st_as_sf(vertices, coords = c("x", "y"), crs = 3857) |>
    group_by(id) |>
    summarise(geometry = sf::st_combine(geometry), .groups = "drop") |>
    sf::st_cast("POLYGON") |>
    mutate(species = species_n, replicate = seed_offset + 1)
}

circles1 <- generate_circles(0)
circles2 <- generate_circles(1)

circles_all <- bind_rows(circles1, circles2)



tm_shape(circles_all) +
  tm_polygons(fill = "species",
                    col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

Image

But... same sf object working fine with slice_sample = 499


tm_shape(circles_all |> slice_sample(n=499)) +
  tm_polygons(fill = "species",
              col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

and no problems with >500 nrow and tmap_mode("plot")

tm_shape(circles_all) +
  tm_polygons(fill = "species",
                    col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

Image

marine-ecologist avatar May 26 '25 04:05 marine-ecologist

Same with circles and simpler sf shapes (squares), and doesn't seem to be linked to crs:

library(dplyr)
library(sf)
library(packcircles)
library(tibble)

n_individuals <- 1000
n_replicates <- 1

generate_random_circles <- function() {
  species_n <- sample(letters[1:11], n_individuals, replace = TRUE)
  radii <- runif(n_individuals, min = 0.1, max = 0.4)
  x <- runif(n_individuals, min = -0.25, max = 20.5)
  y <- runif(n_individuals, min = -0.25, max = 10.5)
  
  layout <- tibble(x = x, y = y, radius = radii, id = seq_along(x))
  vertices <- packcircles::circleLayoutVertices(layout)
  
  sf::st_as_sf(vertices, coords = c("x", "y"), crs = 3857) |>
    group_by(id) |>
    summarise(geometry = sf::st_combine(geometry), .groups = "drop") |>
    sf::st_cast("POLYGON") |>
    mutate(species = species_n)
}

circles_all <- bind_rows(
  replicate(n_replicates, generate_random_circles(), simplify = FALSE),
  .id = "replicate"
)

n_individuals <- 1000
n_replicates <- 1

generate_random_squares <- function() {
  species_n <- sample(letters[1:11], n_individuals, replace = TRUE)
  side <- runif(n_individuals, min = 0.1, max = 0.4)
  x <- runif(n_individuals, min = -0.25, max = 20.5)
  y <- runif(n_individuals, min = -0.25, max = 10.5)
  
  square_list <- lapply(seq_len(n_individuals), function(i) {
    cx <- x[i]
    cy <- y[i]
    s <- side[i] / 2
    coords <- matrix(c(
      cx - s, cy - s,
      cx - s, cy + s,
      cx + s, cy + s,
      cx + s, cy - s,
      cx - s, cy - s
    ), ncol = 2, byrow = TRUE)
    sf::st_polygon(list(coords))
  })
  
  sf::st_sf(
    species = species_n,
    geometry = sf::st_sfc(square_list, crs = 3857)
  )
}

squares_all <- bind_rows(
  replicate(n_replicates, generate_random_squares(), simplify = FALSE),
  .id = "replicate"
)


tm_shape(squares_all |> slice_sample(n=499)) +
  tm_polygons(fill = "species",
              col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

tm_shape(squares_all |> slice_sample(n=501)) +
  tm_polygons(fill = "species",
              col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))

tm_shape(squares_all |> st_transform(4326)) +
  tm_polygons(fill = "species",
              col="black", fill_alpha = 0.7) +
  tm_view(set_zoom_limits = c(22, 30))


marine-ecologist avatar May 26 '25 04:05 marine-ecologist

Thx @marine-ecologist

It's an issue with webgl layers. I'll look into it. For the time being, set use_WebGL = FALSE to disable this (it's disabled by default when n < 500):

tm_shape(squares_all |> slice_sample(n=501)) +
	tm_polygons(fill = "species",
				col="black", fill_alpha = 0.7) +
	tm_view(set_zoom_limits = c(22, 30), use_WebGL = FALSE)

mtennekes avatar May 28 '25 06:05 mtennekes

@marine-ecologist seems to have been solved upstream (strange polygon rendering r-spatial/leafgl#106).

I increased the threshold number of features for which webgl is enabled to 1000 (was 500).

mtennekes avatar Jun 25 '25 12:06 mtennekes