evapotranspiration 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE-ORIGINAL.txt +29 -0
- data/LICENSE.txt +29 -0
- data/README.md +53 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/evapotranspiration.gemspec +23 -0
- data/lib/evapotranspiration.rb +5 -0
- data/lib/evapotranspiration/conversion.rb +37 -0
- data/lib/evapotranspiration/fao.rb +627 -0
- data/lib/evapotranspiration/thornthwaite.rb +122 -0
- data/lib/evapotranspiration/validation.rb +55 -0
- data/lib/evapotranspiration/version.rb +3 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 988048fa8b6ccee0e7bd6fcdaca2797cfcdc7a1a
|
4
|
+
data.tar.gz: 41a4bd21d9595e51d7b716218e03f81f7574c98f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 82a628bf0d71de55a8297da685f3472ea151db031a03846467a2bc8fd731cb3d42c9b74fb2ee49cc1ff22fc8cbf4df9e2e833cd3892f9479453f9ca4a2ce7173
|
7
|
+
data.tar.gz: 221e966a85a752b9160fbb6ba7616f44c8400e791a9c0ac9614e97f5b0b6db48eca476535a130ee6bdca1667dd398799b6fd4f2cb93bee8c374a10c69d6381d7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Copyright (c) 2015, Mark Richards
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
16
|
+
contributors may be used to endorse or promote products derived from this
|
17
|
+
software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
23
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Copyright (c) 2016, Bryce Johnston
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
16
|
+
contributors may be used to endorse or promote products derived from this
|
17
|
+
software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
23
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Evapotranspiration
|
2
|
+
|
3
|
+
[![Gem Version](http://img.shields.io/gem/v/evapotranspiration.svg)][gem]
|
4
|
+
[![Build Status](http://img.shields.io/travis/AgRuby/evapotranspiration.svg)][travis]
|
5
|
+
|
6
|
+
[gem]: https://rubygems.org/gems/evapotranspiration
|
7
|
+
[travis]: http://travis-ci.org/AgRuby/evapotranspiration
|
8
|
+
|
9
|
+
Ruby library for calculating reference crop evapotranspiration (ETo), also referred to as potential evapotranspiration (PET), using the FAO-56 Penman-Monteith method. This is a Ruby port of [Mark Richard's PyETo Python package](https://github.com/woodcrafty/PyETo). The library provides numerous functions for estimating missing meteorological data.
|
10
|
+
|
11
|
+
Three methods for estimating ETo/PET are implemented:
|
12
|
+
|
13
|
+
- FAO-56 Penman-Monteith (Allen et al, 1998)
|
14
|
+
- Hargreaves (Hargreaves and Samani, 1982; 1985)
|
15
|
+
- Thornthwaite (Thornthwaite, 1948)
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'evapotranspiration'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install evapotranspiration
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Coming soon...
|
36
|
+
|
37
|
+
## Development
|
38
|
+
|
39
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`.
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/AgRuby/evapotranspiration.
|
44
|
+
|
45
|
+
## Acknowledgments
|
46
|
+
|
47
|
+
[PyETo](https://github.com/woodcrafty/PyETo) was created by [Mark Richard](https://github.com/woodcrafty) - ported into Ruby and maintained by [Bryce Johnston](https://github.com/brycejohnston)
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
The gem is available as open source under the terms of the BSD 3-Clause License (see [LICENSE.txt](https://github.com/AgRuby/evapotranspiration/blob/master/LICENSE.txt)).
|
52
|
+
|
53
|
+
The original PyETo Python package it is based on was released under the BSD 3-Clause License (see [LICENSE-ORIGINAL.txt](https://github.com/AgRuby/evapotranspiration/blob/master/LICENSE-ORIGINAL.txt)).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "evapotranspiration"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'evapotranspiration/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "evapotranspiration"
|
8
|
+
spec.version = Evapotranspiration::VERSION
|
9
|
+
spec.authors = ["Bryce Johnston"]
|
10
|
+
spec.email = ["johnstonbrc@gmail.com"]
|
11
|
+
spec.summary = %q{Ruby library for calculating reference crop evapotranspiration (ETo)}
|
12
|
+
spec.description = %q{Ruby library for calculating reference crop evapotranspiration (ETo), also referred to as potential evapotranspiration (PET), using the FAO-56 Penman-Monteith method. This is a Ruby port of Mark Richard's PyETo Python package.}
|
13
|
+
spec.homepage = "https://github.com/AgRuby/evapotranspiration"
|
14
|
+
spec.license = "BSD 3-Clause"
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Evapotranspiration
|
2
|
+
module Conversion
|
3
|
+
|
4
|
+
# Convert temperature in degrees Celsius to degrees Kelvin
|
5
|
+
#
|
6
|
+
# @param celsius [Float] Degrees Celsius
|
7
|
+
# @return [Float] Degrees Kelvin
|
8
|
+
def self.celsius_to_kelvin(celsius)
|
9
|
+
celsius + 273.15
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convert temperature in degrees Kelvin to degrees Celsius
|
13
|
+
#
|
14
|
+
# @param kelvin [Float] Degrees Kelvin
|
15
|
+
# @return [Float] Degrees Celsius
|
16
|
+
def self.kelvin_to_celsius(kelvin)
|
17
|
+
kelvin - 273.15
|
18
|
+
end
|
19
|
+
|
20
|
+
# Convert angular degrees to radians
|
21
|
+
#
|
22
|
+
# @param degrees [Float] Value in degrees to be converted
|
23
|
+
# @return [Float] Value in radians
|
24
|
+
def self.deg_to_rad(degrees)
|
25
|
+
degrees * (Math::PI / 180.0)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Convert radians to angular degrees
|
29
|
+
#
|
30
|
+
# @param radians [Float] Value in radians to be converted
|
31
|
+
# @return [Float] Value in angular degrees
|
32
|
+
def self.rad_to_deg(radians)
|
33
|
+
radians * (180.0 / Math::PI)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,627 @@
|
|
1
|
+
require 'evapotranspiration/validation'
|
2
|
+
|
3
|
+
module Evapotranspiration
|
4
|
+
|
5
|
+
# Methods for estimating reference evapotransporation (ETo) for
|
6
|
+
# a grass reference crop using the FAO-56 Penman-Monteith and Hargreaves
|
7
|
+
# equations. The library includes numerous functions for estimating missing
|
8
|
+
# meteorological data.
|
9
|
+
module FAO
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# Solar constant [ MJ m-2 min-1]
|
13
|
+
SOLAR_CONSTANT = 0.0820
|
14
|
+
|
15
|
+
# Stefan Boltzmann constant [MJ K-4 m-2 day-1]
|
16
|
+
STEFAN_BOLTZMANN_CONSTANT = 0.000000004903
|
17
|
+
|
18
|
+
# Estimate atmospheric pressure from altitude.
|
19
|
+
#
|
20
|
+
# Calculated using a simplification of the ideal gas law, assuming 20 degrees
|
21
|
+
# Celsius for a standard atmosphere. Based on equation 7, page 62 in Allen
|
22
|
+
# et al (1998).
|
23
|
+
#
|
24
|
+
# @param altitude [Float] Elevation/altitude above sea level (m)
|
25
|
+
# @return [Float] atmospheric pressure (kPa)
|
26
|
+
def self.atm_pressure(altitude)
|
27
|
+
tmp = (293.0 - (0.0065 * altitude)) / 293.0
|
28
|
+
return (tmp ** 5.26) * 101.3
|
29
|
+
end
|
30
|
+
|
31
|
+
# Estimate actual vapour pressure (*ea*) from minimum temperature.
|
32
|
+
#
|
33
|
+
# This method is to be used where humidity data are lacking or are of
|
34
|
+
# questionable quality. The method assumes that the dewpoint temperature
|
35
|
+
# is approximately equal to the minimum temperature (*tmin*), i.e. the
|
36
|
+
# air is saturated with water vapour at *tmin*.
|
37
|
+
#
|
38
|
+
# **Note**: This assumption may not hold in arid/semi-arid areas.
|
39
|
+
# In these areas it may be better to subtract 2 deg C from the
|
40
|
+
# minimum temperature (see Annex 6 in FAO paper).
|
41
|
+
#
|
42
|
+
# Based on equation 48 in Allen et al (1998).
|
43
|
+
#
|
44
|
+
# @param tmin [Float] Daily minimum temperature (deg C)
|
45
|
+
# @return [Float] Actual vapour pressure (kPa)
|
46
|
+
def self.avp_from_tmin(tmin)
|
47
|
+
return 0.611 * Math.exp((17.27 * tmin) / (tmin + 237.3))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Estimate actual vapour pressure (*ea*) from saturation vapour pressure and
|
51
|
+
# relative humidity.
|
52
|
+
#
|
53
|
+
# Based on FAO equation 17 in Allen et al (1998).
|
54
|
+
#
|
55
|
+
# @param svp_tmin [Float] Saturation vapour pressure at daily minimum
|
56
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
57
|
+
# @param svp_tmax [Float] Saturation vapour pressure at daily maximum
|
58
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
59
|
+
# @param rh_min [Float] Minimum relative humidity (%)
|
60
|
+
# @param rh_max [Float] Maximum relative humidity (%)
|
61
|
+
# @return [Float] Actual vapour pressure (kPa)
|
62
|
+
def self.avp_from_rhmin_rhmax(svp_tmin, svp_tmax, rh_min, rh_max)
|
63
|
+
tmp1 = svp_tmin * (rh_max / 100.0)
|
64
|
+
tmp2 = svp_tmax * (rh_min / 100.0)
|
65
|
+
return (tmp1 + tmp2) / 2.0
|
66
|
+
end
|
67
|
+
|
68
|
+
# Estimate actual vapour pressure (*ea*) from saturation vapour pressure at
|
69
|
+
# daily minimum and maximum temperature, and mean relative humidity.
|
70
|
+
#
|
71
|
+
# Based on FAO equation 19 in Allen et al (1998).
|
72
|
+
#
|
73
|
+
# @param svp_tmin [Float] Saturation vapour pressure at daily minimum
|
74
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
75
|
+
# @param rh_max [Float] Maximum relative humidity (%)
|
76
|
+
# @return [Float] Actual vapour pressure (kPa)
|
77
|
+
def self.avp_from_rhmax(svp_tmin, rh_max)
|
78
|
+
return svp_tmin * (rh_max / 100.0)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Estimate actual vapour pressure (*e*a) from saturation vapour pressure at
|
82
|
+
# daily minimum temperature and maximum relative humidity.
|
83
|
+
#
|
84
|
+
# Based on FAO equation 18 in Allen et al (1998).
|
85
|
+
#
|
86
|
+
# @param svp_tmin [Float] Saturation vapour pressure at daily minimum
|
87
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
88
|
+
# @param svp_tmax [Float] Saturation vapour pressure at daily maximum
|
89
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
90
|
+
# @return [Float] Actual vapour pressure (kPa)
|
91
|
+
def self.avp_from_rhmean(svp_tmin, svp_tmax, rh_mean)
|
92
|
+
return (rh_mean / 100.0) * ((svp_tmax + svp_tmin) / 2.0)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Estimate actual vapour pressure (*ea*) from dewpoint temperature.
|
96
|
+
#
|
97
|
+
# Based on equation 14 in Allen et al (1998). As the dewpoint temperature is
|
98
|
+
# the temperature to which air needs to be cooled to make it saturated, the
|
99
|
+
# actual vapour pressure is the saturation vapour pressure at the dewpoint
|
100
|
+
# temperature.
|
101
|
+
#
|
102
|
+
# This method is preferable to calculating vapour pressure from
|
103
|
+
# minimum temperature.
|
104
|
+
#
|
105
|
+
# @param tdew [Float] Dewpoint temperature (deg C)
|
106
|
+
# @return [Float] Actual vapour pressure (kPa)
|
107
|
+
def self.avp_from_tdew(tdew)
|
108
|
+
return 0.6108 * Math.exp((17.27 * tdew) / (tdew + 237.3))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Estimate actual vapour pressure (*ea*) from wet and dry bulb temperature.
|
112
|
+
#
|
113
|
+
# Based on equation 15 in Allen et al (1998). As the dewpoint temperature
|
114
|
+
# is the temperature to which air needs to be cooled to make it saturated, the
|
115
|
+
# actual vapour pressure is the saturation vapour pressure at the dewpoint
|
116
|
+
# temperature.
|
117
|
+
#
|
118
|
+
# This method is preferable to calculating vapour pressure from
|
119
|
+
# minimum temperature.
|
120
|
+
#
|
121
|
+
# Values for the psychrometric constant of the psychrometer (*psy_const*)
|
122
|
+
# can be calculated using psyc_const_of_psychrometer().
|
123
|
+
#
|
124
|
+
# @param twet [Float] Wet bulb temperature (deg C)
|
125
|
+
# @param tdry [Float] Dry bulb temperature (deg C)
|
126
|
+
# @param svp_twet [Float] Saturated vapour pressure at the wet bulb
|
127
|
+
# temperature (kPa). Can be estimated using svp_from_t()
|
128
|
+
# @param psy_const [Float] Psychrometric constant of the pyschrometer
|
129
|
+
# (kPa deg C-1). Can be estimated using psy_const() or
|
130
|
+
# psy_const_of_psychrometer()
|
131
|
+
# @return [Float] Actual vapour pressure (kPa)
|
132
|
+
def self.avp_from_twet_tdry(twet, tdry, svp_twet, psy_const)
|
133
|
+
return svp_twet - (psy_const * (tdry - twet))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Estimate clear sky radiation from altitude and extraterrestrial radiation.
|
137
|
+
#
|
138
|
+
# Based on equation 37 in Allen et al (1998) which is recommended when
|
139
|
+
# calibrated Angstrom values are not available.
|
140
|
+
#
|
141
|
+
# @param altitude [Float] Elevation above sea level (m)
|
142
|
+
# @param et_rad [Float] Extraterrestrial radiation (MJ m-2 day-1). Can be
|
143
|
+
# estimated using et_rad()
|
144
|
+
# @return [Float] Clear sky radiation (MJ m-2 day-1)
|
145
|
+
def self.cs_rad(altitude, et_rad)
|
146
|
+
return (0.00002 * altitude + 0.75) * et_rad
|
147
|
+
end
|
148
|
+
|
149
|
+
# Estimate mean daily temperature from the daily minimum and maximum
|
150
|
+
# temperatures.
|
151
|
+
#
|
152
|
+
# @param tmin [Float] Minimum daily temperature (deg C)
|
153
|
+
# @param tmax [Float] Maximum daily temperature (deg C)
|
154
|
+
# @return [Float] Mean daily temperature (deg C)
|
155
|
+
def self.daily_mean_t(tmin, tmax)
|
156
|
+
return (tmax + tmin) / 2.0
|
157
|
+
end
|
158
|
+
|
159
|
+
# Calculate daylight hours from sunset hour angle.
|
160
|
+
#
|
161
|
+
# Based on FAO equation 34 in Allen et al (1998).
|
162
|
+
#
|
163
|
+
# @param sha [Float] Sunset hour angle (rad). Can be calculated using
|
164
|
+
# sunset_hour_angle()
|
165
|
+
# @return [Float] Daylight hours
|
166
|
+
def self.daylight_hours(sha)
|
167
|
+
Validation.check_sunset_hour_angle_rad(sha)
|
168
|
+
return (24.0 / Math::PI) * sha
|
169
|
+
end
|
170
|
+
|
171
|
+
# Estimate the slope of the saturation vapour pressure curve at a given
|
172
|
+
# temperature.
|
173
|
+
#
|
174
|
+
# Based on equation 13 in Allen et al (1998). If using in the Penman-Monteith
|
175
|
+
# *t* should be the mean air temperature.
|
176
|
+
#
|
177
|
+
# @param t [Float] Air temperature (deg C). Use mean air temperature for
|
178
|
+
# use in Penman-Monteith
|
179
|
+
# @return [Float] Saturation vapour pressure (kPa degC-1)
|
180
|
+
def self.delta_svp(t)
|
181
|
+
tmp = 4098 * (0.6108 * Math.exp((17.27 * t) / (t + 237.3)))
|
182
|
+
return tmp / ((t + 237.3) ** 2)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Convert energy (e.g. radiation energy) in MJ m-2 day-1 to the equivalent
|
186
|
+
# evaporation, assuming a grass reference crop.
|
187
|
+
#
|
188
|
+
# Energy is converted to equivalent evaporation using a conversion
|
189
|
+
# factor equal to the inverse of the latent heat of vapourisation
|
190
|
+
# (1 / lambda = 0.408).
|
191
|
+
#
|
192
|
+
# Based on FAO equation 20 in Allen et al (1998).
|
193
|
+
#
|
194
|
+
# @param energy [Float] Energy e.g. radiation or heat flux (MJ m-2 day-1)
|
195
|
+
# @return [Float] Equivalent evaporation (mm day-1)
|
196
|
+
def self.energy2evap(energy)
|
197
|
+
return 0.408 * energy
|
198
|
+
end
|
199
|
+
|
200
|
+
# Estimate daily extraterrestrial radiation (*Ra*, 'top of the atmosphere
|
201
|
+
# radiation').
|
202
|
+
#
|
203
|
+
# Based on equation 21 in Allen et al (1998). If monthly mean radiation is
|
204
|
+
# required make sure *sol_dec*. *sha* and *irl* have been calculated using
|
205
|
+
# the day of the year that corresponds to the middle of the month.
|
206
|
+
#
|
207
|
+
# **Note**: From Allen et al (1998): "For the winter months in latitudes
|
208
|
+
# greater than 55 degrees (N or S), the equations have limited validity.
|
209
|
+
# Reference should be made to the Smithsonian Tables to assess possible
|
210
|
+
# deviations."
|
211
|
+
#
|
212
|
+
# @param latitude [Float] Latitude (radians)
|
213
|
+
# @param sol_dec [Float] Solar declination (radians). Can be calculated
|
214
|
+
# using sol_dec()
|
215
|
+
# @param sha [Float] Sunset hour angle (radians). Can be calculated using
|
216
|
+
# sunset_hour_angle()
|
217
|
+
# @param ird [Float] Inverse relative distance earth-sun (dimensionless).
|
218
|
+
# Can be calculated using inv_rel_dist_earth_sun()
|
219
|
+
# @return [Float] Daily extraterrestrial radiation (MJ m-2 day-1)
|
220
|
+
def self.et_rad(latitude, sol_dec, sha, ird)
|
221
|
+
Validation.check_latitude_rad(latitude)
|
222
|
+
Validation.check_sol_dec_rad(sol_dec)
|
223
|
+
Validation.check_sunset_hour_angle_rad(sha)
|
224
|
+
|
225
|
+
tmp1 = (24.0 * 60.0) / Math::PI
|
226
|
+
tmp2 = sha * Math.sin(latitude) * Math.sin(sol_dec)
|
227
|
+
tmp3 = Math.cos(latitude) * Math.cos(sol_dec) * Math.sin(sha)
|
228
|
+
return tmp1 * SOLAR_CONSTANT * ird * (tmp2 + tmp3)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Estimate reference evapotranspiration (ETo) from a hypothetical
|
232
|
+
# short grass reference surface using the FAO-56 Penman-Monteith equation.
|
233
|
+
#
|
234
|
+
# Based on equation 6 in Allen et al (1998).
|
235
|
+
#
|
236
|
+
# @param net_rad [Float] Net radiation at crop surface (MJ m-2 day-1). If
|
237
|
+
# necessary this can be estimated using net_rad()
|
238
|
+
# @param t [Float] Air temperature at 2 m height (deg Kelvin)
|
239
|
+
# @param ws [Float] Wind speed at 2 m height (m s-1). If not measured at 2m,
|
240
|
+
# convert using wind_speed_at_2m()
|
241
|
+
# @param svp [Float] Saturation vapour pressure (kPa). Can be estimated
|
242
|
+
# using svp_from_t()
|
243
|
+
# @param avp [Float] Actual vapour pressure (kPa). Can be estimated using a
|
244
|
+
# range of functions with names beginning with 'avp_from'
|
245
|
+
# @param delta_svp [Float] Slope of saturation vapour pressure curve
|
246
|
+
# (kPa degC-1). Can be estimated using delta_svp()
|
247
|
+
# @param psy [Float] Psychrometric constant (kPa deg C). Can be estimatred
|
248
|
+
# using psy_const_of_psychrometer() or psy_const()
|
249
|
+
# @param shf [Float] Soil heat flux (G) (MJ m-2 day-1) (default is 0.0,
|
250
|
+
# which is reasonable for a daily or 10-day time steps). For monthly time
|
251
|
+
# steps *shf* can be estimated using monthly_soil_heat_flux() or
|
252
|
+
# monthly_soil_heat_flux2()
|
253
|
+
# @return [Float] Reference evapotranspiration (ETo) from a hypothetical
|
254
|
+
# grass reference surface (mm day-1)
|
255
|
+
def self.fao56_penman_monteith(net_rad, t, ws, svp, avp, delta_svp, psy, shf=0.0)
|
256
|
+
a1 = (0.408 * (net_rad - shf) * delta_svp / delta_svp + (psy * (1 + 0.34 * ws)))
|
257
|
+
a2 = (900 * ws / t * (svp - avp) * psy / (delta_svp + (psy * (1 + 0.34 * ws))))
|
258
|
+
return a1 + a2
|
259
|
+
end
|
260
|
+
|
261
|
+
# Estimate reference evapotranspiration over grass (ETo) using the Hargreaves
|
262
|
+
# equation.
|
263
|
+
#
|
264
|
+
# Generally, when solar radiation data, relative humidity data
|
265
|
+
# and/or wind speed data are missing, it is better to estimate them using
|
266
|
+
# the functions available in this module, and then calculate ETo
|
267
|
+
# the FAO Penman-Monteith equation. However, as an alternative, ETo can be
|
268
|
+
# estimated using the Hargreaves ETo equation.
|
269
|
+
#
|
270
|
+
# Based on equation 52 in Allen et al (1998).
|
271
|
+
#
|
272
|
+
# @param tmin [Float] Minimum daily temperature (deg C)
|
273
|
+
# @param tmax [Float] Maximum daily temperature (deg C)
|
274
|
+
# @param tmean [Float] Mean daily temperature (deg C). If measurements not
|
275
|
+
# available it can be estimated as (*tmin* + *tmax*) / 2
|
276
|
+
# @param et_rad [Float] Extraterrestrial radiation (Ra) (MJ m-2 day-1).
|
277
|
+
# Can be estimated using et_rad()
|
278
|
+
# @return [Float] Reference evapotranspiration over grass (ETo) (mm day-1)
|
279
|
+
def self.hargreaves(tmin, tmax, tmean, et_rad)
|
280
|
+
# Note, multiplied by 0.408 to convert extraterrestrial radiation could
|
281
|
+
# be given in MJ m-2 day-1 rather than as equivalent evaporation in
|
282
|
+
# mm day-1
|
283
|
+
return 0.0023 * (tmean + 17.8) * (tmax - tmin) ** 0.5 * 0.408 * et_rad
|
284
|
+
end
|
285
|
+
|
286
|
+
# Calculate the inverse relative distance between earth and sun from
|
287
|
+
# day of the year.
|
288
|
+
#
|
289
|
+
# Based on FAO equation 23 in Allen et al (1998).
|
290
|
+
#
|
291
|
+
# @param day_of_year [Integer] Day of the year (1 to 366)
|
292
|
+
# @return [Float] Inverse relative distance between earth and the sun
|
293
|
+
def self.inv_rel_dist_earth_sun(day_of_year)
|
294
|
+
Validation.check_doy(day_of_year)
|
295
|
+
return 1 + (0.033 * Math.cos((2.0 * Math::PI / 365.0) * day_of_year))
|
296
|
+
end
|
297
|
+
|
298
|
+
# Estimate mean saturation vapour pressure, *es* [kPa] from minimum and
|
299
|
+
# maximum temperature.
|
300
|
+
#
|
301
|
+
# Based on equations 11 and 12 in Allen et al (1998).
|
302
|
+
#
|
303
|
+
# Mean saturation vapour pressure is calculated as the mean of the
|
304
|
+
# saturation vapour pressure at tmax (maximum temperature) and tmin
|
305
|
+
# (minimum temperature).
|
306
|
+
#
|
307
|
+
# @param tmin [Float] Minimum temperature (deg C)
|
308
|
+
# @param tmax [Float] Maximum temperature (deg C)
|
309
|
+
# @return [Float] Mean saturation vapour pressure (*es*) (kPa)
|
310
|
+
def self.mean_svp(tmin, tmax)
|
311
|
+
return (svp_from_t(tmin) + svp_from_t(tmax)) / 2.0
|
312
|
+
end
|
313
|
+
|
314
|
+
# Estimate monthly soil heat flux (Gmonth) [MJ m-2 day-1] from the mean
|
315
|
+
# air temperature of the previous and current month, assuming a grass crop.
|
316
|
+
#
|
317
|
+
# Based on equation 44 in Allen et al (1998). If the air temperature of the
|
318
|
+
# next month is available, use monthly_soil_heat_flux() instead. The
|
319
|
+
# resulting heat flux can be converted to equivalent evaporation (mm day-1)
|
320
|
+
# using energy2evap().
|
321
|
+
#
|
322
|
+
# @param t_month_prev [Float] Mean air temperature of the previous month
|
323
|
+
# (deg Celsius)
|
324
|
+
# @param t_month_next [Float] Mean air temperature of the next month
|
325
|
+
# (deg Celsius)
|
326
|
+
# @return [Float] Monthly soil heat flux (Gmonth) (MJ m-2 day-1)
|
327
|
+
def self.monthly_soil_heat_flux(t_month_prev, t_month_next)
|
328
|
+
return 0.07 * (t_month_next - t_month_prev)
|
329
|
+
end
|
330
|
+
|
331
|
+
# Estimate monthly soil heat flux (Gmonth) from the mean air temperature of
|
332
|
+
# the previous and next month, assuming a grass crop.
|
333
|
+
#
|
334
|
+
# Based on equation 44 in Allen et al (1998). If the air temperature of the
|
335
|
+
# next month is available, use monthly_soil_heat_flux() instead. The
|
336
|
+
# resulting heat flux can be converted to equivalent evaporation [mm day-1]
|
337
|
+
# using ``energy2evap().
|
338
|
+
#
|
339
|
+
# @param t_month_prev [Float] Mean air temperature of the previous month
|
340
|
+
# (deg Celsius)
|
341
|
+
# @param t_month_next [Float] Mean air temperature of the current month
|
342
|
+
# (deg Celsius)
|
343
|
+
# @return [Float] Monthly soil heat flux (Gmonth) (MJ m-2 day-1)
|
344
|
+
def self.monthly_soil_heat_flux2(t_month_prev, t_month_cur)
|
345
|
+
return 0.14 * (t_month_cur - t_month_prev)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Calculate net incoming solar (or shortwave) radiation from gross
|
349
|
+
# incoming solar radiation, assuming a grass reference crop.
|
350
|
+
#
|
351
|
+
# Net incoming solar radiation is the net shortwave radiation resulting
|
352
|
+
# from the balance between incoming and reflected solar radiation. The
|
353
|
+
# output can be converted to equivalent evaporation [mm day-1] using
|
354
|
+
# energy2evap().
|
355
|
+
#
|
356
|
+
# Based on FAO equation 38 in Allen et al (1998).
|
357
|
+
#
|
358
|
+
# @param sol_rad [Float] Gross incoming solar radiation (MJ m-2 day-1).
|
359
|
+
# If necessary this can be estimated using functions whose name begins
|
360
|
+
# with 'sol_rad_from'
|
361
|
+
# @param albedo [Float] Albedo of the crop as the proportion of gross
|
362
|
+
# incoming solar radiation that is reflected by the surface. Default value
|
363
|
+
# is 0.23, which is the value used by the FAO for a short grass reference
|
364
|
+
# crop. Albedo can be as high as 0.95 for freshly fallen snow and as low
|
365
|
+
# as 0.05 for wet bare soil. A green vegetation over has an albedo of
|
366
|
+
# about 0.20-0.25 (Allen et al, 1998)
|
367
|
+
# @return [Float] Net incoming solar (or shortwave) radiation (MJ m-2 day-1)
|
368
|
+
def self.net_in_sol_rad(sol_rad, albedo=0.23)
|
369
|
+
return (1 - albedo) * sol_rad
|
370
|
+
end
|
371
|
+
|
372
|
+
# Estimate net outgoing longwave radiation.
|
373
|
+
#
|
374
|
+
# This is the net longwave energy (net energy flux) leaving the
|
375
|
+
# earth's surface. It is proportional to the absolute temperature of
|
376
|
+
# the surface raised to the fourth power according to the Stefan-Boltzmann
|
377
|
+
# law. However, water vapour, clouds, carbon dioxide and dust are absorbers
|
378
|
+
# and emitters of longwave radiation. This function corrects the Stefan-
|
379
|
+
# Boltzmann law for humidity (using actual vapor pressure) and cloudiness
|
380
|
+
# (using solar radiation and clear sky radiation). The concentrations of all
|
381
|
+
# other absorbers are assumed to be constant.
|
382
|
+
#
|
383
|
+
# The output can be converted to equivalent evaporation [mm day-1] using energy2evap().
|
384
|
+
#
|
385
|
+
# Based on FAO equation 39 in Allen et al (1998).
|
386
|
+
#
|
387
|
+
# @param tmin [Float] Absolute daily minimum temperature (degrees Kelvin)
|
388
|
+
# @param albedo [Float] Absolute daily maximum temperature (degrees Kelvin)
|
389
|
+
# @param sol_rad [Float] Solar radiation (MJ m-2 day-1). If necessary this
|
390
|
+
# can be estimated using sol+rad()
|
391
|
+
# @param cs_rad [Float] Clear sky radiation (MJ m-2 day-1). Can be estimated
|
392
|
+
# using cs_rad()
|
393
|
+
# @param avp [Float] Actual vapour pressure (kPa). Can be estimated using
|
394
|
+
# functions with names beginning with 'avp_from'
|
395
|
+
# @return [Float] Net outgoing longwave radiation (MJ m-2 day-1)
|
396
|
+
def self.net_out_lw_rad(tmin, tmax, sol_rad, cs_rad, avp)
|
397
|
+
tmp1 = (STEFAN_BOLTZMANN_CONSTANT * (((tmax ** 4) + (tmin ** 4)) / 2))
|
398
|
+
tmp2 = (0.34 - (0.14 * Math.sqrt(avp)))
|
399
|
+
tmp3 = 1.35 * (sol_rad / cs_rad) - 0.35
|
400
|
+
return tmp1 * tmp2 * tmp3
|
401
|
+
end
|
402
|
+
|
403
|
+
# Calculate daily net radiation at the crop surface, assuming a grass
|
404
|
+
# reference crop.
|
405
|
+
#
|
406
|
+
# Net radiation is the difference between the incoming net shortwave (or
|
407
|
+
# solar) radiation and the outgoing net longwave radiation. Output can be
|
408
|
+
# converted to equivalent evaporation [mm day-1] using energy2evap().
|
409
|
+
#
|
410
|
+
# Based on equation 40 in Allen et al (1998).
|
411
|
+
#
|
412
|
+
# @param ni_sw_rad [Float] Net incoming shortwave radiation (MJ m-2 day-1).
|
413
|
+
# Can be estimated using net_in_sol_rad()
|
414
|
+
# @param no_lw_rad [Float] Net outgoing longwave radiation (MJ m-2 day-1).
|
415
|
+
# Can be estimated using net_out_lw_rad()
|
416
|
+
# @return [Float] Daily net radiation (MJ m-2 day-1)
|
417
|
+
def self.net_rad(ni_sw_rad, no_lw_rad)
|
418
|
+
return ni_sw_rad - no_lw_rad
|
419
|
+
end
|
420
|
+
|
421
|
+
# Calculate the psychrometric constant.
|
422
|
+
#
|
423
|
+
# This method assumes that the air is saturated with water vapour at the
|
424
|
+
# minimum daily temperature. This assumption may not hold in arid areas.
|
425
|
+
#
|
426
|
+
# Based on equation 8, page 95 in Allen et al (1998).
|
427
|
+
#
|
428
|
+
# @param atmos_pres [Float] Atmospheric pressure (kPa). Can be estimated
|
429
|
+
# using atm_pressure()
|
430
|
+
# @return [Float] Psychrometric constant (kPa degC-1)
|
431
|
+
def self.psy_const(atmos_pres)
|
432
|
+
return 0.000665 * atmos_pres
|
433
|
+
end
|
434
|
+
|
435
|
+
# Calculate the psychrometric constant for different types of
|
436
|
+
# psychrometer at a given atmospheric pressure.
|
437
|
+
#
|
438
|
+
# Based on FAO equation 16 in Allen et al (1998).
|
439
|
+
#
|
440
|
+
# psychrometer types:
|
441
|
+
# 1. ventilated (Asmann or aspirated type) psychrometer with an air movement of approximately 5 m/s
|
442
|
+
# 2. natural ventilated psychrometer with an air movement of approximately 1 m/s
|
443
|
+
# 3. non ventilated psychrometer installed indoors
|
444
|
+
#
|
445
|
+
# @param psychrometer [Float] Integer between 1 and 3 which denotes type of
|
446
|
+
# psychrometer
|
447
|
+
# @param atmos_pres [Float] Atmospheric pressure [kPa]. Can be estimated
|
448
|
+
# using atm_pressure()
|
449
|
+
# @return [Float] Psychrometric constant (kPa degC-1)
|
450
|
+
def self.psy_const_of_psychrometer(psychrometer, atmos_pres)
|
451
|
+
# Select coefficient based on type of ventilation of the wet bulb
|
452
|
+
case psychrometer
|
453
|
+
when 1
|
454
|
+
psy_coeff = 0.000662
|
455
|
+
when 2
|
456
|
+
psy_coeff = 0.000800
|
457
|
+
when 3
|
458
|
+
psy_coeff = 0.001200
|
459
|
+
else
|
460
|
+
raise ArgumentError.new("psychrometer should be in range 1 to 3: #{psychrometer}")
|
461
|
+
end
|
462
|
+
|
463
|
+
return psy_coeff * atmos_pres
|
464
|
+
end
|
465
|
+
|
466
|
+
# Calculate relative humidity as the ratio of actual vapour pressure
|
467
|
+
# to saturation vapour pressure at the same temperature.
|
468
|
+
#
|
469
|
+
# See Allen et al (1998), page 67 for details.
|
470
|
+
#
|
471
|
+
# @param avp [Float] Actual vapour pressure (units do not matter so long as
|
472
|
+
# they are the same as for *svp*). Can be estimated using functions whose
|
473
|
+
# name begins with 'avp_from'
|
474
|
+
# @param svp [Float] Saturated vapour pressure (units do not matter so long
|
475
|
+
# as they are the same as for *avp*). Can be estimated using svp_from_t()
|
476
|
+
# @return [Float] Relative humidity (%)
|
477
|
+
def self.rh_from_avp_svp(avp, svp)
|
478
|
+
return 100.0 * avp / svp
|
479
|
+
end
|
480
|
+
|
481
|
+
# Calculate solar declination from day of the year.
|
482
|
+
#
|
483
|
+
# Based on FAO equation 24 in Allen et al (1998).
|
484
|
+
#
|
485
|
+
# @param day_of_year [Integer] Day of year integer between 1 and 365 or 366
|
486
|
+
# @return [Float] solar declination (radians)
|
487
|
+
def self.sol_dec(day_of_year)
|
488
|
+
Validation.check_doy(day_of_year)
|
489
|
+
return 0.409 * Math.sin(((2.0 * Math::PI / 365.0) * day_of_year - 1.39))
|
490
|
+
end
|
491
|
+
|
492
|
+
# Calculate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
|
493
|
+
# a horizontal plane after scattering by the atmosphere) from relative
|
494
|
+
# sunshine duration.
|
495
|
+
#
|
496
|
+
# If measured radiation data are not available this method is preferable
|
497
|
+
# to calculating solar radiation from temperature. If a monthly mean is
|
498
|
+
# required then divide the monthly number of sunshine hours by number of
|
499
|
+
# days in the month and ensure that *et_rad* and *daylight_hours* was
|
500
|
+
# calculated using the day of the year that corresponds to the middle of
|
501
|
+
# the month.
|
502
|
+
#
|
503
|
+
# Based on equations 34 and 35 in Allen et al (1998).
|
504
|
+
#
|
505
|
+
# @param dl_hours [Integer] Number of daylight hours (hours). Can be
|
506
|
+
# calculated using daylight_hours()
|
507
|
+
# @param sunshine_hours [Integer] Sunshine duration (hours). Can be
|
508
|
+
# calculated using sunshine_hours()
|
509
|
+
# @param et_rad [Float] Extraterrestrial radiation (MJ m-2 day-1). Can be
|
510
|
+
# estimated using et_rad()
|
511
|
+
# @return [Float] Incoming solar (or shortwave) radiation (MJ m-2 day-1)
|
512
|
+
def self.sol_rad_from_sun_hours(daylight_hours, sunshine_hours, et_rad)
|
513
|
+
Validation.check_day_hours(sunshine_hours, 'sun_hours')
|
514
|
+
Validation.check_day_hours(daylight_hours, 'daylight_hours')
|
515
|
+
|
516
|
+
# 0.5 and 0.25 are default values of regression constants (Angstrom values)
|
517
|
+
# recommended by FAO when calibrated values are unavailable.
|
518
|
+
return (0.5 * sunshine_hours / daylight_hours + 0.25) * et_rad
|
519
|
+
end
|
520
|
+
|
521
|
+
# Estimate incoming solar (or shortwave) radiation, *Rs*, (radiation hitting
|
522
|
+
# a horizontal plane after scattering by the atmosphere) from min and max
|
523
|
+
# temperature together with an empirical adjustment coefficient for
|
524
|
+
# 'interior' and 'coastal' regions.
|
525
|
+
#
|
526
|
+
# The formula is based on equation 50 in Allen et al (1998) which is the
|
527
|
+
# Hargreaves radiation formula (Hargreaves and Samani, 1982, 1985). This
|
528
|
+
# method should be used only when solar radiation or sunshine hours data are
|
529
|
+
# not available. It is only recommended for locations where it is not
|
530
|
+
# possible to use radiation data from a regional station (either because
|
531
|
+
# climate conditions are heterogeneous or data are lacking).
|
532
|
+
#
|
533
|
+
# **NOTE**: this method is not suitable for island locations due to the
|
534
|
+
# moderating effects of the surrounding water.
|
535
|
+
#
|
536
|
+
# @param et_rad [Float] Extraterrestrial radiation (MJ m-2 day-1). Can be
|
537
|
+
# estimated using et_rad()
|
538
|
+
# @param cs_rad [Float] Clear sky radiation (MJ m-2 day-1). Can be estimated
|
539
|
+
# using cs_rad()
|
540
|
+
# @param tmin [Float] Daily minimum temperature (deg C)
|
541
|
+
# @param tmax [Float] Daily maximum temperature (deg C)
|
542
|
+
# @param coastal [Boolean] True if site is a coastal location, situated on
|
543
|
+
# or adjacent to coast of a large land mass and where air masses are
|
544
|
+
# influenced by a nearby water body, False if interior location where land
|
545
|
+
# mass dominates and air masses are not strongly influenced by a large
|
546
|
+
# water body.
|
547
|
+
# @return [Float] Incoming solar (or shortwave) radiation (Rs) (MJ m-2 day-1)
|
548
|
+
def self.sol_rad_from_t(et_rad, cs_rad, tmin, tmax, coastal)
|
549
|
+
# Determine value of adjustment coefficient [deg C-0.5] for
|
550
|
+
# coastal/interior locations
|
551
|
+
adj = coastal ? 0.19 : 0.16
|
552
|
+
|
553
|
+
sol_rad = adj * Math.sqrt(tmax - tmin) * et_rad
|
554
|
+
|
555
|
+
# The solar radiation value is constrained by the clear sky radiation
|
556
|
+
return [sol_rad, cs_rad].min
|
557
|
+
end
|
558
|
+
|
559
|
+
# Estimate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
|
560
|
+
# a horizontal plane after scattering by the atmosphere) for an island
|
561
|
+
# location.
|
562
|
+
#
|
563
|
+
# An island is defined as a land mass with width perpendicular to the
|
564
|
+
# coastline <= 20 km. Use this method only if radiation data from
|
565
|
+
# elsewhere on the island is not available.
|
566
|
+
#
|
567
|
+
# **NOTE**: This method is only applicable for low altitudes (0-100 m)
|
568
|
+
# and monthly calculations.
|
569
|
+
#
|
570
|
+
# Based on FAO equation 51 in Allen et al (1998).
|
571
|
+
#
|
572
|
+
# @param et_rad [Float] Extraterrestrial radiation (MJ m-2 day-1). Can be
|
573
|
+
# estimated using et_rad()
|
574
|
+
# @return [Float] Incoming solar (or shortwave) radiation (MJ m-2 day-1)
|
575
|
+
def self.sol_rad_island(et_rad)
|
576
|
+
return (0.7 * et_rad) - 4.0
|
577
|
+
end
|
578
|
+
|
579
|
+
# Calculate sunset hour angle (*Ws*) from latitude and solar
|
580
|
+
# declination.
|
581
|
+
#
|
582
|
+
# Based on FAO equation 25 in Allen et al (1998).
|
583
|
+
#
|
584
|
+
# @param latitude [Float] Latitude (radians). Note: *latitude* should be
|
585
|
+
# negative if it in the southern hemisphere, positive if in the northern
|
586
|
+
# hemisphere
|
587
|
+
# @param sol_dec [Float] Solar declination (radians). Can be calculated
|
588
|
+
# using sol_dec()
|
589
|
+
# @return [Float] Sunset hour angle (radians)
|
590
|
+
def self.sunset_hour_angle(latitude, sol_dec)
|
591
|
+
Validation.check_latitude_rad(latitude)
|
592
|
+
Validation.check_sol_dec_rad(sol_dec)
|
593
|
+
|
594
|
+
cos_sha = -Math.tan(latitude) * Math.tan(sol_dec)
|
595
|
+
# If tmp is >= 1 there is no sunset, i.e. 24 hours of daylight
|
596
|
+
# If tmp is <= 1 there is no sunrise, i.e. 24 hours of darkness
|
597
|
+
# See http://www.itacanet.org/the-sun-as-a-source-of-energy/
|
598
|
+
# part-3-calculating-solar-angles/
|
599
|
+
# Domain of acos is -1 <= x <= 1 radians (this is not mentioned in FAO-56!)
|
600
|
+
return Math.acos([[cos_sha, -1.0].max, 1.0].min)
|
601
|
+
end
|
602
|
+
|
603
|
+
# Estimate saturation vapour pressure (*es*) from air temperature.
|
604
|
+
#
|
605
|
+
# Based on equations 11 and 12 in Allen et al (1998).
|
606
|
+
#
|
607
|
+
# @param t [Float] Temperature (deg C)
|
608
|
+
# @return [Float] Saturation vapour pressure (kPa)
|
609
|
+
def self.svp_from_t(t)
|
610
|
+
return 0.6108 * Math.exp((17.27 * t) / (t + 237.3))
|
611
|
+
end
|
612
|
+
|
613
|
+
# Convert wind speed measured at different heights above the soil
|
614
|
+
# surface to wind speed at 2 m above the surface, assuming a short grass
|
615
|
+
# surface.
|
616
|
+
#
|
617
|
+
# Based on FAO equation 47 in Allen et al (1998).
|
618
|
+
#
|
619
|
+
# @param ws [Float] Measured wind speed (m s-1)
|
620
|
+
# @param z [Float] Height of wind measurement above ground surface (m)
|
621
|
+
# @return [Float] Wind speed at 2 m above the surface (m s-1)
|
622
|
+
def self.wind_speed_2m(ws, z)
|
623
|
+
return ws * (4.87 / Math.log((67.8 * z) - 5.42))
|
624
|
+
end
|
625
|
+
|
626
|
+
end
|
627
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'evapotranspiration/validation'
|
2
|
+
require 'evapotranspiration/fao'
|
3
|
+
|
4
|
+
module Evapotranspiration
|
5
|
+
|
6
|
+
# Calculate potential evapotranspiration using the Thornthwaite (1948 method)
|
7
|
+
#
|
8
|
+
# References
|
9
|
+
# ----------
|
10
|
+
# Thornthwaite CW (1948) An approach toward a rational classification of
|
11
|
+
# climate. Geographical Review, 38, 55-94.
|
12
|
+
module Thornthwaite
|
13
|
+
|
14
|
+
MONTHDAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
15
|
+
LEAP_MONTHDAYS = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
16
|
+
|
17
|
+
# Estimate monthly potential evapotranspiration (PET) using the
|
18
|
+
# Thornthwaite (1948) method.
|
19
|
+
#
|
20
|
+
# Thornthwaite equation:
|
21
|
+
#
|
22
|
+
# *PET* = 1.6 (*l*/12) (*n*/30) (10*ta* / *heat_index*)***a*
|
23
|
+
#
|
24
|
+
# where:
|
25
|
+
#
|
26
|
+
# * *ta* is the mean daily air temperature [deg C, if negative use 0] of the
|
27
|
+
# month being calculated
|
28
|
+
# * *n* is the number of days in the month being calculated
|
29
|
+
# * *l* is the mean day length [hours] of the month being calculated
|
30
|
+
# * *a* = (6.75 x 10-7)*heat_index***3 - (7.71 x 10-5)*heat_index***2 + (1.792 x 10-2)*heat_index* + 0.49239
|
31
|
+
# * *heat_index* is a heat index which depends on the 12 monthly mean temperatures and
|
32
|
+
# is calculated as the sum of (*tai* / 5)**1.514 for each month, where
|
33
|
+
# * *tai* is the air temperature for each month in the year
|
34
|
+
#
|
35
|
+
# @param monthly_t [Array<Float>] Iterable containing mean daily air
|
36
|
+
# temperature for each month of the year (deg C)
|
37
|
+
# @param monthly_mean_dlh [Array<Float>] Iterable containing mean daily
|
38
|
+
# daylight hours for each month of the year (hours). These can be
|
39
|
+
# calculated using monthly_mean_daylight_hours()
|
40
|
+
# @param year [Integer] Year for which PET is required. The only effect of
|
41
|
+
# year is to change the number of days in February to 29 if it is a leap
|
42
|
+
# year. If it is left as the default (None), then the year is assumed not
|
43
|
+
# to be a leap year.
|
44
|
+
# @return [Array<Float>] Estimated monthly potential evaporation of each month of
|
45
|
+
# the year (mm/month)
|
46
|
+
def thornthwaite(monthly_t, monthly_mean_dlh, year=nil)
|
47
|
+
if monthly_t.size != 12
|
48
|
+
raise ArgumentError.new("monthly_t should be length 12 but is length #{monthly_t.size}.")
|
49
|
+
end
|
50
|
+
if monthly_mean_dlh.size != 12
|
51
|
+
raise ArgumentError.new("monthly_mean_dlh should be length 12 but is length #{monthly_mean_dlh.size}.")
|
52
|
+
end
|
53
|
+
|
54
|
+
if year.nil? || !year.leap?
|
55
|
+
month_days = MONTHDAYS
|
56
|
+
else
|
57
|
+
month_days = LEAP_MONTHDAYS
|
58
|
+
end
|
59
|
+
|
60
|
+
# Negative temperatures should be set to zero
|
61
|
+
adj_monthly_t = []
|
62
|
+
monthly_t.each do |t|
|
63
|
+
adj_monthly_t << (t >= 0 ? t : 0)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Calculate the heat index (heat_index)
|
67
|
+
heat_index = 0.0
|
68
|
+
adj_monthly_t.each do |tai|
|
69
|
+
if (tai / 5.0) > 0.0
|
70
|
+
heat_index += (tai / 5.0) ** 1.514
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
a = (6.75e-07 * heat_index ** 3) - (7.71e-05 * heat_index ** 2) + (1.792e-02 * heat_index) + 0.49239
|
75
|
+
|
76
|
+
pet = []
|
77
|
+
adj_monthly_t.zip(monthly_mean_dlh, month_days).each do |ta, l, n|
|
78
|
+
# Multiply by 10 to convert cm/month --> mm/month
|
79
|
+
pet << 1.6 * (l / 12.0) * (n / 30.0) * ((10.0 * ta / heat_index) ** a) * 10.0
|
80
|
+
end
|
81
|
+
return pet
|
82
|
+
end
|
83
|
+
|
84
|
+
# Calculate mean daylight hours for each month of the year for a given
|
85
|
+
# latitude.
|
86
|
+
#
|
87
|
+
# @param latitude [Float] Latitude (radians)
|
88
|
+
# @param year [Integer] Year for the daylight hours are required. The only effect of
|
89
|
+
# *year* is to change the number of days in Feb to 29 if it is a leap
|
90
|
+
# year. If left as the default, None, then a normal (non-leap) year is
|
91
|
+
# assumed.
|
92
|
+
# @return [Array<Float>] Mean daily daylight hours of each month of a year
|
93
|
+
# (hours)
|
94
|
+
def monthly_mean_daylight_hours(latitude, year=nil)
|
95
|
+
Validation.check_latitude_rad(latitude)
|
96
|
+
|
97
|
+
if year.nil? || !year.leap?
|
98
|
+
month_days = MONTHDAYS
|
99
|
+
else
|
100
|
+
month_days = LEAP_MONTHDAYS
|
101
|
+
end
|
102
|
+
monthly_mean_dlh = []
|
103
|
+
doy = 1 # Day of the year
|
104
|
+
|
105
|
+
month_days.each do |mdays|
|
106
|
+
dlh = 0.0 # Cumulative daylight hours for the month
|
107
|
+
[1..(mdays + 1)].each do |daynum|
|
108
|
+
sd = FAO.sol_dec(doy)
|
109
|
+
sha = FAO.sunset_hour_angle(latitude, sd)
|
110
|
+
dlh += FAO.daylight_hours(sha)
|
111
|
+
doy += 1
|
112
|
+
end
|
113
|
+
# Calc mean daylight hours of the month
|
114
|
+
monthly_mean_dlh << (dlh / mdays)
|
115
|
+
end
|
116
|
+
|
117
|
+
return monthly_mean_dlh
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'evapotranspiration/conversion'
|
2
|
+
|
3
|
+
module Evapotranspiration
|
4
|
+
module Validation
|
5
|
+
|
6
|
+
# Latitude
|
7
|
+
MINLAT_RADIANS = Conversion.deg_to_rad(-90.0)
|
8
|
+
MAXLAT_RADIANS = Conversion.deg_to_rad(90.0)
|
9
|
+
|
10
|
+
# Solar declination
|
11
|
+
MINSOLDEC_RADIANS = Conversion.deg_to_rad(-23.5)
|
12
|
+
MAXSOLDEC_RADIANS = Conversion.deg_to_rad(23.5)
|
13
|
+
|
14
|
+
# Sunset hour angle
|
15
|
+
MINSHA_RADIANS = 0.0
|
16
|
+
MAXSHA_RADIANS = Conversion.deg_to_rad(180)
|
17
|
+
|
18
|
+
# Check that *hours* is in the range 1 to 24
|
19
|
+
def self.check_day_hours(hours, arg_name)
|
20
|
+
unless hours.between?(0,24)
|
21
|
+
raise ArgumentError.new("#{arg_name} should be in the range 0-24: #{hours}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Check day of the year is valid
|
26
|
+
def self.check_doy(doy)
|
27
|
+
unless doy.between?(1,366)
|
28
|
+
raise ArgumentError.new("day of the year (doy) must be in range 1-366: #{doy}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.check_latitude_rad(latitude)
|
33
|
+
unless latitude.between?(MINLAT_RADIANS,MAXLAT_RADIANS)
|
34
|
+
raise ArgumentError.new("latitude outside valid range #{MINLAT_RADIANS} to #{MAXLAT_RADIANS} rad: #{latitude}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Solar declination can vary between -23.5 and +23.5 degrees.
|
39
|
+
# See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
|
40
|
+
def self.check_sol_dec_rad(sd)
|
41
|
+
unless sd.between?(MINSOLDEC_RADIANS,MAXSOLDEC_RADIANS)
|
42
|
+
raise ArgumentError.new("solar declination outside valid range #{MINSOLDEC_RADIANS} to #{MAXSOLDEC_RADIANS} rad: #{sd}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sunset hour angle has the range 0 to 180 degrees.
|
47
|
+
# See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
|
48
|
+
def self.check_sunset_hour_angle_rad(sha)
|
49
|
+
unless sha.between?(MINSHA_RADIANS,MAXSHA_RADIANS)
|
50
|
+
raise ArgumentError.new("sunset hour angle outside valid range #{MINSHA_RADIANS} to #{MAXSHA_RADIANS} rad: #{sha}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evapotranspiration
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bryce Johnston
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Ruby library for calculating reference crop evapotranspiration (ETo),
|
56
|
+
also referred to as potential evapotranspiration (PET), using the FAO-56 Penman-Monteith
|
57
|
+
method. This is a Ruby port of Mark Richard's PyETo Python package.
|
58
|
+
email:
|
59
|
+
- johnstonbrc@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".rspec"
|
66
|
+
- ".travis.yml"
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE-ORIGINAL.txt
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- bin/console
|
73
|
+
- bin/setup
|
74
|
+
- evapotranspiration.gemspec
|
75
|
+
- lib/evapotranspiration.rb
|
76
|
+
- lib/evapotranspiration/conversion.rb
|
77
|
+
- lib/evapotranspiration/fao.rb
|
78
|
+
- lib/evapotranspiration/thornthwaite.rb
|
79
|
+
- lib/evapotranspiration/validation.rb
|
80
|
+
- lib/evapotranspiration/version.rb
|
81
|
+
homepage: https://github.com/AgRuby/evapotranspiration
|
82
|
+
licenses:
|
83
|
+
- BSD 3-Clause
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.6.4
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Ruby library for calculating reference crop evapotranspiration (ETo)
|
105
|
+
test_files: []
|
106
|
+
has_rdoc:
|