2121from rf .util import DEG2KM
2222from obspy .taup import TauPyModel
2323import h5py
24-
24+ from obspy . geodetics . base import degrees2kilometers
2525from scipy .interpolate import splev , splrep , sproot , interp1d
2626from scipy .integrate import simps as simpson
2727from scipy .signal import hilbert
3434from affine import Affine
3535import struct
3636from seismic .stream_io import get_obspyh5_index
37- from seismic .misc import rtp2xyz , split_list
37+ from seismic .misc import rtp2xyz , xyz2rtp , split_list
3838from seismic .misc_p import parallel_abort
3939
40+
41+ class ANTVolume ():
42+ def __init__ (self , input_fn , earth_radius = 6371 , max_depth = 100 ):
43+ """
44+ input_fn: path to txt file containing the following columns:
45+ Number Lon Lat depth velocity STD VPVS STD XI STD RHO STD
46+ """
47+ self .input_fn = input_fn
48+ self .earth_radius = earth_radius # km
49+ self .max_depth = max_depth
50+ data = np .loadtxt (input_fn , delimiter = ' ' , skiprows = 1 )
51+
52+ self .lons = data [:, 1 ]
53+ self .lats = data [:, 2 ]
54+ self .depths_km = data [:, 3 ]
55+ self .zs = self .earth_radius - self .depths_km
56+ self .vs = data [:, 4 ]
57+
58+ # clip by max_depth
59+ imask = self .depths_km < max_depth
60+ self .lons = self .lons [imask ]
61+ self .lats = self .lats [imask ]
62+ self .depths_km = self .depths_km [imask ]
63+ self .zs = self .zs [imask ]
64+ self .vs = self .vs [imask ]
65+
66+ self .xyz = rtp2xyz (self .earth_radius - self .depths_km ,
67+ np .radians (90 - self .lats ),
68+ np .radians (self .lons ))
69+ self .tree = cKDTree (self .xyz )
70+
71+ # nominal grid point separation
72+ self .node_sep_km = degrees2kilometers (np .max ([np .median (np .diff (np .unique (self .lats ))),
73+ np .median (np .diff (np .unique (self .lats )))]))
74+ # end func
75+
76+ def get_profile (self , lons , lats , max_depth_km = 20 , ndepths = 200 , nnbr = 10 , p = 4 ):
77+ """
78+ Returns velocities along a vertical profile.
79+ """
80+ depths = np .linspace (0 , max_depth_km , ndepths )
81+
82+ rtp = []
83+ for d in depths :
84+ for lon , lat in zip (lons , lats ):
85+ rtp .append ([d , lat , lon ])
86+ # end for
87+ # end for
88+ rtp = np .array (rtp )
89+ rtp [:, 0 ] = self .earth_radius - rtp [:, 0 ]
90+ rtp [:, 1 ] = np .radians (90 - rtp [:, 1 ])
91+ rtp [:, 2 ] = np .radians (rtp [:, 2 ])
92+
93+ xyz = rtp2xyz (rtp [:, 0 ], rtp [:, 1 ], rtp [:, 2 ])
94+ ds , ids = self .tree .query (xyz , k = nnbr )
95+
96+ if (nnbr > 1 ):
97+ idw = 1. / np .power (ds , p )
98+ vals = np .sum (idw * np .reshape (self .vs [ids .flatten ()], ids .shape ), axis = 1 ) / \
99+ np .sum (idw , axis = 1 )
100+ else :
101+ vals = self .vs [ids ]
102+ # end if
103+
104+ return depths , np .reshape (vals , (ndepths , len (lons )))
105+ # end func
106+
107+ def get_velocity (self , lon , lat , depth_km , default = None ):
108+ """
109+ Return the distance to and the velocity of the closest node. The default
110+ value(s) is returned, if the distance to the closest node is larger than
111+ nominal node separation.
112+ """
113+
114+ rtp = [self .earth_radius - depth_km ,
115+ np .radians (90 - lat ),
116+ np .radians (lon )]
117+
118+ xyz = None
119+ if (np .all ([isinstance (item , np .ndarray ) for item in rtp ])):
120+ xyz = rtp2xyz (rtp [0 ], rtp [1 ], rtp [2 ])
121+ if (default is not None ): assert len (default ) == len (rtp [0 ]), 'Length mismatch detected..'
122+ else :
123+ xyz = rtp2xyz (np .array ([rtp [0 ]]), np .array ([rtp [1 ]]), np .array ([rtp [2 ]]))
124+ if (default is not None ): default = np .array ([default ])
125+ # end if
126+
127+ distances , indices = self .tree .query (xyz , k = 1 )
128+ result = np .zeros (len (distances ))
129+ for i , (d , idx ) in enumerate (zip (distances , indices )):
130+ if (d > self .node_sep_km ):
131+ result [i ] = default [i ] if default is not None else 0
132+ else :
133+ result [i ] = self .vs [idx ]
134+ # end for
135+
136+ if (len (result ) == 1 ): result = result [0 ]
137+ return result
138+ # end func
139+ # end class
140+
40141class Gravity :
41142 def __init__ (self , gravity_grid_fn ):
42143 self ._fn = gravity_grid_fn
@@ -52,7 +153,7 @@ def __init__(self, gravity_grid_fn):
52153 self ._gt = self ._ds .GetGeoTransform ()
53154 self ._affine_forward_transform = Affine .from_gdal (* self ._gt )
54155 self ._affine_reverse_transform = ~ (self ._affine_forward_transform )
55- # end func
156+ # end func
56157
57158 def _readPixel (self , px , py ):
58159 def translateFormat (pt ):
@@ -66,7 +167,6 @@ def translateFormat(pt):
66167 GDT_Float64 : 'd'
67168 }
68169 return fmttypes .get (pt , 'x' )
69-
70170 # end func
71171
72172 structval = self ._band .ReadRaster (int (px ), int (py ), 1 , 1 , buf_type = self ._band .DataType )
@@ -89,11 +189,13 @@ def query(self, lon, lat):
89189# end class
90190
91191class Migrator :
92- def __init__ (self , rf_filename , dz , max_depth , min_slope_ratio = - 1 , earth_radius = 6371 , logger = None ):
192+ def __init__ (self , rf_filename , dz , max_depth , min_slope_ratio = - 1 ,
193+ ant_model : ANTVolume = None , earth_radius = 6371 , logger = None ):
93194 self ._rf_filename = rf_filename
94195 self ._dz = dz
95196 self ._max_depth = max_depth
96197 self ._min_slope_ratio = min_slope_ratio
198+ self .ant_model = ant_model
97199 self ._earth_radius = earth_radius #km
98200 self ._logger = logger
99201 # Initialize MPI
@@ -264,6 +366,20 @@ def process_streams(self, output_file, fmin=None, fmax=None, model='iasp91'):
264366 vp = vmodel .evaluate_above (dnew , 'p' )
265367 vs = vmodel .evaluate_above (dnew , 's' )
266368
369+ # extract velocities from provided ANT model up to a given depth
370+ if (self .ant_model ):
371+ imask = dnew < self .ant_model .max_depth
372+
373+ rtp = xyz2rtp (t2xio (d2tio (dnew [imask ])),
374+ t2yio (d2tio (dnew [imask ])),
375+ t2zio (d2tio (dnew [imask ])))
376+ path_depths = self ._earth_radius - rtp [:, 0 ]
377+ path_lats = 90 - np .degrees (rtp [:, 1 ])
378+ path_lons = np .degrees (rtp [:, 2 ])
379+ vs [imask ] = self .ant_model .get_velocity (path_lons , path_lats ,
380+ path_depths , vs [imask ])
381+ # end if
382+
267383 #========================================================================
268384 # Create an interpolant for the delay time (t_Ps) of a non-vertically
269385 # incident Ps wave as follows:
@@ -280,7 +396,6 @@ def process_streams(self, output_file, fmin=None, fmax=None, model='iasp91'):
280396 np .sqrt ( np .power (vp [:idx ], - 2. ) - p * p ), dnew [:idx ])
281397 #end for
282398
283-
284399 t .trim (starttime = t .stats .onset , endtime = t .stats .endtime )
285400 ipt .trim (starttime = ipt .stats .onset , endtime = ipt .stats .endtime )
286401
0 commit comments