This repository was archived by the owner on Oct 30, 2022. It is now read-only.
forked from RocketPy-Team/RocketPy
-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathEnvironment.py
More file actions
3694 lines (3370 loc) · 147 KB
/
Environment.py
File metadata and controls
3694 lines (3370 loc) · 147 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
from .Function import Function
__author__ = "Giovani Hidalgo Ceotto, Guilherme Fernandes Alves, Lucas Azevedo Pezente, Oscar Mauricio Prada Ramirez, Lucas Kierulff Balabram"
__copyright__ = "Copyright 20XX, RocketPy Team"
__license__ = "MIT"
import bisect
import re
import warnings
from datetime import datetime, timedelta
import os
import matplotlib.pyplot as plt
import numpy as np
import pytz
import requests
try:
import netCDF4
except ImportError:
has_netCDF4 = False
warnings.warn(
"Unable to load netCDF4. NetCDF files and OPeNDAP will not be imported.",
ImportWarning,
)
else:
has_netCDF4 = True
def requires_netCDF4(func):
def wrapped_func(*args, **kwargs):
if has_netCDF4:
func(*args, **kwargs)
else:
raise ImportError(
"This feature requires netCDF4 to be installed. Install it with `pip install netCDF4`"
)
return wrapped_func
class Environment:
"""Keeps all environment information stored, such as wind and temperature
conditions, as well as gravity and rail length.
Attributes
----------
Constants
Environment.earthRadius : float
Value of Earth's Radius = 6.3781e6 m.
Environment.airGasConstant : float
Value of Air's Gas Constant = 287.05287 J/K/Kg
Gravity and Launch Rail Length:
Environment.rl : float
Launch rail length in meters.
Environment.g : float
Positive value of gravitational acceleration in m/s^2.
Coordinates and Date:
Environment.lat : float
Launch site latitude.
Environment.lon : float
Launch site longitude.
Environment.datum: string
The desired reference ellipsoide model, the following options are
available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default
is "SIRGAS2000", then this model will be used if the user make some
typing mistake
Environment.initialEast: float
Launch site East UTM coordinate
Environment.initialNorth: float
Launch site North UTM coordinate
Environment.initialUtmZone: int
Launch site UTM zone number
Environment.initialUtmLetter: string
Launch site UTM letter, to keep the latitude band and describe the
UTM Zone
Environment.initialHemisphere: string
Launch site S/N hemisphere
Environment.initialEW: string
Launch site E/W hemisphere
Environment.elevation : float
Launch site elevation.
Environment.date : datetime
Date time of launch in UTC.
Environment.localDate : datetime
Date time of launch in the local time zone, defined by Environment.timeZone.
Environment.timeZone : string
Local time zone specification. See pytz for time zone info.
Topographic information:
Environment.elevLonArray: array
Unidimensional array containing the longitude coordinates
Environment.elevLatArray: array
Unidimensional array containing the latitude coordinates
Environment.elevArray: array
Two-dimensional Array containing the elevation information
Environment.topographicProfileActivated: bool
True if the user already set a topographic plofile
Atmosphere Static Conditions:
Environment.maxExpectedHeight : float
Maximum altitude in meters to keep weather data.
Used especially for plotting range.
Can be altered as desired.
Environment.pressureISA : Function
Air pressure in Pa as a function of altitude as defined
by the International Standard Atmosphere ISO 2533.
Only defined after load Environment.loadInternationalStandardAtmosphere
has been called.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.temperatureISA : Function
Air temperature in K as a function of altitude as defined
by the International Standard Atmosphere ISO 2533.
Only defined after load Environment.loadInternationalStandardAtmosphere
has been called.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.pressure : Function
Air pressure in Pa as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.temperature : Function
Air temperature in K as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.speedOfSound : Function
Speed of sound in air in m/s as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.density : Function
Air density in kg/m³ as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.dynamicViscosity : Function
Air dynamic viscosity in Pa s as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Atmosphere Wind Conditions:
Environment.windSpeed : Function
Wind speed in m/s as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.windDirection : Function
Wind direction (from which the wind blows)
in degrees relative to north (positive clockwise)
as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.windHeading : Function
Wind heading (direction towards which the wind blows)
in degrees relative to north (positive clockwise)
as a function of altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.windVelocityX : Function
Wind U, or X (east) component of wind velocity in m/s as a function of
altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Environment.windVelocityY : Function
Wind V, or Y (east) component of wind velocity in m/s as a function of
altitude.
Can be accessed as regular array, or called
as a function. See Function for more information.
Atmospheric Model Details
Environment.atmosphericModelType : string
Describes the atmospheric model which is being used.
Can only assume the following values: 'StandardAtmosphere',
'CustomAtmosphere', 'WyomingSounding', 'NOAARucSounding',
'Forecast', 'Reanalysis', 'Ensemble'.
Environment.atmosphericModelFile : string
Address of the file used for the atmospheric model being used.
Only defined for 'WyomingSounding', 'NOAARucSounding',
'Forecast', 'Reanalysis', 'Ensemble'
Environment.atmosphericModelDict : dictionary
Dictionary used to properly interpret netCDF and OPeNDAP
files. Only defined for 'Forecast', 'Reanalysis', 'Ensemble'.
Environment.atmosphericModelInitDate : datetime
Datetime object instance of first availabe date in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelEndDate : datetime
Datetime object instance of last availabe date in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelInterval : int
Hour step between weather condition used in netCDF and
OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelInitLat : float
Latitude of vertex just before the launch site in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelEndLat : float
Latitude of vertex just after the launch site in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelInitLon : float
Longitude of vertex just before the launch site in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Environment.atmosphericModelEndLon : float
Longitude of vertex just after the launch site in netCDF
and OPeNDAP files when using 'Forecast', 'Reanalysis' or
'Ensemble'.
Atmospheric Model Storage
Environment.latArray : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of latitudes
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.lonArray : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of longitudes
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.lonIndex : int
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. Index to a grid longitude which
is just over the launch site longitude, while lonIndex - 1
points to a grid longitude which is just under the launch
site longitude.
Environment.latIndex : int
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. Index to a grid latitude which
is just over the launch site latitude, while lonIndex - 1
points to a grid latitude which is just under the launch
site latitude.
Environment.geopotentials : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of geopotential heights
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.windUs : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of wind U (east) component
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.windVs : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of wind V (north) component
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.levels : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. List of pressure levels available
in the file.
Environment.temperatures : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. 2x2 matrix for each pressure level of temperatures
corresponding to the vertices of the grid cell which surrounds
the launch site.
Environment.timeArray : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. Array of dates available in the
file.
Environment.height : array
Defined if netCDF or OPeNDAP file is used, for Forecasts,
Reanalysis and Ensembles. List of geometric height
corresponding to launch site location.
Atmospheric Model Ensemble Specific Data
Environment.levelEnsemble : array
Only defined when using Ensembles.
Environment.heightEnsemble : array
Only defined when using Ensembles.
Environment.temperatureEnsemble : array
Only defined when using Ensembles.
Environment.windUEnsemble : array
Only defined when using Ensembles.
Environment.windVEnsemble : array
Only defined when using Ensembles.
Environment.windHeadingEnsemble : arrray
Only defined when using Ensembles.
Environment.windDirectionEnsemble : array
Only defined when using Ensembles.
Environment.windSpeedEnsemble : array
Only defined when using Ensembles.
Environment.numEnsembleMembers : int
Number of ensemble members. Only defined when using Ensembles.
Environment.ensembleMember : int
Current selected ensemble member. Only defined when using Ensembles.
"""
def __init__(
self,
railLength,
gravity=9.80665,
date=None,
latitude=0,
longitude=0,
elevation=0,
datum="SIRGAS2000",
timeZone="UTC",
saveImagesPng=False,
saveImagesPdf=False,
):
"""Initialize Environment class, saving launch rail length,
launch date, location coordinates and elevation. Note that
by default the standard atmosphere is loaded until another
atmospheric model is used. See Environment.setAtmosphericModel
for details.
Parameters
----------
railLength : scalar
Length in which the rocket will be attached to the rail, only
moving along a fixed direction, that is, the line parallel to the
rail.
gravity : scalar, optional
Surface gravitational acceleration. Positive values point the
acceleration down. Default value is 9.80665.
date : array, optional
Array of length 4, stating (year, month, day, hour (UTC))
of rocket launch. Must be given if a Forecast, Reanalysis
or Ensemble, will be set as an atmospheric model.
latitude : float, optional
Latitude in degrees (ranging from -90 to 90) of rocket
launch location. Must be given if a Forecast, Reanalysis
or Ensemble will be used as an atmospheric model or if
Open-Elevation will be used to compute elevation.
longitude : float, optional
Longitude in degrees (ranging from -180 to 360) of rocket
launch location. Must be given if a Forecast, Reanalysis
or Ensemble will be used as an atmospheric model or if
Open-Elevation will be used to compute elevation.
elevation : float, optional
Elevation of launch site measured as height above sea
level in meters. Alternatively, can be set as
'Open-Elevation' which uses the Open-Elevation API to
find elevation data. For this option, latitude and
longitude must also be specified. Default value is 0.
datum : string
The desired reference ellipsoide model, the following options are
available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default
is "SIRGAS2000", then this model will be used if the user make some
typing mistake.
timeZone : string, optional
Name of the time zone. To see all time zones, import pytz and run
Returns
-------
None
"""
# Save launch rail length
self.rL = railLength
# Save gravity value
self.g = gravity
# Save datum
self.datum = datum
# Save date
if date != None:
self.setDate(date, timeZone)
else:
self.date = None
self.localDate = None
self.timeZone = None
self.saveImagesPng = saveImagesPng
self.saveImagesPdf = saveImagesPdf
# Initialize constants
self.earthRadius = 6.3781 * (10**6)
self.airGasConstant = 287.05287 # in J/K/Kg
# Initialize atmosphere
self.setAtmosphericModel("StandardAtmosphere")
# Save latitude and longitude
if latitude != None and longitude != None:
self.setLocation(latitude, longitude)
else:
self.lat, self.lon = None, None
# Store launch site coordinates referenced to UTM projection system
if self.lat > -80 and self.lat < 84:
convert = self.geodesicToUtm(self.lat, self.lon, self.datum)
self.initialNorth = convert[1]
self.initialEast = convert[0]
self.initialUtmZone = convert[2]
self.initialUtmLetter = convert[3]
self.initialHemisphere = convert[4]
self.initialEW = convert[5]
# Save elevation
self.setElevation(elevation)
# Recalculate Earth Radius
self.earthRadius = self.calculateEarthRadius(self.lat, self.datum) # in m
return None
def setDate(self, date, timeZone="UTC"):
"""Set date and time of launch and update weather conditions if
date dependent atmospheric model is used.
Parameters
----------
date : Datetime
Datetime object specifying launch date and time.
timeZone : string, optional
Name of the time zone. To see all time zones, import pytz and run
print(pytz.all_timezones). Default time zone is "UTC".
Return
------
None
"""
# Store date and configure time zone
self.timeZone = timeZone
tz = pytz.timezone(self.timeZone)
if type(date) != datetime:
localDate = datetime(*date)
else:
localDate = date
if localDate.tzinfo == None:
localDate = tz.localize(localDate)
self.localDate = localDate
self.date = self.localDate.astimezone(pytz.UTC)
# Update atmospheric conditions if atmosphere type is Forecast,
# Reanalysis or Ensemble
try:
if self.atmosphericModelType in ["Forecast", "Reanalysis", "Ensemble"]:
self.setAtmosphericModel(
self.atmosphericModelFile, self.atmosphericModelDict
)
except AttributeError:
pass
return None
def setLocation(self, latitude, longitude):
"""Set latitude and longitude of launch and update atmospheric
conditions if location dependent model is being used.
Parameters
----------
latitude : float
Latitude of launch site. May range from -90 to 90
degrees.
longitude : float
Longitude of launch site. Either from 0 to 360 degrees
or from -180 to 180 degrees.
Return
------
None
"""
# Store latitude and longitude
self.lat = latitude
self.lon = longitude
# Update atmospheric conditions if atmosphere type is Forecast,
# Reanalysis or Ensemble
if self.atmosphericModelType in ["Forecast", "Reanalysis", "Ensemble"]:
self.setAtmosphericModel(
self.atmosphericModelFile, self.atmosphericModelDict
)
# Return None
def setElevation(self, elevation="Open-Elevation"):
"""Set elevation of launch site given user input or using the
Open-Elevation API.
Parameters
----------
elevation : float, string, optional
Elevation of launch site measured as height above sea
level in meters.
Alternatively, can be set as 'Open-Elevation' which uses
the Open-Elevation API to find elevation data. For this
option, latitude and longitude must have already been
specified. See Environment.setLocation for more details.
Return
------
None
"""
if elevation != "Open-Elevation" and elevation != "SRTM":
self.elevation = elevation
# elif elevation == "SRTM" and self.lat != None and self.lon != None:
# # Trigger the authentication flow.
# #ee.Authenticate()
# # Initialize the library.
# ee.Initialize()
# # Calculate elevation
# dem = ee.Image('USGS/SRTMGL1_003')
# xy = ee.Geometry.Point([self.lon, self.lat])
# elev = dem.sample(xy, 30).first().get('elevation').getInfo()
# self.elevation = elev
elif self.lat != None and self.lon != None:
try:
print("Fetching elevation from open-elevation.com...")
requestURL = "https://api.open-elevation.com/api/v1/lookup?locations={:f},{:f}".format(
self.lat, self.lon
)
response = requests.get(requestURL)
results = response.json()["results"]
self.elevation = results[0]["elevation"]
print("Elevation received:", self.elevation)
except:
raise RuntimeError("Unabel to reach Open-Elevation API servers.")
else:
raise ValueError(
"Latitude and longitude must be set to use"
" Open-Elevation API. See Environment.setLocation."
)
@requires_netCDF4
def setTopographicProfile(self, type, file, dictionary="netCDF4", crs=None):
"""[UNDER CONSTRUCTION] Defines the Topographic profile, importing data
from previous downloaded files. Mainly data from the Shuttle Radar
Topography Mission (SRTM) and NASA Digital Elevation Model will be used
but other models and methods can be implemented in the future.
So far, this function can only handle data from NASADEM, available at:
https://cmr.earthdata.nasa.gov/search/concepts/C1546314436-LPDAAC_ECS.html
Parameters
----------
type : string
Defines the topographic model to be used, usually 'NASADEM Merged
DEM Global 1 arc second nc' can be used. To download this kind of
data, access 'https://search.earthdata.nasa.gov/search'.
NASADEM data products were derived from original telemetry data from
the Shuttle Radar Topography Mission (SRTM).
file : string
The path/name of the topographic file. Usually .nc provided by
dictionary : string, optional
Dictionary which helps to read the specified file. By default
'netCDF4' which works well with .nc files will be used.
crs : string, optional
Coordinate reference system, by default None, which will use the crs
provided by the file.
"""
if type == "NASADEM_HGT":
if dictionary == "netCDF4":
rootgrp = netCDF4.Dataset(file, "r", format="NETCDF4")
self.elevLonArray = rootgrp.variables["lon"][:].tolist()
self.elevLatArray = rootgrp.variables["lat"][:].tolist()
self.elevArray = rootgrp.variables["NASADEM_HGT"][:].tolist()
# crsArray = rootgrp.variables['crs'][:].tolist().
self.topographicProfileActivated = True
print("Region covered by the Topographical file: ")
print(
"Latitude from {:.6f}° to {:.6f}°".format(
self.elevLatArray[-1], self.elevLatArray[0]
)
)
print(
"Longitude from {:.6f}° to {:.6f}°".format(
self.elevLonArray[0], self.elevLonArray[-1]
)
)
return None
def getElevationFromTopograghicProfile(self, lat, lon):
"""Function which receives as inputs the coordinates of a point and finds its
elevation in the provided Topographic Profile
Parameters
----------
lat : float
latitude of the point.
lon : float
longitude of the point.
Returns
-------
elevation: float
Elevation provided by the topographic data, in meters.
Raises
------
ValueError
[description]
ValueError
[description]
"""
if self.topographicProfileActivated == False:
print(
"You must define a Topographic profile first, please use the method Environment.setTopograghicProfile()"
)
return None
# Find latitude index
# Check if reversed or sorted
if self.elevLatArray[0] < self.elevLatArray[-1]:
# Deal with sorted self.elevLatArray
latIndex = bisect.bisect(self.elevLatArray, lat)
else:
# Deal with reversed self.elevLatArray
self.elevLatArray.reverse()
latIndex = len(self.elevLatArray) - bisect.bisect_left(
self.elevLatArray, lat
)
self.elevLatArray.reverse()
# Take care of latitude value equal to maximum longitude in the grid
if (
latIndex == len(self.elevLatArray)
and self.elevLatArray[latIndex - 1] == lat
):
latIndex = latIndex - 1
# Check if latitude value is inside the grid
if latIndex == 0 or latIndex == len(self.elevLatArray):
raise ValueError(
"Latitude {:f} not inside region covered by file, which is from {:f} to {:f}.".format(
lat, self.elevLatArray[0], self.elevLatArray[-1]
)
)
# Find longitude index
# Determine if file uses -180 to 180 or 0 to 360
if self.elevLonArray[0] < 0 or self.elevLonArray[-1] < 0:
# Convert input to -180 - 180
lon = lon if lon < 180 else -180 + lon % 180
else:
# Convert input to 0 - 360
lon = lon % 360
# Check if reversed or sorted
if self.elevLonArray[0] < self.elevLonArray[-1]:
# Deal with sorted self.elevLonArray
lonIndex = bisect.bisect(self.elevLonArray, lon)
else:
# Deal with reversed self.elevLonArray
self.elevLonArray.reverse()
lonIndex = len(self.elevLonArray) - bisect.bisect_left(
self.elevLonArray, lon
)
self.elevLonArray.reverse()
# Take care of longitude value equal to maximum longitude in the grid
if (
lonIndex == len(self.elevLonArray)
and self.elevLonArray[lonIndex - 1] == lon
):
lonIndex = lonIndex - 1
# Check if longitude value is inside the grid
if lonIndex == 0 or lonIndex == len(self.elevLonArray):
raise ValueError(
"Longitude {:f} not inside region covered by file, which is from {:f} to {:f}.".format(
lon, self.elevLonArray[0], self.elevLonArray[-1]
)
)
# Get the elevation
elevation = self.elevArray[latIndex][lonIndex]
return elevation
def setAtmosphericModel(
self,
type,
file=None,
dictionary=None,
pressure=None,
temperature=None,
wind_u=0,
wind_v=0,
):
"""Defines an atmospheric model for the Environment.
Supported functionality includes using data from the
International Standard Atmosphere, importing data from
weather reanalysis, forecasts and ensemble forecasts,
importing data from upper air soundings and inputing
data as custom functions, arrays or csv files.
Parameters
----------
type : string
One of the following options:
- 'StandardAtmosphere': sets pressure and temperature
profiles corresponding to the International Standard
Atmosphere defined by ISO 2533 and ranging from -2 km
to 80 km of altitude above sea level. Note that the wind
profiles are set to zero when this type is chosen.
- 'WyomingSounding': sets pressure, temperature, wind-u
and wind-v profiles and surface elevation obtained from
an upper air sounding given by the file parameter through
an URL. This URL should point to a data webpage given by
selecting plot type as text: list, a station and a time at
http://weather.uwyo.edu/upperair/sounding.html.
An example of a valid link would be:
http://weather.uwyo.edu/cgi-bin/sounding?region=samer&TYPE=TEXT%3ALIST&YEAR=2019&MONTH=02&FROM=0200&TO=0200&STNM=82599
- 'NOAARucSounding': sets pressure, temperature, wind-u
and wind-v profiles and surface elevation obtained from
an upper air sounding given by the file parameter through
an URL. This URL should point to a data webpage obtained
through NOAA's Ruc Sounding servers, which can be accessed
in https://rucsoundings.noaa.gov/. Selecting ROABs as the
initial data source, specifying the station through it's
WMO-ID and opting for the ASCII (GSD format) button, the
following example URL opens up: https://rucsoundings.noaa.gov/get_raobs.cgi?data_source=RAOB&latest=latest&start_year=2019&start_month_name=Feb&start_mday=5&start_hour=12&start_min=0&n_hrs=1.0&fcst_len=shortest&airport=83779&text=Ascii%20text%20%28GSD%20format%29&hydrometeors=false&start=latest
Any ASCII GSD format page from this server can be read,
so information from virtual soundings such as GFS and NAM
can also be imported.
- 'Forecast': sets pressure, temperature, wind-u and wind-v
profiles and surface elevation obtained from a weather
forecast file in netCDF format or from an OPeNDAP URL, both
given through the file parameter. When this type
is chosen, the date and location of the launch
should already have been set through the date and
location parameters when initializing the Environment.
The netCDF and OPeNDAP datasets must contain at least
geopotential height or geopotential, temperature,
wind-u and wind-v profiles as a function of pressure levels.
If surface geopotential or geopotential height is given,
elevation is also set. Otherwise, elevation is not changed.
Profiles are interpolated bi-linearly using supplied
latitude and longitude. The date used is the nearest one
to the date supplied. Furthermore, a dictionary must be
supplied through the dictionary parameter in order for the
dataset to be accurately read. Lastly, the dataset must use
a rectangular grid sorted in either ascending or descending
order of latitude and longitude.
- 'Reanalysis': sets pressure, temperature, wind-u and wind-v
profiles and surface elevation obtained from a weather
forecast file in netCDF format or from an OPeNDAP URL, both
given through the file parameter. When this type
is chosen, the date and location of the launch
should already have been set through the date and
location parameters when initializing the Environment.
The netCDF and OPeNDAP datasets must contain at least
geopotential height or geopotential, temperature,
wind-u and wind-v profiles as a function of pressure levels.
If surface geopotential or geopotential height is given,
elevation is also set. Otherwise, elevation is not changed.
Profiles are interpolated bi-linearly using supplied
latitude and longitude. The date used is the nearest one
to the date supplied. Furthermore, a dictionary must be
supplied through the dictionary parameter in order for the
dataset to be accurately read. Lastly, the dataset must use
a rectangular grid sorted in either ascending or descending
order of latitude and longitude.
- 'Ensemble': sets pressure, temperature, wind-u and wind-v
profiles and surface elevation obtained from a weather
forecast file in netCDF format or from an OPeNDAP URL, both
given through the file parameter. When this type
is chosen, the date and location of the launch
should already have been set through the date and
location parameters when initializing the Environment.
The netCDF and OPeNDAP datasets must contain at least
geopotential height or geopotential, temperature,
wind-u and wind-v profiles as a function of pressure
levels. If surface geopotential or geopotential height
is given, elevation is also set. Otherwise, elevation is not
changed. Profiles are interpolated bi-linearly using supplied
latitude and longitude. The date used is the nearest one
to the date supplied. Furthermore, a dictionary must be
supplied through the dictionary parameter in order for the
dataset to be accurately read. Lastly, the dataset must use
a rectangular grid sorted in either ascending or descending
order of latitude and longitude. By default the first ensemble
forecast is activated. To activate other ensemble forecasts
see Environment.selectEnsembleMemberMember().
- 'CustomAtmosphere': sets pressure, temperature, wind-u
and wind-v profiles given though the pressure, temperature,
wind-u and wind-v parameters of this method. If pressure
or temperature is not given, it will default to the
International Standard Atmosphere. If the wind components
are not given, it will default to 0.
file : string, optional
String that must be given when type is either
'WyomingSounding', 'Forecast', 'Reanalysis' or 'Ensemble'.
It specifies the location of the data given, either through
a local file address or a URL.
If type is 'Forecast', this parameter can also be either
'GFS', 'FV3', 'RAP' or 'NAM' for latest of these forecasts.
References: GFS: Global - 0.25deg resolution - Updates every 6 hours, forecast for 81 points spaced by 3 hours
FV3: Global - 0.25deg resolution - Updates every 6 hours, forecast for 129 points spaced by 3 hours
RAP: Regional USA - 0.19deg resolution - Updates hourly, forecast for 40 points spaced hourly
NAM: Regional CONUS Nest - 5 km resolution - Updates every 6 hours, forecast for 21 points spaced by 3 hours
If type is 'Ensemble', this parameter can also be either
'GEFS', or 'CMC' for the latest of these ensembles.
References: GEFS: Global, bias-corrected, 0.5deg resolution, 21 forecast members, Updates every 6 hours, forecast for 65 points spaced by 4 hours
CMC: Global, 0.5deg resolution, 21 forecast members, Updates every 12 hours, forecast for 65 points spaced by 4 hours
dictionary : dictionary, string, optional
Dictionary that must be given when type is either
'Forecast', 'Reanalysis' or 'Ensemble'.
It specifies the dictionary to be used when reading netCDF
and OPeNDAP files, allowing the correct retrieval of data.
Acceptable values include 'ECMWF', 'NOAA' and 'UCAR' for
default dictionaries which can generally be used to read
datasets from these institutes.
Alternatively, a dictionary structure can also be given,
specifying the short names used for time, latitude, longitude,
pressure levels, temperature profile, geopotential or
geopotential height profile, wind-u and wind-v profiles in
the dataset given in the file parameter. Additionally,
ensemble dictionaries must have the ensemble as well.
An example is the following dictionary, used for 'NOAA':
{'time': 'time',
'latitude': 'lat',
'longitude': 'lon',
'level': 'lev',
'ensemble': 'ens',
'temperature': 'tmpprs',
'surface_geopotential_height': 'hgtsfc',
'geopotential_height': 'hgtprs',
'geopotential': None,
'u_wind': 'ugrdprs',
'v_wind': 'vgrdprs'}
pressure : float, string, array, callable, optional
This defines the atmospheric pressure profile.
Should be given if the type parameter is 'CustomAtmosphere'. If not,
than the the Standard Atmosphere pressure will be used.
If a float is given, it will define a constant pressure
profile. The float should be in units of Pa.
If a string is given, it should point to a .CSV file
containing at most one header line and two columns of data.
The first column must be the geometric height above sea level in
meters while the second column must be the pressure in Pa.
If an array is given, it is expected to be a list or array
of coordinates (height in meters, pressure in Pa).
Finally, a callable or function is also accepted. The
function should take one argument, the height above sea
level in meters and return a corresponding pressure in Pa.
temperature : float, string, array, callable, optional
This defines the atmospheric temperature profile.
Should be given if the type parameter is 'CustomAtmosphere'. If not,
than the the Standard Atmosphere temperature will be used.
If a float is given, it will define a constant temperature
profile. The float should be in units of K.
If a string is given, it should point to a .CSV file
containing at most one header line and two columns of data.
The first column must be the geometric height above sea level in
meters while the second column must be the temperature in K.
If an array is given, it is expected to be a list or array
of coordinates (height in meters, temperature in K).
Finally, a callable or function is also accepted. The
function should take one argument, the height above sea
level in meters and return a corresponding temperature in K.
wind_u : float, string, array, callable, optional
This defines the atmospheric wind-u profile, corresponding
the the magnitude of the wind speed heading East.
Should be given if the type parameter is 'CustomAtmosphere'. If not,
it will be assumed to be constant and equal to 0.
If a float is given, it will define a constant wind-u
profile. The float should be in units of m/s.
If a string is given, it should point to a .CSV file
containing at most one header line and two columns of data.
The first column must be the geometric height above sea level in
meters while the second column must be the wind-u in m/s.
If an array is given, it is expected to be an array of
coordinates (height in meters, wind-u in m/s).
Finally, a callable or function is also accepted. The
function should take one argument, the height above sea
level in meters and return a corresponding wind-u in m/s.
wind_v : float, string, array, callable, optional
This defines the atmospheric wind-v profile, corresponding
the the magnitude of the wind speed heading North.
Should be given if the type parameter is 'CustomAtmosphere'. If not,
it will be assumed to be constant and equal to 0.
If a float is given, it will define a constant wind-v
profile. The float should be in units of m/s.
If a string is given, it should point to a .CSV file
containing at most one header line and two columns of data.
The first column must be the geometric height above sea level in
meters while the second column must be the wind-v in m/s.
If an array is given, it is expected to be an array of
coordinates (height in meters, wind-v in m/s).
Finally, a callable or function is also accepted. The
function should take one argument, the height above sea
level in meters and return a corresponding wind-v in m/s.
Return
------
None
"""
# Save atmospheric model type
self.atmosphericModelType = type
# Handle each case
if type == "StandardAtmosphere":
self.processStandardAtmosphere()
elif type == "WyomingSounding":
self.processWyomingSounding(file)
# Save file
self.atmosphericModelFile = file
elif type == "NOAARucSounding":
self.processNOAARUCSounding(file)
# Save file
self.atmosphericModelFile = file
elif type == "Forecast" or type == "Reanalysis":
# Process default forecasts if requested
if file == "GFS":
# Define dictionary
dictionary = {
"time": "time",
"latitude": "lat",
"longitude": "lon",
"level": "lev",
"temperature": "tmpprs",
"surface_geopotential_height": "hgtsfc",
"geopotential_height": "hgtprs",
"geopotential": None,
"u_wind": "ugrdprs",
"v_wind": "vgrdprs",
}
# Attempt to get latest forecast
timeAttempt = datetime.utcnow()
success = False
attemptCount = 0
while not success and attemptCount < 10:
timeAttempt -= timedelta(hours=6 * attemptCount)
file = "https://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs{:04d}{:02d}{:02d}/gfs_0p25_{:02d}z".format(
timeAttempt.year,
timeAttempt.month,
timeAttempt.day,
6 * (timeAttempt.hour // 6),
)
try:
self.processForecastReanalysis(file, dictionary)
success = True
except OSError:
attemptCount += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for GFS through " + file
)
elif file == "FV3":
# Define dictionary
dictionary = {
"time": "time",
"latitude": "lat",
"longitude": "lon",
"level": "lev",
"temperature": "tmpprs",
"surface_geopotential_height": "hgtsfc",
"geopotential_height": "hgtprs",
"geopotential": None,
"u_wind": "ugrdprs",
"v_wind": "vgrdprs",
}
# Attempt to get latest forecast
timeAttempt = datetime.utcnow()
success = False
attemptCount = 0
while not success and attemptCount < 10:
timeAttempt -= timedelta(hours=6 * attemptCount)
file = "https://nomads.ncep.noaa.gov/dods/gfs_0p25_parafv3/gfs{:04d}{:02d}{:02d}/gfs_0p25_parafv3_{:02d}z".format(
timeAttempt.year,
timeAttempt.month,
timeAttempt.day,
6 * (timeAttempt.hour // 6),
)
try:
self.processForecastReanalysis(file, dictionary)
success = True
except OSError:
attemptCount += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for FV3 through " + file
)
elif file == "NAM":
# Define dictionary
dictionary = {
"time": "time",
"latitude": "lat",
"longitude": "lon",
"level": "lev",
"temperature": "tmpprs",
"surface_geopotential_height": "hgtsfc",
"geopotential_height": "hgtprs",
"geopotential": None,
"u_wind": "ugrdprs",
"v_wind": "vgrdprs",
}
# Attempt to get latest forecast
timeAttempt = datetime.utcnow()
success = False
attemptCount = 0
while not success and attemptCount < 10:
timeAttempt -= timedelta(hours=6 * attemptCount)
file = "https://nomads.ncep.noaa.gov/dods/nam/nam{:04d}{:02d}{:02d}/nam_conusnest_{:02d}z".format(