forked from sandialabs/StrideSearch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
StormTracks.f90
466 lines (426 loc) · 16.3 KB
/
StormTracks.f90
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
module TrackModule
!> @file StormTracks.f90
!! @brief Provides a container class for holding storm track information, and methods for constructing tracks and
!! applying temporal storm identification criteria.
!> @author Peter Bosler SNL
!!
!> @defgroup TrackListNode TrackListNode
!! @brief Linked-list data structure for storm tracks.
!!
!! @{
use StormListNodeModule, only : DEG_2_RAD, SameLocation
use StrideSearchModule, only : SphereDistance
use TropicalStrideSearchModule, only : GetLandMask
use StormTrackDataModule
implicit none
private
public TrackListNode
public GenerateStormTracks, PrintTrajectories, DeleteStormTracks, MarkStormsOverLand
integer, parameter :: maxPointsPerTrack = 200 ! 200 6-hourly timesteps = 50 days
!> @class TrackListNode
!> @brief Linked-list node for storm track.
!> Statically allocates memory for each track, so no track can be longer than maxPointsPerTrack timesteps.
type TrackListNode
real, dimension(maxPointsPerTrack) :: trackLons = 0.0 !< longitudes of a storm along its track
real, dimension(maxPointsPerTrack) :: trackLats = 0.0 !< latitudes of a storm along its track
real, dimension(maxPointsPerTrack) :: trackWind = 0.0 !< max windspeed of a storm along its track
real, dimension(maxPointsPerTrack) :: trackPsl = 0.0 !< min sea level pressure of a storm along its track
real, dimension(maxPointsPerTrack) :: trackVort = 0.0 !< max cyclonic vorticity of a storm along its track
integer, dimension(4, maxPointsPerTrack) :: trackDate = 0 !< date along a storm track
real :: maxWind = 0.0 !< maximum wind speed achieved by a storm over its lifetime
real :: minPsl = 0.0 !< minimum pressure over a storm's lifetime
real :: maxVort = 0.0 !< maximum cyclonic vorticity over a storm's lifetime
integer, dimension(4) :: startDate = 0 !< storm's starting date
integer, dimension(4) :: endDate = 0 !< storm's ending date
integer :: startTimeIndex = 0 !< time index in netcdf file corresponding to startDate
integer :: trackLength = 0 !< number of points in storm track (@f$ \le @f$ maxPointsPerTrack)
integer :: nTracks = 0 !< total number of tracks in this list
logical :: originOverWater = .True. !< true if the first point in a storm track is not over land
type(TrackListNode), pointer :: nextTrack => null()
contains
procedure, public :: initializeTrackNode
end type
contains
!> @brief Prints storm tracks to an output file
!> @param trackList linked-list of storm tracks
!> @param fileunit integer unit of output file
subroutine PrintTrajectories( trackList, fileunit )
type(TrackListNode), pointer, intent(in) :: trackList
integer, intent(in) :: fileUnit
!
type(TrackListNode), pointer :: current
integer :: i
print *, "writing ", trackList%nTracks, " tracks to file."
current => trackList
do while ( associated( current ) )
if ( current%originOverWater .AND. current%trackLength > 0) then
write(fileunit, *) "start ", current%trackLength, current%startDate
do i = 1, current%trackLength
write(fileunit, *) current%trackLons(i), current%trackLats(i), current%trackWind(i), current%trackPsl(i)*0.01, &
current%trackDate(:,i)
enddo
endif
current => current%nextTrack
enddo
end subroutine
!> @brief Builds storm tracks from output of a spatial search driver program.
!> @param trackList list to be constructed. On output, contains storm tracks.
!> @param trackData Container output from stormtrackdatamodule::readstormtrackdatafile.
!> @param minimumDuration tracks that are not at least this many of timesteps long will be ignored.
!> @param maxTravelSpeed maximum storm travel speed in m/s (not maximum wind speed)
!> @param hoursPerTimestep integer number of hours for each timestep
subroutine GenerateStormTracks( trackList, trackData, minimumDuration, maxTravelSpeed, hoursPerTimestep )
type(TrackListNode), pointer, intent(inout) :: trackList
type(StormTrackData), intent(inout) :: trackData
integer, intent(in) :: minimumDuration
real, intent(in) :: maxTravelSpeed
integer, intent(in) :: hoursPerTimestep
!
type(datedStormListNode), pointer :: possTrack, candidateList, current, tempDatedNode, query, currentLoc
integer :: i, startTimeIndex, timeIndex, j
logical :: keepGoing
real :: maxDistPerTimestep, testDist
type(TrackListNode), pointer :: tempTrackPtr
nullify(possTrack)
nullify(candidateList)
nullify(tempTrackPtr)
nullify(tempDatedNode)
maxDistPerTimestep = maxTravelSpeed * 3.6 ! convert m/s to kph
maxDistPerTimestep = maxDistPerTimestep * hoursPerTimestep ! convert kph to km per timestep
do i = 1, trackData%nTimesteps - 1
current => trackData%stormLists(i)
! print *, i, ", listSize = ", current%listSize
j = 0
if ( current%listSize > 0 ) then
do while ( associated(current) )
!
! search storms at this timestep for possible track origins
!
j = j + 1
if ( .NOT. current%removeThisNode ) then
!
! possible storm track origin
!
allocate(possTrack)
startTimeIndex = i
timeIndex = startTimeIndex
call initializeDatedNode( possTrack, current%lon, current%lat, current%lonIndex, current%latIndex, &
current%vort, current%psl, current%wind, current%date )
!
! build possible track
!
keepGoing = .TRUE.
currentLoc => possTrack
!print *, "possible track initialized"
do while ( keepGoing )
!
! search subsequent timestep for successor candidates
!
allocate(candidateList)
timeIndex = timeIndex + 1
if ( timeIndex > trackData%nTimesteps ) then
!
! end of data
!
keepGoing = .FALSE.
!print *, "end of data."
exit
endif
query => trackData%stormLists(timeIndex)
do while ( associated(query) )
testDist = SphereDistance( currentLoc%lon * DEG_2_RAD, currentLoc%lat * DEG_2_RAD, &
query%lon * DEG_2_RAD, query%lat * DEG_2_RAD )
if ( testDist < maxDistPerTimestep .AND. (.NOT. query%removeThisNode ) ) then
!
! candidate found
!
allocate(tempDatedNode)
call initializeDatedNode( tempDatedNode, query%lon, query%lat, query%lonIndex, query%latIndex, &
query%vort, query%psl, query%wind, query%date)
call AddDatedNodeToList( candidateList, tempDatedNode )
deallocate(tempDatedNode)
endif
query => query%nextDatedNode
enddo
if ( candidateList%listSize == 0 ) then
!
! no successors found, end track
!
keepGoing = .FALSE.
!print *, "no candidates found"
elseif ( candidateList%listSize == 1 ) then
!
! one successor found, add to track
!
!print *, "one candidate found"
!print *, "PossTrack Info : "
!call PrintTrackLocations(possTrack)
!print *, "candidateList Info : "
!call PrintTrackLocations(candidateList)
call AddDatedNodeToList( possTrack, candidateList )
call UpdateCurrentLocation( currentLoc, possTrack)
else
!
! multiple possible successors found; choose closest one, add to track
!
!print *, "multiple candidates found"
call FindClosestCandidate( tempDatedNode, currentLoc, candidateList )
call AddDatedNodeToList( possTrack, tempDatedNode )
call UpdateCurrentLocation( currentLoc, possTrack)
endif
call deleteDatedList(candidateList)
enddo
!
! check temporal criteria along possible track
!
if ( possTrack%listSize >= minimumDuration ) then
!print *, "adding track"
!
! storm track found: record to track list, remove entries from data to prevent duplication
!
call MarkTrackNodesForRemoval( trackData, possTrack, startTimeIndex )
!print *, "used nodes marked for removal"
!print *, "calling initialize for track with ", possTrack%listSize, " entries."
allocate(tempTrackPtr)
call initializeTrackNode( tempTrackPtr, possTrack, startTimeIndex )
call AddTrackNodeToList( trackList, tempTrackPtr )
deallocate(tempTrackPtr)
endif
call deleteDatedList(possTrack)
endif
! write(6,'(I4,A)',advance='no') j, " "
current => current%nextDatedNode
enddo
endif
enddo
end subroutine
!> @brief Prints basic storm track info to console
!> @param track Track list
subroutine PrintTrackLocations( track )
type(datedStormListNode), pointer, intent(in) :: track
type(datedStormListNode), pointer :: current
current => track
do while ( associated(current) )
print *, current%lon, current%lat, current%wind, current%psl, current%date
current => current%nextDatedNode
enddo
end subroutine
!> @brief Updates a storm's location within a track to the previously searched timestep.
!> @param currLoc
!> @param track
subroutine UpdateCurrentLocation( currLoc, track )
type(datedStormListNode), pointer, intent(inout) :: currLoc
type(datedStormListNode), pointer, intent(in) :: track
type(datedStormListNode), pointer :: current
!print *, "updating current location"
current => track
do while ( associated(current%nextDatedNode) )
current => current%nextDatedNode
enddo
currLoc => current
end subroutine
!> @brief When storm tracks get close together, a storm may have more than one possible candidate location at the next timestep.
!> This subroutine eliminates all but the closest one to a storm's current location.
!> @param closestCandidate (output)
!> @param currentLoc location of storm at current time step
!> @param candidateList list of possible storm locations at next time step
subroutine FindClosestCandidate( closestCandidate, currentLoc, candidateList )
type(datedStormListNode), pointer, intent(inout) :: closestCandidate
type(datedStormListNode), pointer, intent(in) :: currentLoc, candidateList
!
real :: minDist, testDist
type(datedStormListNode), pointer :: query
!print *,"finding Closest Candidate"
query => candidateList
minDist = 1.0e20
do while ( associated(query) )
testDist = SphereDistance( currentLoc%lon * DEG_2_RAD, currentLoc%lat * DEG_2_RAD, &
query%lon * DEG_2_RAD, query%lat * DEG_2_RAD )
if ( testDist < minDist ) then
minDist = testDist
closestCandidate => query
endif
query => query%nextDatedNode
enddo
end subroutine
!> @brief Once a datedStormListNode has been used to construct a storm track, it should not be used for other storms'
!> possible tracks.
!> This subroutine marks used nodes to prevent duplicate uses of the same storm.
!> @param trackData
!> @param trackToRemove new track to be added to track list, so it should be deleted from trackData
!> @param startTimeIndex starting time index of trackToRemove
subroutine MarkTrackNodesForRemoval( trackData, trackToRemove, startTimeIndex )
type(StormTrackData), intent(inout) :: trackData
type(datedStormListNode), pointer, intent(in) :: trackToRemove
integer, intent(in) :: startTimeIndex
!
type(datedStormListNode), pointer :: query, querynext, trackDataPtr, nextTrackDataPtr
integer :: i
!print *, "Marking used nodes for removal..."
query => trackToRemove
i = startTimeIndex
do while ( associated(query) )
querynext => query%nextDatedNode
trackDataPtr => trackData%stormLists(i)
do while ( associated(trackDataPtr) )
nextTrackDataPtr => trackDataPtr%nextDatedNode
if ( SameLocation(query, trackDataPtr ) ) then
trackDataPtr%removeThisNode = .TRUE.
i = i + 1
exit
endif
trackDataPtr => nextTrackDataPtr
enddo
query => querynext
enddo
end subroutine
!> @brief Initializes a TrackListNode.
!> @param trackList list to be initialized
!> @param stormTrack linked list of datedStormListNode objects that defines a storm track
!> @param startIndex starting time index in netCDF file associated with stormTrack's origin
subroutine initializeTrackNode( trackList, stormtrack, startIndex )
class(TrackListNode), intent(inout) :: trackList
type(datedStormListNode), pointer, intent(in) :: stormtrack
integer, intent(in) :: startIndex
!
type(datedStormListNode), pointer :: current
integer :: i
!print *, "initializing a new track..."
if ( stormTrack%listSize > 0 .AND. stormTrack%listSize <= maxPointsPerTrack ) then
trackList%trackLength = stormTrack%listSize
trackList%startTimeIndex = startIndex
i = 0
current => stormtrack
do while ( associated(current) )
i = i + 1
trackList%trackLons(i) = current%lon
trackList%trackLats(i) = current%lat
trackList%trackWind(i) = current%wind
trackList%trackPsl(i) = current%psl
trackList%trackVort(i) = current%vort
trackList%trackDate(:,i) = current%date
if ( i == 1 ) then
trackList%startDate = current%date
elseif ( i == trackList%trackLength) then
trackList%endDate = current%date
endif
current => current%nextDatedNode
enddo
trackList%maxWind = maxval(trackList%trackWind)
trackList%maxVort = maxval(trackList%trackVort)
trackList%minPSl = minval(trackList%trackPsl)
trackList%nTracks = 1
trackList%originOverWater = .TRUE.
nullify(trackList%nextTrack)
endif
end subroutine
!> @brief Copies a TrackListNode object
!> @param dest (output) destination node
!> @param source source node
subroutine CopyTrack( dest, source )
type(TrackListNode), intent(out) :: dest
type(TrackListNode), intent(in) :: source
dest%trackLons = source%trackLons
dest%trackLats = source%trackLats
dest%trackWind = source%trackWind
dest%trackPsl = source%trackPsl
dest%trackVort = source%trackVort
dest%trackDate = source%trackDate
dest%maxWind = source%maxWind
dest%minPsl = source%minPSl
dest%maxVort = source%maxVort
dest%startDate = source%startDate
dest%endDate = source%endDate
dest%startTimeIndex = source%startTimeIndex
dest%nTracks = source%nTracks
dest%trackLength = source%trackLength
dest%originOverWater = source%originOverWater
dest%nextTrack => source%nextTrack
end subroutine
!> @brief Adds an intialized TrackListNode to a linked list.
!> @param trackList list root
!> @param newNode node to add to list
subroutine AddTrackNodeToList( trackList, newNode )
type(TrackListNode), pointer, intent(inout) :: trackList
type(TrackListNode), pointer, intent(in) :: newNode
type(TrackListNode), pointer :: current, next
current => trackList
if ( current%nTracks == 0 ) then
call CopyTrack( trackList, newNode )
trackList%nTracks = 1
nullify(trackList%nextTrack)
else
next => current%nextTrack
do while ( associated(next) )
current => next
next => current%nextTrack
enddo
allocate(next)
call CopyTrack(next, newNode)
trackList%nTracks = trackList%nTracks + 1
current%nextTrack => next
nullify(next%nextTrack)
endif
end subroutine
!> @brief Frees memory used by a list of TrackListNode objects.
!> @param trackList list root
recursive subroutine DeleteStormTracks( trackList )
type(TrackListNode), pointer, intent(inout) :: trackList
type(TrackListNode), pointer :: next
next => trackList%nextTrack
deallocate(trackList)
if ( associated(next) ) call DeleteStormTracks( next )
end subroutine
!> @brief Marks storms whose origins are over land.
!> This subroutine will not remove tracks that originate over water and then move over land.
!> @param trackList
subroutine MarkStormsOverLand( trackList )
type(TrackListNode), pointer, intent(inout) :: trackList
type(TrackListNode), pointer :: current
integer, dimension(360,180) :: mask
real :: dLon, dLat, lon0, lat0, origLon, origLat
integer :: i, j, counter
mask = GetLandMask()
lon0 = 0.0
dLon = 1.0
lat0 = -89.5
dLat = 1.0
current => trackList
counter = 0
do while ( associated(current) )
origLon = current%trackLons(1)
origLat = current%trackLats(1)
j = ( origLat - lat0 ) + 1.5
i = ( origLon - lon0 ) + 1.5
if ( i == 0 ) i = 360
if ( i > 360 ) i = i - 360
if ( mask(i,j) /= 0 ) then
current%originOverWater = .FALSE.
counter = counter + 1
endif
current => current%nextTrack
enddo
print *, counter, " of ", trackList%nTracks, " storms originate over land."
end subroutine
!> @brief Given a storm's maximum wind speed, returns the hurricane category.
!> @param maxWind
!> @return hurricaneCat, according to the Saffir-Simpson scale.
pure function hurricaneCat( maxWind )
integer :: hurricaneCat
real, intent(in) :: maxWind
if ( maxWind < 33.0 ) then
hurricaneCat = 0
elseif ( maxWind >= 33.0 .and. maxWind < 43.0 ) then
hurricaneCat = 1
elseif ( maxWind >= 43.0 .and. maxWind < 50.0 ) then
hurricaneCat = 2
elseif ( maxWind >= 50.0 .and. maxWind < 58.0 ) then
hurricaneCat = 3
elseif ( maxWind >= 58.0 .and. maxWind < 70.0 ) then
hurricaneCat = 4
elseif ( maxWind >= 70.0 ) then
hurricaneCat = 5
endif
end function
!> @}
end module