atmospheris 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +25 -0
- data/README.adoc +1254 -0
- data/lib/atmospheris/export/altitude_attrs.rb +296 -0
- data/lib/atmospheris/export/altitude_convertable_model.rb +80 -0
- data/lib/atmospheris/export/altitude_table.rb +49 -0
- data/lib/atmospheris/export/hypsometrical_table.rb +36 -0
- data/lib/atmospheris/export/iso_25331975/group_one.rb +21 -0
- data/lib/atmospheris/export/iso_25331975/group_one_attrs.rb +101 -0
- data/lib/atmospheris/export/iso_25331975/group_three.rb +21 -0
- data/lib/atmospheris/export/iso_25331975/group_three_attrs.rb +101 -0
- data/lib/atmospheris/export/iso_25331975/group_two.rb +21 -0
- data/lib/atmospheris/export/iso_25331975/group_two_attrs.rb +113 -0
- data/lib/atmospheris/export/iso_25331975.rb +28 -0
- data/lib/atmospheris/export/iso_25331985/pressure_attrs.rb +16 -0
- data/lib/atmospheris/export/iso_25331985/table_five_six_attrs.rb +16 -0
- data/lib/atmospheris/export/iso_25331985.rb +113 -0
- data/lib/atmospheris/export/iso_25331997.rb +86 -0
- data/lib/atmospheris/export/iso_25332025/altitude_attrs_group.rb +25 -0
- data/lib/atmospheris/export/iso_25332025/combined_altitude_attrs_group.rb +41 -0
- data/lib/atmospheris/export/iso_25332025.rb +81 -0
- data/lib/atmospheris/export/precision_value.rb +9 -0
- data/lib/atmospheris/export/pressure_attrs.rb +87 -0
- data/lib/atmospheris/export/utils.rb +30 -0
- data/lib/atmospheris/export.rb +17 -0
- data/lib/atmospheris/isa.rb +493 -0
- data/lib/atmospheris/namespace.rb +8 -0
- data/lib/atmospheris/unit_value_float.rb +23 -0
- data/lib/atmospheris/unit_value_integer.rb +23 -0
- data/lib/atmospheris/version.rb +5 -0
- data/lib/atmospheris.rb +14 -0
- metadata +117 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atmospheris
|
|
4
|
+
module Export
|
|
5
|
+
class PressureAttrs < Lutaml::Model::Serializable
|
|
6
|
+
include Utils
|
|
7
|
+
attribute :pressures, UnitValueFloat, collection: true
|
|
8
|
+
attribute :geometric_altitudes, UnitValueFloat, collection: true
|
|
9
|
+
attribute :geopotential_altitudes, UnitValueFloat, collection: true
|
|
10
|
+
|
|
11
|
+
key_value do
|
|
12
|
+
map "pressure", to: :pressures
|
|
13
|
+
map "geopotential-altitude", to: :geopotential_altitudes
|
|
14
|
+
map "geometric-altitude", to: :geometric_altitudes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
xml do
|
|
18
|
+
element "hypsometrical-attributes"
|
|
19
|
+
namespace Atmospheris::Iso2533Namespace
|
|
20
|
+
map_element "pressure", to: :pressures
|
|
21
|
+
map_element "geometric-altitude", to: :geometric_altitudes
|
|
22
|
+
map_element "geopotential-altitude", to: :geopotential_altitudes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_pressure(value:, unit: :mbar, precision: :reduced)
|
|
26
|
+
method_name = "geopotential_altitude_from_pressure_#{unit}"
|
|
27
|
+
gp_h_m = Isa::NormalPrecision.instance.send(method_name, value)
|
|
28
|
+
gp_h_ft = m_to_ft(gp_h_m)
|
|
29
|
+
gm_h_m = Isa::NormalPrecision.instance.geometric_altitude_from_geopotential(gp_h_m)
|
|
30
|
+
gm_h_ft = m_to_ft(gm_h_m)
|
|
31
|
+
|
|
32
|
+
realize_altitudes(gm_h_m, gm_h_ft, gp_h_m, gp_h_ft, precision: precision)
|
|
33
|
+
realize_pressures(value, unit: unit)
|
|
34
|
+
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def realize_altitudes(hgmm, hgmf, hgpm, hgpf, precision: :reduced)
|
|
39
|
+
self.geometric_altitudes = [
|
|
40
|
+
UnitValueFloat.new(
|
|
41
|
+
value: precision == :reduced ? hgmm.round(1) : hgmm,
|
|
42
|
+
unitsml: "m"
|
|
43
|
+
),
|
|
44
|
+
UnitValueFloat.new(
|
|
45
|
+
value: precision == :reduced ? hgmf.round : hgmf,
|
|
46
|
+
unitsml: "ft"
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
self.geopotential_altitudes = [
|
|
51
|
+
UnitValueFloat.new(
|
|
52
|
+
value: precision == :reduced ? hgpm.round(1) : hgpm,
|
|
53
|
+
unitsml: "m"
|
|
54
|
+
),
|
|
55
|
+
UnitValueFloat.new(
|
|
56
|
+
value: precision == :reduced ? hgpf.round : hgpf,
|
|
57
|
+
unitsml: "ft"
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def realize_pressures(input, unit:)
|
|
63
|
+
self.pressures = case unit
|
|
64
|
+
when :mbar
|
|
65
|
+
[
|
|
66
|
+
UnitValueFloat.new(value: input, unitsml: "mbar"),
|
|
67
|
+
UnitValueFloat.new(
|
|
68
|
+
value: Isa::NormalPrecision.instance.mbar_to_mmhg(input),
|
|
69
|
+
unitsml: "mm_Hg"
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
when :mmhg
|
|
73
|
+
[
|
|
74
|
+
UnitValueFloat.new(
|
|
75
|
+
value: Isa::NormalPrecision.instance.mmhg_to_mbar(input),
|
|
76
|
+
unitsml: "mbar"
|
|
77
|
+
),
|
|
78
|
+
UnitValueFloat.new(value: input, unitsml: "mm_Hg")
|
|
79
|
+
]
|
|
80
|
+
else
|
|
81
|
+
raise ArgumentError,
|
|
82
|
+
"Invalid unit: #{unit}. Use :mbar or :mmhg."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atmospheris
|
|
4
|
+
module Export
|
|
5
|
+
module Utils
|
|
6
|
+
def values_in_m_ft(value, unit: :meters)
|
|
7
|
+
case unit
|
|
8
|
+
when :meters
|
|
9
|
+
[value.to_f, m_to_ft(value)]
|
|
10
|
+
when :feet
|
|
11
|
+
[ft_to_m(value), value.to_f]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def round_to_sig_figs(num, num_sig_figs)
|
|
16
|
+
return 0.0 if num.nil? || num.zero?
|
|
17
|
+
|
|
18
|
+
num.round(num_sig_figs - Math.log10(num.abs).ceil).to_f
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def m_to_ft(meters)
|
|
22
|
+
meters / 0.3048
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ft_to_m(feet)
|
|
26
|
+
feet * 0.3048
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atmospheris
|
|
4
|
+
module Export
|
|
5
|
+
autoload :Utils, "atmospheris/export/utils"
|
|
6
|
+
autoload :AltitudeTable, "atmospheris/export/altitude_table"
|
|
7
|
+
autoload :AltitudeConvertableModel, "atmospheris/export/altitude_convertable_model"
|
|
8
|
+
autoload :AltitudeAttrs, "atmospheris/export/altitude_attrs"
|
|
9
|
+
autoload :PressureAttrs, "atmospheris/export/pressure_attrs"
|
|
10
|
+
autoload :HypsometricalTable, "atmospheris/export/hypsometrical_table"
|
|
11
|
+
autoload :PrecisionValue, "atmospheris/export/precision_value"
|
|
12
|
+
autoload :Iso25331975, "atmospheris/export/iso_25331975"
|
|
13
|
+
autoload :Iso25331985, "atmospheris/export/iso_25331985"
|
|
14
|
+
autoload :Iso25331997, "atmospheris/export/iso_25331997"
|
|
15
|
+
autoload :Iso25332025, "atmospheris/export/iso_25332025"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
require "bigdecimal/math"
|
|
5
|
+
require "singleton"
|
|
6
|
+
|
|
7
|
+
module Atmospheris
|
|
8
|
+
module Isa
|
|
9
|
+
# International Standard Atmosphere (ISA) (ISO 2533:1975)
|
|
10
|
+
# ICAO Standard Atmosphere (ICAO Doc 7488/3, 1994)
|
|
11
|
+
|
|
12
|
+
class Algorithms
|
|
13
|
+
attr_accessor :precision
|
|
14
|
+
attr_reader :pressure_layers, :constants, :sqrt2, :pi
|
|
15
|
+
|
|
16
|
+
def set_precision(precision)
|
|
17
|
+
@precision = precision == :high ? :high : :normal
|
|
18
|
+
remove_instance_variable(:@pressure_layers) \
|
|
19
|
+
if defined?(@pressure_layers)
|
|
20
|
+
make_constants
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 2.3 Geopotential and geometric altitides; acceleration of free fall
|
|
24
|
+
|
|
25
|
+
# 2.3 Formula (8)
|
|
26
|
+
# H to h
|
|
27
|
+
# h(m)
|
|
28
|
+
def geometric_altitude_from_geopotential(geopotential_alt)
|
|
29
|
+
@constants[:radius] * geopotential_alt \
|
|
30
|
+
/ (@constants[:radius] - geopotential_alt)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 2.3 Formula (9)
|
|
34
|
+
# h to H
|
|
35
|
+
# H(m)
|
|
36
|
+
def geopotential_altitude_from_geometric(geometric_alt)
|
|
37
|
+
@constants[:radius] * geometric_alt \
|
|
38
|
+
/ (@constants[:radius] + geometric_alt)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 2.3 Formula (7)
|
|
42
|
+
# g(h)
|
|
43
|
+
def gravity_at_geometric(geometric_alt)
|
|
44
|
+
temp = @constants[:radius] / (@constants[:radius] + geometric_alt)
|
|
45
|
+
@constants[:g_n] * temp * temp
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def gravity_at_geopotential(geopotential_alt)
|
|
49
|
+
geometric_h = geometric_altitude_from_geopotential(geopotential_alt)
|
|
50
|
+
gravity_at_geometric(geometric_h)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 2.4 Atmospheric composition and air molar mass
|
|
54
|
+
|
|
55
|
+
# 2.5 Physical characteristics of the atmosphere at mean sea level
|
|
56
|
+
|
|
57
|
+
# 2.6 Temperature and vertical temperature gradient
|
|
58
|
+
|
|
59
|
+
# Formula (11)
|
|
60
|
+
# T
|
|
61
|
+
def temperature_at_layer_from_geopotential(geopotential_alt)
|
|
62
|
+
lower_layer_index = locate_lower_layer(geopotential_alt)
|
|
63
|
+
lower_layer = TEMPERATURE_LAYERS[lower_layer_index]
|
|
64
|
+
beta = num(lower_layer[:B])
|
|
65
|
+
capital_t_b = num(lower_layer[:T])
|
|
66
|
+
capital_h_b = num(lower_layer[:H])
|
|
67
|
+
|
|
68
|
+
capital_t_b + (beta * (geopotential_alt - capital_h_b))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def temperature_at_layer_celcius(geopotential_alt)
|
|
72
|
+
kelvin_to_celsius(
|
|
73
|
+
temperature_at_layer_from_geopotential(geopotential_alt)
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def locate_lower_layer(geopotential_alt)
|
|
78
|
+
# Return first layer if lower than lowest
|
|
79
|
+
return 0 if geopotential_alt < num(TEMPERATURE_LAYERS[0][:H])
|
|
80
|
+
|
|
81
|
+
# Return second last layer if beyond last layer
|
|
82
|
+
i = TEMPERATURE_LAYERS.length - 1
|
|
83
|
+
return i - 1 if geopotential_alt >= num(TEMPERATURE_LAYERS[i][:H])
|
|
84
|
+
|
|
85
|
+
# find last layer with H smaller than our H
|
|
86
|
+
TEMPERATURE_LAYERS.each_with_index do |layer, ind|
|
|
87
|
+
return ind - 1 if num(layer[:H]) > geopotential_alt
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Table 4 - Temperature and vertical temperature gradients
|
|
94
|
+
#
|
|
95
|
+
TEMPERATURE_LAYERS = [
|
|
96
|
+
# H is Geopotential altitude (base altitude) above mean sea level, m
|
|
97
|
+
# T is Temperature, K
|
|
98
|
+
# B is Temperature gradient, "beta", K m^-1
|
|
99
|
+
|
|
100
|
+
# This line is from ICAO 7488/3
|
|
101
|
+
# { H: "-5000", T: "320.65", B: "-0.0065" },
|
|
102
|
+
|
|
103
|
+
# This line is from ISO 2533:1975
|
|
104
|
+
{ H: "-2000", T: "301.15", B: "-0.0065" },
|
|
105
|
+
{ H: "0", T: "288.15", B: "-0.0065" },
|
|
106
|
+
{ H: "11000", T: "216.65", B: "0" },
|
|
107
|
+
{ H: "20000", T: "216.65", B: "0.001" },
|
|
108
|
+
{ H: "32000", T: "228.65", B: "0.0028" },
|
|
109
|
+
{ H: "47000", T: "270.65", B: "0" },
|
|
110
|
+
{ H: "51000", T: "270.65", B: "-0.0028" },
|
|
111
|
+
{ H: "71000", T: "214.65", B: "-0.002" },
|
|
112
|
+
{ H: "80000", T: "196.65" }
|
|
113
|
+
].freeze
|
|
114
|
+
|
|
115
|
+
# 2.7 Pressure
|
|
116
|
+
|
|
117
|
+
# Base pressure values given defined `TEMPERATURE_LAYERS` and constants
|
|
118
|
+
def pressure_layers
|
|
119
|
+
return @pressure_layers if @pressure_layers
|
|
120
|
+
|
|
121
|
+
# assume TEMPERATURE_LAYERS index 1 base altitude is 0 (mean sea level)
|
|
122
|
+
p = []
|
|
123
|
+
|
|
124
|
+
TEMPERATURE_LAYERS.each_with_index do |_x, i|
|
|
125
|
+
last_i = i.zero? ? 0 : i - 1
|
|
126
|
+
last_layer = TEMPERATURE_LAYERS[last_i]
|
|
127
|
+
beta = num(last_layer[:B])
|
|
128
|
+
|
|
129
|
+
if num(last_layer[:H]) <= 0
|
|
130
|
+
p_b = @constants[:p_n]
|
|
131
|
+
capital_h_b = 0
|
|
132
|
+
capital_t_b = @constants[:T_n]
|
|
133
|
+
else
|
|
134
|
+
p_b = p[last_i]
|
|
135
|
+
capital_h_b = num(last_layer[:H])
|
|
136
|
+
capital_t_b = num(last_layer[:T])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
current_layer = TEMPERATURE_LAYERS[i]
|
|
140
|
+
geopotential_alt = num(current_layer[:H])
|
|
141
|
+
temp = num(current_layer[:T])
|
|
142
|
+
height_diff = geopotential_alt - capital_h_b
|
|
143
|
+
|
|
144
|
+
p_i = if beta.zero?
|
|
145
|
+
# Formula (13)
|
|
146
|
+
pressure_formula_beta_zero(p_b, temp, height_diff)
|
|
147
|
+
else
|
|
148
|
+
# Formula (12)
|
|
149
|
+
pressure_formula_beta_nonzero(p_b, beta, capital_t_b,
|
|
150
|
+
height_diff)
|
|
151
|
+
end
|
|
152
|
+
p[i] = p_i
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
@pressure_layers = p
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Formula (12)
|
|
159
|
+
def pressure_formula_beta_nonzero(p_b, beta, temp, height_diff)
|
|
160
|
+
p_b * (1 + ((beta / temp) * height_diff)) \
|
|
161
|
+
**(-@constants[:g_n] / (beta * @constants[:R]))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Formula (13)
|
|
165
|
+
def pressure_formula_beta_zero(p_b, temp, height_diff)
|
|
166
|
+
p_b *
|
|
167
|
+
Math.exp(-(@constants[:g_n] / (@constants[:R] * temp)) * height_diff)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# puts "PRE-CALCULATED PRESSURE LAYERS:"
|
|
171
|
+
# pp @pressure_layers
|
|
172
|
+
|
|
173
|
+
def pa_to_mmhg(pascal)
|
|
174
|
+
pascal * num("0.007500616827")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def pa_to_mbar(pascal)
|
|
178
|
+
pascal * num("0.01")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Pressure for a given geopotential altitude `H` (m) above mean sea level
|
|
182
|
+
def pressure_from_geopotential(geopotential_alt)
|
|
183
|
+
i = locate_lower_layer(geopotential_alt)
|
|
184
|
+
lower_temperature_layer = TEMPERATURE_LAYERS[i]
|
|
185
|
+
beta = num(lower_temperature_layer[:B])
|
|
186
|
+
capital_h_b = num(lower_temperature_layer[:H])
|
|
187
|
+
capital_t_b = num(lower_temperature_layer[:T])
|
|
188
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
189
|
+
p_b = pressure_layers[i]
|
|
190
|
+
height_diff = geopotential_alt - capital_h_b
|
|
191
|
+
|
|
192
|
+
if beta.zero?
|
|
193
|
+
# Formula (13)
|
|
194
|
+
pressure_formula_beta_zero(p_b, temp, height_diff)
|
|
195
|
+
else
|
|
196
|
+
# Formula (12)
|
|
197
|
+
pressure_formula_beta_nonzero(p_b, beta, capital_t_b, height_diff)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def pressure_from_geopotential_mbar(geopotential_alt)
|
|
202
|
+
pa_to_mbar(pressure_from_geopotential(geopotential_alt))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def pressure_from_geopotential_mmhg(geopotential_alt)
|
|
206
|
+
pa_to_mmhg(pressure_from_geopotential(geopotential_alt))
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def p_p_n_from_geopotential(geopotential_alt)
|
|
210
|
+
pressure_from_geopotential(geopotential_alt) / @constants[:p_n]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# 2.8 Density and specific weight
|
|
214
|
+
|
|
215
|
+
# Density for a given geopotential altitude `H` (m) above mean sea level
|
|
216
|
+
# Formula (14)
|
|
217
|
+
# rho
|
|
218
|
+
def density_from_geopotential(geopotential_alt)
|
|
219
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
220
|
+
p = pressure_from_geopotential(geopotential_alt)
|
|
221
|
+
|
|
222
|
+
p / (@constants[:R] * temp)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def rho_rho_n_from_geopotential(geopotential_alt)
|
|
226
|
+
density_from_geopotential(geopotential_alt) / @constants[:rho_n]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def root_rho_rho_n_from_geopotential(geopotential_alt)
|
|
230
|
+
sqrt(rho_rho_n_from_geopotential(geopotential_alt))
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Specific weight
|
|
234
|
+
# Formula (15)
|
|
235
|
+
# gamma
|
|
236
|
+
def specific_weight_from_geopotential(geopotential_alt)
|
|
237
|
+
density_from_geopotential(geopotential_alt) *
|
|
238
|
+
gravity_at_geopotential(geopotential_alt)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# 2.9 Pressure scale height
|
|
242
|
+
# Formula (16)
|
|
243
|
+
# H_p
|
|
244
|
+
def pressure_scale_height_from_temp(temp)
|
|
245
|
+
(@constants[:R] * temp) / @constants[:g_n]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def pressure_scale_height_from_geopotential(geopotential_alt)
|
|
249
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
250
|
+
(@constants[:R] * temp) / gravity_at_geopotential(geopotential_alt)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# 2.10 Air number density
|
|
254
|
+
# Formula (17)
|
|
255
|
+
# n
|
|
256
|
+
def air_number_density_from_geopotential(geopotential_alt)
|
|
257
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
258
|
+
p = pressure_from_geopotential(geopotential_alt)
|
|
259
|
+
|
|
260
|
+
@constants[:N_A] * p / (@constants[:R_star] * temp)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# 2.11 Mean air-particle speed
|
|
264
|
+
# Formula (18)
|
|
265
|
+
# v_bar
|
|
266
|
+
# CORRECT
|
|
267
|
+
def mean_air_particle_speed_from_temp(temp)
|
|
268
|
+
num("1.595769") * sqrt(@constants[:R] * temp)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def mean_air_particle_speed_from_geopotential(geopotential_alt)
|
|
272
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
273
|
+
mean_air_particle_speed_from_temp(temp)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# 2.12 Mean free path of air particles
|
|
277
|
+
# Formula (19)
|
|
278
|
+
# l
|
|
279
|
+
def mean_free_path_of_air_particles_from_geopotential(geopotential_alt)
|
|
280
|
+
# 1 / (sqrt(2) * Pi * (3.65e-10**2) * air_number_density
|
|
281
|
+
1 / (@sqrt2 * @pi * num("0.133225e-18") * \
|
|
282
|
+
air_number_density_from_geopotential(geopotential_alt))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# 2.13 Air-particle collision frequency
|
|
286
|
+
# Formula (20)
|
|
287
|
+
# omega
|
|
288
|
+
def air_particle_collision_frequency_from_temp(air_number_density, temp)
|
|
289
|
+
# 4 * (3.65e-10**2) * ...
|
|
290
|
+
4 * num("0.133225e-18") *
|
|
291
|
+
((@pi / (@constants[:R_star] * @constants[:M]))**num("0.5")) *
|
|
292
|
+
air_number_density * @constants[:R_star] * (temp**num("0.5"))
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def air_particle_collision_frequency_from_geopotential(geopotential_alt)
|
|
296
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
297
|
+
n = air_number_density_from_geopotential(geopotential_alt)
|
|
298
|
+
air_particle_collision_frequency_from_temp(n, temp)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# 2.14 Speed of sound
|
|
302
|
+
# Formula (21)
|
|
303
|
+
# a (ms-1)
|
|
304
|
+
# CORRECT
|
|
305
|
+
def speed_of_sound_from_temp(temp)
|
|
306
|
+
# `kappa` (ratio of c_p / c_v) = 1.4 (see 2.14)
|
|
307
|
+
kappa = num("1.4")
|
|
308
|
+
sqrt(kappa * @constants[:R] * temp)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def speed_of_sound_from_geopotential(geopotential_alt)
|
|
312
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
313
|
+
speed_of_sound_from_temp(temp)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# 2.15 Dynamic viscosity
|
|
317
|
+
# Formula (22)
|
|
318
|
+
# mu (Pa s)
|
|
319
|
+
def dynamic_viscosity(temp)
|
|
320
|
+
# Sutherland's empirical constants in the equation for dynamic viscosity
|
|
321
|
+
capital_b_s = num("1.458e-6")
|
|
322
|
+
capital_s = num("110.4")
|
|
323
|
+
|
|
324
|
+
(capital_b_s * (temp**num("1.5"))) / (temp + capital_s)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def dynamic_viscosity_from_geopotential(geopotential_alt)
|
|
328
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
329
|
+
dynamic_viscosity(temp)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# 2.16 Kinematic viscosity
|
|
333
|
+
# Formula (23)
|
|
334
|
+
# v
|
|
335
|
+
def kinematic_viscosity(temp)
|
|
336
|
+
dynamic_viscosity(temp) / @constants[:rho_n]
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def kinematic_viscosity_from_geopotential(geopotential_alt)
|
|
340
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
341
|
+
dynamic_viscosity(temp) / density_from_geopotential(geopotential_alt)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# 2.17 Thermal conductivity
|
|
345
|
+
# Formula (24)
|
|
346
|
+
# lambda
|
|
347
|
+
def thermal_conductivity_from_temp(temp)
|
|
348
|
+
(num("2.648151e-3") * (temp**num("1.5"))) \
|
|
349
|
+
/ (temp + (num("245.4") * (num("10")**(num("-12") / temp))))
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def thermal_conductivity_from_geopotential(geopotential_alt)
|
|
353
|
+
temp = temperature_at_layer_from_geopotential(geopotential_alt)
|
|
354
|
+
thermal_conductivity_from_temp(temp)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def kelvin_to_celsius(kelvin)
|
|
358
|
+
kelvin - num("273.15")
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# ADD 1
|
|
362
|
+
# Formulae used in the calculation of the relationships
|
|
363
|
+
# between geopotential altitude and pressure
|
|
364
|
+
def geopotential_altitude_from_pressure_mbar(pressure)
|
|
365
|
+
if pressure >= pa_to_mbar(pressure_layers[2]) # H <= 11 000 m
|
|
366
|
+
(num("3.731444") - pressure**num("0.1902631")) / num("8.41728e-5")
|
|
367
|
+
elsif pressure >= pa_to_mbar(pressure_layers[3]) # H <= 20 000 m
|
|
368
|
+
(num("3.1080387") - log10(pressure)) / num("6.848325e-5")
|
|
369
|
+
elsif pressure >= pa_to_mbar(pressure_layers[4]) # H <= 32 000 m
|
|
370
|
+
(num("1.2386515") - pressure**num("0.02927125")) \
|
|
371
|
+
/ (num("5.085177e-6") * pressure**num("0.02927125"))
|
|
372
|
+
elsif pressure >= pa_to_mbar(pressure_layers[5]) # H <= 47 000 m
|
|
373
|
+
(num("1.9630052") - pressure**num("0.08195949")) \
|
|
374
|
+
/ (num("2.013664e-5") * pressure**num("0.08195949"))
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def geopotential_altitude_from_pressure_mmhg(pressure)
|
|
379
|
+
if pressure >= pa_to_mmhg(pressure_layers[2]) # H <= 11 000 m
|
|
380
|
+
(num("3.532747") - pressure**num("0.1902631")) / num("7.96906e-5")
|
|
381
|
+
elsif pressure >= pa_to_mmhg(pressure_layers[3]) # H <= 20 000 m
|
|
382
|
+
(num("2.9831357") - log10(pressure)) / num("6.848325e-5")
|
|
383
|
+
elsif pressure >= pa_to_mmhg(pressure_layers[4]) # H <= 32 000 m
|
|
384
|
+
(num("1.2282678") - pressure**num("0.02927125")) \
|
|
385
|
+
/ (num("5.085177e-6") * pressure**num("0.02927125"))
|
|
386
|
+
elsif pressure >= pa_to_mmhg(pressure_layers[5]) # H <= 47 000 m
|
|
387
|
+
(num("1.9172753") - pressure**num("0.08195949")) \
|
|
388
|
+
/ (num("2.013664e-5") * pressure**num("0.08195949"))
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def mbar_to_mmhg(mbar)
|
|
393
|
+
# Convert mbar to Pa, then Pa to mmHg
|
|
394
|
+
pa = mbar / num("0.01") # or mbar * 100
|
|
395
|
+
pa_to_mmhg(pa)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def mmhg_to_mbar(mmhg)
|
|
399
|
+
# Convert mmHg to Pa, then Pa to mbar
|
|
400
|
+
pa = mmhg / num("0.007500616827")
|
|
401
|
+
pa_to_mbar(pa)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
private
|
|
405
|
+
|
|
406
|
+
def num(str)
|
|
407
|
+
if @precision == :high
|
|
408
|
+
BigDecimal(str)
|
|
409
|
+
else
|
|
410
|
+
str.to_f
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def sqrt(num)
|
|
415
|
+
if @precision == :high
|
|
416
|
+
BigMath.sqrt(num, 100)
|
|
417
|
+
else
|
|
418
|
+
Math.sqrt(num)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def log10(num)
|
|
423
|
+
if @precision == :high
|
|
424
|
+
BigMath.log(num, 100) / BigMath.log(10, 100)
|
|
425
|
+
else
|
|
426
|
+
Math.log10(num)
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def make_constants
|
|
431
|
+
# 2.1 Primary constants and characteristics
|
|
432
|
+
# Table 1 - Main constants and characteristics adopted for
|
|
433
|
+
# the calculation of the ISO Standard Atmosphere
|
|
434
|
+
@constants = {
|
|
435
|
+
# g_n gravitation at mean sea level (m.s-2)
|
|
436
|
+
g_n: num("9.80665"),
|
|
437
|
+
|
|
438
|
+
# Avogadro constant (mol-1)
|
|
439
|
+
N_A: num("6.02257e+23"),
|
|
440
|
+
|
|
441
|
+
# p_n pressure at mean sea level (Pa)
|
|
442
|
+
p_n: num("101325"),
|
|
443
|
+
|
|
444
|
+
# rho_n standard air density
|
|
445
|
+
rho_n: num("1.225"),
|
|
446
|
+
|
|
447
|
+
# T_n standard thermodynamic air temperature at mean sea level
|
|
448
|
+
T_n: num("288.15"),
|
|
449
|
+
|
|
450
|
+
# universal gas constant
|
|
451
|
+
R_star: num("8.31432"),
|
|
452
|
+
|
|
453
|
+
# radius of the Earth (m)
|
|
454
|
+
radius: num("6356766"),
|
|
455
|
+
|
|
456
|
+
# adiabatic index (dimensionless)
|
|
457
|
+
k: num("1.4")
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# 2.2 The equation of the static atmosphere and the perfect gas law
|
|
461
|
+
# Formula (2)
|
|
462
|
+
# M: air molar mass at sea level, kg.kmol-1
|
|
463
|
+
# Value given in 2.1 as M: 28.964720
|
|
464
|
+
@constants[:M] =
|
|
465
|
+
(@constants[:rho_n] * @constants[:R_star] * @constants[:T_n]) / @constants[:p_n]
|
|
466
|
+
|
|
467
|
+
# Formula (3)
|
|
468
|
+
# R: specific gas constant, J.K-1.kg-1.
|
|
469
|
+
# Value given in 2.1 as R: 287.05287
|
|
470
|
+
@constants[:R] = @constants[:R_star] / @constants[:M]
|
|
471
|
+
|
|
472
|
+
@sqrt2 = sqrt(num("2"))
|
|
473
|
+
@pi = @precision == :high ? BigMath.PI(100) : Math::PI
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
class HighPrecision < Algorithms
|
|
478
|
+
include Singleton
|
|
479
|
+
|
|
480
|
+
def initialize
|
|
481
|
+
set_precision(:high)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
class NormalPrecision < Algorithms
|
|
486
|
+
include Singleton
|
|
487
|
+
|
|
488
|
+
def initialize
|
|
489
|
+
set_precision(:normal)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atmospheris
|
|
4
|
+
class UnitValueFloat < Lutaml::Model::Serializable
|
|
5
|
+
attribute :value, :float
|
|
6
|
+
attribute :unitsml, :string
|
|
7
|
+
attribute :type, :string, default: -> { "float" }
|
|
8
|
+
|
|
9
|
+
key_value do
|
|
10
|
+
map "value", to: :value
|
|
11
|
+
map "unitsml", to: :unitsml
|
|
12
|
+
map "type", to: :type, render_default: true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
xml do
|
|
16
|
+
element "unit-value-float"
|
|
17
|
+
namespace Atmospheris::Iso2533Namespace
|
|
18
|
+
map_content to: :value
|
|
19
|
+
map_attribute :unitsml, to: :unitsml
|
|
20
|
+
map_attribute :type, to: :type, render_default: true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atmospheris
|
|
4
|
+
class UnitValueInteger < Lutaml::Model::Serializable
|
|
5
|
+
attribute :value, :integer
|
|
6
|
+
attribute :unitsml, :string
|
|
7
|
+
attribute :type, :string, default: -> { "integer" }
|
|
8
|
+
|
|
9
|
+
key_value do
|
|
10
|
+
map "value", to: :value
|
|
11
|
+
map "unitsml", to: :unitsml
|
|
12
|
+
map "type", to: :type, render_default: true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
xml do
|
|
16
|
+
element "unit-value-integer"
|
|
17
|
+
namespace Atmospheris::Iso2533Namespace
|
|
18
|
+
map_content to: :value
|
|
19
|
+
map_attribute :unitsml, to: :unitsml
|
|
20
|
+
map_attribute :type, to: :type, render_default: true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|