visvalingam

{visvilingam} implements Visvilngam’s Line Simplification Algorithm, which is described well here, here and here.

Algorithm Overview

Visvalingam’s algorithm iteratively deletes vertices from a line with the deletion decision based upon the effective area of the triangle defined by the vertex and its current neighbours.

The vertex with the minimal area is removed from the line at each iteration until the target number of points is reached.

Note that when vertices are removed from the line, the effective areas of the neighbouring vertices need to be recalculated.

The need to find/delete the node with minimum effective area at every iteration means that a min heap (AKA priority queue) will be needed for efficient calculation.

A priority queue is used in the C version of the algorithm in this package, but the R version remains a straightforward/naive implementation for illustrative purposes.

Algorithm Visual Explanation

Image Credit: Mike Bostock

What’s in the box

For illustrative purposes

Installation

You can install from GitHub with:

# install.package('remotes')
# remotes::install_github('coolbutuseless/visvalingam')
install.packages("visvalingam", repos = c('https://coolbutuseless.r-universe.dev', 'https://cloud.r-project.org'))

Line Simplification

Raw map of Mainland Australia

library(rworldmap)
library(rworldxtra)

# Extract map of mainland Australia
map       <- rworldmap::getMap(resolution = 'high')
australia <- map@polygons[[which(map$ADMIN.1 == 'Australia')]]
mainland  <- australia@Polygons[[1]]@coords
nrow(mainland)
#> [1] 9475

plot(mainland, type = 'l', asp = 1)
title(sprintf("Mainland Australia\n%i points", nrow(mainland)))

Simplified map of Australia (n = 100)

# `simplify()` returns a simple list of the remaining x,y coordinates
res <- visvalingam::vis_simplify(mainland[,1], mainland[,2], n = 100)
class(res)
#> [1] "list"
names(res)
#> [1] "x" "y"

plot(res, type = 'l', asp = 1)
title(sprintf("Mainland Australia\n%i points", 100))

Manually filtering using effective areas

Raw map of Tasmania

library(rworldmap)
library(rworldxtra)

# Extract map of Tasmania
map       <- rworldmap::getMap(resolution = 'high')
australia <- map@polygons[[which(map$ADMIN.1 == 'Australia')]]
tassie    <- australia@Polygons[[2]]@coords
nrow(tassie)
#> [1] 1017

plot(tassie, type = 'l', asp = 1)
title(sprintf("Tasmania\n%i points", nrow(tassie)))

Map of Tasmania removing 99% of points

# `effective_areas` returns a numeric area associated with each vertex
areas <- visvalingam::vis_effective_areas(tassie[,1], tassie[,2])
quantile(areas)
#>           0%          25%          50%          75%         100% 
#> 1.602275e-07 3.723681e-05 1.362252e-04 8.383088e-04          Inf

# Remove 99% of the vertices
threshold <- quantile(areas, probs = 0.99)
tassie_small <- tassie[areas > threshold,]

plot(tassie_small, type = 'l', asp = 1)
title(sprintf("Tasmania\n%i points", nrow(tassie_small)))

Benchmark

Test case: Simplify a line of 1000 points down to just 10 points.

Most other related packages that offer line simplification need sp or sf or other spatial geometry objects as input.

If anyone wanted to replicate this current benchmark for these pacakges, I will happily add them here.

set.seed(1)
N <- 1000
x <- runif(N)
y <- runif(N)

res <- bench::mark(
  vis_simplify(x, y, 10),
  vis_simplify_r(x, y, 10)
)
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
expression min median itr/sec mem_alloc
vis_simplify(x, y, 10) 449µs 478µs 2024.092612 0B
vis_simplify_r(x, y, 10) 111ms 120ms 6.617408 96.8MB

Related Software

Acknowledgements