zero2geospatial3d-sdk 0.1.0__py3-none-any.whl

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.
Files changed (89) hide show
  1. zero2geospatial3d/__init__.py +105 -0
  2. zero2geospatial3d/acoustics.py +284 -0
  3. zero2geospatial3d/classify.py +379 -0
  4. zero2geospatial3d/cli.py +367 -0
  5. zero2geospatial3d/comfort.py +687 -0
  6. zero2geospatial3d/data/epw_stations.json.gz +0 -0
  7. zero2geospatial3d/daylight.py +337 -0
  8. zero2geospatial3d/dispersion.py +249 -0
  9. zero2geospatial3d/epw.py +162 -0
  10. zero2geospatial3d/geo.py +138 -0
  11. zero2geospatial3d/grid.py +119 -0
  12. zero2geospatial3d/microclimate.py +388 -0
  13. zero2geospatial3d/morphology.py +283 -0
  14. zero2geospatial3d/nsga2.py +315 -0
  15. zero2geospatial3d/osm.py +450 -0
  16. zero2geospatial3d/osm_tags.py +180 -0
  17. zero2geospatial3d/py.typed +0 -0
  18. zero2geospatial3d/results.py +231 -0
  19. zero2geospatial3d/road_sources.py +141 -0
  20. zero2geospatial3d/scene.py +555 -0
  21. zero2geospatial3d/server.py +332 -0
  22. zero2geospatial3d/shading.py +443 -0
  23. zero2geospatial3d/solar.py +207 -0
  24. zero2geospatial3d/terrain.py +610 -0
  25. zero2geospatial3d/urban_form.py +326 -0
  26. zero2geospatial3d/walkability.py +380 -0
  27. zero2geospatial3d/weather.py +373 -0
  28. zero2geospatial3d/web/index.html +50 -0
  29. zero2geospatial3d/web/src/analysis/climate.js +278 -0
  30. zero2geospatial3d/web/src/analysis/compliance.js +345 -0
  31. zero2geospatial3d/web/src/analysis/gpu.js +431 -0
  32. zero2geospatial3d/web/src/analysis/optimize.js +208 -0
  33. zero2geospatial3d/web/src/analysis/runners.js +1644 -0
  34. zero2geospatial3d/web/src/analysis/terrain_results.js +145 -0
  35. zero2geospatial3d/web/src/app.js +630 -0
  36. zero2geospatial3d/web/src/core/acoustics.js +341 -0
  37. zero2geospatial3d/web/src/core/classify.js +158 -0
  38. zero2geospatial3d/web/src/core/comfort.js +686 -0
  39. zero2geospatial3d/web/src/core/daylight_rule.js +199 -0
  40. zero2geospatial3d/web/src/core/dispersion.js +264 -0
  41. zero2geospatial3d/web/src/core/microclimate.js +372 -0
  42. zero2geospatial3d/web/src/core/morphology.js +153 -0
  43. zero2geospatial3d/web/src/core/nsga2.js +181 -0
  44. zero2geospatial3d/web/src/core/party_walls.js +60 -0
  45. zero2geospatial3d/web/src/core/road_sources.js +127 -0
  46. zero2geospatial3d/web/src/core/solar.js +112 -0
  47. zero2geospatial3d/web/src/core/terrain_analysis.js +267 -0
  48. zero2geospatial3d/web/src/core/terrain_math.js +279 -0
  49. zero2geospatial3d/web/src/core/urban_form.js +405 -0
  50. zero2geospatial3d/web/src/core/walkability.js +316 -0
  51. zero2geospatial3d/web/src/core/weather.js +225 -0
  52. zero2geospatial3d/web/src/core/wind.js +131 -0
  53. zero2geospatial3d/web/src/export/check_pdf.js +74 -0
  54. zero2geospatial3d/web/src/export/exporters.js +556 -0
  55. zero2geospatial3d/web/src/export/pdf.js +149 -0
  56. zero2geospatial3d/web/src/export/snapshot.js +234 -0
  57. zero2geospatial3d/web/src/export/snapshot_scene.js +150 -0
  58. zero2geospatial3d/web/src/main.js +75 -0
  59. zero2geospatial3d/web/src/scene/objects.js +645 -0
  60. zero2geospatial3d/web/src/scene/overlay.js +145 -0
  61. zero2geospatial3d/web/src/scene/terrain.js +546 -0
  62. zero2geospatial3d/web/src/scene/viewer.js +275 -0
  63. zero2geospatial3d/web/src/scene/wind_streamlines.js +336 -0
  64. zero2geospatial3d/web/src/style.css +225 -0
  65. zero2geospatial3d/web/src/ui/building_report.js +404 -0
  66. zero2geospatial3d/web/src/ui/charts.js +441 -0
  67. zero2geospatial3d/web/src/ui/colorize.js +101 -0
  68. zero2geospatial3d/web/src/ui/compare.js +362 -0
  69. zero2geospatial3d/web/src/ui/dom.js +104 -0
  70. zero2geospatial3d/web/src/ui/panel_analyze.js +742 -0
  71. zero2geospatial3d/web/src/ui/panel_compare.js +294 -0
  72. zero2geospatial3d/web/src/ui/panel_layers.js +172 -0
  73. zero2geospatial3d/web/src/ui/panel_terrain.js +277 -0
  74. zero2geospatial3d/web/src/ui/qgis_link.js +127 -0
  75. zero2geospatial3d/web/src/ui/sunclock.js +45 -0
  76. zero2geospatial3d/web/src/ui/tools.js +197 -0
  77. zero2geospatial3d/web/src/ui/viewtools.js +173 -0
  78. zero2geospatial3d/web/vendor/three/addons/controls/OrbitControls.js +1417 -0
  79. zero2geospatial3d/web/vendor/three/addons/exporters/GLTFExporter.js +3309 -0
  80. zero2geospatial3d/web/vendor/three/addons/utils/TextureUtils.js +98 -0
  81. zero2geospatial3d/web/vendor/three/three.module.js +53044 -0
  82. zero2geospatial3d/wind.py +178 -0
  83. zero2geospatial3d_sdk-0.1.0.dist-info/METADATA +219 -0
  84. zero2geospatial3d_sdk-0.1.0.dist-info/RECORD +89 -0
  85. zero2geospatial3d_sdk-0.1.0.dist-info/WHEEL +5 -0
  86. zero2geospatial3d_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  87. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  88. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +52 -0
  89. zero2geospatial3d_sdk-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,686 @@
