Skip to contents

Lake Ōhau example

In this vignette, based on the example of Lake Ōhau, the keep parameter in cnt_skeleton() and cnt_path_guess() is demystified. Simply put, it is responsible for the simplification/densification of the input polygon. That is, when keep < 1, geometry simplification is performed (using the geos::geos_simplify() function). Meanwhile, when keep > 1, the densification algorithm is used (through geos::geos_densify()).

library(centerline)

lake <-
  sf::st_read(
    system.file("extdata/example.gpkg", package = "centerline"),
    layer = "lake",
    quiet = TRUE
  )

The influence of keep is best observed in the example of generating the skeleton of a polygon. Since simplification reduces the number of nodes in the polygon, while densification, on the contrary, increases it. Therefore, generating skeletons using the Voronoi method will produce more lines as the keep parameter increases.

skeleton_original <-
  lake |>
  cnt_skeleton(keep = 1)

skeleton_simplified <-
  lake |>
  cnt_skeleton(keep = 0.25)

skeleton_densified <-
  lake |>
  cnt_skeleton(keep = 1.5)
Comparison between original, simplified, and densified skeletons of the Lake Ōhau generated using Voronoi diagrams. Zoom to the borders to see the differences.

On the other hand, the difference in centerline results is negligible. Indeed, the simplified centerline is less dense than the original, while the densified one is almost identical to the original. Additionally, the simplified one ends in a slightly different location compared to the others.

cnt_original <-
  lake |>
  cnt_path_guess(keep = 1)

cnt_simplified <-
  lake |>
  cnt_path_guess(keep = 0.25)

cnt_densified <-
  lake |>
  cnt_path_guess(keep = 1.5)
Comparison between original, simplified, and densified centerlines of the Lake Ōhau

However, when it comes to speed comparison, the simplification significantly helps to reduce the computation time. It is approximately 3-4 times faster than the estimation based on the original geometry.

Overall, I would say that it all depends on the goals of your calculations. If you are interested in quickly labelling the polygon along the center, it is better to use cnt_path_guess() with keep ≈ 0.5 to preserve the overall outline of the centerline while significantly speeding up the calculation process. However, if the goal is to measure the length of the polygon, then cnt_path_guess() with keep >= 1 may turn out to be the best option.

bench::mark(
  original = cnt_path_guess(lake, keep = 1),
  simplified = cnt_path_guess(lake, keep = 0.25),
  densified = cnt_path_guess(lake, keep = 1.5),
  relative = TRUE,
  check = FALSE,
  iterations = 5L
)
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 3 × 6
#>   expression   min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
#> 1 original    5.74   5.80      3.89      9.33      Inf
#> 2 simplified  1      1        22.2       1         NaN
#> 3 densified  21.5   22.1       1        31.3       Inf

Why not use %packagename% for simplification?

That’s a good question. Initially, the centerline package was developed with rmapshaper::ms_simplify() under the hood by default. I really like the mapshaper JavaScript library and its bindings to R because it can preserve topology and keep the overall shape (see the rmapshaper vignette) while dealing with extremely large geometries. However, it is not as fast as you might imagine, creates an additional package dependency, and there are problems with installation on Linux machines and CRAN checks. So we decided to ditch it.

Instead, we created a geos-based function (centerline:::geos_ms_simplify()) to mimic the rmapshaper::ms_simplify() behavior. It is not yet accessible via export, as it is an internal function used only in cnt_skeleton(). It performs approximately 60 times faster (see benchmarks) and produces similar (but not identical) outputs.

bench::mark(
  rmapshaper = rmapshaper::ms_simplify(lake, keep = 0.5),
  centerline = centerline:::geos_ms_simplify(lake_geos, keep = 0.5),
  check = FALSE,
  relative = TRUE,
  iterations = 5L
)
#> # A tibble: 2 × 6
#>   expression   min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
#> 1 rmapshaper  75.8   75.4       1         Inf      NaN
#> 2 centerline   1      1        71.2       NaN      NaN
Comparison between {rmapshaper}, and {centerline} simplification algorithms on the lake polygon of the Lake Ōhau.

However, if you still think that the built-in simplification is mediocre, or if it produces an error, consider generating centerlines/skeletons as follows:

lake_ms_centerline <-
  lake |>
  rmapshaper::ms_simplify(keep = 0.5) |>
  cnt_path_guess(keep = 1) # Mind the 'keep' parameter

lake_centerline <-
  lake |>
  cnt_path_guess(keep = 0.5)
Comparison between {rmapshaper}, and {centerline} simplification algorithms on the centerlines of the Lake Ōhau.