/
wind_functions.R
229 lines (221 loc) · 8.29 KB
/
wind_functions.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# -------------------------------------------------------------------
# - AUTHOR: Reto Stauffer
# - DATE: 2018-12-21
# -------------------------------------------------------------------
# - DESCRIPTION:
# -------------------------------------------------------------------
# - EDITORIAL: 2018-12-21, RS: Created file on thinkreto.
# -------------------------------------------------------------------
# - L@ST MODIFIED: 2019-08-16 15:07 on marvin
# -------------------------------------------------------------------
#' Convert u and v Wind Components to Wind Speed and Wind Direction
#'
#' Takes u/v wind components (zonal and meridional wind components) as input
#' and returns wind speed and meteorological wind direction.
#'
#' @param u \code{numeric} vector with u components or a
#' \code{data.frame} or \code{zoo} object containing
#' a column named \code{u} and a column named \code{v}.
#' @param v \code{NULL} (default) or \code{numeric} vector.
#' If input \code{u} is a single vector \code{v} has to be specified.
#' @param rad \code{logical}, default is \code{FALSE}. Returns wind
#' direction in radiant rather than degrees.
#'
#' @return Returns a \code{data.frame} or \code{zoo} object (depending on input
#' \code{u}) with two columns named \code{dd}
#' and \code{ff} containing wind speed (same physical unit as
#' input \code{u}/\code{v}) and wind direction. Wind direction
#' is either in meteorological degrees (0 from North, from 90 East,
#' 180 from South, and 270 from West) or in mathematical radiant
#' if input \code{rad = TRUE}.
#'
#' @author Reto Stauffer
#'
#' @details Note: if both, \code{u} and \code{v} are provided they
#' do have to be of the same length OR one of them has to be of length 1.
#' The one with length 1 will be recycled.
#'
#' @seealso ddff2uv
#' @examples
#' ## Generate data.frame with u/v components for all 4 main wind directions
#' data <- data.frame(name = c("N","E","S","W"),
#' u = c( 0, -1 , 0, 1 ),
#' v = c(-1, 0, 1, 0 ))
#' ## Use data.frame input
#' ddff <- uv2ddff(data)
#' cbind(data, ddff)
#' ## Use u/v components as two separate vector inputs
#' ddff <- uv2ddff(data$u, data$v)
#' cbind(data, ddff)
#' ## Radiant
#' ddff <- uv2ddff(data, rad = TRUE)
#' cbind(data, ddff)
#'
#' ## Use with zoo
#' library("zoo")
#' set.seed(100)
#' Sys.setenv("TZ" = "UTC")
#' data <- zoo(data.frame(u = rnorm(20, 2, 5), v = rnorm(20, 0, 4)),
#' as.POSIXct("2019-01-01 12:00") + 0:19 * 3600)
#' head(data)
#' class(data)
#' ## Calculate dd/ff
#' ddff <- uv2ddff(data)
#' head(ddff)
#' class(ddff)
#'
#' @export
uv2ddff <- function(u, v = NULL, rad = FALSE){
# if input u is zoo or data.frame
zoo_index <- NULL # Default
if (inherits(u, c("zoo", "data.frame"))) {
# If input 'u' is zoo: keep index
if (inherits(u, "zoo")) zoo_index <- index(u)
if (!all(c("u", "v") %in% names(u)))
stop("necessary colums \"u\" and/or \"v\" missing")
# If "v" is set in addition: warn
if (!is.null(v)) {
warning(sprintf("input \"u\" to uv2ddff is \"%s\":", class(u)),
"\"v\" specified as well but will be ignored!")
}
v = as.numeric(u$v)
u = as.numeric(u$u)
# if u has 2 columns the second column is taken as v
} else if (NCOL(u) == 2) {
v <- u[,2]
u <- u[,1]
} else {
if (is.null(v)) stop("input \"v\" missing")
# If lenths do not match and none of them is of length 1: stop.
if (!(length(u) == length(v)) & !any(c(length(u), length(v)) == 1L)) {
stop("Length of \"u\" and \"v\" not identical")
# Else recycle the one with length one to the length of the other one.
# Damn it, so much one's in one sentence!
} else if (length(u) == 1) {
u <- rep(u, length(v))
} else if (length(v) == 1) {
v <- rep(v, length(u))
}
}
# polar coordinates:
ff <- sqrt(u^2 + v^2)
dd <- atan(v/u) + (u < 0) * pi
# Only non-na combis
idx <- which(!is.na(dd) & !is.na(ff)); dd[idx] <- dd[idx] + 2 * pi
# convert angle to meteorological convention
dd <- 3 * pi / 2 - dd
idx <- which(!is.na(dd) & !is.na(ff)); dd[idx] <- dd[idx] + 2 * pi
# if rad (radiants) = F we have to convert to degrees.
if (!rad) dd <- dd * 180 / pi
res <- data.frame(dd, ff)
if (is.null(zoo_index)) return(res) else return(zoo(res, zoo_index))
}
#' Convert Wind Speed and Wind Direction to u/v Wind Components
#'
#' Converts wind direction (\code{dd}) and wind speed (\code{ff})
#' information into \code{u}/\code{v} wind components (zonal and
#' meridional wind component).
#'
#' @param ff \code{numeric}, \code{matrix}, \code{data.frame}, or
#' \code{zoo} object (see 'Details' section).
#' @param dd only necessary if \code{ff} is a numeric vector. Use NULL if
#' input \code{ff} is containing both variables (\code{ff}, \code{dd}), else
#' \code{dd} is a data vector containing the wind direction in degrees (0:
#' north, 90: east, ...)
#'
#' @details Converts data from wind speed and direction into the zonal
#' and meridional wind components (\code{u}/\code{v}).
#'
#' Different inputs are allowed:
#' \itemize{
#' \item if \code{ff} is a matrix: requires to contains at least the
#' two columns \code{"ff"} and \code{"dd"}.
#' \item if \code{ff} is a \code{data.frame} or \code{zoo} object:
#' requires to contains at least the
#' two variables \code{"ff"} and \code{"dd"}.
#' \item if \code{ff} is \code{numeric}: \code{dd} has to be provided
#' in advance. \code{ff} and \code{dd} have to be of the same length
#' or one has to be of length \code{1} (will be recycled).
#' }
#'
#' @return Returns a \code{data.frame} or \code{zoo} object (depending on input \code{ff})
#' containing the \code{u} and \code{v} components
#' of the data. In addition, \code{rad} (mathematical representation
#' of wind direction in radiant) is returned.
#'
#' @author Reto Stauffer
#'
#' @seealso uv2ddff
#' @examples
#' ## Generate dd and ff variable
#' set.seed(0)
#' ff <- floor(abs(rnorm(20))*10)
#' dd <- sample(seq(0,359),20)
#' df <- data.frame('ff'=ff,'dd'=dd)
#'
#' ## Using with vectors
#' print(head(ddff2uv('dd' = dd, 'ff' = ff)))
#'
#' ## Using with data.frame
#' print(head(ddff2uv(df)))
#'
#' ## Using with matrix
#' print(head(ddff2uv(as.matrix(df))))
#'
#' ## Use with zoo
#' library("zoo")
#' set.seed(100)
#' Sys.setenv("TZ" = "UTC")
#' data <- zoo(data.frame(dd = runif(20, 0, 360), ff = rnorm(20, .2, 2)^2),
#' as.POSIXct("2019-01-01 12:00") + 0:19 * 3600)
#' data <- round(data,2)
#' head(data)
#' class(data)
#' ## Calculate dd/ff
#' uv <- ddff2uv(data)
#' head(uv)
#' class(uv)
#' @export
ddff2uv <- function(dd, ff = NULL){
# if ff is a data.frame or a zoo object we have to
# search for necessary 'dd' and 'ff'
zoo_index <- NULL # Default
if (inherits(dd, c("zoo", "data.frame"))) {
if (sum(! c('ff','dd') %in% names(dd) ) > 0)
stop('necessary colums "ff" and/or "dd" missing')
# If "ff" is set in addition: warn
if (!is.null(ff)) {
warning(sprintf("input \"dd\" to uv2ddff is \"%s\":", class(u)),
"\"ff\" specified as well but will be ignored!")
}
if (inherits(dd, "zoo")) zoo_index <- index(dd)
ff = as.numeric(dd$ff)
dd = as.numeric(dd$dd)
# if ff has 2 columns the second column is taken as dd
} else if(NCOL(dd) == 2) {
ff <- dd[,2]
dd <- dd[,1]
} else {
if (is.null(ff)) stop("input \"ff\" missing")
# If lenths do not match and none of them is of length 1: stop.
if (!length(dd) == length(ff) & !any(c(length(dd), length(ff)) == 1L)) {
stop("Length of \"dd\" and \"ff\" not identical")
# Else recycle the one with length one to the length of the other one.
# Damn it, so much one's in one sentence!
} else if (length(dd) == 1) {
dd <- rep(dd, length(ff))
} else if (length(ff) == 1) {
ff <- rep(ff, length(dd))
}
}
# Convert into polar coordinates
metrad <- dd * pi / 180
## u and v
u <- ff * (-sin(metrad))
v <- ff * (-cos(metrad))
# Mathematical metrad
rad <- 2*pi - metrad - pi/2
rad <- ifelse(rad >= 2*pi, rad - 2*pi, rad)
res <- data.frame(u, v, rad)
if (is.null(zoo_index)) return(res) else return(zoo(res, zoo_index))
}