1
+ // @ts-check
2
+ // Outdoor thermal comfort: UTCI and mean radiant temperature.
3
+ //
4
+ // UTCI: the 6th-order polynomial approximation of Broede et al. (2012),
5
+ // "Deriving the operational procedure for the Universal Thermal Climate
6
+ // Index (UTCI)", Int J Biometeorol 56:481-494, doi:10.1007/s00484-011-0454-1.
7
+ // The polynomial and the saturation vapour pressure function are transcribed
8
+ // from pythermalcomfort (models/utci.py), MIT License, Copyright (c) 2019
9
+ // Federico Tartarini, and checked against its documented values
10
+ // (utci(25, 25, 1.0, 50) = 24.6; utci(40, 25, 1.0, 50) = 40.4).
11
+ //
12
+ // MRT: longwave exchange with sky and surroundings plus the SolarCal
13
+ // short-wave increment (Arens et al. 2015, "Modeling the comfort effects of
14
+ // short-wave solar radiation indoors", Building and Environment 88:3-9,
15
+ // doi:10.1016/j.buildenv.2014.09.004; ANSI/ASHRAE Standard 55, Appendix C).
16
+ // Surrounding surfaces are taken at air temperature: a screening model, not
17
+ // a surface energy balance.
18
+
19
+ const SIGMA = 5.67e-8;
20
+
21
+ /** Saturation vapour pressure over water, hPa (as used by the UTCI reference code). */
22
+ export function saturationVapourPressure(tdb) {
23
+ const g = [-2836.5744, -6028.076559, 19.54263612, -0.02737830188, 0.000016261698, 7.0229056e-10, -1.8680009e-13];
24
+ const tk = tdb + 273.15;
25
+ let es = 2.7150305 * Math.log(tk);
26
+ for (let i = 0; i < g.length; i++) es += g[i] * Math.pow(tk, i - 2);
27
+ return Math.exp(es) * 0.01;
28
+ }
29
+
30
+ /**
31
+ * UTCI equivalent temperature, degrees C.
32
+ * @param {number} tdb air temperature, C
33
+ * @param {number} tr mean radiant temperature, C
34
+ * @param {number} v wind speed at 10 m, m/s
35
+ * @param {number} rh relative humidity, %
36
+ * @param {{ limit?: boolean }} [opts] limit: NaN outside the model's validity range
37
+ */
38
+ export function utci(tdb, tr, v, rh, { limit = false } = {}) {
39
+ if (limit && (tdb < -50 || tdb > 50 || tr - tdb < -30 || tr - tdb > 70 || v < 0.5 || v > 17)) return NaN;
40
+ const pa = saturationVapourPressure(tdb) * (rh / 100) / 10; // kPa
41
+ const delta_t_tr = tr - tdb;
42
+ return (
43
+ tdb
44
+ + 0.607562052
45
+ + (-0.0227712343) * tdb
46
+ + (8.06470249 * (10 ** (-4))) * tdb * tdb
47
+ + (-1.54271372 * (10 ** (-4))) * tdb * tdb * tdb
48
+ + (-3.24651735 * (10 ** (-6))) * tdb * tdb * tdb * tdb
49
+ + (7.32602852 * (10 ** (-8))) * tdb * tdb * tdb * tdb * tdb
50
+ + (1.35959073 * (10 ** (-9))) * tdb * tdb * tdb * tdb * tdb * tdb
51
+ + (-2.25836520) * v
52
+ + 0.0880326035 * tdb * v
53
+ + 0.00216844454 * tdb * tdb * v
54
+ + (-1.53347087 * (10 ** (-5))) * tdb * tdb * tdb * v
55
+ + (-5.72983704 * (10 ** (-7))) * tdb * tdb * tdb * tdb * v
56
+ + (-2.55090145 * (10 ** (-9))) * tdb * tdb * tdb * tdb * tdb * v
57
+ + (-0.751269505) * v * v
58
+ + (-0.00408350271) * tdb * v * v
59
+ + (-5.21670675 * (10 ** (-5))) * tdb * tdb * v * v
60
+ + (1.94544667 * (10 ** (-6))) * tdb * tdb * tdb * v * v
61
+ + (1.14099531 * (10 ** (-8))) * tdb * tdb * tdb * tdb * v * v
62
+ + 0.158137256 * v * v * v
63
+ + (-6.57263143 * (10 ** (-5))) * tdb * v * v * v
64
+ + (2.22697524 * (10 ** (-7))) * tdb * tdb * v * v * v
65
+ + (-4.16117031 * (10 ** (-8))) * tdb * tdb * tdb * v * v * v
66
+ + (-0.0127762753) * v * v * v * v
67
+ + (9.66891875 * (10 ** (-6))) * tdb * v * v * v * v
68
+ + (2.52785852 * (10 ** (-9))) * tdb * tdb * v * v * v * v
69
+ + (4.56306672 * (10 ** (-4))) * v * v * v * v * v
70
+ + (-1.74202546 * (10 ** (-7))) * tdb * v * v * v * v * v
71
+ + (-5.91491269 * (10 ** (-6))) * v * v * v * v * v * v
72
+ + 0.398374029 * delta_t_tr
73
+ + (1.83945314 * (10 ** (-4))) * tdb * delta_t_tr
74
+ + (-1.73754510 * (10 ** (-4))) * tdb * tdb * delta_t_tr
75
+ + (-7.60781159 * (10 ** (-7))) * tdb * tdb * tdb * delta_t_tr
76
+ + (3.77830287 * (10 ** (-8))) * tdb * tdb * tdb * tdb * delta_t_tr
77
+ + (5.43079673 * (10 ** (-10))) * tdb * tdb * tdb * tdb * tdb * delta_t_tr
78
+ + (-0.0200518269) * v * delta_t_tr
79
+ + (8.92859837 * (10 ** (-4))) * tdb * v * delta_t_tr
80
+ + (3.45433048 * (10 ** (-6))) * tdb * tdb * v * delta_t_tr
81
+ + (-3.77925774 * (10 ** (-7))) * tdb * tdb * tdb * v * delta_t_tr
82
+ + (-1.69699377 * (10 ** (-9))) * tdb * tdb * tdb * tdb * v * delta_t_tr
83
+ + (1.69992415 * (10 ** (-4))) * v * v * delta_t_tr
84
+ + (-4.99204314 * (10 ** (-5))) * tdb * v * v * delta_t_tr
85
+ + (2.47417178 * (10 ** (-7))) * tdb * tdb * v * v * delta_t_tr
86
+ + (1.07596466 * (10 ** (-8))) * tdb * tdb * tdb * v * v * delta_t_tr
87
+ + (8.49242932 * (10 ** (-5))) * v * v * v * delta_t_tr
88
+ + (1.35191328 * (10 ** (-6))) * tdb * v * v * v * delta_t_tr
89
+ + (-6.21531254 * (10 ** (-9))) * tdb * tdb * v * v * v * delta_t_tr
90
+ + (-4.99410301 * (10 ** (-6))) * v * v * v * v * delta_t_tr
91
+ + (-1.89489258 * (10 ** (-8))) * tdb * v * v * v * v * delta_t_tr
92
+ + (8.15300114 * (10 ** (-8))) * v * v * v * v * v * delta_t_tr
93
+ + (7.55043090 * (10 ** (-4))) * delta_t_tr * delta_t_tr
94
+ + (-5.65095215 * (10 ** (-5))) * tdb * delta_t_tr * delta_t_tr
95
+ + (-4.52166564 * (10 ** (-7))) * tdb * tdb * delta_t_tr * delta_t_tr
96
+ + (2.46688878 * (10 ** (-8))) * tdb * tdb * tdb * delta_t_tr * delta_t_tr
97
+ + (2.42674348 * (10 ** (-10))) * tdb * tdb * tdb * tdb * delta_t_tr * delta_t_tr
98
+ + (1.54547250 * (10 ** (-4))) * v * delta_t_tr * delta_t_tr
99
+ + (5.24110970 * (10 ** (-6))) * tdb * v * delta_t_tr * delta_t_tr
100
+ + (-8.75874982 * (10 ** (-8))) * tdb * tdb * v * delta_t_tr * delta_t_tr
101
+ + (-1.50743064 * (10 ** (-9))) * tdb * tdb * tdb * v * delta_t_tr * delta_t_tr
102
+ + (-1.56236307 * (10 ** (-5))) * v * v * delta_t_tr * delta_t_tr
103
+ + (-1.33895614 * (10 ** (-7))) * tdb * v * v * delta_t_tr * delta_t_tr
104
+ + (2.49709824 * (10 ** (-9))) * tdb * tdb * v * v * delta_t_tr * delta_t_tr
105
+ + (6.51711721 * (10 ** (-7))) * v * v * v * delta_t_tr * delta_t_tr
106
+ + (1.94960053 * (10 ** (-9))) * tdb * v * v * v * delta_t_tr * delta_t_tr
107
+ + (-1.00361113 * (10 ** (-8))) * v * v * v * v * delta_t_tr * delta_t_tr
108
+ + (-1.21206673 * (10 ** (-5))) * delta_t_tr * delta_t_tr * delta_t_tr
109
+ + (-2.18203660 * (10 ** (-7))) * tdb * delta_t_tr * delta_t_tr * delta_t_tr
110
+ + (7.51269482 * (10 ** (-9))) * tdb * tdb * delta_t_tr * delta_t_tr * delta_t_tr
111
+ + (9.79063848 * (10 ** (-11)))
112
+ * tdb
113
+ * tdb
114
+ * tdb
115
+ * delta_t_tr
116
+ * delta_t_tr
117
+ * delta_t_tr
118
+ + (1.25006734 * (10 ** (-6))) * v * delta_t_tr * delta_t_tr * delta_t_tr
119
+ + (-1.81584736 * (10 ** (-9))) * tdb * v * delta_t_tr * delta_t_tr * delta_t_tr
120
+ + (-3.52197671 * (10 ** (-10)))
121
+ * tdb
122
+ * tdb
123
+ * v
124
+ * delta_t_tr
125
+ * delta_t_tr
126
+ * delta_t_tr
127
+ + (-3.36514630 * (10 ** (-8))) * v * v * delta_t_tr * delta_t_tr * delta_t_tr
128
+ + (1.35908359 * (10 ** (-10)))
129
+ * tdb
130
+ * v
131
+ * v
132
+ * delta_t_tr
133
+ * delta_t_tr
134
+ * delta_t_tr
135
+ + (4.17032620 * (10 ** (-10)))
136
+ * v
137
+ * v
138
+ * v
139
+ * delta_t_tr
140
+ * delta_t_tr
141
+ * delta_t_tr
142
+ + (-1.30369025 * (10 ** (-9)))
143
+ * delta_t_tr
144
+ * delta_t_tr
145
+ * delta_t_tr
146
+ * delta_t_tr
147
+ + (4.13908461 * (10 ** (-10)))
148
+ * tdb
149
+ * delta_t_tr
150
+ * delta_t_tr
151
+ * delta_t_tr
152
+ * delta_t_tr
153
+ + (9.22652254 * (10 ** (-12)))
154
+ * tdb
155
+ * tdb
156
+ * delta_t_tr
157
+ * delta_t_tr
158
+ * delta_t_tr
159
+ * delta_t_tr
160
+ + (-5.08220384 * (10 ** (-9)))
161
+ * v
162
+ * delta_t_tr
163
+ * delta_t_tr
164
+ * delta_t_tr
165
+ * delta_t_tr
166
+ + (-2.24730961 * (10 ** (-11)))
167
+ * tdb
168
+ * v
169
+ * delta_t_tr
170
+ * delta_t_tr
171
+ * delta_t_tr
172
+ * delta_t_tr
173
+ + (1.17139133 * (10 ** (-10)))
174
+ * v
175
+ * v
176
+ * delta_t_tr
177
+ * delta_t_tr
178
+ * delta_t_tr
179
+ * delta_t_tr
180
+ + (6.62154879 * (10 ** (-10)))
181
+ * delta_t_tr
182
+ * delta_t_tr
183
+ * delta_t_tr
184
+ * delta_t_tr
185
+ * delta_t_tr
186
+ + (4.03863260 * (10 ** (-13)))
187
+ * tdb
188
+ * delta_t_tr
189
+ * delta_t_tr
190
+ * delta_t_tr
191
+ * delta_t_tr
192
+ * delta_t_tr
193
+ + (1.95087203 * (10 ** (-12)))
194
+ * v
195
+ * delta_t_tr
196
+ * delta_t_tr
197
+ * delta_t_tr
198
+ * delta_t_tr
199
+ * delta_t_tr
200
+ + (-4.73602469 * (10 ** (-12)))
201
+ * delta_t_tr
202
+ * delta_t_tr
203
+ * delta_t_tr
204
+ * delta_t_tr
205
+ * delta_t_tr
206
+ * delta_t_tr
207
+ + 5.12733497 * pa
208
+ + (-0.312788561) * tdb * pa
209
+ + (-0.0196701861) * tdb * tdb * pa
210
+ + (9.99690870 * (10 ** (-4))) * tdb * tdb * tdb * pa
211
+ + (9.51738512 * (10 ** (-6))) * tdb * tdb * tdb * tdb * pa
212
+ + (-4.66426341 * (10 ** (-7))) * tdb * tdb * tdb * tdb * tdb * pa
213
+ + 0.548050612 * v * pa
214
+ + (-0.00330552823) * tdb * v * pa
215
+ + (-0.00164119440) * tdb * tdb * v * pa
216
+ + (-5.16670694 * (10 ** (-6))) * tdb * tdb * tdb * v * pa
217
+ + (9.52692432 * (10 ** (-7))) * tdb * tdb * tdb * tdb * v * pa
218
+ + (-0.0429223622) * v * v * pa
219
+ + 0.00500845667 * tdb * v * v * pa
220
+ + (1.00601257 * (10 ** (-6))) * tdb * tdb * v * v * pa
221
+ + (-1.81748644 * (10 ** (-6))) * tdb * tdb * tdb * v * v * pa
222
+ + (-1.25813502 * (10 ** (-3))) * v * v * v * pa
223
+ + (-1.79330391 * (10 ** (-4))) * tdb * v * v * v * pa
224
+ + (2.34994441 * (10 ** (-6))) * tdb * tdb * v * v * v * pa
225
+ + (1.29735808 * (10 ** (-4))) * v * v * v * v * pa
226
+ + (1.29064870 * (10 ** (-6))) * tdb * v * v * v * v * pa
227
+ + (-2.28558686 * (10 ** (-6))) * v * v * v * v * v * pa
228
+ + (-0.0369476348) * delta_t_tr * pa
229
+ + 0.00162325322 * tdb * delta_t_tr * pa
230
+ + (-3.14279680 * (10 ** (-5))) * tdb * tdb * delta_t_tr * pa
231
+ + (2.59835559 * (10 ** (-6))) * tdb * tdb * tdb * delta_t_tr * pa
232
+ + (-4.77136523 * (10 ** (-8))) * tdb * tdb * tdb * tdb * delta_t_tr * pa
233
+ + (8.64203390 * (10 ** (-3))) * v * delta_t_tr * pa
234
+ + (-6.87405181 * (10 ** (-4))) * tdb * v * delta_t_tr * pa
235
+ + (-9.13863872 * (10 ** (-6))) * tdb * tdb * v * delta_t_tr * pa
236
+ + (5.15916806 * (10 ** (-7))) * tdb * tdb * tdb * v * delta_t_tr * pa
237
+ + (-3.59217476 * (10 ** (-5))) * v * v * delta_t_tr * pa
238
+ + (3.28696511 * (10 ** (-5))) * tdb * v * v * delta_t_tr * pa
239
+ + (-7.10542454 * (10 ** (-7))) * tdb * tdb * v * v * delta_t_tr * pa
240
+ + (-1.24382300 * (10 ** (-5))) * v * v * v * delta_t_tr * pa
241
+ + (-7.38584400 * (10 ** (-9))) * tdb * v * v * v * delta_t_tr * pa
242
+ + (2.20609296 * (10 ** (-7))) * v * v * v * v * delta_t_tr * pa
243
+ + (-7.32469180 * (10 ** (-4))) * delta_t_tr * delta_t_tr * pa
244
+ + (-1.87381964 * (10 ** (-5))) * tdb * delta_t_tr * delta_t_tr * pa
245
+ + (4.80925239 * (10 ** (-6))) * tdb * tdb * delta_t_tr * delta_t_tr * pa
246
+ + (-8.75492040 * (10 ** (-8))) * tdb * tdb * tdb * delta_t_tr * delta_t_tr * pa
247
+ + (2.77862930 * (10 ** (-5))) * v * delta_t_tr * delta_t_tr * pa
248
+ + (-5.06004592 * (10 ** (-6))) * tdb * v * delta_t_tr * delta_t_tr * pa
249
+ + (1.14325367 * (10 ** (-7))) * tdb * tdb * v * delta_t_tr * delta_t_tr * pa
250
+ + (2.53016723 * (10 ** (-6))) * v * v * delta_t_tr * delta_t_tr * pa
251
+ + (-1.72857035 * (10 ** (-8))) * tdb * v * v * delta_t_tr * delta_t_tr * pa
252
+ + (-3.95079398 * (10 ** (-8))) * v * v * v * delta_t_tr * delta_t_tr * pa
253
+ + (-3.59413173 * (10 ** (-7))) * delta_t_tr * delta_t_tr * delta_t_tr * pa
254
+ + (7.04388046 * (10 ** (-7))) * tdb * delta_t_tr * delta_t_tr * delta_t_tr * pa
255
+ + (-1.89309167 * (10 ** (-8)))
256
+ * tdb
257
+ * tdb
258
+ * delta_t_tr
259
+ * delta_t_tr
260
+ * delta_t_tr
261
+ * pa
262
+ + (-4.79768731 * (10 ** (-7))) * v * delta_t_tr * delta_t_tr * delta_t_tr * pa
263
+ + (7.96079978 * (10 ** (-9)))
264
+ * tdb
265
+ * v
266
+ * delta_t_tr
267
+ * delta_t_tr
268
+ * delta_t_tr
269
+ * pa
270
+ + (1.62897058 * (10 ** (-9)))
271
+ * v
272
+ * v
273
+ * delta_t_tr
274
+ * delta_t_tr
275
+ * delta_t_tr
276
+ * pa
277
+ + (3.94367674 * (10 ** (-8)))
278
+ * delta_t_tr
279
+ * delta_t_tr
280
+ * delta_t_tr
281
+ * delta_t_tr
282
+ * pa
283
+ + (-1.18566247 * (10 ** (-9)))
284
+ * tdb
285
+ * delta_t_tr
286
+ * delta_t_tr
287
+ * delta_t_tr
288
+ * delta_t_tr
289
+ * pa
290
+ + (3.34678041 * (10 ** (-10)))
291
+ * v
292
+ * delta_t_tr
293
+ * delta_t_tr
294
+ * delta_t_tr
295
+ * delta_t_tr
296
+ * pa
297
+ + (-1.15606447 * (10 ** (-10)))
298
+ * delta_t_tr
299
+ * delta_t_tr
300
+ * delta_t_tr
301
+ * delta_t_tr
302
+ * delta_t_tr
303
+ * pa
304
+ + (-2.80626406) * pa * pa
305
+ + 0.548712484 * tdb * pa * pa
306
+ + (-0.00399428410) * tdb * tdb * pa * pa
307
+ + (-9.54009191 * (10 ** (-4))) * tdb * tdb * tdb * pa * pa
308
+ + (1.93090978 * (10 ** (-5))) * tdb * tdb * tdb * tdb * pa * pa
309
+ + (-0.308806365) * v * pa * pa
310
+ + 0.0116952364 * tdb * v * pa * pa
311
+ + (4.95271903 * (10 ** (-4))) * tdb * tdb * v * pa * pa
312
+ + (-1.90710882 * (10 ** (-5))) * tdb * tdb * tdb * v * pa * pa
313
+ + 0.00210787756 * v * v * pa * pa
314
+ + (-6.98445738 * (10 ** (-4))) * tdb * v * v * pa * pa
315
+ + (2.30109073 * (10 ** (-5))) * tdb * tdb * v * v * pa * pa
316
+ + (4.17856590 * (10 ** (-4))) * v * v * v * pa * pa
317
+ + (-1.27043871 * (10 ** (-5))) * tdb * v * v * v * pa * pa
318
+ + (-3.04620472 * (10 ** (-6))) * v * v * v * v * pa * pa
319
+ + 0.0514507424 * delta_t_tr * pa * pa
320
+ + (-0.00432510997) * tdb * delta_t_tr * pa * pa
321
+ + (8.99281156 * (10 ** (-5))) * tdb * tdb * delta_t_tr * pa * pa
322
+ + (-7.14663943 * (10 ** (-7))) * tdb * tdb * tdb * delta_t_tr * pa * pa
323
+ + (-2.66016305 * (10 ** (-4))) * v * delta_t_tr * pa * pa
324
+ + (2.63789586 * (10 ** (-4))) * tdb * v * delta_t_tr * pa * pa
325
+ + (-7.01199003 * (10 ** (-6))) * tdb * tdb * v * delta_t_tr * pa * pa
326
+ + (-1.06823306 * (10 ** (-4))) * v * v * delta_t_tr * pa * pa
327
+ + (3.61341136 * (10 ** (-6))) * tdb * v * v * delta_t_tr * pa * pa
328
+ + (2.29748967 * (10 ** (-7))) * v * v * v * delta_t_tr * pa * pa
329
+ + (3.04788893 * (10 ** (-4))) * delta_t_tr * delta_t_tr * pa * pa
330
+ + (-6.42070836 * (10 ** (-5))) * tdb * delta_t_tr * delta_t_tr * pa * pa
331
+ + (1.16257971 * (10 ** (-6))) * tdb * tdb * delta_t_tr * delta_t_tr * pa * pa
332
+ + (7.68023384 * (10 ** (-6))) * v * delta_t_tr * delta_t_tr * pa * pa
333
+ + (-5.47446896 * (10 ** (-7))) * tdb * v * delta_t_tr * delta_t_tr * pa * pa
334
+ + (-3.59937910 * (10 ** (-8))) * v * v * delta_t_tr * delta_t_tr * pa * pa
335
+ + (-4.36497725 * (10 ** (-6))) * delta_t_tr * delta_t_tr * delta_t_tr * pa * pa
336
+ + (1.68737969 * (10 ** (-7)))
337
+ * tdb
338
+ * delta_t_tr
339
+ * delta_t_tr
340
+ * delta_t_tr
341
+ * pa
342
+ * pa
343
+ + (2.67489271 * (10 ** (-8)))
344
+ * v
345
+ * delta_t_tr
346
+ * delta_t_tr
347
+ * delta_t_tr
348
+ * pa
349
+ * pa
350
+ + (3.23926897 * (10 ** (-9)))
351
+ * delta_t_tr
352
+ * delta_t_tr
353
+ * delta_t_tr
354
+ * delta_t_tr
355
+ * pa
356
+ * pa
357
+ + (-0.0353874123) * pa * pa * pa
358
+ + (-0.221201190) * tdb * pa * pa * pa
359
+ + 0.0155126038 * tdb * tdb * pa * pa * pa
360
+ + (-2.63917279 * (10 ** (-4))) * tdb * tdb * tdb * pa * pa * pa
361
+ + 0.0453433455 * v * pa * pa * pa
362
+ + (-0.00432943862) * tdb * v * pa * pa * pa
363
+ + (1.45389826 * (10 ** (-4))) * tdb * tdb * v * pa * pa * pa
364
+ + (2.17508610 * (10 ** (-4))) * v * v * pa * pa * pa
365
+ + (-6.66724702 * (10 ** (-5))) * tdb * v * v * pa * pa * pa
366
+ + (3.33217140 * (10 ** (-5))) * v * v * v * pa * pa * pa
367
+ + (-0.00226921615) * delta_t_tr * pa * pa * pa
368
+ + (3.80261982 * (10 ** (-4))) * tdb * delta_t_tr * pa * pa * pa
369
+ + (-5.45314314 * (10 ** (-9))) * tdb * tdb * delta_t_tr * pa * pa * pa
370
+ + (-7.96355448 * (10 ** (-4))) * v * delta_t_tr * pa * pa * pa
371
+ + (2.53458034 * (10 ** (-5))) * tdb * v * delta_t_tr * pa * pa * pa
372
+ + (-6.31223658 * (10 ** (-6))) * v * v * delta_t_tr * pa * pa * pa
373
+ + (3.02122035 * (10 ** (-4))) * delta_t_tr * delta_t_tr * pa * pa * pa
374
+ + (-4.77403547 * (10 ** (-6))) * tdb * delta_t_tr * delta_t_tr * pa * pa * pa
375
+ + (1.73825715 * (10 ** (-6))) * v * delta_t_tr * delta_t_tr * pa * pa * pa
376
+ + (-4.09087898 * (10 ** (-7)))
377
+ * delta_t_tr
378
+ * delta_t_tr
379
+ * delta_t_tr
380
+ * pa
381
+ * pa
382
+ * pa
383
+ + 0.614155345 * pa * pa * pa * pa
384
+ + (-0.0616755931) * tdb * pa * pa * pa * pa
385
+ + 0.00133374846 * tdb * tdb * pa * pa * pa * pa
386
+ + 0.00355375387 * v * pa * pa * pa * pa
387
+ + (-5.13027851 * (10 ** (-4))) * tdb * v * pa * pa * pa * pa
388
+ + (1.02449757 * (10 ** (-4))) * v * v * pa * pa * pa * pa
389
+ + (-0.00148526421) * delta_t_tr * pa * pa * pa * pa
390
+ + (-4.11469183 * (10 ** (-5))) * tdb * delta_t_tr * pa * pa * pa * pa
391
+ + (-6.80434415 * (10 ** (-6))) * v * delta_t_tr * pa * pa * pa * pa
392
+ + (-9.77675906 * (10 ** (-6))) * delta_t_tr * delta_t_tr * pa * pa * pa * pa
393
+ + 0.0882773108 * pa * pa * pa * pa * pa
394
+ + (-0.00301859306) * tdb * pa * pa * pa * pa * pa
395
+ + 0.00104452989 * v * pa * pa * pa * pa * pa
396
+ + (2.47090539 * (10 ** (-4))) * delta_t_tr * pa * pa * pa * pa * pa
397
+ + 0.00148348065 * pa * pa * pa * pa * pa * pa
398
+ );
399
+ }
400
+
401
+ export const UTCI_CATEGORIES = [
402
+ { max: -40, label: 'Extreme cold stress', color: '#08306b' },
403
+ { max: -27, label: 'Very strong cold stress', color: '#08519c' },
404
+ { max: -13, label: 'Strong cold stress', color: '#2171b5' },
405
+ { max: 0, label: 'Moderate cold stress', color: '#6baed6' },
406
+ { max: 9, label: 'Slight cold stress', color: '#c6dbef' },
407
+ { max: 26, label: 'No thermal stress', color: '#a1d99b' },
408
+ { max: 32, label: 'Moderate heat stress', color: '#fdd0a2' },
409
+ { max: 38, label: 'Strong heat stress', color: '#fd8d3c' },
410
+ { max: 46, label: 'Very strong heat stress', color: '#d94801' },
411
+ { max: Infinity, label: 'Extreme heat stress', color: '#7f2704' }
412
+ ];
413
+
414
+ export function utciCategory(value) {
415
+ if (!Number.isFinite(value)) return null;
416
+ return UTCI_CATEGORIES.find((c) => value < c.max) || UTCI_CATEGORIES[UTCI_CATEGORIES.length - 1];
417
+ }
418
+
419
+ /**
420
+ * Projected area factor of a standing person for a sun elevation in degrees
421
+ * (Underwood and Ward 1966, as used by SOLWEIG): projected area over the
422
+ * effective radiating area.
423
+ */
424
+ export function projectedAreaFactor(elevationDeg) {
425
+ const b = Math.max(0, Math.min(90, elevationDeg));
426
+ return 0.308 * Math.cos((b * (0.998 - (b * b) / 50000)) * Math.PI / 180);
427
+ }
428
+
429
+ /** Sky temperature (C) from EPW horizontal infrared, or Swinbank (1963) when missing. */
430
+ export function skyTemperature(airTemp, horizontalIr) {
431
+ if (Number.isFinite(horizontalIr) && horizontalIr > 50) return Math.pow(horizontalIr / SIGMA, 0.25) - 273.15;
432
+ const ta = airTemp + 273.15;
433
+ return 0.0552 * Math.pow(ta, 1.5) - 273.15;
434
+ }
435
+
436
+ /**
437
+ * Mean radiant temperature of a standing person outdoors, C.
438
+ * @param {object} p
439
+ * @param {number} p.airTemp C
440
+ * @param {number} p.skyTemp C
441
+ * @param {number} p.svf sky view factor of the spot (0..1)
442
+ * @param {boolean|number} p.sunlit 1 if the person is in the sun (fraction allowed)
443
+ * @param {number} p.dni direct normal irradiance, W/m2
444
+ * @param {number} p.dhi diffuse horizontal irradiance, W/m2
445
+ * @param {number} p.ghi global horizontal irradiance, W/m2
446
+ * @param {number} p.elevationDeg sun elevation, degrees
447
+ * @param {number} [p.albedo] ground reflectance
448
+ * @param {number} [p.absorptivity] clothing/skin short-wave absorptivity
449
+ * @param {number} [p.groundTemp] ground surface temperature, C
450
+ * @param {number} [p.wallTemp] surrounding wall temperature, C
451
+ */
452
+ export function meanRadiantTemperature({
453
+ airTemp,
454
+ skyTemp,
455
+ svf,
456
+ sunlit,
457
+ dni,
458
+ dhi,
459
+ ghi,
460
+ elevationDeg,
461
+ albedo = 0.2,
462
+ absorptivity = 0.7,
463
+ groundTemp = NaN,
464
+ wallTemp = NaN
465
+ }) {
466
+ const fEff = 0.725; // standing person
467
+ const hr = 6.012; // W/m2K
468
+ const emissivity = 0.95; // long-wave absorptivity
469
+ const s = Math.max(0, Math.min(1, svf));
470
+ const tSky = skyTemp + 273.15;
471
+ const tAir = airTemp + 273.15;
472
+
473
+ let tLw;
474
+ if (!Number.isFinite(groundTemp) && !Number.isFinite(wallTemp)) {
475
+ // Exact backward-compatible formula when surface temperatures are omitted
476
+ const fSky = 0.5 * s;
477
+ tLw = Math.pow(fSky * Math.pow(tSky, 4) + (1 - fSky) * Math.pow(tAir, 4), 0.25) - 273.15;
478
+ } else {
479
+ // Surface-aware longwave exchange:
480
+ // Upper hemisphere (weight 0.5) has svf sky at tSky and (1 - svf) walls at tWall.
481
+ // Lower hemisphere (weight 0.5) is ground at tGnd.
482
+ const tWall = (Number.isFinite(wallTemp) ? wallTemp : airTemp) + 273.15;
483
+ const tGnd = (Number.isFinite(groundTemp) ? groundTemp : airTemp) + 273.15;
484
+ tLw = Math.pow(0.5 * s * Math.pow(tSky, 4) + 0.5 * (1 - s) * Math.pow(tWall, 4) + 0.5 * Math.pow(tGnd, 4), 0.25) - 273.15;
485
+ }
486
+
487
+ if (!(elevationDeg > 0) || !(ghi > 0)) return tLw;
488
+ const fBes = Math.max(0, Math.min(1, Number(sunlit)));
489
+ const apOverAd = projectedAreaFactor(elevationDeg) * fEff;
490
+ const eDiff = 0.5 * fEff * s * (dhi + ghi * albedo);
491
+ const eDir = apOverAd * fBes * dni;
492
+ const erf = (eDiff + eDir) * (absorptivity / emissivity);
493
+ return tLw + erf / (fEff * hr);
494
+ }
495
+
496
+ export const PET_CATEGORIES = [
497
+ { max: 4, label: 'Extreme cold stress', color: '#08306b' },
498
+ { max: 8, label: 'Strong cold stress', color: '#2171b5' },
499
+ { max: 13, label: 'Moderate cold stress', color: '#6baed6' },
500
+ { max: 18, label: 'Slight cold stress', color: '#c6dbef' },
501
+ { max: 23, label: 'Comfortable', color: '#a1d99b' },
502
+ { max: 29, label: 'Slight heat stress', color: '#fdd0a2' },
503
+ { max: 35, label: 'Moderate heat stress', color: '#fd8d3c' },
504
+ { max: 41, label: 'Strong heat stress', color: '#d94801' },
505
+ { max: Infinity, label: 'Extreme heat stress', color: '#7f2704' }
506
+ ];
507
+
508
+ /**
509
+ * Return thermal stress category for a PET value (°C).
510
+ * @param {number} value
511
+ * @returns {{ max: number, label: string, color: string } | null}
512
+ */
513
+ export function petCategory(value) {
514
+ if (!Number.isFinite(value)) return null;
515
+ return PET_CATEGORIES.find((c) => value < c.max) || PET_CATEGORIES[PET_CATEGORIES.length - 1];
516
+ }
517
+
518
+ /**
519
+ * Physiological Equivalent Temperature (PET) following Höppe (1999) / Walther & Goestchel (2018) MEMI.
520
+ * @param {number} tdb dry-bulb air temperature, C
521
+ * @param {number} tr mean radiant temperature, C
522
+ * @param {number} v wind speed, m/s
523
+ * @param {number} rh relative humidity, %
524
+ * @param {{ met?: number, clo?: number }} [opts] met in W (default 80), clo (default 0.9)
525
+ * @returns {number} PET in degrees C
526
+ */
527
+ export function pet(tdb, tr, v, rh, { met = 80, clo = 0.9 } = {}) {
528
+ if (!Number.isFinite(tdb) || !Number.isFinite(tr)) return NaN;
529
+ const va = Math.max(0.1, Math.min(20, Number.isFinite(v) ? v : 0.5));
530
+ const rhClamped = Math.max(0, Math.min(100, Number.isFinite(rh) ? rh : 50));
531
+ const Ta = tdb;
532
+ const Tmrt = tr;
533
+ const vpa = saturationVapourPressure(Ta) * (rhClamped / 100);
534
+
535
+ const age = 35;
536
+ const ht = 1.8;
537
+ const mbody = 75.0;
538
+ const p = 1013.25;
539
+ const cb = 3640.0;
540
+ const cair = 1010.0;
541
+ const emsk = 0.99;
542
+ const emcl = 0.95;
543
+ const Lvap = 2.42e6;
544
+ const sigm = 5.67e-8;
545
+ const eta = 0.0;
546
+ const tc_set = 36.6;
547
+ const tsk_set = 34.0;
548
+ const tbody_set = 0.1 * tsk_set + 0.9 * tc_set;
549
+
550
+ const Adu = 0.203 * Math.pow(mbody, 0.425) * Math.pow(ht, 0.725);
551
+ const feff = 0.725;
552
+ const fcl = 1.0 + 0.31 * clo;
553
+ const facl = Math.max(0, Math.min(1, (173.51 * clo - 2.36 - 100.76 * clo * clo + 19.28 * Math.pow(clo, 3)) / 100));
554
+ const Aclo = Adu * facl + Adu * (fcl - 1.0);
555
+ const Aeffr = Adu * feff;
556
+
557
+ const hc = 2.67 + 6.5 * Math.pow(va, 0.67);
558
+ const metab_male = 3.45 * Math.pow(mbody, 0.75) * (1.0 + 0.004 * (30.0 - age) + 0.01 * (ht * 100.0 / Math.pow(mbody, 1.0 / 3.0) - 43.4));
559
+ const he = (met + metab_male) / Adu;
560
+ const h = he * (1.0 - eta);
561
+
562
+ const texp = 0.47 * Ta + 21.0;
563
+ const dventpulm = he * 1.44e-6;
564
+ const eres = cair * (Ta - texp) * dventpulm;
565
+ const vpexp = 6.11 * Math.pow(10.0, 7.45 * texp / (235.0 + texp));
566
+ const erel = 0.623 * (Lvap / p) * (vpa - vpexp) * dventpulm;
567
+ const ere = eres + erel;
568
+
569
+ const rcl = clo > 0.01 ? clo / 6.45 : 0.001;
570
+ const y = (ht - 0.2) / ht;
571
+ const r2 = Adu * (fcl - 1.0 + facl) / (6.28 * ht * y);
572
+ const r1 = facl * Adu / (6.28 * ht * y);
573
+ const di = r2 - r1;
574
+ const htcl = clo > 0.01 ? (6.28 * ht * y * di) / (rcl * Math.log(r2 / r1) * Aclo) : 1e5;
575
+
576
+ const Lw = 1.67;
577
+ const he_diff = hc * Lw;
578
+ const fecl = 1.0 / (1.0 + 0.92 * hc * rcl);
579
+ const R_ecl = (1.0 / (fcl * hc) + rcl) / (Lw * 0.38);
580
+
581
+ function syst3(T) {
582
+ const Tcr = T[0], Tsk = T[1], Tcl = T[2];
583
+ const sig_skin = Math.max(0, tsk_set - Tsk);
584
+ const sig_core = Math.max(0, Tcr - tc_set);
585
+ const qmblood = Math.min(90.0, (6.3 + 75.0 * sig_core) / (1.0 + 0.5 * sig_skin));
586
+ const alpha = 0.1;
587
+
588
+ const tbody = alpha * Tsk + (1.0 - alpha) * Tcr;
589
+ const sig_body = Math.max(0, tbody - tbody_set);
590
+ const qmsw = Math.min(500.0, 304.94e-3 * sig_body * 1000.0);
591
+ const esw = (Lvap / 1000.0) * (qmsw / 3600.0);
592
+
593
+ const Pvsk = 6.105 * Math.exp((17.27 * (Tsk + 273.15) - 4717.03) / (237.7 + Tsk));
594
+ const emax = he_diff * fecl * (Pvsk - vpa);
595
+ const w = emax > 0 ? Math.min(1.0, Math.max(0, esw / emax)) : 1.0;
596
+ const ediff = (1.0 - w) * (Pvsk - vpa) / R_ecl;
597
+ const evap = -(ediff + esw);
598
+
599
+ const rbare = Aeffr * (1.0 - facl) * emsk * sigm * (Math.pow(Tmrt + 273.15, 4) - Math.pow(Tsk + 273.15, 4)) / Adu;
600
+ const rclo = feff * Aclo * emcl * sigm * (Math.pow(Tmrt + 273.15, 4) - Math.pow(Tcl + 273.15, 4)) / Adu;
601
+ const cbare = hc * (Ta - Tsk) * (1.0 - facl);
602
+ const cclo = hc * (Ta - Tcl) * Aclo / Adu;
603
+
604
+ const Kcs = (qmblood / 3600.0) * cb + 5.28;
605
+
606
+ return [
607
+ h + ere - Kcs * (Tcr - Tsk),
608
+ rbare + cbare + evap + Kcs * (Tcr - Tsk) - htcl * (Tsk - Tcl),
609
+ cclo + rclo + htcl * (Tsk - Tcl)
610
+ ];
611
+ }
612
+
613
+ // 3-variable Newton-Raphson system solver
614
+ const T = [37.0, 34.0, (Ta + Tmrt) / 2.0];
615
+ for (let it = 0; it < 40; it++) {
616
+ const f = syst3(T);
617
+ if (Math.max(Math.abs(f[0]), Math.abs(f[1]), Math.abs(f[2])) < 1e-4) break;
618
+ const J = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
619
+ const eps = 1e-5;
620
+ for (let j = 0; j < 3; j++) {
621
+ const T_plus = [T[0], T[1], T[2]];
622
+ T_plus[j] += eps;
623
+ const f_plus = syst3(T_plus);
624
+ for (let i = 0; i < 3; i++) J[i][j] = (f_plus[i] - f[i]) / eps;
625
+ }
626
+ const det = (J[0][0] * (J[1][1] * J[2][2] - J[1][2] * J[2][1])
627
+ - J[0][1] * (J[1][0] * J[2][2] - J[1][2] * J[2][0])
628
+ + J[0][2] * (J[1][0] * J[2][1] - J[1][1] * J[2][0]));
629
+ if (Math.abs(det) < 1e-12) break;
630
+ const d0 = (-f[0] * (J[1][1] * J[2][2] - J[1][2] * J[2][1]) + f[1] * (J[0][1] * J[2][2] - J[0][2] * J[2][1]) - f[2] * (J[0][1] * J[1][2] - J[0][2] * J[1][1])) / det;
631
+ const d1 = (f[0] * (J[1][0] * J[2][2] - J[1][2] * J[2][0]) - f[1] * (J[0][0] * J[2][2] - J[0][2] * J[2][0]) + f[2] * (J[0][0] * J[1][2] - J[0][2] * J[1][0])) / det;
632
+ const d2 = (-f[0] * (J[1][0] * J[2][1] - J[1][1] * J[2][0]) + f[1] * (J[0][0] * J[2][1] - J[0][1] * J[2][0]) - f[2] * (J[0][0] * J[1][1] - J[0][1] * J[1][0])) / det;
633
+ const damping = Math.min(1.0, 5.0 / (Math.abs(d0) + Math.abs(d1) + Math.abs(d2) + 1e-6));
634
+ T[0] += damping * d0;
635
+ T[1] += damping * d1;
636
+ T[2] += damping * d2;
637
+ }
638
+
639
+ // Indoor reference state: v = 0.1 m/s, rh = 50% (vpa = 12 hPa), clo = 0.9, met = 80 W
640
+ const Tcr = T[0], Tsk = T[1], Tcl = T[2];
641
+ const ref_icl = 0.9;
642
+ const ref_fcl = 1.0 + 0.31 * ref_icl;
643
+ const ref_facl = Math.max(0, Math.min(1, (173.51 * ref_icl - 2.36 - 100.76 * ref_icl * ref_icl + 19.28 * Math.pow(ref_icl, 3)) / 100));
644
+ const ref_Aclo = Adu * ref_facl + Adu * (ref_fcl - 1.0);
645
+ const ref_rcl = ref_icl / 6.45;
646
+ const ref_r2 = Adu * (ref_fcl - 1.0 + ref_facl) / (6.28 * ht * y);
647
+ const ref_r1 = ref_facl * Adu / (6.28 * ht * y);
648
+ const ref_htcl = (6.28 * ht * y * (ref_r2 - ref_r1)) / (ref_rcl * Math.log(ref_r2 / ref_r1) * ref_Aclo);
649
+ const ref_hc = 2.67 + 6.5 * Math.pow(0.1, 0.67);
650
+ const ref_he_diff = ref_hc * Lw;
651
+ const ref_fecl = 1.0 / (1.0 + 0.92 * ref_hc * ref_rcl);
652
+ const ref_R_ecl = (1.0 / (ref_fcl * ref_hc) + ref_rcl) / (Lw * 0.38);
653
+ const ref_vpa = 12.0;
654
+
655
+ const tbody = 0.1 * Tsk + 0.9 * Tcr;
656
+ const sig_body = Math.max(0, tbody - tbody_set);
657
+ const qmsw = Math.min(500.0, 304.94e-3 * sig_body * 1000.0);
658
+ const esw = (Lvap / 1000.0) * (qmsw / 3600.0);
659
+ const Pvsk = 6.105 * Math.exp((17.27 * (Tsk + 273.15) - 4717.03) / (237.7 + Tsk));
660
+ const emax = ref_he_diff * ref_fecl * (Pvsk - ref_vpa);
661
+ const w = emax > 0 ? Math.min(1.0, Math.max(0, esw / emax)) : 1.0;
662
+ const ediff = (1.0 - w) * (Pvsk - ref_vpa) / ref_R_ecl;
663
+ const evap = -(ediff + esw);
664
+
665
+ function evalRef(Tx) {
666
+ const texp_ref = 0.47 * Tx + 21.0;
667
+ const eres_ref = cair * (Tx - texp_ref) * dventpulm;
668
+ const vpexp_ref = 6.11 * Math.pow(10.0, 7.45 * texp_ref / (235.0 + texp_ref));
669
+ const erel_ref = 0.623 * (Lvap / p) * (ref_vpa - vpexp_ref) * dventpulm;
670
+ const ere_ref = eres_ref + erel_ref;
671
+
672
+ const rbare_ref = Aeffr * (1.0 - ref_facl) * emsk * sigm * (Math.pow(Tx + 273.15, 4) - Math.pow(Tsk + 273.15, 4)) / Adu;
673
+ const rclo_ref = feff * ref_Aclo * emcl * sigm * (Math.pow(Tx + 273.15, 4) - Math.pow(Tcl + 273.15, 4)) / Adu;
674
+ const cbare_ref = ref_hc * (Tx - Tsk) * (1.0 - ref_facl);
675
+ const cclo_ref = ref_hc * (Tx - Tcl) * ref_Aclo / Adu;
676
+
677
+ return h + ere_ref + rbare_ref + rclo_ref + cbare_ref + cclo_ref + evap;
678
+ }
679
+
680
+ let lo = -50.0, hi = 70.0;
681
+ for (let it = 0; it < 40; it++) {
682
+ const mid = (lo + hi) / 2.0;
683
+ if (evalRef(mid) < 0) lo = mid; else hi = mid;
684
+ }
685
+ return (lo + hi) / 2.0;
686
+ }