--- title: "Angle Correction" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Angle Correction} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.ext = "svg", dev = "svg" ) ``` ## A matter of perspective When you have a data set in which vector angles are specified, it is important to realise that that angle should have some frame of reference. For instance if you specify an angle in an xy-plane (e.g. 30 degrees from North) and then plot it in a plane with an aspect ratio of 1:2 (i.e., values on the y-axis are twice as tall as those on the x-axis), the angle changes. In the plot the original angle of 30 degrees North would appear as 15 degrees due to the aspect ratio. The same is true is you specify geographical directions in spherical coordinates, angles will change if you plot it in a different projection where the spherical coordinates are squashed to the flat surface of a plot. This can be illustrated by setting up a spatial grid with vectors that all point Northwards: ```{r init} north_arrows <- expand.grid( x = seq(-5, 15, length.out = 10), y = seq(89.85, 89.9, length.out = 10) ) |> sf::st_as_sf(coords = c("x", "y"), crs = 4326) |> sf::st_make_grid() |> stars::st_as_stars(nx = 10, ny = 10) |> dplyr::mutate(angle = 0*(2*pi/360)) ``` Note that if we plot this in WGS84 lon lat projection (i.e., spherical coordinates), all arrows point upwards: ```{r angle_lonlat} library(ggfields) library(ggplot2) theme_set(theme_light()) ggplot() + geom_fields(data = north_arrows, aes(angle = angle), radius = 1) ``` Now if we plot the same data in UTM (zone 31 N in meters, EPSG code 32631), and do so without correction (red) and with correction (green), you will see what happens: ```{r angle_diff} no_correction <- geom_fields(data = north_arrows, aes(angle = angle, col = "no correction"), radius = 1, .angle_correction = NULL, max_radius = ggplot2::unit(0.7, "cm")) p <- ggplot() + theme(legend.position = "top") + labs(colour = NULL) p + no_correction + geom_fields(data = north_arrows, aes(angle = angle, col = "corrected"), radius = 1, max_radius = ggplot2::unit(0.7, "cm")) + scale_colour_manual(values = c(`no correction` = "red", corrected = "green")) + coord_sf(crs = 32631) ``` Remember that in reality (and in this plot) the parallels of latitude converge at the North Pole. If we don't correct the angles of zero degrees (red), they will still point upwards, but in the projection of the plot that doesn't make sense (they don't point towards the North Pole). You can see that after correction, the arrows do point to the North Pole and follow the curved parallels of latitude. ## Correction mechanism of `ggfield` ### Default correction `ggfield` uses the `angle_correction()` function to correct angles. If your data has a coordinate reference system (see `sf::st_crs()`) specified, it will be assumed that angles are specified in relation to the Earth's spherical lon-lat coordinates. In other cases, it is assumed that the angle is specified in the xy-plane given by the coordinates specified with `ggplot2::aes()`. In the latter case, angles are only corrected for the aspect ratio of the plot. The `angle_correction()` function is passed to the `.angle_correction` argument of the `geom_fields()` function. This correction function is called when the plot is rendered. When this argument is set to `NULL` no correction is applied (see red arrows in plot above). ### Custom correction It is also possible to provide a custom correction function. You just need to define a function that accepts the arguments `data`, `panel_params`, and `coord`. The function should return a modified version of the `data.frame` `data` that contains a column named `angle_correction`. `angle_correction` should contain `numeric` radials that should be added to the original `angle`. Let's say we want a custom correction function that rotates all angles by 90 degrees (pi/2 in radials), it would look like this: ```{r custom_corr} custom_correct <- function(data, panel_params, coord) { data |> dplyr::mutate(angle_correction = pi/2) } no_correction[[1]]$geom_params$max_radius <- ggplot2::unit(0.3, "cm") p + no_correction + geom_fields(data = north_arrows, aes(angle = angle, col = "custom correction"), radius = 1, .angle_correction = custom_correct, max_radius = ggplot2::unit(0.3, "cm")) + scale_colour_manual(values = c(`no correction` = "red", `custom correction` = "orange")) ``` ## Conclusion The take home message is that angles in your data have been defined from a certain perspective, about which `ggfield` knows nothing. Although the default `angle_correction()` function does its best to correct angles in your plot, it is up to you to make sure that these corrections actually make sense.