## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.5, dpi = 96, out.width = "100%") have <- all(vapply(c("s2", "wk", "wkpool", "maps"), requireNamespace, logical(1), quietly = TRUE)) knitr::opts_chunk$set(eval = have) ## the last drawing uses the PROJ package; only run it where PROJ can ## find its database and build a transform proj_ok <- have && requireNamespace("PROJ", quietly = TRUE) && isTRUE(tryCatch(!is.null(PROJ::proj_trans_create("OGC:CRS84", "EPSG:3857")), error = function(e) FALSE)) ## ----style, include = FALSE--------------------------------------------------- ## one colour per package in every figure col_s2 <- "#1a8a8a" col_bc <- "black" col_planar <- "grey50" col_coast <- "grey78" R <- 6378137 coast <- do.call(cbind, maps::map(plot = FALSE)[1:2]) coast[which(coast[, 1] > 180), ] <- NA ## ----setup-------------------------------------------------------------------- library(bigcurve) library(s2) the_line <- rbind(c(0, 60), c(100, 60)) line <- s2_make_line(the_line[, 1], the_line[, 2]) ## ----midpoint----------------------------------------------------------------- s2_as_text(s2_interpolate_normalized(line, 0.5)) ## how far is the planar middle from the true line, in metres? s2_distance(s2_lnglat(50, 60), line) ## ----fig-meanings, echo = FALSE, fig.width = 9, fig.height = 4.6-------------- gc_path <- function(a, b, n = 400) { l <- s2_make_line(c(a[1], b[1]), c(a[2], b[2])) p <- s2_interpolate_normalized(l, seq(0, 1, length.out = n)) as.matrix(wk::wk_coords(p)[, c("x", "y")]) } true_ll <- gc_path(the_line[1, ], the_line[2, ]) planar_ll <- cbind(seq(0, 100, length.out = 200), 60) mid_planar <- c(50, 60) mid_gc <- as.numeric(wk::wk_coords(s2_interpolate_normalized(line, 0.5))[, c("x", "y")]) gap_km <- s2_distance(s2_lnglat(mid_planar[1], mid_planar[2]), s2_lnglat(mid_gc[1], mid_gc[2])) / 1000 op <- par(mfrow = c(1, 2), mar = c(0.5, 0.5, 2.2, 0.5)) plot(NA, xlim = c(-20, 120), ylim = c(35, 85), asp = 1 / cos(60 * pi / 180), axes = FALSE, xlab = "", ylab = "") lines(coast, col = col_coast) lines(planar_ll, col = col_planar, lty = 2, lwd = 2) lines(true_ll, col = col_bc, lwd = 2) segments(mid_planar[1], mid_planar[2], mid_gc[1], mid_gc[2], col = "firebrick", lwd = 1.5) points(rbind(mid_planar, mid_gc), pch = 21, bg = "white", cex = 1.1) points(the_line, pch = 19) text(mean(c(mid_gc[1], mid_planar[1])), mean(c(mid_gc[2], mid_planar[2])), sprintf("%.0f km", gap_km), pos = 4, col = "firebrick") title("Longitude and latitude as x and y", font.main = 1, cex.main = 1) ortho <- "+proj=ortho +lon_0=50 +lat_0=55 +R=6378137" ang <- seq(0, 2 * pi, length.out = 361) plot(R * cos(ang), R * sin(ang), type = "n", asp = 1, axes = FALSE, xlab = "", ylab = "") polygon(R * cos(ang), R * sin(ang), col = "grey97", border = "grey60") lines(rproj_xy(coast, ortho), col = col_coast) lines(rproj_xy(planar_ll, ortho), col = col_planar, lty = 2, lwd = 2) lines(rproj_xy(true_ll, ortho), col = col_bc, lwd = 2) mids <- rproj_xy(rbind(mid_planar, mid_gc), ortho) segments(mids[1, 1], mids[1, 2], mids[2, 1], mids[2, 2], col = "firebrick", lwd = 1.5) points(mids, pch = 21, bg = "white", cex = 1.1) points(rproj_xy(the_line, ortho), pch = 19) title("The same two points on the globe", font.main = 1, cex.main = 1) legend("bottomleft", bty = "n", cex = 0.85, lty = c(2, 1), lwd = 2, col = c(col_planar, col_bc), legend = c("planar: straight in lon, lat", "geodesic: great circle (s2, bigcurve)")) par(op) ## ----on-the-arc--------------------------------------------------------------- d <- densify(the_line, "+proj=laea +lon_0=50 +lat_0=60", tolerance = 1000) nrow(d) max(s2_distance(s2_lnglat(d[, 1], d[, 2]), line)) ## ----per-projection----------------------------------------------------------- c(laea = nrow(densify(the_line, "+proj=laea +lon_0=50 +lat_0=60", tolerance = 1000)), stere = nrow(densify(the_line, "+proj=stere +lat_0=90", tolerance = 1000)), merc = nrow(densify(the_line, "+proj=merc", tolerance = 1000))) ## ----tessellate--------------------------------------------------------------- merc <- "+proj=merc +R=6378137" tol <- 1000 ## s2, in its own Mercator, then back to longitude and latitude m <- wk::wk_handle(line, wk::wkb_writer(), s2_projection = s2_projection_mercator(), s2_tessellate_tol = tol / s2_earth_radius_meters()) v_s2 <- rproj_xy(as.matrix(wk::wk_coords(wk::wkb(m))[, c("x", "y")]), "OGC:CRS84", source = merc) ## bigcurve, for the same Mercator v_bc <- densify(the_line, merc, tolerance = tol) c(s2 = nrow(v_s2), bigcurve = nrow(v_bc)) ## ----fig-where, echo = FALSE, fig.width = 9, fig.height = 5.2----------------- along <- function(ll) { as.numeric(s2_distance(s2_lnglat(the_line[1, 1], the_line[1, 2]), s2_lnglat(ll[, 1], ll[, 2]))) / s2_length(line) } layout(matrix(1:2, 2), heights = c(3, 1.4)) op <- par(mar = c(0.5, 0.5, 2.2, 0.5)) xy_true <- rproj_xy(true_ll, merc) pad <- 6e5 plot(NA, xlim = range(xy_true[, 1]) + c(-pad, pad), ylim = range(xy_true[, 2]) + c(-pad, pad), asp = 1, axes = FALSE, xlab = "", ylab = "") lines(rproj_xy(coast, merc), col = col_coast) lines(xy_true, col = "grey60", lwd = 4) points(sweep(rproj_xy(v_s2, merc), 2, c(0, 1.2e5), "+"), pch = 19, cex = 0.45, col = col_s2) points(sweep(rproj_xy(v_bc, merc), 2, c(0, 1.2e5), "-"), pch = 19, cex = 0.45, col = col_bc) title(sprintf("One line in Mercator, 1 km tolerance: s2 %d vertices, bigcurve %d", nrow(v_s2), nrow(v_bc)), font.main = 1, cex.main = 1) legend("bottomright", bty = "n", pch = 19, col = c(col_s2, col_bc), cex = 0.85, legend = c("s2 tessellation (drawn just above the line)", "bigcurve (drawn just below)")) par(mar = c(3.2, 7.5, 0.5, 0.5)) plot(NA, xlim = c(0, 1), ylim = c(0.5, 2.5), axes = FALSE, xlab = "", ylab = "") segments(along(v_s2), 1.65, along(v_s2), 2.35, col = col_s2) segments(along(v_bc), 0.65, along(v_bc), 1.35, col = col_bc) axis(1, at = seq(0, 1, 0.25), labels = c("start", "", "half way", "", "end")) axis(2, at = c(2, 1), labels = c("s2", "bigcurve"), las = 1, tick = FALSE) mtext("position of each vertex along the line", side = 1, line = 2.1, cex = 0.85) par(op) ## ----fig-tolerance, echo = FALSE, fig.width = 9, fig.height = 4.2------------- map_error <- function(ll, prj) { t <- along(ll) xy <- rproj_xy(ll, prj) out <- numeric(nrow(ll) - 1L) for (i in seq_along(out)) { arc <- rproj_xy(gc_path(ll[i, ], ll[i + 1, ], n = 64), prj) a <- xy[i, ]; b <- xy[i + 1, ] d <- b - a; L <- sqrt(sum(d^2)) p <- sweep(arc, 2, a) out[i] <- max(abs(d[1] * p[, 2] - d[2] * p[, 1]) / L) } data.frame(t0 = t[-length(t)], t1 = t[-1], err = out) } step_lines <- function(e, ...) { segments(e$t0, pmax(e$err, 1), e$t1, pmax(e$err, 1), ...) } stere <- "+proj=stere +lat_0=90 +lon_0=50 +R=6378137" v_s2_ll <- as.matrix(wk::wk_coords(wk::wkt( s2_as_text(line, planar = TRUE, tessellate_tol_m = tol)))[, c("x", "y")]) v_bc_st <- densify(the_line, stere, tolerance = tol) e_s2_merc <- map_error(v_s2, merc) e_bc_merc <- map_error(v_bc, merc) e_s2_st <- map_error(v_s2_ll, stere) e_bc_st <- map_error(v_bc_st, stere) op <- par(mfrow = c(1, 2), mar = c(3.4, 4.2, 3.2, 0.8)) ylim <- c(1, max(c(e_s2_merc$err, e_bc_merc$err, e_s2_st$err, e_bc_st$err)) * 1.5) panel <- function(es2, ebc, main, s2_label) { plot(NA, xlim = c(0, 1), ylim = ylim, log = "y", axes = FALSE, xlab = "", ylab = "") abline(h = tol, col = "grey60", lty = 3) step_lines(ebc, col = col_bc, lwd = 3.5) step_lines(es2, col = col_s2, lwd = 1.8) axis(1, at = seq(0, 1, 0.5), labels = c("start", "half way", "end")) at <- c(1, 10, 100, 1000, 1e4, 1e5) at <- at[at <= ylim[2]] axis(2, at = at, labels = c("1 m", "10 m", "100 m", "1 km", "10 km", "100 km")[seq_along(at)], las = 1) mtext("drawn chord to true line, on the map", side = 2, line = 3.2, cex = 0.8) title(main, font.main = 1, cex.main = 1) legend("bottomleft", bty = "n", lwd = 2, col = c(col_s2, col_bc), cex = 0.8, legend = c(sprintf("s2: %s (%d vertices)", s2_label, nrow(es2) + 1L), sprintf("bigcurve: 1 km on the map (%d vertices)", nrow(ebc) + 1L))) } panel(e_s2_merc, e_bc_merc, "Mercator (s2 can tessellate here)", "1 km on the sphere") panel(e_s2_st, e_bc_st, "North polar stereographic (s2 cannot)", "1 km in lon, lat, then projected") par(op) ## ----together----------------------------------------------------------------- box <- s2_make_polygon(c(100, 160, 160, 100), c(-35, -35, -20, -20)) aus <- s2_data_countries("Australia") cut <- s2_intersection(aus, box) pool <- wkpool::establish_topology(wk::as_wkb(cut)) out <- densify(pool, "+proj=eqc") c(before = nrow(wkpool::pool_vertices(pool)), after = nrow(wkpool::pool_vertices(out))) ## ----fig-cut, echo = FALSE, fig.width = 9, fig.height = 6--------------------- v0 <- wkpool::pool_vertices(pool) v1 <- wkpool::pool_vertices(out) added <- v1[!v1$.vx %in% v0$.vx, ] flat <- function(x) wk::wk_set_geodesic(wk::wk_set_crs(x, NULL), FALSE) geom <- wkpool::cycles_to_wkb(wkpool::merge_coincident(out)) op <- par(mar = c(2.5, 3.2, 2.2, 0.5)) plot(NA, xlim = c(112, 155), ylim = c(-44, -10), asp = 1, xlab = "", ylab = "", las = 1) plot(flat(wk::as_wkb(aus)), col = "grey95", border = col_coast, add = TRUE) plot(flat(geom), col = "grey85", border = "grey40", add = TRUE) abline(h = c(-20, -35), col = col_planar, lty = 2, lwd = 1.5) text(c(154.5, 154.5), c(-20, -35), c("latitude -20", "latitude -35"), pos = 3, col = col_planar, cex = 0.85) lines(gc_path(c(100, -20), c(160, -20)), col = col_bc, lwd = 1) points(added$x, added$y, pch = 19, cex = 0.8, col = col_bc) title(sprintf("Cutting Australia with the box: its north side is a great circle (%d vertices added)", nrow(added)), font.main = 1, cex.main = 0.95) legend("bottomleft", bty = "n", cex = 0.85, lty = c(2, 1), pch = c(NA, 19), col = c(col_planar, col_bc), lwd = c(1.5, 1), legend = c("the box read as planar (parallels)", "the box as s2 and bigcurve read it, with added vertices")) par(op) ## ----together-draw, eval = proj_ok, fig.height = 3.5-------------------------- prj <- "+proj=laea +lon_0=130 +lat_0=-30" geom <- wkpool::cycles_to_wkb(wkpool::merge_coincident(densify(pool, prj))) drawn <- wk::wk_transform(geom, PROJ::proj_trans_create(wk::wk_crs(geom), prj)) dc <- wk::wk_coords(drawn) op <- par(mar = rep(0.5, 4)) plot(range(dc$x), range(dc$y), type = "n", asp = 1, axes = FALSE, xlab = "", ylab = "") plot(drawn, col = "grey85", add = TRUE) par(op)