|
| 1 | +#' Plot a Triangular Mesh with Smooth Coloring (Boundary Handling) |
| 2 | +#' |
| 3 | +#' Rasterizes a triangular mesh with smooth color interpolation, rotated 90° clockwise |
| 4 | +#' for geospatial plotting (longitude on x-axis, latitude on y-axis, north-up). |
| 5 | +#' Supports optional triangle edges, colorbar, z-scale transformations, NA handling, |
| 6 | +#' and optional map boundary points via \code{mapsta}. |
| 7 | +#' |
| 8 | +#' @param points_df A data.frame with columns \code{lon}, \code{lat}, \code{z}, |
| 9 | +#' and optionally \code{mapsta}. |
| 10 | +#' @param tri_mat A 3 x n matrix of integers defining triangles (indices into points_df). |
| 11 | +#' @param n Integer. Raster resolution (n x n). Ignored if \code{nx} and \code{ny} are provided. |
| 12 | +#' @param nx Number of pixels in x-direction. Overrides \code{n} if provided. |
| 13 | +#' @param ny Number of pixels in y-direction. Overrides \code{n} if provided. |
| 14 | +#' @param draw_edges Logical. If TRUE, triangle edges are drawn. |
| 15 | +#' @param palette Viridis palette: "viridis", "magma", "plasma", "cividis", etc. |
| 16 | +#' @param add_colorbar Logical. If TRUE, adds a colorbar to the plot. |
| 17 | +#' @param z_trans Function. Optional transformation of the z values for coloring |
| 18 | +#' (e.g., \code{sqrt}, \code{log}). |
| 19 | +#' @param na_color Color for pixels outside any triangle. Can be "transparent" or any valid R color. |
| 20 | +#' @param mapsta Optional numeric vector in \code{points_df} identifying boundary vertices |
| 21 | +#' (1 = boundary, 0 = interior). |
| 22 | +#' @param ... Additional arguments passed to \code{image()} |
| 23 | +#' (e.g., \code{main}, \code{asp}, \code{xlab}, \code{ylab}, \code{zlim}). |
| 24 | +#' |
| 25 | +#' @return Invisibly returns a list with rasterized grid and triangle vertices. |
| 26 | +#' @examples |
| 27 | +#'# Create a simple grid of points |
| 28 | +#'points_df <- expand.grid(lon = seq(0, 1, length.out = 5), |
| 29 | +#' lat = seq(0, 1, length.out = 5)) |
| 30 | +#'points_df$z <- sqrt(points_df$lon^2+points_df$lat^2) *sin(2*pi * points_df$lon) * cos(2*pi * points_df$lat) |
| 31 | +#' |
| 32 | +#'# Define a simple triangular mesh manually (each square split into 2 triangles) |
| 33 | +#'tri_mat <- matrix(nrow = 3, ncol = 32) # 4x4 squares * 2 triangles per square |
| 34 | +#'count <- 1 |
| 35 | +#'for(i in 1:4){ |
| 36 | +#' for(j in 1:4){ |
| 37 | +#' p1 <- (i-1)*5 + j |
| 38 | +#' p2 <- p1 + 1 |
| 39 | +#' p3 <- p1 + 5 |
| 40 | +#' p4 <- p3 + 1 |
| 41 | +#' # Triangle 1 |
| 42 | +#' tri_mat[, count] <- c(p1, p2, p4); count <- count + 1 |
| 43 | +#' # Triangle 2 |
| 44 | +#' tri_mat[, count] <- c(p1, p4, p3); count <- count + 1 |
| 45 | +#' } |
| 46 | +#'} |
| 47 | +#' |
| 48 | +#'# Define boundary points: all points on the edge of the grid |
| 49 | +#'points_df$mapsta <- 0 |
| 50 | +#'points_df$mapsta[points_df$lon == 0 | points_df$lon == 1 | |
| 51 | +#' points_df$lat == 0 | points_df$lat == 1] <- 1 |
| 52 | +#' |
| 53 | +#'# Plot using plot_mesh |
| 54 | +#'plot_mesh(points_df, tri_mat, n = 200, |
| 55 | +#' palette = "viridis", |
| 56 | +#' draw_edges = TRUE, |
| 57 | +#' add_colorbar = TRUE, |
| 58 | +#' na_color = "transparent", |
| 59 | +#' mapsta = points_df$mapsta, |
| 60 | +#' xlab = "Longitude", ylab = "Latitude", main = "Simple Map Example") |
| 61 | +#' \dontrun{ |
| 62 | +#' # Example on Resourcecode data |
| 63 | +#' plot_mesh(points_df = data.frame(lon = resourcecodedata::rscd_field$longitude, |
| 64 | +#' lat = resourcecodedata::rscd_field$latitude, |
| 65 | +#' z = resourcecodedata::rscd_field$depth), |
| 66 | +#' tri_mat = resourcecodedata::rscd_triangles, |
| 67 | +#' z_trans = log ,draw_edges = T, n=5000, |
| 68 | +#' asp = 1.5, |
| 69 | +#' zlim = c(-0.70,9), |
| 70 | +#' main = "Bathymetry map (sqrt scale)" |
| 71 | +#' ) |
| 72 | +#' } |
| 73 | +#' @export |
| 74 | +plot_mesh <- function( |
| 75 | + points_df, |
| 76 | + tri_mat, |
| 77 | + n = 500, |
| 78 | + nx = NULL, |
| 79 | + ny = NULL, |
| 80 | + draw_edges = FALSE, |
| 81 | + palette = "viridis", |
| 82 | + add_colorbar = TRUE, |
| 83 | + z_trans = NULL, |
| 84 | + na_color = "transparent", |
| 85 | + mapsta = NULL, |
| 86 | + ... |
| 87 | +) { |
| 88 | + if (is.null(nx)) { |
| 89 | + nx <- n |
| 90 | + } |
| 91 | + if (is.null(ny)) { |
| 92 | + ny <- n |
| 93 | + } |
| 94 | + |
| 95 | + if (!all(c("lon", "lat", "z") %in% names(points_df))) { |
| 96 | + stop("points_df must have columns: lon, lat, z") |
| 97 | + } |
| 98 | + |
| 99 | + # Rasterize triangles |
| 100 | + res <- rasterize_triangles( |
| 101 | + tri_mat, |
| 102 | + points_df$lon, |
| 103 | + points_df$lat, |
| 104 | + points_df$z, |
| 105 | + nx = nx, |
| 106 | + ny = ny, |
| 107 | + draw_edges = draw_edges |
| 108 | + ) |
| 109 | + |
| 110 | + # Apply optional z transformation |
| 111 | + zvals <- res$gridZ |
| 112 | + if (!is.null(z_trans) && is.function(z_trans)) { |
| 113 | + zvals <- z_trans(zvals) |
| 114 | + } |
| 115 | + |
| 116 | + # Fill boundary triangle edges if mapsta is provided |
| 117 | + if (!is.null(mapsta)) { |
| 118 | + if (length(mapsta) != nrow(points_df)) { |
| 119 | + stop( |
| 120 | + "mapsta must have the same length as the number of points in points_df" |
| 121 | + ) |
| 122 | + } |
| 123 | + boundary_vertices <- which(mapsta != 0) |
| 124 | + boundary_triangles <- apply(tri_mat, 2, function(tri) { |
| 125 | + any(tri %in% boundary_vertices) |
| 126 | + }) |
| 127 | + |
| 128 | + # For simplicity, fill NAs with triangle vertex mean |
| 129 | + if (any(boundary_triangles)) { |
| 130 | + mean_boundary <- mean(points_df$z[unlist(tri_mat[, boundary_triangles])]) |
| 131 | + zvals[is.na(zvals)] <- mean_boundary |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + # Rotate 90° clockwise |
| 136 | + z_rot <- t(apply(zvals, 2, rev)) # rotate 90° clockwise |
| 137 | + |
| 138 | + # Define colors |
| 139 | + cols <- viridisLite::viridis(100, option = palette) |
| 140 | + |
| 141 | + # Handle NA pixels |
| 142 | + if (!is.null(na_color) && na_color != "transparent") { |
| 143 | + z_rot_na <- is.na(z_rot) |
| 144 | + z_rot[z_rot_na] <- min(z_rot, na.rm = TRUE) - 1 |
| 145 | + } |
| 146 | + |
| 147 | + # Plot with image.plot |
| 148 | + fields::imagePlot( |
| 149 | + x = seq(res$xmin, res$xmax, length.out = ncol(z_rot)), |
| 150 | + y = seq(res$ymin, res$ymax, length.out = nrow(z_rot)), |
| 151 | + z = z_rot, |
| 152 | + col = cols, |
| 153 | + useRaster = TRUE, |
| 154 | + ... |
| 155 | + ) |
| 156 | + |
| 157 | + # Optional triangle edges |
| 158 | + if (draw_edges) { |
| 159 | + lines(res$triY, res$triX, col = "black", lwd = 0.5) |
| 160 | + } |
| 161 | + |
| 162 | + invisible(res) |
| 163 | +} |
0 commit comments