Skip to content

Commit ebfe018

Browse files
committed
Add first idea for plotting a colored mesh
1 parent 64cab64 commit ebfe018

7 files changed

Lines changed: 368 additions & 3 deletions

File tree

DESCRIPTION

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Depends:
2222
Imports:
2323
abind,
2424
curl,
25+
fields,
2526
geosphere,
2627
ggplot2,
2728
grid,
@@ -37,13 +38,13 @@ Imports:
3738
sf,
3839
stats,
3940
tibble,
40-
tidyr
41+
tidyr,
42+
viridisLite
4143
Suggests:
4244
knitr,
4345
mockery,
4446
rmarkdown,
45-
testthat,
46-
vdiffr
47+
testthat
4748
LinkingTo:
4849
Rcpp,
4950
RcppArmadillo

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export(mean_direction)
2020
export(metconv2zmcomp)
2121
export(plot_1d_specta)
2222
export(plot_2d_specta)
23+
export(plot_mesh)
2324
export(rscd_mapplot)
2425
export(weather_windows)
2526
export(zmcomp2metconv)

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ fastTrapz <- function(x, Y, dim = 1L) {
1818
.Call(`_resourcecode_fastTrapz`, x, Y, dim)
1919
}
2020

21+
rasterize_triangles <- function(tri_mat, x, y, z, nx = 500L, ny = 500L, draw_edges = FALSE) {
22+
.Call(`_resourcecode_rasterize_triangles`, tri_mat, x, y, z, nx, ny, draw_edges)
23+
}
24+
2125
ww_calc_cpp <- function(times, winlen_hours, allow_overlap, tstep_secs) {
2226
.Call(`_resourcecode_ww_calc_cpp`, times, winlen_hours, allow_overlap, tstep_secs)
2327
}

R/plot_mesh.R

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
}

man/plot_mesh.Rd

Lines changed: 107 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ BEGIN_RCPP
2424
return rcpp_result_gen;
2525
END_RCPP
2626
}
27+
// rasterize_triangles
28+
List rasterize_triangles(NumericMatrix tri_mat, NumericVector x, NumericVector y, NumericVector z, int nx, int ny, bool draw_edges);
29+
RcppExport SEXP _resourcecode_rasterize_triangles(SEXP tri_matSEXP, SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP nxSEXP, SEXP nySEXP, SEXP draw_edgesSEXP) {
30+
BEGIN_RCPP
31+
Rcpp::RObject rcpp_result_gen;
32+
Rcpp::RNGScope rcpp_rngScope_gen;
33+
Rcpp::traits::input_parameter< NumericMatrix >::type tri_mat(tri_matSEXP);
34+
Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP);
35+
Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP);
36+
Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP);
37+
Rcpp::traits::input_parameter< int >::type nx(nxSEXP);
38+
Rcpp::traits::input_parameter< int >::type ny(nySEXP);
39+
Rcpp::traits::input_parameter< bool >::type draw_edges(draw_edgesSEXP);
40+
rcpp_result_gen = Rcpp::wrap(rasterize_triangles(tri_mat, x, y, z, nx, ny, draw_edges));
41+
return rcpp_result_gen;
42+
END_RCPP
43+
}
2744
// ww_calc_cpp
2845
NumericVector ww_calc_cpp(NumericVector times, double winlen_hours, bool allow_overlap, double tstep_secs);
2946
RcppExport SEXP _resourcecode_ww_calc_cpp(SEXP timesSEXP, SEXP winlen_hoursSEXP, SEXP allow_overlapSEXP, SEXP tstep_secsSEXP) {
@@ -41,6 +58,7 @@ END_RCPP
4158

4259
static const R_CallMethodDef CallEntries[] = {
4360
{"_resourcecode_fastTrapz", (DL_FUNC) &_resourcecode_fastTrapz, 3},
61+
{"_resourcecode_rasterize_triangles", (DL_FUNC) &_resourcecode_rasterize_triangles, 7},
4462
{"_resourcecode_ww_calc_cpp", (DL_FUNC) &_resourcecode_ww_calc_cpp, 4},
4563
{NULL, NULL, 0}
4664
};

0 commit comments

Comments
 (0)