I want to make a world map with a voronoi tessellation using the spherical nature of the world (not a projection of it), similar to this using D3.js, but with R.
As I understand ("Goodbye flat Earth, welcome S2 spherical geometry") the sf
package is now fully based on the s2
package and should perform as I needed. But I don't think that I am getting the results as expected. A reproducible example:
library(tidyverse)
library(sf)
library(rnaturalearth)
library(tidygeocoder)
# just to be sure
sf::sf_use_s2(TRUE)
# download map
world_map <- rnaturalearth::ne_countries(
scale = 'small',
type = 'map_units',
returnclass = 'sf')
# addresses that you want to find lat long and to become centroids of the voronoi tessellation
addresses <- tribble(
~addr,
"Juneau, Alaska" ,
"Saint Petersburg, Russia" ,
"Melbourne, Australia"
)
# retrive lat long using tidygeocoder
points <- addresses %>%
tidygeocoder::geocode(addr, method = 'osm')
# Transform lat long in a single geometry point and join with sf-base of the world
points <- points %>%
dplyr::rowwise() %>%
dplyr::mutate(point = list(sf::st_point(c(long, lat)))) %>%
sf::st_as_sf() %>%
sf::st_set_crs(4326)
# voronoi tessellation
voronoi <- sf::st_voronoi(sf::st_union( points ) ) %>%
sf::st_as_sf() %>%
sf::st_set_crs(4326)
# plot
ggplot2::ggplot() +
geom_sf(data = world_map,
mapping = aes(geometry = geometry),
fill = "gray95") +
geom_sf(data = points,
mapping = aes(geometry = point),
colour = "red") +
geom_sf(data = voronoi,
mapping = aes(geometry = x),
colour = "red",
alpha = 0.5)
The whole Antarctica should be closer to Melbourne than to the other two points. What am I missing here? How to calculate a voronoi on a sphere using sf
?
Here's a method that builds on Stéphane Laurent's approach, but outputs
sf
objects.Let us obtain an
sf
object of all the world capitals:And our world map:
Now we use Stéphane Laurent's approach to tesselate the sphere, but then reverse the projection back into spherical co-ordinates. This allows translation back to
sf
, though we have to be careful to split any objects that "wrap around" the 180/-180 longitude line:Now we have our Voronoi grid as an
sf
object, so we can plot it usingggplot
:Addendum
Although the above solution works for drawing tesselations over the whole globe, if we want to get polygons of land areas only, we can do it as follows:
First, we make a union of all land masses from our world map
Now we get the co-ordinates of the vertices of our Voronoi tiles:
Now we need to find tiles that span the -180 / 180 line:
We now split these and turn them into multipolygons, finding their intersection with the world map:
We can do intersections for the non-wraparound tiles like this:
Finally, we bind all these together into a single
sf
object:Now we're ready to plot. Let's define a palette function that gives results similar to your linked example:
We'll also create a background "globe" and a smoothed grid to draw over our map, as in the example:
The final result is a faithful
sf
version of the linked example:Created on 2023-06-24 with reprex v2.0.2