Title: | Generate Isolines and Isobands from Regularly Spaced Elevation Grids |
---|---|
Description: | A fast C++ implementation to generate contour lines (isolines) and contour polygons (isobands) from regularly spaced grids containing elevation data. |
Authors: | Hadley Wickham [aut, cre] , Claus O. Wilke [aut] (Original author, <https://orcid.org/0000-0002-7470-9261>), Thomas Lin Pedersen [aut] |
Maintainer: | Hadley Wickham <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.2.7.9000 |
Built: | 2024-11-11 06:31:31 UTC |
Source: | https://github.com/r-lib/isoband |
Function factories that return functions to standardize rotation angles to specific angle ranges.
angle_halfcircle_bottom() angle_halfcircle_right() angle_fixed(theta = 0) angle_identity()
angle_halfcircle_bottom() angle_halfcircle_right() angle_fixed(theta = 0) angle_identity()
theta |
Fixed angle, in radians. |
angle_halfcircle_bottom()
standardizes angles to (-pi/2, pi/2].
angle_halfcircle_right()
standardizes angles to (0, pi].
angle_fixed()
sets all angles to a fixed value (0 by default).
angle_identity()
does not modify any angles.
Convert isolines or isobands to an sf geometry collection (sfg
) object. Further downstream
processing needs to happen via the sf package.
iso_to_sfg(x)
iso_to_sfg(x)
x |
The object to convert. |
The function iso_to_sfg()
is a generic that takes an object created by either isolines()
or isobands()
and turns it into a simple features (sf) geometry collection. Importantly,
the isobanding algorithm can produce polygons that do not represent valid simple features. This
happens usually when the lower limit of an isoband is exactly equal to some data values (see
examples for a demonstration). This can be worked around either by slightly shifting the data
or band limits (e.g., round all data values and then shift them by a value smaller than the
rounding error) or by fixing the geometries using the function st_make_valid()
.
if (requireNamespace("sf", quietly = TRUE)) { library(sf) library(ggplot2) # Example 1: simple 5x5 matrix m <- matrix(c(0, 2, 2, 2, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0), 5, 5, byrow = TRUE) z <- isolines(1:ncol(m), nrow(m):1, m, c(0.5, 1.5)) lines <- iso_to_sfg(z) x <- st_sf(level = names(lines), geometry = st_sfc(lines)) ggplot(x) + geom_sf(aes(color = level)) # Example 2: volcano dataset m <- volcano b <- isobands((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 10*9:19, 10*10:20) bands <- iso_to_sfg(b) x <- st_sf(level = as.numeric(sub(":.*", "", names(bands))), geometry = st_sfc(bands)) ggplot(x) + geom_sf(aes(color = level, fill = level)) # Example 3: invalid simple features m <- matrix(c(1.5, 1.5, 1.5, 1.5, 0.6, 0.5, 1.5, 1.5, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0.7, 0, 0.9, 1.3, 1.8, 1.4, 0.4), 5, 5, byrow = TRUE) raw <- isobands(1:5, 5:1, m, levels_low = 0:1, levels_high = 1:2) bands <- iso_to_sfg(raw) iso <- st_sf( id = factor(1:length(bands)), geometry = st_sfc(bands) ) # the geometries are not valid st_is_valid(iso, reason = TRUE) # this doesn't prevent us from plotting them ggplot(iso, aes(fill = id)) + geom_sf() # make all geometries valid, requires GEOS >= 3.8.0 if (sf_extSoftVersion()["GEOS"] >= "3.8.0") { iso2 <- st_make_valid(iso) st_is_valid(iso2, reason=TRUE) # the plot should be unchanged ggplot(iso2, aes(fill = id)) + geom_sf() } # alternatively, if we shift all data values by a tiny # amount (here, 1e-10) so they don't coincide with the band # limits, no invalid geometries are generated. raw <- isobands(1:5, 5:1, m + 1e-10, levels_low = 0:1, levels_high = 1:2) bands <- iso_to_sfg(raw) iso <- st_sf(id = factor(1:length(bands)), geometry = st_sfc(bands)) st_is_valid(iso, reason = TRUE) }
if (requireNamespace("sf", quietly = TRUE)) { library(sf) library(ggplot2) # Example 1: simple 5x5 matrix m <- matrix(c(0, 2, 2, 2, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0), 5, 5, byrow = TRUE) z <- isolines(1:ncol(m), nrow(m):1, m, c(0.5, 1.5)) lines <- iso_to_sfg(z) x <- st_sf(level = names(lines), geometry = st_sfc(lines)) ggplot(x) + geom_sf(aes(color = level)) # Example 2: volcano dataset m <- volcano b <- isobands((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 10*9:19, 10*10:20) bands <- iso_to_sfg(b) x <- st_sf(level = as.numeric(sub(":.*", "", names(bands))), geometry = st_sfc(bands)) ggplot(x) + geom_sf(aes(color = level, fill = level)) # Example 3: invalid simple features m <- matrix(c(1.5, 1.5, 1.5, 1.5, 0.6, 0.5, 1.5, 1.5, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0.7, 0, 0.9, 1.3, 1.8, 1.4, 0.4), 5, 5, byrow = TRUE) raw <- isobands(1:5, 5:1, m, levels_low = 0:1, levels_high = 1:2) bands <- iso_to_sfg(raw) iso <- st_sf( id = factor(1:length(bands)), geometry = st_sfc(bands) ) # the geometries are not valid st_is_valid(iso, reason = TRUE) # this doesn't prevent us from plotting them ggplot(iso, aes(fill = id)) + geom_sf() # make all geometries valid, requires GEOS >= 3.8.0 if (sf_extSoftVersion()["GEOS"] >= "3.8.0") { iso2 <- st_make_valid(iso) st_is_valid(iso2, reason=TRUE) # the plot should be unchanged ggplot(iso2, aes(fill = id)) + geom_sf() } # alternatively, if we shift all data values by a tiny # amount (here, 1e-10) so they don't coincide with the band # limits, no invalid geometries are generated. raw <- isobands(1:5, 5:1, m + 1e-10, levels_low = 0:1, levels_high = 1:2) bands <- iso_to_sfg(raw) iso <- st_sf(id = factor(1:length(bands)), geometry = st_sfc(bands)) st_is_valid(iso, reason = TRUE) }
Efficient calculation of isolines and isobands from elevation grid
isobands(x, y, z, levels_low, levels_high) isolines(x, y, z, levels)
isobands(x, y, z, levels_low, levels_high) isolines(x, y, z, levels)
x |
Numeric vector specifying the x locations of the grid points. |
y |
Numeric vector specifying the y locations of the grid points. |
z |
Numeric matrix specifying the elevation values for each grid point. |
levels_low , levels_high
|
Numeric vectors of minimum/maximum z values
for which isobands should be generated. Any z values that are exactly
equal to a value in |
levels |
Numeric vector of z values for which isolines should be generated. |
library(grid) #' # one simple connected shape m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) df_bands <- isobands((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 0.5, 1.5)[[1]] df_lines <- isolines((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 0.5)[[1]] g <- expand.grid(x = (1:ncol(m))/(ncol(m)+1), y = (nrow(m):1)/(nrow(m)+1)) grid.newpage() grid.points(g$x, g$y, default.units = "npc", pch = 19, size = unit(0.5, "char")) grid.path(df_bands$x, df_bands$y, df_bands$id, gp = gpar(fill = "cornsilk", col = NA)) grid.polyline(df_lines$x, df_lines$y, df_lines$id) # a similar plot can be generated with the plot_iso() function, # which is useful for exploring how the algorithm works plot_iso(m, 0.5, 1.5) # NAs are ignored m <- matrix(c(NA, NA, NA, 0, 0, 0, NA, NA, NA, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5) # two separate shapes m <- matrix(c(0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0.8, 0), 4, 4, byrow = TRUE) plot_iso(m, 0.5, 1.5) # shape with hole m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 2, 2, 1, 0, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5)
library(grid) #' # one simple connected shape m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) df_bands <- isobands((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 0.5, 1.5)[[1]] df_lines <- isolines((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 0.5)[[1]] g <- expand.grid(x = (1:ncol(m))/(ncol(m)+1), y = (nrow(m):1)/(nrow(m)+1)) grid.newpage() grid.points(g$x, g$y, default.units = "npc", pch = 19, size = unit(0.5, "char")) grid.path(df_bands$x, df_bands$y, df_bands$id, gp = gpar(fill = "cornsilk", col = NA)) grid.polyline(df_lines$x, df_lines$y, df_lines$id) # a similar plot can be generated with the plot_iso() function, # which is useful for exploring how the algorithm works plot_iso(m, 0.5, 1.5) # NAs are ignored m <- matrix(c(NA, NA, NA, 0, 0, 0, NA, NA, NA, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5) # two separate shapes m <- matrix(c(0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0.8, 0), 4, 4, byrow = TRUE) plot_iso(m, 0.5, 1.5) # shape with hole m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 2, 2, 1, 0, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5)
This function generates a grid grob that represents isobands.
isobands_grob(bands, gp = gpar(), units = "npc")
isobands_grob(bands, gp = gpar(), units = "npc")
bands |
Isobands, as produced by the |
gp |
Grid graphical parameters. Parameters are recycled among the total number of bands drawn. |
units |
A character string specifying the units in which to
interpret the isobands coordinates. Defaults to |
See isolines_grob()
for drawing of isolines.
library(grid) viridis_pal <- colorRampPalette( c("#440154", "#414487", "#2A788E", "#22A884", "#7AD151", "#FDE725"), space = "Lab" ) x <- (1:ncol(volcano))/(ncol(volcano)+1) y <- (nrow(volcano):1)/(nrow(volcano)+1) bands <- isobands(x, y, volcano, 5*(18:38), 5*(19:39)) b <- isobands_grob( bands, gp = gpar(col = "black", fill = viridis_pal(21), alpha = 0.5) ) grid.newpage() grid.draw(b)
library(grid) viridis_pal <- colorRampPalette( c("#440154", "#414487", "#2A788E", "#22A884", "#7AD151", "#FDE725"), space = "Lab" ) x <- (1:ncol(volcano))/(ncol(volcano)+1) y <- (nrow(volcano):1)/(nrow(volcano)+1) bands <- isobands(x, y, volcano, 5*(18:38), 5*(19:39)) b <- isobands_grob( bands, gp = gpar(col = "black", fill = viridis_pal(21), alpha = 0.5) ) grid.newpage() grid.draw(b)
This function generates a grid grob that represents labeled isolines.
isolines_grob( lines, gp = gpar(), breaks = NULL, labels = NULL, margin = unit(c(1, 1, 1, 1), "pt"), label_col = NULL, label_alpha = NULL, label_placer = label_placer_minmax(), units = "npc" )
isolines_grob( lines, gp = gpar(), breaks = NULL, labels = NULL, margin = unit(c(1, 1, 1, 1), "pt"), label_col = NULL, label_alpha = NULL, label_placer = label_placer_minmax(), units = "npc" )
lines |
Isolines, as produced by the |
gp |
Grid graphical parameters. Parameters applying to lines
(such as |
breaks |
Character vector specifying the isolines that should be
labeled. If |
labels |
Character vector specifying the labels for each break.
If |
margin |
Unit object of length 4 specifying the top, right, bottom, and left margins around each text label. The same margins are applied to all labels. |
label_col |
Color applied to labels. Can be used to override the
color provided in |
label_alpha |
Alpha applied to labels. Can be used to override the
alpha value provided in |
label_placer |
Function that controls how labels are placed along
the isolines. Uses |
units |
A character string specifying the units in which to
interpret the isolines coordinates. Defaults to |
See isobands_grob()
for drawing of isobands. See label_placer_minmax()
for
label placement strategies.
library(grid) viridis_pal <- colorRampPalette( c("#440154", "#414487", "#2A788E", "#22A884", "#7AD151", "#FDE725"), space = "Lab" ) x <- (1:ncol(volcano))/(ncol(volcano)+1) y <- (nrow(volcano):1)/(nrow(volcano)+1) lines <- isolines(x, y, volcano, 5*(19:38)) bands <- isobands(x, y, volcano, 5*(18:38), 5*(19:39)) b <- isobands_grob( bands, gp = gpar(col = NA, fill = viridis_pal(21), alpha = 0.4) ) l <- isolines_grob( lines, breaks = 20*(5:10), gp = gpar( lwd = c(.3, 1, .3, .3) ) ) grid.newpage() grid.draw(b) grid.draw(l)
library(grid) viridis_pal <- colorRampPalette( c("#440154", "#414487", "#2A788E", "#22A884", "#7AD151", "#FDE725"), space = "Lab" ) x <- (1:ncol(volcano))/(ncol(volcano)+1) y <- (nrow(volcano):1)/(nrow(volcano)+1) lines <- isolines(x, y, volcano, 5*(19:38)) bands <- isobands(x, y, volcano, 5*(18:38), 5*(19:39)) b <- isobands_grob( bands, gp = gpar(col = NA, fill = viridis_pal(21), alpha = 0.4) ) l <- isolines_grob( lines, breaks = 20*(5:10), gp = gpar( lwd = c(.3, 1, .3, .3) ) ) grid.newpage() grid.draw(b) grid.draw(l)
These functions set up various label placement strategies.
label_placer_minmax( placement = "tb", rot_adjuster = angle_halfcircle_bottom(), n = 2 ) label_placer_none() label_placer_manual(breaks, x, y, theta) label_placer_middle(rot_adjuster = angle_halfcircle_bottom())
label_placer_minmax( placement = "tb", rot_adjuster = angle_halfcircle_bottom(), n = 2 ) label_placer_none() label_placer_manual(breaks, x, y, theta) label_placer_middle(rot_adjuster = angle_halfcircle_bottom())
placement |
String consisting of any combination of the letters "t", "r", "b", "l" indicating the placement of labels at the top, to the right, at the bottom, to the left of the isoline. |
rot_adjuster |
Function that standardizes the rotation angles of the labels.
See e.g. |
n |
Size of the point neighborhood over which the rotation angle should be calculated. |
breaks |
Character vector specifying the isolines to be labeled,
as in |
x , y , theta
|
Numeric vectors specifying the x and y positions and angles (in radians) for each label corresponding to each break. |
label_placer_minmax()
places labels at the horizontal or vertical minima or maxima of
the respective isolines.
label_placer_none()
places no labels at all.
label_placer_manual()
places labels at manually defined locations.
label_placer_middle()
places labels at the middle of each isoline.
This function visualizes a single isoband calculated from a matrix. It is mainly useful
for debugging and visualizing the isobanding algorithm. See isobands()
for more
examples.
plot_iso( m, vlo, vhi, fill_lo = "gray95", fill_mid = "gray50", fill_hi = "black", fill_band = "cornsilk", col_lo = "black", col_hi = "black", newpage = TRUE )
plot_iso( m, vlo, vhi, fill_lo = "gray95", fill_mid = "gray50", fill_hi = "black", fill_band = "cornsilk", col_lo = "black", col_hi = "black", newpage = TRUE )
m |
input matrix |
vlo |
lower cutoff for isobanding |
vhi |
higher cutoff for isobanding |
fill_lo |
fill color for points below the lower cutoff |
fill_mid |
fill color for points between the two cutoffs |
fill_hi |
fill color for points above the higher cutoff |
fill_band |
fill color for the isoband |
col_lo |
line color for lower cutoff |
col_hi |
line color for higher cutoff |
newpage |
boolean, indicating whether |
m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5)
m <- matrix(c(0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0), 6, 6, byrow = TRUE) plot_iso(m, 0.5, 1.5)