def hypo_dist(trace): """Compute hypocentral and epicentral distance (in km) for a trace.""" try: coords = trace.stats.coords hypo = trace.stats.hypo except (KeyError, AttributeError): return None if None in (coords, hypo): return None stla = coords.latitude stlo = coords.longitude stel = coords.elevation evla = hypo.latitude evlo = hypo.longitude evdp = hypo.depth if None in (stla, stlo, stel, evla, evlo, evdp): return None epi_dist, az, baz = gps2dist_azimuth( hypo.latitude, hypo.longitude, trace.stats.coords.latitude, trace.stats.coords.longitude) epi_dist /= 1e3 # in km gcarc = kilometers2degrees(epi_dist) hypo_dist = math.sqrt(epi_dist**2 + (stel+evdp)**2) trace.stats.azimuth = az trace.stats.back_azimuth = baz trace.stats.epi_dist = epi_dist trace.stats.hypo_dist = hypo_dist trace.stats.gcarc = gcarc return hypo_dist
def _download_crandall(self): """download waveform/station info for dataset.""" bank = WaveBank(self.waveform_path) domain = CircularDomain( self.latitude, self.longitude, minradius=0, maxradius=kilometers2degrees(self.max_dist), ) cat = obspy.read_events(str(self.source_path / "events.xml")) df = events_to_df(cat) for _, row in df.iterrows(): starttime = row.time - self.time_before endtime = row.time + self.time_after restrictions = Restrictions( starttime=UTC(starttime), endtime=UTC(endtime), minimum_length=0.90, minimum_interstation_distance_in_m=100, channel_priorities=["HH[ZNE]", "BH[ZNE]"], location_priorities=["", "00", "01", "--"], ) kwargs = dict( domain=domain, restrictions=restrictions, mseed_storage=str(self.waveform_path), stationxml_storage=str(self.station_path), ) MassDownloader(providers=[self._download_client]).download( **kwargs) # ensure data have actually been downloaded bank.update_index() assert not bank.read_index(starttime=starttime, endtime=endtime).empty
def kernel(event_pair, stations=None): """ kernel: the core function to get the information. """ model = TauPyModel(model="ak135") # for each station, we calculate the travel time result = {} evla = event_pair.lat evlo = event_pair.lon evdp = event_pair.dep / 1000 event_time = event_pair.time for row in stations: result_template = { "event_time": event_time, "evla": evla, "evlo": evlo, "evdp": evdp, "gcarc": None, "az": None, "baz": None, "S": None, "sS": None, "SS": None, "P": None, "pP": None, "sP": None, "PP": None, "3.3kmps": None, "4.6kmps": None, "ScS": None } station = row[0] network = row[1] net_sta = f"{network}.{station}" stla = float(row[2]) stlo = float(row[3]) arrivals = model.get_travel_times_geo(evdp, evla, evlo, stla, stlo, PHASE_LIST) gcarc_m, az, baz = gps2dist_azimuth(evla, evlo, stla, stlo) gcarc = kilometers2degrees(gcarc_m / 1000) result_template["gcarc"] = gcarc result_template["az"] = az result_template["baz"] = baz for each_arrival in arrivals: name = each_arrival.name time = each_arrival.time if ((name in PHASE_LIST)): if (name == "p"): name = "P" if (name == "s"): name = "S" if (result_template[name] == None): result_template[name] = time result[net_sta] = result_template return result
def dist_baz_az2(eve, sta): global StaDict global AllEveDict gcarcinfo = gps2dist_azimuth(StaDict[sta]['stla'], StaDict[sta]['stlo'], AllEveDict[eve]['EVLA'], AllEveDict[eve]['EVLO']) gcarc = kilometers2degrees(gcarcinfo[0] / 1000) dist = gcarcinfo[0] / 1000 baz = gcarcinfo[1] az = gcarcinfo[2] return f"{gcarc:.3f} {dist:.3f} {baz:.3f} {az:.3f}"
def cal_derivative(model_path, eqlon, eqlat, eqdep, stlon, stlat, phase, dx=0.1, dy=0.1, dz=0.1, dt=0.04): from obspy.taup import TauPyModel from obspy.geodetics import kilometers2degrees #model_path: path of velocity npz file #eqlon,eqlat,eqdep:longitude/lat/depth(km) of earthquake #stlon,stlat: array of stations lon/lat #phase: calculate in P or S phase (array same length as stlon/stlat) #dx,dy,dz: perturtabation used for calculating gradient. [input unit in km] model = TauPyModel(model=model_path) #convert km to degree dx = kilometers2degrees(dx) dy = kilometers2degrees(dy) G = [] #avail_idx = [] #all the available index. sometime the P or S arrival is not available, T0 = travel_time(model, stlon, stlat, phase, eqlon, eqlat, eqdep) T_dx = travel_time(model, stlon, stlat, phase, eqlon + dx, eqlat, eqdep) T_dy = travel_time(model, stlon, stlat, phase, eqlon, eqlat + dy, eqdep) T_dz = travel_time(model, stlon, stlat, phase, eqlon, eqlat, eqdep + dz) #time changes dT_dx = T_dx - T0 dT_dy = T_dy - T0 dT_dz = T_dz - T0 #combine all into a G array G = np.hstack( [dT_dx.reshape(-1, 1), dT_dy.reshape(-1, 1), dT_dz.reshape(-1, 1)]) return G
def _get_bounding_box(circular_kwargs: dict) -> dict: """ Return a dict containing the bounding box for circular params. """ circular_kwargs = dict(circular_kwargs) # we dont want to mutate this dict out = {} # init empty dict for outputs if "maxradius" in circular_kwargs.keys(): maxradius = circular_kwargs["maxradius"] if not circular_kwargs.get("degrees", True): # If distance is in m we will just assume a spherical earth maxradius = kilometers2degrees(maxradius / 1000.0) # Make the approximated box a bit bigger to cope with flattening. out.update( dict( minlatitude=circular_kwargs["latitude"] - (1.2 * maxradius), maxlatitude=circular_kwargs["latitude"] + (1.2 * maxradius), minlongitude=circular_kwargs["longitude"] - (1.2 * maxradius), maxlongitude=circular_kwargs["longitude"] + (1.2 * maxradius), )) return out
def get_dist_and_parrivals(stations, depth): """ Calculate distance between the source and each station for an event and the theoretical p-wave arrival times and appends them to the station stats. Args: stations (array): Combined streams of acceleration data for each station. depth (float): Depth of earthquake origin in km. Returns: """ # Define TauPyModel. model = TauPyModel(model="iasp91") for sta in stations: for i in range(len(sta)): trace = sta[i] # Compute distance. dist_az_baz = gps2dist_azimuth( trace.stats.coordinates['latitude'], trace.stats.coordinates['longitude'], trace.stats.source_lat, trace.stats.source_lon) distance_meters = dist_az_baz[0] distance_km = distance_meters / 1000.0 dd = kilometers2degrees(distance_km) trace.stats.distance = distance_meters trace.stats.distkm = distance_km # Estimate travel time for p wave. p = model.get_travel_times(depth, dd, phase_list=['p', 'P']) trace.stats.P_arriv = p[0].time return ()
def _read_picks(f, new_event): """ Internal pick reader. Use read_nordic instead. :type f: file :param f: File open in read mode :type wav_names: list :param wav_names: List of waveform files in the sfile :type new_event: :class:`~obspy.core.event.event.Event` :param new_event: event to associate picks with. :returns: :class:`~obspy.core.event.event.Event` """ f.seek(0) evtime = new_event.origins[0].time pickline = [] # Set a default, ignored later unless overwritten snr = None for lineno, line in enumerate(f): if line[79] == '7': header = line break for lineno, line in enumerate(f): if len(line.rstrip('\n').rstrip('\r')) in [80, 79] and \ line[79] in ' 4\n': pickline += [line] for line in pickline: if line[18:28].strip() == '': # If line is empty miss it continue weight = line[14] if weight == '_': phase = line[10:17] weight = 0 polarity = '' else: phase = line[10:14].strip() polarity = line[16] if weight == ' ': weight = 0 polarity_maps = {"": "undecidable", "C": "positive", "D": "negative"} try: polarity = polarity_maps[polarity] except KeyError: polarity = "undecidable" # It is valid nordic for the origin to be hour 23 and picks to be hour # 00 or 24: this signifies a pick over a day boundary. if int(line[18:20]) == 0 and evtime.hour == 23: day_add = 86400 pick_hour = 0 elif int(line[18:20]) == 24: day_add = 86400 pick_hour = 0 else: day_add = 0 pick_hour = int(line[18:20]) try: time = UTCDateTime(evtime.year, evtime.month, evtime.day, pick_hour, int(line[20:22]), float(line[23:28])) + day_add except ValueError: time = UTCDateTime(evtime.year, evtime.month, evtime.day, int(line[18:20]), pick_hour, float("0." + line[23:38].split('.')[1])) +\ 60 + day_add # Add 60 seconds on to the time, this copes with s-file # preference to write seconds in 1-60 rather than 0-59 which # datetime objects accept if header[57:60] == 'AIN': ain = _float_conv(line[57:60]) warnings.warn('AIN: %s in header, currently unsupported' % ain) elif header[57:60] == 'SNR': snr = _float_conv(line[57:60]) else: warnings.warn('%s is not currently supported' % header[57:60]) # finalweight = _int_conv(line[68:70]) # Create a new obspy.event.Pick class for this pick _waveform_id = WaveformStreamID(station_code=line[1:6].strip(), channel_code=line[6:8].strip(), network_code='NA') pick = Pick(waveform_id=_waveform_id, phase_hint=phase, polarity=polarity, time=time) try: pick.onset = onsets[line[9]] except KeyError: pass if line[15] == 'A': pick.evaluation_mode = 'automatic' else: pick.evaluation_mode = 'manual' # Note these two are not always filled - velocity conversion not yet # implemented, needs to be converted from km/s to s/deg # if not velocity == 999.0: # new_event.picks[pick_index].horizontal_slowness = 1.0 / velocity if _float_conv(line[46:51]) is not None: pick.backazimuth = _float_conv(line[46:51]) # Create new obspy.event.Amplitude class which references above Pick # only if there is an amplitude picked. if _float_conv(line[33:40]) is not None: _amplitude = Amplitude(generic_amplitude=_float_conv(line[33:40]), period=_float_conv(line[41:45]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) if pick.phase_hint == 'IAML': # Amplitude for local magnitude _amplitude.type = 'AML' # Set to be evaluating a point in the trace _amplitude.category = 'point' # Default AML unit in seisan is nm (Page 139 of seisan # documentation, version 10.0) _amplitude.generic_amplitude /= 1e9 _amplitude.unit = 'm' _amplitude.magnitude_hint = 'ML' else: # Generic amplitude type _amplitude.type = 'A' if snr: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) elif _int_conv(line[28:33]) is not None: # Create an amplitude instance for code duration also _amplitude = Amplitude(generic_amplitude=_int_conv(line[28:33]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) # Amplitude for coda magnitude _amplitude.type = 'END' # Set to be evaluating a point in the trace _amplitude.category = 'duration' _amplitude.unit = 's' _amplitude.magnitude_hint = 'Mc' if snr is not None: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) # Create new obspy.event.Arrival class referencing above Pick if _float_conv(line[33:40]) is None: arrival = Arrival(phase=pick.phase_hint, pick_id=pick.resource_id) if weight is not None: arrival.time_weight = weight if _int_conv(line[60:63]) is not None: arrival.backazimuth_residual = _int_conv(line[60:63]) if _float_conv(line[63:68]) is not None: arrival.time_residual = _float_conv(line[63:68]) if _float_conv(line[70:75]) is not None: arrival.distance = kilometers2degrees(_float_conv(line[70:75])) if _int_conv(line[76:79]) is not None: arrival.azimuth = _int_conv(line[76:79]) new_event.origins[0].arrivals.append(arrival) new_event.picks.append(pick) return new_event
def travel_times(ref, deg=None, km=None, depth=0.): """ Get *approximate* relative travel time(s). Parameters ---------- ref : list or tuple of strings and/or floats Reference phase names or horizontal velocities [km/sec]. deg : float, optional Degrees of arc between two points of interest (spherical earth). km : float, optional Horizontal kilometers between two points of interest (spherical earth). depth : float, optional. default, 0. Depth (positive down) of event, in kilometers. Returns ------- numpy.ndarray Relative times, in seconds, same length as "ref". NaN if requested time is undefined. Examples -------- Get relative P arrival and 2.7 km/sec surface wave arrival at 35 degrees distance. >>> times = travel_times(['P', 2.7], deg=35.0) To get absolute window, add the origin time like: >>> w1, w2 = times + epoch_origin_time Notes ----- Either deg or km must be indicated. The user is responsible for adding/subtracting time (such as origin time, pre-window noise time, etc.) from those predicted in order to define a window. Phase travel times use ak135. """ times = np.zeros(len(ref), dtype='float') tt = None for i, iref in enumerate(ref): if isinstance(iref, str): # phase time requested if not tt: if not deg: deg = geod.kilometers2degrees(km) tt = taup.getTravelTimes(deg, depth, model='ak135') try: idx = [ph['phase_name'] for ph in tt].index(iref) itt = [ph['time'] for ph in tt][idx] except ValueError: # phase not found itt = None else: # horizontal velocity if not km: km = deg * (2 * math.pi / 360.0) * 6371.0 itt = km / iref times[i] = itt return times
def _get_event_data(tr, tt_model, phase, acc_type, depth_unit="km"): """ Update a sac trace to a obspy trace and update trace header, and calculate theoretical traveltime of a specific model and phase :param tr: :param tt_model: :param phase: :param acc_type: :param depth_unit: :return: .. Note:: The input trace should be read from sac-formatted files. depth_unit is not used. if depth>1000 then unit should be meter, since no events deeper than 700 km on the earth. """ model = TauPyModel(model=tt_model) event_longitude = tr.stats.sac.evlo event_latitude = tr.stats.sac.evla event_depth = tr.stats.sac.evdp try: event_magnitude = tr.stats.sac.mag except: event_magnitude = 6.66 # if depth_unit == "m": # event_depth /= 1000.0 # in this case, the depth_unit is considered to be m. if event_depth > 1000: event_depth /= 1000 station_longitude = tr.stats.sac.stlo station_latitude = tr.stats.sac.stla station_elevation = tr.stats.sac.stel try: component_azimuth = tr.stats.sac.cmpaz component_inclination = tr.stats.sac.cmpinc except: # print(tr.stats) if tr.stats.channel[-1] == "Z": component_azimuth = 0 component_inclination = 0 elif tr.stats.channel[-1] == "N": component_azimuth = 0 component_inclination = 90 elif tr.stats.channel[-1] == "E": component_azimuth = 90 component_inclination = 90 else: print("component is not ZNE. ", tr.stats.channel) os._exit(0) event_time = _get_sac_origin(tr) distance, azimuth, back_azimuth = gps2dist_azimuth(lat1=event_latitude, lon1=event_longitude, lat2=station_latitude, lon2=station_longitude, a=6378137.0, f=0.0033528106647474805) distance = kilometers2degrees(kilometer=distance / 1000.0) # travel time, slowness, inclinations arrivals = model.get_travel_times(source_depth_in_km=event_depth, distance_in_degree=distance, phase_list=[phase]) if len(arrivals) < 1: return None arr = arrivals[0] onset = event_time + arr.time phase = phase inclination = arr.incident_angle slowness = arr.ray_param # pierce points # pp_latitude # pp_longitude # pp_depth # ray paths # arrivals = model.get_travel_times(source_depth_in_km=event_depth, # distance_in_degree=distance, # phase_list=[phase]) header = { "model": tt_model, "type": acc_type, "event_latitude": event_latitude, "event_longitude": event_longitude, "event_depth": event_depth, "event_time": event_time, "event_magnitude": event_magnitude, "station_latitude": station_latitude, "station_longitude": station_longitude, "station_elevation": station_elevation, "component_azimuth": component_azimuth, "component_inclination": component_inclination, "onset": onset, "phase": phase, "inclination": inclination, "slowness": slowness, "distance": distance, "azimuth": azimuth, "back_azimuth": back_azimuth } tr.stats.update(header) return tr
for i, center in enumerate(seislist): print('#### Start sorting ' + ' #' + str(i) + '/' + str(len(seislist)) + ' ' + center + ' ########') center_seis = read(center, format='PICKLE') # read seismogram if center_seis[0].stats['dist']<dist_min or center_seis[0].stats['dist']>dist_max \ or center_seis[0].stats['az']<az_min or center_seis[0].stats['az']>az_max: continue distance_rank = dict() name = os.path.splitext(os.path.split(center)[1])[0] category_folder = newfilefolder + name + '/' for periphery in seislist: periphery_seis = read(periphery, format='PICKLE') distm, az, baz = obspy.geodetics.base.gps2dist_azimuth( center_seis[0].stats['stla'], center_seis[0].stats['stlo'], periphery_seis[0].stats['stla'], periphery_seis[0].stats['stlo']) distdg = kilometers2degrees(distm / 1.0e3) distance_rank[periphery] = distdg distance_rank_sorted = sorted(distance_rank.items(), key=lambda kv: kv[1]) if distance_rank_sorted[20][ 1] < 2: # Make sure the closest 20 stations are within 4 degree grid if not os.path.exists(category_folder): os.makedirs(category_folder) np.save(category_folder + 'stla.npy', center_seis[0].stats['stla']) np.save(category_folder + 'stlo.npy', center_seis[0].stats['stlo']) for name, distance in distance_rank_sorted[:20]: copy2(name, category_folder) print(name + ' : ' + str(distance) + ' successfully copied !!')
def iter_inv(model_path, eqlon, eqlat, eqdep, eqlon_init, eqlat_init, eqdep_init, stlon, stlat, phase, D, invsion_params): from obspy.taup import TauPyModel from obspy.geodetics import kilometers2degrees #=====iterative inversion========= ''' model_path: path of the velocity model .npz file eqlon,eqlat,eqdep: original EQlocation (i.e. a referenced location that we known) eqlon_init,eqlat_init,eqdep_init: initial guess of the unknown location stlon,stlat: station location phase: array or list of P,S wave D: measured travel time differences from the reference location to an unknown location invsion_params={ 'CCC_threshold':0.3, #use observed shift with CCC greater than this threshold 'min_stan':5, #minumim observations 'max_shift':2, #maximum shift seconds(observation) 'update_thres':1e-9, #if update smaller than this, but inversion havent converged, add a perturbation 'misfit_thres':1e-3, #modeled arrival time close enough to observation, inversion converged 'max_iter':50, #maximum iterations 'dx':0.05, #small step in x-dir to calculate time changes 'dy':0.05, # 'dz':0.05, # 'dt':0.04, # } ''' #----------set default values---------- if not ('update_thres' in invsion_params): invsion_params['update_thres'] = 1e-9 #set default iter if not ('misfit_thres' in invsion_params): invsion_params['misfit_thres'] = 1e-3 #set default iter if not ('max_iter' in invsion_params): invsion_params['max_iter'] = 50 #set default iter if not ('dx' in invsion_params): invsion_params['dx'] = 0.05 #set default dx if not ('dy' in invsion_params): invsion_params['dy'] = 0.05 #set default dy if not ('dz' in invsion_params): invsion_params['dz'] = 0.05 #set default dz if not ('dt' in invsion_params): invsion_params['dt'] = 0.04 #set default dt dx = invsion_params['dx'] dy = invsion_params['dy'] dz = invsion_params['dz'] dt = invsion_params['dt'] update_thres = invsion_params['update_thres'] misfit_thres = invsion_params['misfit_thres'] #-------------------------------------- model = TauPyModel(model=model_path) #calculate original time, this will be then compared with new_time calculated by new location orig_time = travel_time(model, stlon, stlat, phase, eqlon, eqlat, eqdep) converged_flag = 0 sav_invlon = [] sav_invlat = [] sav_invdep = [] sav_misft = [ ] #save all misfit. if inversion cannot converge, find the best result pre_time = orig_time.copy() for i in range(invsion_params['max_iter']): G = cal_derivative(model_path, eqlon_init, eqlat_init, eqdep_init, stlon, stlat, phase, dx, dy, dz, dt) availid_G = np.where(~np.isnan(G.sum(axis=1)))[0] availid_D = np.where(~np.isnan(D))[ 0] #the index without nan value in D intersect_availid = list(set(availid_G).intersection(set(availid_D))) intersect_availid.sort() availid = np.array(intersect_availid) M = GMD_solve(G[availid], D[availid]) MM = M * np.array([ dx, dy, dz ]) # M to the real original scale dx,dy,dz [unit:km], dt [sec] sh_deg = kilometers2degrees( MM[:2]) # convert shifted_km to shifted_deg eqlon_init += sh_deg[0] eqlat_init += sh_deg[1] #prevent depth become negative value if eqdep_init + MM[2] < 0: #print('depth become negative, assign depth') eqdep_init = 0.1 # np.random.rand()*10 else: eqdep_init += MM[2] new_time = travel_time(model, stlon, stlat, phase, eqlon_init, eqlat_init, eqdep_init) diff_time = new_time - pre_time D = D - diff_time pre_time = new_time.copy() #print(np.mean(np.abs(diff_time))) #update value #print(np.abs(D).sum()) #print D misfit to see how D converge sav_invlon.append(eqlon_init) sav_invlat.append(eqlat_init) sav_invdep.append(eqdep_init) sav_misft.append(np.abs(D).sum()) #case 1. no update(diff_time almost zero), D still large if (np.mean(np.abs(diff_time)) < update_thres) & (np.abs(D).sum() > misfit_thres): #print('Add a small perturbation') eqlon_init += np.random.randn() * 0.05 #std=0.05 deg eqlat_init += np.random.randn() * 0.05 #std=0.05 deg eqdep_init += np.random.randn() * 0.05 #std=0.05 deg continue #case 2. Converged, D very small, no need to update if np.abs(D).sum() < misfit_thres: print('Inversion converged after %d iterations' % (i + 1), eqlon_init, eqlat_init, eqdep_init) converged_flag = 1 break sav_misft = np.array(sav_misft) sav_invlon = np.array(sav_invlon) sav_invlat = np.array(sav_invlat) sav_invdep = np.array(sav_invdep) if converged_flag: pass else: #compare not nan value notnan = np.where(~np.isnan(sav_misft))[0] if len(notnan) == 0: return eqlon_init, eqlat_init, eqdep_init, -1 sav_misft = sav_misft[notnan] sav_invlon = sav_invlon[notnan] sav_invlat = sav_invlat[notnan] sav_invdep = sav_invdep[notnan] idx_minmisft = np.where(sav_misft == np.min(sav_misft))[0][0] eqlon_init = sav_invlon[idx_minmisft] eqlat_init = sav_invlat[idx_minmisft] eqdep_init = sav_invdep[idx_minmisft] # print('Inversion result:',sav_invlon[idx_minmisft],sav_invlat[idx_minmisft],sav_invdep[idx_minmisft]) return eqlon_init, eqlat_init, eqdep_init, converged_flag
def buffer(self, minutes, speed): self.max_traveled_dist(minutes, speed) self.buff = gpd.GeoDataFrame(geometry=[ self.station_xy.buffer(kilometers2degrees(self.max_distance)) ]) self.buff.crs = "EPSG:4326"
def plotw_rs(win, rssort=2, iabs=0, tshift=[], tmark=[], T1=[], T2=[], pmax=50, iintp=0, inorm=[1], tlims=[], nfac=1, azstart=[], iunit=1, imap=1, wsyn=[], bplotrs=True, displayfigs='on'): ''' %PLOTW_RS processes waveform object and plots as a record section % % INPUT % w waveform object (WHAT ADDED FIELDS SHOULD WE REQUIRE?) % INPUT (OPTIONAL) -- these can be omitted or set as [] to get default settings % rssort =0 input sorting of records % =1 azimuthal sorting of records % =2 distance sorting of records % =3 alphabetical sorting of records (by station name or event ID) % iabs =1 to plot by absolute distance or azimuth (this means unequal % vertical spacing between the waveforms) % tshift time shift (in seconds) to apply to the waveforms ([] for none) % tmark absolute time markers (Matlab serial date) ([] for none) % note: if tshift varies, then you can't use this option % Tfilt =[Tmin Inf] for high-pass filter % =[0 Tmax] for low-pass filter % =[Tmin Tmax] for band-pass filter % T1 minimum period of filter: =[] for low-pass or no filter % T2 maximum period of bandpass: =[] for high-pass or no filter % pmax maximum number of time series per record section % iintp =1 to integrate, =-1 to differentiate, =0 for nothing % inorm =0 for no amplitude scaling % =1 for scaling by max(abs(d(t))) per trace % =2 for no amplitude scaling except correcting for geometric spreading % inorm can have up to 4 entries: % inorm(=2) % GEOFAC % plot_geometric_spreading % K % tlims time limits for x-axis ([] to use default) % note that the 'tshift' field will shift each seismogram % nfac controls spacing between seismograms (use a smaller nfac value for more prominent peaks) % azstart azimuthal angle for top record (applies with rssort=1 only) % iunit units for distance record section (applies with rssort=2 only) % =1 for km sphere, =2 for deg sphere, =3 for km flat % imap plot a simple map showing the station and source locations % wsyn second waveform object to superimpose on w (e.g., synthetic seismograms) % bplotrs =true to plot record section (can have two additional arguments odir and ftag -- see below) % =false to not plot record section (but presumably to return processed waveforms) % % OUTPUT (OPTIONAL) % ivec index in which w(i) are ordered in the plot; % w(ivec) gives the plotting order from top to bottom (if iabs=0) % w processed waveform (typically data) % wsyn processed waveform (typically synthetics) % K 2nd parameter for geometric spreading % fH figure handles for plots % % % DETAILS ABOUT THE TIME AXIS % The following variables are pertinent to the time axis: % tshift, tmark, and tlims. tmark is intended to mark absolute times with % vertical bars. If tshift varies from station to station, then the time % axis is relative time, and tmark cannot be used. If there is no tshift % specified, then t=0 will be the earliest time of all the seismograms, % and tlims will be specified w.r.t. this t=0. % % See examples in run_getwaveform_short.m % % To print figures to files, set bprint_record_section=true % % FUTURE WORK: % - if padding zeros, it should be done AFTER demean, taper, etc % - allow pmax to represent the first X sorted seismograms, while not % plotting the rest (which may have too-high SNR, for example) % - combine station and network code to allow for a station to have two % different network codes (e.g., MCK) % % See GISMO plotting in GISMO/@waveform/plot.m % % Carl Tape 11/21/2011 % Yun Wang 11/2011 % % Translated to python - % Nealey Sims 1/2021 % % Further upgrades - % Aakash Gupta 9/2021 %========================================================================== ''' ######################################### INITIALIZATION - 1 ################################################# start = datetime.now() print('--> entering plotw_rs.m') narg0 = 18 # number of input arguments spdy = 86400 # seconds per day synplot = 'r' # plotting a second set of seismograms deg = 180 / np.pi GEOFAC = 0.5 # default geometric spreading factor # =0.5 for surface waves (between 0.5 and 1.0 for regional surface waves) # note: GEOFAC = inorm(2) bplot_geometric_speading = True T1 = np.atleast_1d(T1) T2 = np.atleast_1d(T2) tshift = np.atleast_1d(tshift) tmark = np.atleast_1d(tmark) inorm = np.atleast_1d(inorm) tlims = np.atleast_1d(tlims) azstart = np.atleast_1d(azstart) # options for printing record sections (see also bplotrs) bprint_record_section = False odir = './' otag = '' bprint_map = False fhct = 1 # initialize number of figure handle counts #-------------------- ####################################### CHECK INPUT ARGUMENTS ############################################ w = win.copy() print(len(w)) #w.merge(method=1, fill_value=0)#,interpolation_samples=0) ## merge any traces with duplicate sta/chans if len(w) == 0: print('empty w') return wtemp = Stream() for tr in w: if statistics.mean(tr.data) == 0: print('nan trace') else: wtemp.append(tr) w = wtemp #print('%i/%i input variables:' % (nargin,narg0)) # note: the variable will not be listed if it is not present #whos w rssort iabs tshift tmark T1 T2 pmax iintp inorm tlims nfac azstart iunit imap wsyn bplotrs if len(wsyn) == 0: isyn = 0 ws = None else: print( 'second waveform object detected -- waveforms will be superimposed' ) isyn = 1 ws = wsyn.copy() geoinorm = inorm # exit here if user enters impermissible values if rssort not in [0, 1, 2, 3]: raise ValueError('input rssort = %f must be 0, 1, 2, 3' % (rssort)) if iabs not in [0, 1]: raise ValueError('input iabs = %f must be 0 or 1' % (iabs)) if len(inorm) >= 2: if len(inorm) == 3: bplot_geometric_speading = inorm[2] GEOFAC = inorm[1] inorm = inorm[0] print('seismogram normalization:') print(' inorm = %s' % (inorm)) print(' bplot_geometric_speading = %s' % (bplot_geometric_speading)) print(' GEOFAC = %.2f' % (GEOFAC)) if iintp not in [-1, 0, 1]: raise ValueError('input iintp = %f must be -1 or 0 or 1' % (iintp)) if bplotrs == False: raise ValueError( 'check input: you say you do not want a plot and do not want to return any variables' ) # if vertical axis is absolute (distance or azimuth), then plot all # waveforms on the same record section (pmax huge) if iabs == 1: pmax = 1000 if rssort not in [1, 2]: print(iabs, rssort) raise ValueError( 'if iabs=1, then rssort=1 (az) or rssort=2 (dist)') ########################################################################################################## nw = len(w) ncomp = 1 # w could be nw x ncomp # warning: a 1 x 3 w could still all have the same component if ncomp not in [1, 2, 3]: #w = w[:] #w = np.concatenate(w) nw, ncomp = np.shape(w)[0], np.shape(w)[1] #w = np.concatenate(w) # convert w to vector #tshift = tshift[:] ##if len(tshift) != 0: ##tshift = np.concatenate(tshift) nseis = len(w) if pmax == 0: pmax = nseis ####################################### TIME SHIFTS AND MARKERS ########################################### # Allows for alignment of seismograms on a time that varies from one trace to the next, say, a P arrival if len(tshift) == 0: print('no time shift applied (default)') tshift = np.zeros((nseis, 1)) elif len(tshift) == 1: print('time shift of %.2f s applied to all waveforms' % (tshift[0])) tshift = tshift * np.ones((nseis, 1)) elif len(tshift) == nw: print('input tshift has dimension %i x %i' % (np.shape(tshift))) tshift = tshift[:] tshift = np.matlib.repmat(tshift, 1, ncomp) print('output tshift has dimension %i x %i' % (np.shape(tshift))) else: if nw != 1: raise ValueError('tshift (%i) must be same length as w (%i)' % (len(tshift), nseis)) # time markers (absolute times) if len(tmark) == 0: nmark = 0 print('no time markers') else: if len(np.unique(tshift)) > 1: nmark = 0 print( 'variable time shifts, so there can be no absolute time markers' ) else: nmark = len(tmark) print('%i time markers to plot' % (nmark)) # relative time or absolute time if len(np.unique(tshift)) > 1: itrel = 1 else: tshift0 = tshift[0] itrel = 0 ######################################### INITIALIZATION - 2 ################################################# starttime = [] endtime = [] netwk = [] chans = [] sta = [] rlat = [] rlon = [] elat = [] elon = [] edep = [] eid = [] mag = [] loc = [] tdata = [] trtimes = [] evidst = [] print(len(w)) for i, tr in enumerate(w): chans.append(tr.stats.channel) try: rlat.append(tr.stats.sac.stla) rlon.append(tr.stats.sac.stlo) except: rlat.append(tr.stats.coordinates[0]) rlon.append(tr.stats.coordinates[1]) starttime.append(tr.stats.starttime) endtime.append(tr.stats.endtime) evid = str(tr.stats.starttime).replace(":", '') evidst.append(evid.replace("-", '')) netwk.append(tr.stats.network) sta.append(tr.stats.station) loc.append(tr.stats.location) edep.append('dep ') eid.append(evidst[0]) mag.append('NaN') #elat= elat*np.ones((len(w),1)) #elon= elon*np.ones((len(w),1)) elat.append(tr.stats.sac.evla) elon.append(tr.stats.sac.evlo) tdata.append(tr.data) trtimes.append(tr.times("timestamp")) if nseis == 1: stchan = chans else: #unique channels uchan = np.unique(chans) nuchan = len(uchan) stchan = [] ii = 0 for ii in range(nuchan): stchan.append(uchan[ii]) # FUTURE WORK # NEED TO EXIT IF ANY OF THE ABOVE FIELDS ARE EMPTY (isempty does not work) # we assume that there is either one event or one station in the set of waveforms if nseis == 1: nsta = 1 neve = 1 irs = 1 eid = [str(ee) for ee in eid] ii = 0 while ii < len(netwk): slabs.append( str(netwk[ii]) + '.' + str(sta[ii]) + '.' + str(loc[ii])) ii += 1 sta = [str(ss) for ss in sta] else: #nsta = length(sta); % temporary (MCK with two networks) nsta = len(np.unique(sta)) neve = len(np.unique(eid)) #[nsta,~] = size(unique([rlon(:) rlat(:)],'rows')); #[neve,~] = size(unique([elon(:) elat(:)],'rows')); if neve == 1 and nsta > 0: irs = 1 # 1 event, multiple stations #nsta = nseis; #slabs = sta; #slabs = np.matlib.repmat(str(''),nseis,1) slabs = np.empty(((nseis), 1), dtype=object) ii = 0 while ii < nseis: if nuchan > 1: slabs[ii] = (str(netwk[ii]) + '.' + str(sta[ii]) + '.' + str(loc[ii]) + '.' + str(chans[ii])) else: slabs[ii] = (str(netwk[ii]) + '.' + str(sta[ii]) + '.' + str(loc[ii])) ii += 1 elif nsta == 1 and neve > 0: irs = 0 # 1 station, multiple events slabs = eid #neve = nseis; else: # consider TCOL and COLA -- we might want to compare these in a # record section for the same event, so here we check the station # locations and do NOT exit with an error if the stations are close # to each other irs = 0 # 1 station, multiple events slabs = eid dmax = 1e6 # initialize to large number xdists = [] i = 0 if nsta > 1: #xdists = distance.distance(rlat[0]*np.ones(np.shape(rlon)), rlon[0]*np.ones(np.shape(rlon)), rlat, rlon).km while i < len(rlat): xdists.append( distance.distance((rlat[0], rlon[0]), (rlat[i], rlon[i])).km) i += 1 dmax = max(xdists) if dmax > 0.5: print('nsta = %i, neve = %i -- error with waveform data' % (nsta, neve)) print('check headers KEVNM and station:') print(eid) print(sta) print('check headers KEVNM and station') print( 'must have a single event or station common to all input waveforms' ) print('%i event, %i station' % (neve, nsta)) ####################################### REFERENCE START TIME ############################################# tstartmin = min(starttime) imin = starttime.index(tstartmin) print('minimum start time of all waveforms is %s (%s)' % (tstartmin, sta[imin])) tref = dates.date2num(tstartmin) * spdy if itrel == 1 and len(tmark) == 1: tref = dates.date2num(tmark[0]) * spdy stref = print('reference time is %s' % (dates.num2date(tref / spdy))) #print(stref); print('--> this will be subtracted from all time vectors') ############################## STATION (OR EVENT) DISTANCES & AZIMUTHS ##################################### if irs == 1: lat1 = elat lon1 = elon lat2 = rlat lon2 = rlon else: lat1 = rlat[0] lon1 = rlon[0] lat2 = elat lon2 = elon ###[dist] = distance(lat1,lon1,lat2,lon2, 'degrees') def get_bearing(lat1, long1, lat2, long2): dLon = (long2 - long1) x = math.cos(math.radians(lat2)) * math.sin(math.radians(dLon)) y = math.cos(math.radians(lat1)) * math.sin( math.radians(lat2)) - math.sin(math.radians(lat1)) * math.cos( math.radians(lat2)) * math.cos(math.radians(dLon)) brng = arctan2(x, y) brng = degrees(brng) return brng dist = [] azi = [] i = 0 #while i < len(lat2): for i in range(len(lat2)): #dist.append(distance.distance((lat1[i], lon1[i]), (lat2[i], lon2[i])).km) dist.append( (gps2dist_azimuth(lat1[i], lon1[i], lat2[i], lon2[i])[0]) / 1000) #azimuth=get_bearing(lat1[i], lon1[i], lat2[i], lon2[i]) azimuth = gps2dist_azimuth(lat1[i], lon1[i], lat2[i], lon2[i])[1] if azimuth < 0: azi.append(azimuth + 360) #azi.append(get_bearing(lat1[i], lon1[i], lat2[i], lon2[i])) else: azi.append(azimuth) #i+=1 dist_deg = dist if iunit not in [1, 2, 3]: warnings.warn('iunit not 1,2,3 -- setting iunit = 1') iunit = 1 if iunit == 1: sunit = 'km' #dist = deg2km(dist) if iunit == 2: sunit = 'deg' for d in range(len(dist)): dist[d] = kilometers2degrees(dist[d]) if iunit == 3: # input lon2 and lon1 are assumed to be utmx and utmy, # so dist (from above) is over-written sunit = 'km' i = 0 dist = [] while i < len(lat2): dist.append(1e-3 * np.sqrt((lon2[i] - lon1[i]) ^ 2 + (lat2[i] - lat1[i]) ^ 2)) i += 1 dran = max(dist) - min(dist) aran = max(azi) - min(azi) print('distance range is %.1f - %.1f = %.1f %s' % (max(dist), min(dist), dran, sunit)) print(' azimuth range is %.1f - %.1f = %.1f' % (max(azi), min(azi), aran)) ################################################# SORT ###################################################### # NEED TO IMPLEMENT A MULTI-SORT FOR THE CASE OF MULTIPLE SEISMOGRAMS AT # THE SAME SITE (if the distance or az are exactly the same, then sort # based on the net.sta.loc.chan string) sortlab = ['input', 'azimuth', 'distance', 'label'] if rssort == 0: # default is no sorting nsei = nseis + 1 #ivec = [1:nseis].T ivec = np.arange(1, nsei) elif rssort == 1: # azimuth if len(azstart) == 0: print(len(azstart)) azstart = 0 warnings.warn('azstart not specified -- setting azstart = 0') ###ivec = np.argsort((azi-azstart) % 360); ivec = np.argsort(azi) #[~,ivec] = np.argsort(azi-azstar) elif rssort == 2: # distance ivec = np.argsort(dist) elif rssort == 3: # alphabetical ivec = np.argsort(slabs) ################################# RECORD SECTION LABELS -- UNSORTED ######################################### rlabels = np.empty(((nseis), 1), dtype=object) if iabs == 0: jj = 0 for jj in range(nseis): # variable time shift if len(np.unique(tshift)) > 1: if max(tshift) < spdy: if tshift[jj] < 100: stshift = ('DT %.1f s' % (tshift[jj])) else: stshift = ('DT %.1f min' % (tshift[jj] / 60)) else: stshift = '' #stshift = sprintf('DT %.1f',tshift(jj)-min(tshift)); else: stshift = '' if irs == 1: # 1 event, multiple stations rlabels[jj] = ('%s (%.0f, %.0f %s) %s' % (str(slabs[jj])[2:-2], math.floor( azi[jj]), dist[jj], sunit, stshift)) else: # 1 station, multiple events (list event depths) rlabels[jj] = ( '%s %s (%.0f, %.0f %s) %.0f km %.1f %s' % (str(slabs[jj])[2:-2], chans[jj], math.floor( azi[jj]), dist[jj], sunit, edep[jj], mag[jj], stshift)) else: rlabels = slabs ########################################### FILTER WAVEFORMS ############################################### RTAPER = 0.05 # filter if len(T1) == 0 and len(T2) == 0: ifilter = 0 print('NO FILTER WILL BE APPLIED') stfilt = '--' wtemp = Stream() for tr in w: fval = statistics.mean(tr.data) #tr.trim(min(starttime), max(endtime), pad=True, fill_value=fval) tr.trim((tr.stats.starttime), (tr.stats.endtime), pad=True, fill_value=fval) wtemp.append(tr) w = wtemp else: ifilter = 1 npoles = 2 # fill gaps with mean value # w = fillgaps(w,'meanAll'); wtemp = Stream() for tr in w: fval = statistics.mean(tr.data) tr.trim((tr.stats.starttime), (tr.stats.endtime), pad=True, fill_value=fval) #tr.trim(min(starttime), max(endtime), pad=True, fill_value=fval) wtemp.append(tr) w = wtemp print(max(w[0])) # these operations might depend on whether the input is displacements # (which could have static offsets) or velocities print('pre-processing: detrend, demean, taper') w.detrend('demean') '''wtemp=Stream() for tr in w: fval=statistics.mean(tr.data) dmean=tr.data/abs(fval) tr.data=dmean wtemp.append(tr) w=wtemp''' #w = demean(w); w.taper(max_percentage=RTAPER, type='cosine') wfilt = Stream() Tmax_for_mHz = 100 # list mHz, not Hz, for T2 >= Tmax_for_mHz if len(T1) != 0 and len(T2) == 0: print('%i-pole low-pass T > %.1f s (f < %.2f Hz)' % (npoles, T1[0], 1 / T1[0])) #f = filterobject('L',1/T1,npoles); stfilt = ('T > %.1f s (f < %.2f Hz)' % (T1[0], 1 / T1[0])) if T1[0] >= Tmax_for_mHz: stfilt = ('T > %.1f s (f < %.1f mHz)' % (T1[0], 1 / T1[0] * 1e3)) try: wfilt = w.filter("lowpass", freq=1 / T1[0], zerophase=True) except: print("filter didn't work") elif len(T1) == 0 and len(T2) != 0: print('%i-pole high-pass T < %.1f s (f > %.2f Hz)' % (npoles, T2[0], 1 / T2[0])) #f = filterobject('H',1/T2,npoles); stfilt = ('T < %.1f s (f > %.2f Hz)' % (T2[0], 1 / T2[0])) if T2[0] >= Tmax_for_mHz: stfilt = ('T < %.1f s (f > %.1f mHz)' % (T2[0], 1 / T2[0] * 1e3)) try: wfilt = w.filter("highpass", freq=1 / T2[0], zerophase=True) except: print("filter didn't work") elif len(T1) != 0 and len(T2) != 0: print('%i-pole band-pass filter between T = %.1f - %.1f s' % (npoles, T1[0], T2[0])) #f = filterobject('B',[1/T2 1/T1],npoles); stfilt = ('T = %.1f-%.1f s (%.2f-%.2f Hz)' % (T1[0], T2[0], 1 / T2[0], 1 / T1[0])) if T2[0] >= Tmax_for_mHz: stfilt = ('T = %.1f-%.1f s (%.1f-%.1f mHz)' % (T1[0], T2[0], 1 / T2[0] * 1e3, 1 / T1[0] * 1e3)) try: for tr in w: #sos = butter(5, [1/T2[0], 1/T1[0]], 'bandpass', output='sos'); lowcut = 1 / T2[0] highcut = 1 / T1[0] nyq = 0.5 * tr.stats.sampling_rate low = lowcut / nyq high = highcut / nyq sos = butter(5, [low, high], analog=False, btype='band', output='sos') #y = filtfilt(b, a, tr.data, padtype = 'odd', padlen=3*(max(len(b),len(a))-1)) y = sosfiltfilt(sos, tr.data.copy()) tr.data = y wfilt.append(tr) #wfilt=w.filter("bandpass", freqmin=1/T2[0], freqmax=1/T1[0],zerophase=True) except: print("filter didn't work") w = wfilt '''wtemp=Stream() for tr in w: fval=statistics.mean(tr.data) dmean=tr.data/abs(fval) tr.data=dmean wtemp.append(tr) w=wtemp''' # apply identical filtering to synthetics, if present if isyn == 1: ws.detrend() #wsyn = demean(wsyn); ws.taper(max_percentage=RTAPER) ws.filter("bandpass", freqmin=1 / T2[0], freqmax=1 / T1[0], corners=npoles, zerophase=True) ##for trr in w: ##trr.data=trr.data/max(abs(trr.data)) # integrate or differentiate # note: units of w will automatically change units = 'nm / sec' if iintp == 1: if ifilter == 0: w.detrend() w = w.integrate() units = 'nm' if isyn == 1: if ifilter == 0: ws.detrend() ws.integrate() if iintp == -1: w.differentiate() # help waveform/diff units = 'nm / sec^2' if isyn == 1: ws.differentiate() # compute amplitude scaling for plots # NOTE: This will normalize by the full time series, not simply by the time # specified within the window denoted by tlims. nlabs = ['none', 'max(abs(d_i))', ('(sin D)^-%.2f' % (GEOFAC))] nlab = 'norm --> ' + str(nlabs[inorm[0]]) nvec = np.ones((nseis, 1)) # normalization vector for seismograms #w.merge(fill_value=0) wmaxvec = [] #w.normalize(global_max=True) ftrs = [] fttimes = [] for tra in w: #print(max(abs(tra.data))) wmaxvec.append(max(abs(tra.data[100:-100]))) ftrs.append(tra.data) fttimes.append(tra.times("timestamp")) #tra.plot() #wmaxvec = max(abs(w.merge(fill_value=0))) # will handle empty records #print(wmaxvec) ### come back to deal with synthetic arguments ### wsynmaxvec = [] ftrs_syn = [] fttimes_syn = [] stimes_syn = [] if isyn == 1: wtemp = Stream() for tr in ws: fval = statistics.mean(tr.data) tr.trim((tr.stats.starttime), (tr.stats.endtime), pad=True, fill_value=fval) wtemp.append(tr) wsynmaxvec.append(max(abs(tr.data[100:-100]))) ftrs_syn.append(tr.data) fttimes_syn.append(tr.times("timestamp")) stimes_syn.append(tr.stats.starttime) ws = wtemp print('amplitude comparison between data and synthetics:') for ii in range(len(w)): print(' %5s %s ln(data/syn) = %6.2f' % (sta[ii], chans[ii], np.log(wmaxvec[ii] / wsynmaxvec[ii]))) #wmaxvec = max(max(wmaxvec),max(wsynmaxvec)) K = [] fH = [] if geoinorm[0] == 2: #if inorm(1)==2 # For information on geometric spreading, see Stein and Wysession, # Section 4.3.4, Eq 20 (which is for Rayleigh waves. GEOFAC = 0.5). # For our purposes, this will fold the attenuation factor into the same # single factor that accounts for geometric spreading. # WARNING: This does not take into account the variation in amplitude. print('correct for geometrical spreading using (sin x)^-%.2f' % (GEOFAC)) sindel = np.sin(np.asarray(dist_deg) / deg) # note: stations that are father away get enhanced by a larger value Kvec = wmaxvec * (sindel**GEOFAC) # single parameter for fitting (GEOFAC fixed) if len(geoinorm) == 4: K = geoinorm[3] # user-specified K value else: K = statistics.median(Kvec) wvec = K / (sindel**GEOFAC ) # factor will depend on source-station distance # but not on source depth ii = 0 while ii < len(w): # THIS CHANGES THE AMPLITUDE OF w(ii) w[ii].data = w[ii].data / wvec[ii] if isyn == 1: ws[ii] = ws[ii] / wvec[ii] print( '%5s %8.3f deg, max = %8.2e, maxG = %5.2f, K/sin(del)^x = %16.2f' % (sta[ii], dist_deg[ii], wmaxvec[ii], max(abs( w[ii].data)), wvec[ii])) ii += 1 # extra figure with best-fitting curve if bplot_geometric_speading: fsizeg = 14 msizeg = 20 if len(w) > 10: fsizeg = 10 msizeg = 12 geofig = plt.figure() plt.plot(dist_deg, wmaxvec, 'ko', markersize=msizeg, markerfacecolor='r') plt.text(dist_deg, wmaxvec, sta, fontsize=fsizeg) # best-fitting curve x = np.linspace(0, min([1.1 * max(dist_deg), 180])) plt.plot(x, K / (sin(x / deg)**GEOFAC), 'r--', linewidth=2) plt.ylim(0, 1.05 * max(wmaxvec)) plt.xlabel('\Delta, source-station arc distance, deg', fontsize=14) plt.ylabel('max( |v(t)| )', fontsize=14) plt.title('A(\\Delta) = %.2e / (sin \\Delta)^{%.2f}' % (K, GEOFAC), fontsize=16) # update wmaxvec if isyn == 1: wtemp = Stream() for tr in ws: fval = statistics.mean(tr.data) tr.trim((tr.stats.starttime), (tr.stats.endtime), pad=True, fill_value=fval) wtemp.append(tr) wsynmaxvec.append(max(abs(tr.data[100:-100]))) ws = wtemp #wmaxvec = max(max(wmaxvec),max(wsynmaxvec)) wmax = max(wmaxvec) ################################### PLOT RECORD SECTIONS ######################################## if bplotrs == False: return # get units, which may have changed units = 'nm / sec' if nseis == 1: stunit = units else: # unique units #uunit = np.unique(get(w,'units')); uunit = np.unique(units) nuunit = len(uunit) if nuunit > 1: warnings.warn('multiple units are present on waveforms') stunit = [] ii = 0 while ii < nuunit: stunit.append(uunit[ii]) ii += 1 nfig = np.ceil(nseis / pmax) fsize = 10 kk = 0 az1 = azstart azinc = 45 #azbin = np.arange(azstart , azstart+360, azinc) % 360 #print(azbin) try: azbin = np.arange(azstart, azstart + 360, azinc) # %360 except: azbin = [] azbin_fwid = 0.1 for a in range(len(azbin)): azbin[a] = azbin[a] % 360 # vertical separation between seismograms wsep = 1.5 * statistics.mean(wmaxvec) yshift = wsep * nfac if inorm[0] == 1: #nvec = max(abs(w)); nvec = wmaxvec yshift = nfac if iabs == 1: nvec = np.ones((len(wmaxvec), 1)) if inorm[0] == 1: if rssort == 1: yran = aran else: yran = dran if iunit == 2: yran = kilometers2degrees(dran) for ii in range(len(wmaxvec)): nvec[ii] = nfac * nseis / yran * wmaxvec[ii] else: # inorm = 0 or 2 wvec = wmax * np.ones((nseis, 1)) for jj in range(len(wvec)): nvec[jj] = nfac * wvec[jj] # sign flip is needed, since y-axis direction is flipped nvec = -nvec print('wmax = %.3e, wsep = %.3e, yshift = %.3e' % (wmax, wsep, yshift)) # debugging for variable nvec: #disp(sprintf('summary of nvec (%i): min/mean/median/max = %.3e / %.3e / %.3e / %.3e',... # length(nvec),min(abs(nvec)),mean(abs(nvec)),median(abs(nvec)),max(abs(nvec)))); # get time limits for record section # NOTE: THIS SHOULD BE AVOIDED SINCE IT INVOLVES READING ALL THE WAVEFORMS if len(tlims) == 0: #tic tlim1 = np.zeros((nw, 1)) tlim2 = np.zeros((nw, 1)) ii = 0 while ii < nw: #ti3 = get(w(ii),'timevector') ti3 = trtimes[ii] # KEY: time vector for plotting tplot = (ti3 - tref) - tshift[ii] tlim1[ii] = min(tplot) tlim2[ii] = max(tplot) ii += 1 #print('it took %.2f s to establish the time limits for plotting' % (toc)) tlims = [min(tlim1), max(tlim2)] print('tlims', tlims) pp = 0 jj = 0 kk = 0 while pp < nfig: print('record section page %i/%i (max %i per page)' % (pp, nfig, pmax)) #tempfh = figure('Visible',displayfigs); hold on; figname = 'fig' + str(pp) figname = plt.figure(figsize=(9, 11)) ax = plt.subplot(111) #fig=plt.figure() if nfig == 1: jmax = nseis else: jmax = pmax # initialize arrays dy = np.zeros((jmax, 1)) rlabs = np.empty(((jmax), 1), dtype=object) dplotmax = 0 wtemp = Stream() jj = 0 for jj in range(jmax): # loop over seismograms if kk < nseis: ii = ivec[kk] # key sorting rlabs[jj] = rlabels[ii] # get seismogram ti3 = fttimes[ii] di3 = ftrs[ii] # KEY: time vector for plotting tplot = (ti3 - tref) - tshift[ii] if iabs == 0: # plot from top to bottom dy[jj] = (jmax + 1 - jj) * yshift else: # use absolute scaling (e.g., plot records at their actual distance) if rssort == 1: dy[jj] = azi[ii] else: dy[jj] = dist[ii] #norm=np.linalg.norm(di3) dplot = di3 / nvec[ii] # key amplitude scaling dplotshift = dplot + dy[jj] # get the max value of the seismogram within the plotted time interval #btplot = and(tplot > tlims(1),tplot < tlims(2)); #dplotmax = max([dplotmax max(abs(dplot(btplot)))]); #plt.plot(ti3,dplot,'b') ax.plot(tplot, dplotshift, 'b', linewidth=0.5) # PLOT LABELS FOR EACH WAVEFORM txtplace = max(rlabels, key=len) txtplce = len(str(rlabels[ii])[2:-2]) plt.text(tlims[0], statistics.mean(dplotshift), str(rlabels[ii])[2:-2], ha='right', fontsize=8) # specify the amplitude of the first seismogram plotted # (note that this is not the maximum over all seismograms plotted) if jj == 0: imx = np.argmax(abs(di3)) mx = di3.max() stmx = ('%s max %.2e %s at t = %.1f s ' % (sta[ii], di3[imx], units, tplot[imx])) ### come back to deal with synthetic arguments ### if isyn == 1: # be careful about the reference time tisyn = fttimes_syn[ii] disyn = ftrs_syn[ii] tstartsyn = stimes_syn[ii] tplot = (tisyn - dates.date2num(tstartsyn) * spdy) - tshift[ii] dplotshift = disyn / nvec[ii] + dy[ jj] # key amplitude scaling ax.plot(tplot, dplotshift, synplot, linewidth=0.5) # partition if plotting by azimuth (see azbin above) if rssort == 1 and iabs == 0: # azimuth of previous (az0) and current (az1) stations in the sorted list az0 = az1 az1 = azi[ii] # (this boolean clause could probably be simplified) #if (az1 > az0 and (any(az1>azbin) and any(azbin>az0))) or(az1 < az0 and (any(az1>azbin)or any(azbin>az0))): if az1 > az0: for ii in range(len(azbin)): if az1 > azbin[ii] and azbin[ii] > az0: # note: it would nice if these bars extended to the LEFT, # outside the plotting axes tp1 = tlims[0] tp2 = tlims[0] + azbin_fwid * (tlims[1] - tlims[0]) #disp(sprintf('%i %i %.2f %.2f',pp,jj,tp1,tp2)); % testing if ws != None: # and var != None: pc = 'r' else: pc = 'k' plt.plot( [tp1, tp2], [dy[jj] + yshift / 2, dy[jj] + yshift / 2], pc, 'linewidth') break elif az1 < az0: for ii in range(len(azbin)): if az1 > azbin[ii] or azbin[ii] > az0: # note: it would nice if these bars extended to the LEFT, # outside the plotting axes tp1 = tlims[0] tp2 = tlims[0] + azbin_fwid * (tlims[1] - tlims[0]) #disp(sprintf('%i %i %.2f %.2f',pp,jj,tp1,tp2)); % testing if ws != None: # and var != None: pc = 'r' else: pc = 'k' plt.plot( [tp1, tp2], [dy[jj] + yshift / 2, dy[jj] + yshift / 2], pc, 'linewidth') break # exit early for the last page of the multi-page record section if pp == nfig and jj == nseis % pmax: print('ending early') if iabs == 0: dy = (jmax + 1 - arange(jmax)) * yshift return kk = kk + 1 if iabs == 0: # this may chop off a large-amplitude trace near the boundary, but # at least the gap will be the same for a sequence of plots ylims = [0, yshift * (jmax + 2)] plt.yticks([]) #plt.ylim(ylims) # this will provide more space if one of the traces has a large # amplitude, but the gap may vary for a sequence of plots #ylims = yshift*[1 ,jmax] + dplotmax*[-1 ,1] else: # using actual values of distance or azimuth ax.yaxis.tick_right() if rssort == 1: ylims = [min(azi) - 5, max(azi) + 5] if max(azi) - min(azi) > 300: ylims = [-10, 370] else: ylims = [min(dist) - 0.1 * dran, max(dist) + 0.1 * dran] print('tlims = %.2f to %.2f' % (tlims[0], tlims[1])) print('ylims = %.3e to %.3e (yshift = %.3e, jmax=%i)' % (ylims[0], ylims[1], yshift, jmax)) # plot tshift marker #plt.plot([0, 0],ax0[2:3],'r','linewidth') plt.vlines(0, ylims[0], ylims[1], 'r') # plot absolute-time marker mm = 0 for mm in range(nmark): tm = (dates.date2num(tmark[mm]) - dates.date2num(tstartmin)) * spdy - tshift0 #print((dates.date2num(tmark[mm]) - dates.date2num(tstartmin))*spdy) #plt.plot(tm*[1, 1],ax0[2:3],'r','linewidth') plt.vlines(tm, ylims[0], ylims[1], 'r') plt.xlim(tlims) plt.ylim(ylims[0], ylims[1]) if iabs == 1: #if rssort ==1: plt.gca().invert_yaxis() #plt.subplots_adjust(left=0.10) plt.xlabel('Time (s)', fontweight='bold') # title string explaining the time axis if itrel == 0: t1a = dates.date2num(tstartmin) + (tshift0 + tlims[0]) / spdy t2a = t1a + np.diff(tlims) stdur = ('%s + %.2f s' % (dates.num2date(np.double(t1a)), t1a)) else: # relative time shifts stdur = ('variable time shifts: %s' % (stref)) # if length(tmark)==1 # stdur = sprintf('variable time shifts: %s',stref); #else # % just pick the iith record as an example -- this should be the # % bottom time series on the record section # ix = ii; # t1a = tstartmin + (tshift(ix)+tlims(1))/spdy; # t2a = t1a + diff(tlims); # stdur = sprintf('variable time shifts: w(%i) (%s) is %s + %.2f s',... # ix,sta{ix},datestr(double(t1a),31),t2a-t1a); # plot title # note: might want to label the channel, too #if ifilter==1, stfilt = sprintf('T = %.1f-%.1f s (%.2f-%.2f Hz)',T1,T2,1/T2,1/T1); else stfilt = '--'; end st0 = ('%s [%s, %s]' % (stchan, stunit, stfilt)) if irs == 1: stline1 = ( 'event %s (%s, M%s, %.1f, %.1f, z = %s km)' % (eid[0], starttime[0], mag[0], elon[0], elat[0], edep[0])) stline2 = ('%i / %i seismograms (%i stations) ordered by %s, %s' % (jmax, nseis, nsta, sortlab[rssort], nlab)) else: stline1 = ('station %s (%.1f, %.1f)' % (sta[0], rlon[0], rlat[0])) stline2 = ('%i / %i seismograms (%i events) ordered by %s, %s' % (jmax, nseis, neve, sortlab[rssort], nlab)) plt.title(str(stdur) + '; ' + str(stmx) + '\n' + str(st0) + '\n' + str(stline1) + '\n' + str(stline2), fontsize=8) #title({[stdur '; ' stmx],sprintf('%s %s',st0),stline1,stline2},'fontsize',fsize,'interpreter','none'); #plt.ion plt.tight_layout() plt.show() pp += 1 #return fig #-------------------------------------------------------------------------- # plot map # future work would be to add some more options for alaska plots (alaska_basemap.m) if imap == 1: if iunit == 3: fac = 1e-3 else: fac = 1 iplotsrc = 1 fsize = 10 figsta = plt.figure(figsize=(8, 10)) plt.plot(rlon * fac, rlat * fac, 'bv') #if iplotsrc==1: plt.plot(elon * fac, elat * fac, 'k*', markersize=15, markerfacecolor='r') # plot station labels (or eid labels) if irs == 1: for i, lab in enumerate(sta): plt.text(rlon[i] * fac, rlat[i] * fac, str(lab), fontsize=fsize) else: plt.text(elon[i] * fac, elat[i] * fac, eid[i], fontsize=fsize) plt.title(str(stline1) + '\n' + str(stchan), fontsize=8) plt.show() print('--> leaving plotw_rs.m') # Print download time d = datetime.now() - start print(d, " s to plot waveforms")
def travel_times(ref, deg=None, km=None, depth=0.): """ Get *approximate* relative travel time(s). Parameters ---------- ref : list or tuple of strings and/or floats Reference phase names or horizontal velocities [km/sec]. deg : float, optional Degrees of arc between two points of interest (spherical earth). km : float, optional Horizontal kilometers between two points of interest (spherical earth). depth : float, optional. default, 0. Depth (positive down) of event, in kilometers. Returns ------- numpy.ndarray Relative times, in seconds, same length as "ref". NaN if requested time is undefined. Examples -------- Get relative P arrival and 2.7 km/sec surface wave arrival at 35 degrees distance. >>> times = travel_times(['P', 2.7], deg=35.0) To get absolute window, add the origin time like: >>> w1, w2 = times + epoch_origin_time Notes ----- Either deg or km must be indicated. The user is responsible for adding/subtracting time (such as origin time, pre-window noise time, etc.) from those predicted in order to define a window. Phase travel times use ak135. """ times = np.zeros(len(ref), dtype='float') tt = None for i, iref in enumerate(ref): if isinstance(iref, str): # phase time requested if not tt: if not deg: deg = geod.kilometers2degrees(km) tt = taup.getTravelTimes(deg, depth, model='ak135') try: idx = [ph['phase_name'] for ph in tt].index(iref) itt = [ph['time'] for ph in tt][idx] except ValueError: # phase not found itt = None else: # horizontal velocity if not km: km = deg*(2*math.pi/360.0)*6371.0 itt = km/iref times[i] = itt return times
def obs_kilometer2degrees(km): return kilometers2degrees(float(km))
def _read_picks(f, new_event): """ Internal pick reader. Use read_nordic instead. :type f: file :param f: File open in read mode :type wav_names: list :param wav_names: List of waveform files in the sfile :type new_event: :class:`~obspy.core.event.event.Event` :param new_event: event to associate picks with. :returns: :class:`~obspy.core.event.event.Event` """ f.seek(0) evtime = new_event.origins[0].time pickline = [] # Set a default, ignored later unless overwritten snr = None for line in f: if line[79] == '7': header = line break for line in f: if len(line.rstrip('\n').rstrip('\r')) in [80, 79] and \ line[79] in ' 4\n': pickline += [line] for line in pickline: if line[18:28].strip() == '': # If line is empty miss it continue weight = line[14] if weight == '_': phase = line[10:17] weight = 0 polarity = '' else: phase = line[10:14].strip() polarity = line[16] if weight == ' ': weight = 0 polarity_maps = {"": "undecidable", "C": "positive", "D": "negative"} try: polarity = polarity_maps[polarity] except KeyError: polarity = "undecidable" # It is valid nordic for the origin to be hour 23 and picks to be hour # 00 or 24: this signifies a pick over a day boundary. if int(line[18:20]) == 0 and evtime.hour == 23: day_add = 86400 pick_hour = 0 elif int(line[18:20]) == 24: day_add = 86400 pick_hour = 0 else: day_add = 0 pick_hour = int(line[18:20]) try: time = UTCDateTime(evtime.year, evtime.month, evtime.day, pick_hour, int(line[20:22]), float(line[23:28])) + day_add except ValueError: time = UTCDateTime(evtime.year, evtime.month, evtime.day, int(line[18:20]), pick_hour, float("0." + line[23:38].split('.')[1])) +\ 60 + day_add # Add 60 seconds on to the time, this copes with s-file # preference to write seconds in 1-60 rather than 0-59 which # datetime objects accept if header[57:60] == 'AIN': ain = _float_conv(line[57:60]) warnings.warn('AIN: %s in header, currently unsupported' % ain) elif header[57:60] == 'SNR': snr = _float_conv(line[57:60]) else: warnings.warn('%s is not currently supported' % header[57:60]) # finalweight = _int_conv(line[68:70]) # Create a new obspy.event.Pick class for this pick _waveform_id = WaveformStreamID(station_code=line[1:6].strip(), channel_code=line[6:8].strip(), network_code='NA') pick = Pick(waveform_id=_waveform_id, phase_hint=phase, polarity=polarity, time=time) try: pick.onset = onsets[line[9]] except KeyError: pass if line[15] == 'A': pick.evaluation_mode = 'automatic' else: pick.evaluation_mode = 'manual' # Note these two are not always filled - velocity conversion not yet # implemented, needs to be converted from km/s to s/deg # if not velocity == 999.0: # new_event.picks[pick_index].horizontal_slowness = 1.0 / velocity if _float_conv(line[46:51]) is not None: pick.backazimuth = _float_conv(line[46:51]) # Create new obspy.event.Amplitude class which references above Pick # only if there is an amplitude picked. if _float_conv(line[33:40]) is not None: _amplitude = Amplitude(generic_amplitude=_float_conv(line[33:40]), period=_float_conv(line[41:45]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) if pick.phase_hint == 'IAML': # Amplitude for local magnitude _amplitude.type = 'AML' # Set to be evaluating a point in the trace _amplitude.category = 'point' # Default AML unit in seisan is nm (Page 139 of seisan # documentation, version 10.0) _amplitude.generic_amplitude /= 1e9 _amplitude.unit = 'm' _amplitude.magnitude_hint = 'ML' else: # Generic amplitude type _amplitude.type = 'A' if snr: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) elif _int_conv(line[28:33]) is not None: # Create an amplitude instance for code duration also _amplitude = Amplitude(generic_amplitude=_int_conv(line[28:33]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) # Amplitude for coda magnitude _amplitude.type = 'END' # Set to be evaluating a point in the trace _amplitude.category = 'duration' _amplitude.unit = 's' _amplitude.magnitude_hint = 'Mc' if snr is not None: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) # Create new obspy.event.Arrival class referencing above Pick if _float_conv(line[33:40]) is None: arrival = Arrival(phase=pick.phase_hint, pick_id=pick.resource_id) if weight is not None: arrival.time_weight = weight if _int_conv(line[60:63]) is not None: arrival.backazimuth_residual = _int_conv(line[60:63]) if _float_conv(line[63:68]) is not None: arrival.time_residual = _float_conv(line[63:68]) if _float_conv(line[70:75]) is not None: arrival.distance = kilometers2degrees(_float_conv(line[70:75])) if _int_conv(line[76:79]) is not None: arrival.azimuth = _int_conv(line[76:79]) new_event.origins[0].arrivals.append(arrival) new_event.picks.append(pick) return new_event
def km2deg(distance_in_km): return kilometers2degrees(distance_in_km, radius=6371.)
def input_chen_tele_body(tensor_info, data_prop): """We write some text files, which are based on teleseismic body wave data, as inputs for Chen's scripts. :param tensor_info: dictionary with moment tensor information :param data_prop: dictionary with properties of waveform data :type tensor_info: dict :type data_prop: dict .. warning:: Make sure the filters of teleseismic data agree with the values in sampling_filter.json! """ if not os.path.isfile('tele_waves.json'): return traces_info = json.load(open('tele_waves.json')) date_origin = tensor_info['date_origin'] dt = traces_info[0]['dt'] dt = round(dt, 1) filtro = data_prop['tele_filter'] low_freq = filtro['low_freq'] high_freq = filtro['high_freq'] with open('filtro_tele', 'w') as outfile: outfile.write('Corners: {} {}\n'.format(low_freq, high_freq)) outfile.write('dt: {}'.format(dt)) nsta = len(traces_info) model = TauPyModel(model="ak135f_no_mud") depth = tensor_info['depth'] event_lat = tensor_info['lat'] event_lon = tensor_info['lon'] string = '{0:2d} FAR GDSN {1:>6} {1:>6}BHZ.DAT {2:5.2f} {3:6.2f} '\ '{4:5.2f} {5:6.2f} {6:6.2f} 0 0 {7} {8} {9} 1 0\n' sin_fun = lambda p: p * 3.6 / 111.12 angle_fun = lambda p:\ np.arctan2(sin_fun(p), np.sqrt(1 - sin_fun(p)**2)) * 180.0 / np.pi string_fun1 = lambda i, name, dist, az, lat, lon, p_slowness, disp_or_vel:\ string.format( i, name, dist, az, lat, lon, angle_fun(p_slowness), disp_or_vel, 1.0, 0) string_fun2 = lambda i, name, dist, az, lat, lon, s_slowness, disp_or_vel:\ string.format( i, name, dist, az, lat, lon, angle_fun(s_slowness), disp_or_vel, 4.0, 2) with open('Readlp.das', 'w') as outfile: outfile.write('30 30 30 0 0 0 0 0 0 1.1e+20\n') outfile.write('3 10 {}\n{}{}{}{}{}{}.{}\n{}\n'.format( dt, date_origin.year, date_origin.month, date_origin.day, date_origin.hour, date_origin.minute, date_origin.second, date_origin.microsecond, nsta)) i = 0 for file in traces_info: #header in headers: name = file['name'] channel = file['component'] lat, lon = file['location'] dist, az, back_azimuth = mng._distazbaz(lat, lon, event_lat, event_lon) dist = kilometers2degrees(dist) derivative = False if not 'derivative' in file\ else file['derivative'] derivative = int(derivative) arrivals = mng.theoretic_arrivals(model, dist, depth) p_slowness = arrivals['p_slowness'] s_slowness = arrivals['s_slowness'] if channel == 'BHZ': outfile.write( string_fun1(i + 1, name, dist, az, lat, lon, p_slowness, derivative)) else: outfile.write( string_fun2(i + 1, name, dist, az, lat, lon, s_slowness, derivative)) i = i + 1 with open('Wave.tele', 'w') as file1, open('Obser.tele', 'w') as file2: write_files_wavelet_observed(file1, file2, dt, data_prop, traces_info) # # instrumental response common to all body waves # string2 = '\n3\n' + '0. 0.\n' * 3 + '4\n-6.17E-03 6.17E-03\n'\ '-6.17E-03 -6.17E-03\n-39.18 49.12\n-39.18 '\ '-49.12\n3948\n' with open('instrumental_response', 'w') as outfile: outfile.write('{}\n'.format(nsta)) outfile.write(string2 * len(traces_info)) write_wavelet_freqs(dt, 'Wavelets_tele_body') with open('Weight', 'w') as outfile: for info in traces_info: sta = info['name'] channel = info['component'] weight = info['trace_weight'] outfile.write('{} {} {}\n'.format(weight, sta, channel)) return 'tele_body'