We load two packages, our ‘photobiology’ and ‘lubridate’, as they will be used in the examples.
library(photobiology)
library(lubridate)
The functions and methods described in this section return either values that represent angles or times. In the current version angles are always expressed in degrees. In the case of times, the unit of expression, can be changed through parameter unit.out
which accepts the following arguments "datetime"
, "hours"
, "minutes"
, "seconds"
. For backwards compatibility "date"
is also accepted as equivalent to "datetime"
but deprecated.
In photobiology research we sometimes need to calculate the position on the sun at arbitrary geographic locations and times. The function sun_angles
returns the azimuth in degrees eastwards, altitude in degrees above the horizon, solar disk diameter in degrees and sun to earth distance in astronomical units. The time should be a POSIXct
vector, possibly of length one. The easiest way create date and time constant values is to use package lubridate
.
geocode
most functions also had the redundant formal parameters lon
and lat
which were removed in version 0.9.12. This change may require users’ scripts to be revised.
For calculation of the position of the sun we need to supply geographic coordinates and a time instant. The object to be supplied as argument for geocode
is a data frame with variables lon
and lat
giving the location of Earth. This matches the return value of function ggmap::geocode()
, function that can be used to find the coordinates using any address entered as a character string understood by the Google maps API. We save the “geocode” for Helsinki.
Be aware that to obtain results based on local time, the correct time zone needs to be set. In the examples we use functions from package ‘lubridate’ for working with times and dates. The argument passed to parameter time
can be a “vector” of POSIXct
values. The returned value is a data.frame
.
my.geocode <- data.frame(lat = 60.16, lon = 24.93, address = "Helsinki")
The position of the sun at Helsinki, at the given time instant decoded for time zone Eastern European Time.
sun_angles(time = ymd_hms("2017-06-20 08:00:00", tz = "EET"), geocode = my.geocode)
## time tz solartime longitude latitude address azimuth
## 1 2017-06-20 08:00:00 EET 06:38:05 24.93 60.16 Helsinki 85.81608
## elevation
## 1 24.86576
Functions have defaults for their arguments, but rarely Greenwich will be the location you are interested in.
sun_angles()
## time tz solartime longitude latitude address azimuth
## 1 2018-04-02 16:23:33 UTC 16:20:08 0 51.5 Greenwich 253.1997
## elevation
## 1 19.42415
A vector of times is accepted as argument, and as performance is optimized for this case, vectorization will markedly improve performance compared to multiple calls to the function. The vector of times can be created on the fly, or stored beforehand.
sun_angles(time = ymd_hms("2014-01-01 0:0:0", tz = "EET") + hours(c(0, 6, 12)),
geocode = my.geocode)
## time tz solartime longitude latitude address azimuth
## 1 2014-01-01 00:00:00 EET 23:36:18 24.93 60.16 Helsinki 350.99883
## 2 2014-01-01 06:00:00 EET 05:36:11 24.93 60.16 Helsinki 96.94086
## 3 2014-01-01 12:00:00 EET 11:36:04 24.93 60.16 Helsinki 174.45468
## elevation
## 1 -52.613245
## 2 -22.719139
## 3 6.733197
my.times <- ymd_hms("2014-01-01 0:0:0", tz = "EET") + hours(c(0, 6, 12))
sun_angles(time = my.times, geocode = my.geocode)
## time tz solartime longitude latitude address azimuth
## 1 2014-01-01 00:00:00 EET 23:36:18 24.93 60.16 Helsinki 350.99883
## 2 2014-01-01 06:00:00 EET 05:36:11 24.93 60.16 Helsinki 96.94086
## 3 2014-01-01 12:00:00 EET 11:36:04 24.93 60.16 Helsinki 174.45468
## elevation
## 1 -52.613245
## 2 -22.719139
## 3 6.733197
Geocodes for several locations can be stored in a data frame with multiple rows.
two.geocodes <- data.frame(lat = c(60.16, 65.02),
lon = c(24.93, 25.47),
address = c("Helsinki", "Oulu"))
sun_angles(time = my.times, geocode = two.geocodes)
## time tz solartime longitude latitude address azimuth
## 1 2014-01-01 00:00:00 EET 23:36:18 24.93 60.16 Helsinki 350.99883
## 2 2014-01-01 06:00:00 EET 05:36:11 24.93 60.16 Helsinki 96.94086
## 3 2014-01-01 12:00:00 EET 11:36:04 24.93 60.16 Helsinki 174.45468
## 4 2014-01-01 00:00:00 EET 23:38:27 25.47 65.02 Oulu 352.60743
## 5 2014-01-01 06:00:00 EET 05:38:20 25.47 65.02 Oulu 95.37135
## 6 2014-01-01 12:00:00 EET 11:38:13 25.47 65.02 Oulu 174.98707
## elevation
## 1 -52.613245
## 2 -22.719139
## 3 6.733197
## 4 -47.838059
## 5 -22.993285
## 6 1.916681
When spectra contain suitable metadata, the position of the sun for the spectral irradiance data measurement can be easily obtained.
sun_angles(time = getWhenMeasured(sun.spct), geocode = getWhereMeasured(sun.spct))
## time tz solartime longitude latitude
## 1 2010-06-22 09:51:00 UTC 11:28:50 24.96474 60.20911
## address azimuth elevation
## 1 kumpula, 00560 helsinki, finland 168.1227 52.82344
One what is needed is only one of the solar angles, functions returning vectors instead of data frames can be useful.
sun_elevation(time = my.times, geocode = my.geocode)
## [1] -52.613245 -22.719139 6.733197
sun_zenith_angle(time = my.times, geocode = my.geocode)
## [1] 142.6132 112.7191 83.2668
sun_azimuth(time = my.times, geocode = my.geocode)
## [1] 350.99883 96.94086 174.45468
Functions sunrise_time
, sunset_time
, noon_time
, day_length
and night_length
have all the same parameter signature. An additional function, day_night
returns a data frame containing all the quantities returned by the other functions. They are all vectorized for the date
and geocode
parameters. As arguments are the same for all these functions, we use sunrise_time
in the examples below, but they apply to the other functions described in this section.
Both latitude and longitude should be supplied through a geocode
, but be aware that if the returned value is desired in the local time coordinates of the argument passed to geocode
, the time zone should match the geographic coordinates. If geocodes contain a variable "address"
it will be copied to the output. We reuse the geocode data frames created above, and create a vector of datetime objects differing in their date. The default time zone of function ymd
is UTC, so we override it with EET to match the geocodes for Finnish cities.
dates <- ymd("2015-03-01", tz = "EET") + months(0:5)
dates
## [1] "2015-03-01 EET" "2015-04-01 EEST" "2015-05-01 EEST" "2015-06-01 EEST"
## [5] "2015-07-01 EEST" "2015-08-01 EEST"
sunrise_time(now("UTC"), tz = "UTC", geocode = my.geocode)
## [1] "2018-04-02 03:40:56 UTC"
sunrise_time(now("EET"), tz = "EET", geocode = my.geocode)
## [1] "2018-04-02 06:40:56 EEST"
Southern hemisphere latitudes as well as longitudes to the West of the Greenwich meridian should be supplied as negative numbers.
sunrise_time(dates, geocode = data.frame(lat = 60, lon = 0))
## [1] "2015-02-28 09:01:01 EET" "2015-03-31 08:27:48 EEST"
## [3] "2015-04-30 07:00:02 EEST" "2015-05-31 05:50:22 EEST"
## [5] "2015-06-30 05:41:27 EEST" "2015-07-31 06:38:55 EEST"
sunrise_time(dates, geocode = data.frame(lat = -60, lon = 0))
## [1] "2015-02-28 07:10:10 EET" "2015-03-31 09:27:01 EEST"
## [3] "2015-04-30 10:38:52 EEST" "2015-05-31 11:44:54 EEST"
## [5] "2015-06-30 12:04:16 EEST" "2015-07-31 11:16:47 EEST"
The angle used in the twilight calculation can be supplied, either as the name of a standard definition, or as an angle in degrees (negative for sun positions below the horizon). Positive angles can be used when the time of sun occlusion behind a building, mountain, or other obstacle needs to be calculated. The default for twilight
is "none"
meaning that times correspond to the occlusion of the upper rim of the sun below the theoretical horizon.
sunrise_time(ymd("2017-03-21", tz = "EET"),
tz = "EET",
geocode = my.geocode,
twilight = "civil")
## [1] "2017-03-20 05:38:03 EET"
sunrise_time(ymd("2017-03-21", tz = "EET"),
tz = "EET",
geocode = my.geocode,
twilight = -10)
## [1] "2017-03-20 05:04:49 EET"
sunrise_time(ymd("2017-03-21", tz = "EET"),
tz = "EET",
geocode = my.geocode,
twilight = +12)
## [1] "2017-03-20 08:05:17 EET"
Default latitude is zero (the Equator), the default longitude is zero (Greenwich). Be also aware that for summer dates the times are formatted for printing accordingly. In the examples below this can be recognized by the time zone being reported as EEST instead of EET during the summer for Eastern Europe.
The main function is called day_night
and returns a data frame containing both the input values and the results of the calculations. See below for additional convenience functions useful in the case one needs only one of the calculated variables. In other cases it is more efficient to compute the whole data frame and later select the columns of interest.
day_night(dates[1:3],
geocode = my.geocode)
## day tz twilight.rise twilight.set longitude latitude address
## 1 2015-02-28 UTC 0 0 24.93 60.16 Helsinki
## 2 2015-03-31 UTC 0 0 24.93 60.16 Helsinki
## 3 2015-04-30 UTC 0 0 24.93 60.16 Helsinki
## sunrise noon sunset daylength nightlength
## 1 5.476120 10.54678 15.61744 10.14132 13.858676
## 2 3.910671 10.40715 16.90363 12.99296 11.007041
## 3 2.456515 10.29174 18.12696 15.67045 8.329551
The default for unit.out
is "hours"
with decimal fractions, as seen above. However other useful units like "seconds"
, "minutes"
, and "days"
can be useful.
day_night(dates[1:2],
geocode = my.geocode,
unit.out = "days")
## day tz twilight.rise twilight.set longitude latitude address
## 1 2015-02-28 UTC 0 0 24.93 60.16 Helsinki
## 2 2015-03-31 UTC 0 0 24.93 60.16 Helsinki
## sunrise noon sunset daylength nightlength
## 1 0.2281717 0.4394492 0.6507268 0.4225551 0.5774449
## 2 0.1629446 0.4336312 0.7043179 0.5413733 0.4586267
Finally it is also possible to have the timing of solar events returned as POSIXct
time values, in which case lengths of time are still returned as fractional hours.
day_night(dates[1:2],
geocode = my.geocode,
unit.out = "datetime")
## day tz twilight.rise twilight.set longitude latitude address
## 1 2015-02-28 UTC 0 0 24.93 60.16 Helsinki
## 2 2015-03-31 UTC 0 0 24.93 60.16 Helsinki
## sunrise noon sunset daylength
## 1 2015-02-28 05:28:34 2015-02-28 10:32:48 2015-02-28 15:37:02 10.14132
## 2 2015-03-31 03:54:38 2015-03-31 10:24:25 2015-03-31 16:54:13 12.99296
## nightlength
## 1 13.85868
## 2 11.00704
When multiple times and locations are supplied as arguments, the values returned are for all combinations of locations and times.
day_night(dates[1:3],
geocode = two.geocodes)
## day tz twilight.rise twilight.set longitude latitude address
## 1 2015-02-28 UTC 0 0 24.93 60.16 Helsinki
## 2 2015-03-31 UTC 0 0 24.93 60.16 Helsinki
## 3 2015-04-30 UTC 0 0 24.93 60.16 Helsinki
## 4 2015-02-28 UTC 0 0 25.47 65.02 Oulu
## 5 2015-03-31 UTC 0 0 25.47 65.02 Oulu
## 6 2015-04-30 UTC 0 0 25.47 65.02 Oulu
## sunrise noon sunset daylength nightlength
## 1 5.476120 10.54678 15.61744 10.141324 13.858676
## 2 3.910671 10.40715 16.90363 12.992959 11.007041
## 3 2.456515 10.29174 18.12696 15.670449 8.329551
## 4 5.661113 10.51078 15.36045 9.699339 14.300661
## 5 3.758946 10.37115 16.98335 13.224408 10.775592
## 6 1.943318 10.25574 18.56816 16.624843 7.375157
Different convenience functions return the calculated variables individually as vectors.
sunrise_time(date = dates, geocode = my.geocode)
## [1] "2015-02-28 07:21:37 EET" "2015-03-31 06:47:51 EEST"
## [3] "2015-04-30 05:19:30 EEST" "2015-05-31 04:09:05 EEST"
## [5] "2015-06-30 03:59:56 EEST" "2015-07-31 04:58:06 EEST"
As seen above the default for tz
is the time zone of the argument passed to date
. This can be overridden with an explicit value as argument. In this example, when interpreted in the UTC time zone, the time instants correspond to the previous calendar day compared to the EET time zone. We can also see that “summer time” applies to the EET time zone but not to UTC (universal time coordinates).
sunrise_time(date = dates, tz = "UTC", geocode = my.geocode)
## [1] "2015-02-28 05:21:37 UTC" "2015-03-31 03:47:51 UTC"
## [3] "2015-04-30 02:19:30 UTC" "2015-05-31 01:09:05 UTC"
## [5] "2015-06-30 00:59:56 UTC" "2015-07-31 01:58:06 UTC"
noon_time(date = dates, geocode = my.geocode)
## [1] "2015-02-28 12:32:48 EET" "2015-03-31 13:24:25 EEST"
## [3] "2015-04-30 13:17:30 EEST" "2015-05-31 13:17:58 EEST"
## [5] "2015-06-30 13:23:57 EEST" "2015-07-31 13:26:40 EEST"
sunset_time(date = dates, geocode = my.geocode)
## [1] "2015-02-28 17:43:59 EET" "2015-03-31 20:01:00 EEST"
## [3] "2015-04-30 21:15:30 EEST" "2015-05-31 22:26:51 EEST"
## [5] "2015-06-30 22:47:57 EEST" "2015-07-31 21:55:14 EEST"
The default for date
is the current day, using the system time zone settings.
noon_time(geocode = my.geocode)
## [1] "2018-04-02 13:23:45 EEST"
Parameter unit.out
can be used to obtain the returned value expressed as time-of-day in hours, minutes, or seconds since midnight, instead of the default datetime
.
sunrise_time(ymd("2017-03-21", tz = "EET"),
tz = "EET",
geocode = my.geocode)
## [1] "2017-03-20 06:19:53 EET"
sunrise_time(ymd("2017-03-21", tz = "EET"),
tz = "EET",
geocode = my.geocode,
unit.out = "hours")
## [1] 6.331521
Functions day_length
and night_length
return by default the length of time in hours.
day_length(dates, geocode = my.geocode)
## [1] 10.37265 13.21916 15.93326 18.29610 18.80025 16.95222
night_length(dates, geocode = my.geocode)
## [1] 13.627346 10.780844 8.066744 5.703898 5.199751 7.047784
day_length(dates, geocode = my.geocode, unit.out = "days")
## [1] 0.4321939 0.5507982 0.6638857 0.7623376 0.7833437 0.7063423
night_length(dates, geocode = my.geocode, unit.out = "days")
## [1] 0.5678061 0.4492018 0.3361143 0.2376624 0.2166563 0.2936577
In field research it is in many cases preferable to sample or measure, and present and plot data based on local solar time. Two functions are provided. They differ in the value returned, either a time of day in hours, or a datetime.
Paris.geo <- data.frame(lon = 2.352222, lat = 48.85661, address = "Paris")
Paris.time <- ymd_hms("2016-09-30 06:00:00", tz = "UTC")
solar_time(Paris.time, geocode = Paris.geo)
## [1] "06:19:33"
solar_time(Paris.time, geocode = Paris.geo, unit.out = "datetime")
## [1] "2016-09-30 06:19:33 solar"
my.solar.t <- solar_time(Paris.time, geocode = Paris.geo)
is.solar_time(my.solar.t)
## [1] TRUE
is.numeric(my.solar.t)
## [1] TRUE
my.solar.d <- solar_time(Paris.time, geocode = Paris.geo, unit.out = "datetime")
is.solar_date(my.solar.d)
## [1] TRUE
is.timepoint(my.solar.d)
## [1] TRUE
Function as_tod()
facilitates conversion of R’s time date objects into a numeric value representing the time of day in one of hour, minute or second as unit of expression.
times <- now(tzone = "UTC") + hours(0:6)
times
## [1] "2018-04-02 16:23:34 UTC" "2018-04-02 17:23:34 UTC"
## [3] "2018-04-02 18:23:34 UTC" "2018-04-02 19:23:34 UTC"
## [5] "2018-04-02 20:23:34 UTC" "2018-04-02 21:23:34 UTC"
## [7] "2018-04-02 22:23:34 UTC"
as_tod(times)
## [1] 16.3929 17.3929 18.3929 19.3929 20.3929 21.3929 22.3929
as_tod(times, unit.out = "minutes")
## [1] 983.5741 1043.5741 1103.5741 1163.5741 1223.5741 1283.5741 1343.5741
Solar elevation determines the length of the path of the sun beam through the Earth’s atmosphere. This affects the solar spectrum at ground level, specially the UVB region. Function relative_AM()
can be used to calculate an empirical estimate of this quantity from elevation angles in degrees. This function is vectorised.
relative_AM(33)
## [1] 1.83
relative_AM(c(80, 60, 40, 20))
## [1] 1.01 1.15 1.55 2.90