def align_phases(stream, event, inventory, phase_name, method="simple"): """ Aligns the waveforms with the theoretical travel times for some phase. The theoretical travel times are calculated with obspy.taup. :param stream: Waveforms for the array processing. :type stream: :class:`obspy.core.stream.Stream` :param event: The event for which to calculate phases. :type event: :class:`obspy.core.event.Event` :param inventory: Station metadata. :type inventory: :class:`obspy.station.inventory.Inventory` :param phase_name: The name of the phase you want to align. Must be contained in all traces. Otherwise the behaviour is undefined. :type phase_name: str :param method: Method is either `simple` or `fft`. Simple will just shift the starttime of Trace, while 'fft' will do the shift in the frequency domain. Defaults to `simple`. :type method: str """ method = method.lower() if method not in ['simple', 'fft']: msg = "method must be 'simple' or 'fft'" raise ValueError(msg) stream = stream.copy() attach_coordinates_to_traces(stream, inventory, event) stream.traces = sorted(stream.traces, key=lambda x: x.stats.distance)[::-1] tr_1 = stream[-1] tt_1 = getTravelTimes(tr_1.stats.distance, event.origins[0].depth / 1000.0, "ak135") cont = 0. for tt in tt_1: if tt["phase_name"] != phase_name: continue if tt["phase_name"] == phase_name: cont = 1. tt_1 = tt["time"] break if cont == 0: msg = "The selected phase is not present in your seismograms!!!" raise ValueError(msg) for tr in stream: tt = getTravelTimes(tr.stats.distance, event.origins[0].depth / 1000.0, "ak135") for t in tt: if t["phase_name"] != phase_name: continue tt = t["time"] break if method == "simple": tr.stats.starttime -= (tt - tt_1) else: shifttrace_freq(Stream(traces=[tr]), [-((tt - tt_1))]) return stream
def align_phases(stream, event, inventory, phase_name, method="simple"): """ Aligns the waveforms with the theoretical travel times for some phase. The theoretical travel times are calculated with obspy.taup. :param stream: Waveforms for the array processing. :type stream: :class:`obspy.core.stream.Stream` :param event: The event for which to calculate phases. :type event: :class:`obspy.core.event.Event` :param inventory: Station metadata. :type inventory: :class:`obspy.station.inventory.Inventory` :param phase_name: The name of the phase you want to align. Must be contained in all traces. Otherwise the behaviour is undefined. :type phase_name: str :param method: Method is either `simple` or `fft`. Simple will just shift the starttime of Trace, while 'fft' will do the shift in the frequency domain. Defaults to `simple`. :type method: str """ method = method.lower() if method not in ["simple", "fft"]: msg = "method must be 'simple' or 'fft'" raise ValueError(msg) stream = stream.copy() attach_coordinates_to_traces(stream, inventory, event) stream.traces = sorted(stream.traces, key=lambda x: x.stats.distance)[::-1] tr_1 = stream[-1] tt_1 = getTravelTimes(tr_1.stats.distance, event.origins[0].depth / 1000.0, "ak135") cont = 0.0 for tt in tt_1: if tt["phase_name"] != phase_name: continue if tt["phase_name"] == phase_name: cont = 1.0 tt_1 = tt["time"] break if cont == 0: msg = "The selected phase is not present in your seismograms!!!" raise ValueError(msg) for tr in stream: tt = getTravelTimes(tr.stats.distance, event.origins[0].depth / 1000.0, "ak135") for t in tt: if t["phase_name"] != phase_name: continue tt = t["time"] break if method == "simple": tr.stats.starttime -= tt - tt_1 else: shifttrace_freq(Stream(traces=[tr]), [-((tt - tt_1))]) return stream
def calculate_time_phase(event, sta): """ calculate arrival time of the requested phase to use in retrieving waveforms. :param event: :param sta: :return: """ ev_lat = event['latitude'] ev_lon = event['longitude'] ev_dp = abs(float(event['depth'])) sta_lat = float(sta[4]) sta_lon = float(sta[5]) delta = locations2degrees(ev_lat, ev_lon, sta_lat, sta_lon) tt = getTravelTimes(delta, ev_dp) phase_list = ['P', 'Pdiff', 'PKIKP'] time_ph = 0 flag = False for ph in phase_list: for i in range(len(tt)): if tt[i]['phase_name'] == ph: flag = True time_ph = tt[i]['time'] break else: continue if flag: print 'Phase: %s' % ph break t_start = event['t1'] + time_ph t_end = event['t2'] + time_ph return t_start, t_end
def calculate_time_phase(event, sta, bg_model='iasp91'): """ calculate arrival time of the requested phase :param event: :param sta: :param bg_model: :return: """ phase_list = ['P', 'Pdiff', 'PKIKP'] time_ph = 0 ev_lat = event['latitude'] ev_lon = event['longitude'] evdp = abs(float(event['depth'])) sta_lat = float(sta[4]) sta_lon = float(sta[5]) dist = locations2degrees(ev_lat, ev_lon, sta_lat, sta_lon) try: from obspy.taup import tau tau_bg = tau.TauPyModel(model=bg_model) except: tau_bg = False if not tau_bg: try: tt = getTravelTimes(dist, evdp) flag = False for ph in phase_list: for i in range(len(tt)): if tt[i]['phase_name'] == ph: flag = True time_ph = tt[i]['time'] break else: continue if not flag: time_ph = 0 except: time_ph = 0 else: try: for ph in phase_list: tt = tau_bg.get_travel_times(evdp, dist, phase_list=[ph])[0].time if not tt: time_ph = 0 continue else: time_ph = tt break except: time_ph = 0 t_start = event['t1'] + time_ph t_end = event['t2'] + time_ph return t_start, t_end
def calculate_ttimes(self): """ Calculate theoretical travel times. Only call if station and event information is available! """ dist_in_deg = geodetics.locations2degrees( self.station.latitude, self.station.longitude, self.event.latitude, self.event.longitude) tts = getTravelTimes(dist_in_deg, self.event.depth_in_m / 1000.0, model=self.config.earth_model) self.ttimes = sorted(tts, key=lambda x: x["time"]) logger.info("Calculated travel times.")
def calculate_ttimes(self): """ Calculate theoretical travel times. Only call if station and event information is available! """ dist_in_deg = geodetics.locations2degrees(self.station.latitude, self.station.longitude, self.event.latitude, self.event.longitude) tts = getTravelTimes(dist_in_deg, self.event.depth_in_m / 1000.0, model=self.config.earth_model) self.ttimes = sorted(tts, key=lambda x: x["time"]) logger.info("Calculated travel times.")
def travel_time_calc(evla, evlo, stla, stlo, evdp, bg_model): """ calculate arrival time of different seismic phases :param evla: :param evlo: :param stla: :param stlo: :param evdp: :param bg_model: :return: """ # --------------- TAUP dist = locations2degrees(evla, evlo, stla, stlo) try: tt = [_i for _i in getTravelTimes(dist, evdp, bg_model) if 'Pdiff' == _i['phase_name']][0]['time'] except Exception, e: tt = False
def travel_time_calc(evla, evlo, stla, stlo, evdp, bg_model): """ calculate arrival time of different seismic phases :param evla: :param evlo: :param stla: :param stlo: :param evdp: :param bg_model: :return: """ # --------------- TAUP dist = locations2degrees(evla, evlo, stla, stlo) try: tt = [ _i for _i in getTravelTimes(dist, evdp, bg_model) if 'Pdiff' == _i['phase_name'] ][0]['time'] except Exception, e: tt = False
import numpy as np from obspy import taup seismic_phase = ['P', 'Pdiff'] min_degree = 90. max_degree = 180. step_degree = 1. evdepth = 0. dist_pdiff = [] dist_all = [] take_off = [] for dist in np.arange(min_degree, max_degree, step_degree): flag = False tt = taup.getTravelTimes(delta=dist, depth=evdepth) for i in range(len(tt)): if tt[i]['phase_name'] in seismic_phase: flag = True take_off_tmp = tt[i]['take-off angle'] time_tmp = tt[i]['time'] if tt[i]['phase_name'] == 'Pdiff': dist_pdiff.append(dist) break if flag: take_off.append(take_off_tmp) dist_all.append(dist) plt.plot(dist_all, take_off, lw=3) plt.xlabel('Distance (deg)', size='large', weight='bold') plt.ylabel('Take-off angle', size='large', weight='bold')
def select_windows(data_trace, synthetic_trace, ev_lat, ev_lng, ev_depth_in_km, st_lat, st_lng, minimum_period, maximum_period): """ Window selection algorithm for picking windows suitable for misfit calculation based on phase differences. :param data_trace: :param synthetic_trace: :param ev_lat: :param ev_lng: :param ev_depth_in_km: :param st_lat: :param st_lng: :param minimum_period: :param maximum_period: """ print "* ---------------------------" print "* autoselect " + data_trace.stats.channel # ========================================================================= # set a couple of selection parameters - might become part of the input in # future versions # ========================================================================= # Minimum normalised correlation coefficient of the complete traces. min_cc = 0.0 # Maximum relative noise level for the whole trace. Measured from maximum # amplitudes before and after the first arrival. max_noise = 0.3 # Maximum relative noise level for individual windows. max_noise_window = 0.4 # All arrivals later than those corresponding to the threshold velocity # [km/s] will be excluded. threshold_velocity = 2.4 # Maximum allowable time shift within a window, as a fraction of the # minimum period. threshold_shift = 0.2 # Minimum normalised correlation coeficient within a window. threshold_correlation = 0.5 # Minimum length of the time windows relative to the minimum period. min_length_period = 1.5 # Minimum number of extreme in an individual time window (excluding the # edges). min_peaks_troughs = 2 # Maximum energy ratio between data and synthetics within a time window. max_energy_ratio = 3.0 # ========================================================================= # initialisations # ========================================================================= dt = synthetic_trace.stats.delta npts = synthetic_trace.stats.npts dist_in_deg = geodetics.locations2degrees(st_lat, st_lng, ev_lat, ev_lng) dist_in_km = geodetics.calcVincentyInverse( st_lat, st_lng, ev_lat, ev_lng)[0] / 1000.0 tts = getTravelTimes(dist_in_deg, ev_depth_in_km, model="ak135") first_tt_arrival = min([_i["time"] for _i in tts]) # Number of samples in the sliding window. Currently, the length of the # window is set to a multiple of the dominant period of the synthetics. # Make sure it is an uneven number; just to have an easy midpoint # definition. window_length = int(round(float(2 * minimum_period) / dt)) if not window_length % 2: window_length += 1 # Allocate arrays to collect the time dependent values. sliding_time_shift = np.zeros(npts, dtype="float32") max_cc_coeff = np.zeros(npts, dtype="float32") taper = np.hanning(window_length) # ========================================================================= # check if whole seismograms are sufficiently correlated and estimate noise # level # ========================================================================= synth = synthetic_trace.data data = data_trace.data # compute correlation coefficient norm = np.sqrt(np.sum(data ** 2)) * np.sqrt(np.sum(synth ** 2)) cc = np.sum(data * synth) / norm print "** correlation coefficient: " + str(cc) # estimate noise level from waveforms prior to the first arrival idx = int(np.ceil((first_tt_arrival - minimum_period * 0.5) / dt)) noise_absolute = data[50:idx].ptp() noise_relative = noise_absolute / data.ptp() print "** absolute noise level: " + str(noise_absolute) + " m/s" print "** relative noise level: " + str(noise_relative) # rejection criteria accept = True if cc < min_cc: print "** no windows selected, correlation " + str(cc) + \ " is below threshold value of " + str(min_cc) accept = False if noise_relative > max_noise: print "** no windows selected, noise level " + str(noise_relative) + \ " is above threshold value of " + str(max_noise) accept = False if accept is False: print "* autoselect done" return [] # ========================================================================= # compute sliding time shifts and correlation coefficients # ========================================================================= for start_idx, end_idx, midpoint_idx in _window_generator(npts, window_length): # Slice windows. Create a copy to be able to taper without affecting # the original time series. data_window = data_trace.data[start_idx: end_idx].copy() * taper synthetic_window = \ synthetic_trace.data[start_idx: end_idx].copy() * taper # Skip windows that have essentially no energy to avoid instabilities. if synthetic_window.ptp() < synthetic_trace.data.ptp() * 0.001: continue # Calculate the time shift. Here this is defined as the shift of the # synthetics relative to the data. So a value of 2, for instance, means # that the synthetics are 2 timesteps later then the data. cc = np.correlate(data_window, synthetic_window, mode="full") time_shift = cc.argmax() - window_length + 1 # Express the time shift in fraction of the minimum period. sliding_time_shift[midpoint_idx] = (time_shift * dt) / minimum_period # Normalized cross correlation. max_cc_value = cc.max() / np.sqrt((synthetic_window ** 2).sum() * (data_window ** 2).sum()) max_cc_coeff[midpoint_idx] = max_cc_value # ========================================================================= # compute the initial mask, i.e. intervals (windows) where no measurements # are made. # ========================================================================= # Step 1: Initialise masked arrays. The mask will be set to True where no # windows are chosen. time_windows = np.ma.ones(npts) time_windows.mask = np.zeros(npts) # Step 2: Mark everything more then half a dominant period before the first # theoretical arrival as positive. time_windows.mask[:int(np.ceil( (first_tt_arrival - minimum_period * 0.5) / dt))] = True # Step 3: Mark everything more then half a dominant period after the # threshold arrival time - computed from the threshold velocity - as # negative. time_windows.mask[int(np.floor(dist_in_km / threshold_velocity / dt)):] = \ True # Step 4: Mark everything with an absolute travel time shift of more than # threshold_shift times the dominant period as negative time_windows.mask[np.abs(sliding_time_shift) > threshold_shift] = True # Step 5: Mark the area around every "travel time shift jump" (based on # the traveltime time difference) negative. The width of the area is # currently chosen to be a tenth of a dominant period to each side. sample_buffer = int(np.ceil(minimum_period / dt * 0.1)) indices = np.ma.where(np.abs(np.diff(sliding_time_shift)) > 0.1)[0] for index in indices: time_windows.mask[index - sample_buffer: index + sample_buffer] = True # Step 6: Mark all areas where the normalized cross correlation coefficient # is under threshold_correlation as negative time_windows.mask[max_cc_coeff < threshold_correlation] = True # ========================================================================= # Make the final window selection. # ========================================================================= min_length = min( minimum_period / dt * min_length_period, maximum_period / dt) final_windows = [] # loop through all the time windows for i in np.ma.flatnotmasked_contiguous(time_windows): window_npts = i.stop - i.start synthetic_window = synthetic_trace.data[i.start: i.stop] data_window = data_trace.data[i.start: i.stop] # Step 7: Throw away all windows with a length of less then # min_length_period the dominant period. if (i.stop - i.start) < min_length: continue # Step 8: Exclude windows without a real peak or trough (except for the # edges). data_p, data_t, data_extrema = find_local_extrema(data_window, 0) synth_p, synth_t, synth_extrema = find_local_extrema(synthetic_window, 0) if np.min([len(synth_p), len(synth_t), len(data_p), len(data_t)]) < \ min_peaks_troughs: continue # Step 9: Peak and trough matching algorithm window_mask = np.ones(window_npts, dtype="bool") closest_peaks = find_closest(data_p, synth_p) diffs = np.diff(closest_peaks) for idx in np.where(diffs == 1)[0]: if idx > 0: start = synth_p[idx - 1] else: start = 0 if idx < (len(synth_p) - 1): end = synth_p[idx + 1] else: end = -1 window_mask[start: end] = False closest_troughs = find_closest(data_t, synth_t) diffs = np.diff(closest_troughs) for idx in np.where(diffs == 1)[0]: if idx > 0: start = synth_t[idx - 1] else: start = 0 if idx < (len(synth_t) - 1): end = synth_t[idx + 1] else: end = -1 window_mask[start: end] = False window_mask = np.ma.masked_array(window_mask, mask=window_mask) if window_mask.mask.all(): continue # Step 10: Check if the time windows have sufficiently similar energy # and are above the noise for j in np.ma.flatnotmasked_contiguous(window_mask): # Again assert a certain minimal length. if (j.stop - j.start) < min_length: continue # Compare the energy in the data window and the synthetic window. data_energy = (data_window[j.start: j.stop] ** 2).sum() synth_energy = (synthetic_window[j.start: j.stop] ** 2).sum() energies = sorted([data_energy, synth_energy]) if energies[1] > max_energy_ratio * energies[0]: continue # Check that amplitudes in the data are above the noise if noise_absolute / data_window[j.start: j.stop].ptp() > \ max_noise_window: continue final_windows.append((i.start + j.start, i.start + j.stop)) print "* autoselect done" return final_windows
def vespagram(stream, ev, inv, method, frqlow, frqhigh, baz, scale, nthroot=4, filter=True, static3D=False, vel_corr=4.8, sl=(0.0, 10.0, 0.5), align=False, align_phase=['P', 'Pdiff'], plot_trace=True): """ vespagram wrapper routine for MESS 2014. :param stream: Waveforms for the array processing. :type stream: :class:`obspy.core.stream.Stream` :param inventory: Station metadata for waveforms :type inventory: :class:`obspy.station.inventory.Inventory` :param method: Method used for the array analysis (one of "DLS": Delay and Sum, "PWS": Phase Weighted Stack). :type method: str :param frqlow: Low corner of frequency range for array analysis :type frqlow: float :param frqhigh: High corner of frequency range for array analysis :type frqhigh: float :param baz: pre-defined (theoretical or calculated) backazimuth used for calculation :type baz_plot: float :param scale: scale for plotting :type scale: float :param nthroot: estimating the nthroot for calculation of the beam :type nthroot: int :param filter: Whether to bandpass data to selected frequency range :type filter: bool :param static3D: static correction of topography using `vel_corr` as velocity (slow!) :type static3D: bool :param vel_corr: Correction velocity for static topography correction in km/s. :type vel_corr: float :param sl: Min/Max and stepwidthslowness for analysis :type sl: (float, float,float) :param align: whether to align the vespagram to a certain phase :type align: bool :param align_phase: phase to be aligned with (might be a list if simulateneous arivials are expected (P,PcP,Pdif) :type align: str :param plot_trace: if True plot the vespagram as wiggle plot, if False as density map :type align: bool """ starttime = max([tr.stats.starttime for tr in stream]) endtime = min([tr.stats.endtime for tr in stream]) stream.trim(starttime, endtime) org = ev.preferred_origin() or ev.origins[0] ev_lat = org.latitude ev_lon = org.longitude ev_depth = org.depth/1000. # in km ev_otime = org.time sll, slm, sls = sl sll /= KM_PER_DEG slm /= KM_PER_DEG sls /= KM_PER_DEG center_lon = 0. center_lat = 0. center_elv = 0. seismo = stream seismo.attach_response(inv) seismo.merge() sz = Stream() i = 0 for tr in seismo: for station in inv[0].stations: if tr.stats.station == station.code: tr.stats.coordinates = \ AttribDict({'latitude': station.latitude, 'longitude': station.longitude, 'elevation': station.elevation}) center_lon += station.longitude center_lat += station.latitude center_elv += station.elevation i += 1 sz.append(tr) center_lon /= float(i) center_lat /= float(i) center_elv /= float(i) starttime = max([tr.stats.starttime for tr in stream]) stt = starttime endtime = min([tr.stats.endtime for tr in stream]) e = endtime stream.trim(starttime, endtime) #nut = 0 max_amp = 0. sz.trim(stt, e) sz.detrend('simple') print sz fl, fh = frqlow, frqhigh if filter: sz.filter('bandpass', freqmin=fl, freqmax=fh, zerophase=True) if align: deg = [] shift = [] res = gps2DistAzimuth(center_lat, center_lon, ev_lat, ev_lon) deg.append(kilometer2degrees(res[0]/1000.)) tt = getTravelTimes(deg[0], ev_depth, model='ak135') for item in tt: phase = item['phase_name'] if phase in align_phase: try: travel = item['time'] travel = ev_otime.timestamp + travel dtime = travel - stt.timestamp shift.append(dtime) except: break for i, tr in enumerate(sz): res = gps2DistAzimuth(tr.stats.coordinates['latitude'], tr.stats.coordinates['longitude'], ev_lat, ev_lon) deg.append(kilometer2degrees(res[0]/1000.)) tt = getTravelTimes(deg[i+1], ev_depth, model='ak135') for item in tt: phase = item['phase_name'] if phase in align_phase: try: travel = item['time'] travel = ev_otime.timestamp + travel dtime = travel - stt.timestamp shift.append(dtime) except: break shift = np.asarray(shift) shift -= shift[0] AA.shifttrace_freq(sz, -shift) baz += 180. nbeam = int((slm - sll)/sls + 0.5) + 1 kwargs = dict( # slowness grid: X min, X max, Y min, Y max, Slow Step sll=sll, slm=slm, sls=sls, baz=baz, stime=stt, method=method, nthroot=nthroot, etime=e, correct_3dplane=False, static_3D=static3D, vel_cor=vel_corr) start = UTCDateTime() slow, beams, max_beam, beam_max = AA.vespagram_baz(sz, **kwargs) print "Total time in routine: %f\n" % (UTCDateTime() - start) df = sz[0].stats.sampling_rate # Plot the seismograms npts = len(beams[0]) print npts T = np.arange(0, npts/df, 1/df) sll *= KM_PER_DEG slm *= KM_PER_DEG sls *= KM_PER_DEG slow = np.arange(sll, slm, sls) max_amp = np.max(beams[:, :]) #min_amp = np.min(beams[:, :]) scale *= sls fig = plt.figure(figsize=(12, 8)) if plot_trace: ax1 = fig.add_axes([0.1, 0.1, 0.85, 0.85]) for i in xrange(nbeam): if i == max_beam: ax1.plot(T, sll + scale*beams[i]/max_amp + i*sls, 'r', zorder=1) else: ax1.plot(T, sll + scale*beams[i]/max_amp + i*sls, 'k', zorder=-1) ax1.set_xlabel('Time [s]') ax1.set_ylabel('slowness [s/deg]') ax1.set_xlim(T[0], T[-1]) data_minmax = ax1.yaxis.get_data_interval() minmax = [min(slow[0], data_minmax[0]), max(slow[-1], data_minmax[1])] ax1.set_ylim(*minmax) ##### else: #step = (max_amp - min_amp)/100. #level = np.arange(min_amp, max_amp, step) #beams = beams.transpose() #cmap = cm.hot_r cmap = cm.rainbow ax1 = fig.add_axes([0.1, 0.1, 0.85, 0.85]) #ax1.contour(slow,T,beams,level) #extent = (slow[0], slow[-1], \ # T[0], T[-1]) extent = (T[0], T[-1], slow[0] - sls * 0.5, slow[-1] + sls * 0.5) ax1.set_ylabel('slowness [s/deg]') ax1.set_xlabel('T [s]') beams = np.flipud(beams) ax1.imshow(beams, cmap=cmap, interpolation="nearest", extent=extent, aspect='auto') #### result = "BAZ: %.2f Time %s" % (baz-180., stt) ax1.set_title(result) plt.show() return slow, beams, max_beam, beam_max
def show_distance_plot(stream, event, inventory, starttime, endtime, plot_travel_times=True): """ Plots distance dependent seismogramm sections. :param stream: The waveforms. :type stream: :class:`obspy.core.stream.Stream` :param event: The event. :type event: :class:`obspy.core.event.Event` :param inventory: The station information. :type inventory: :class:`obspy.station.inventory.Inventory` :param starttime: starttime of traces to be plotted :type starttime: UTCDateTime :param endttime: endttime of traces to be plotted :type endttime: UTCDateTime :param plot_travel_times: flag whether phases are marked as traveltime plots in the section obspy.taup is used to calculate the phases :type pot_travel_times: bool """ stream = stream.slice(starttime=starttime, endtime=endtime).copy() event_depth_in_km = event.origins[0].depth / 1000.0 event_time = event.origins[0].time attach_coordinates_to_traces(stream, inventory, event=event) cm = plt.cm.jet stream.traces = sorted(stream.traces, key=lambda x: x.stats.distance)[::-1] # One color for each trace. colors = [cm(_i) for _i in np.linspace(0, 1, len(stream))] # Relative event times. times_array = stream[0].times() + (stream[0].stats.starttime - event_time) distances = [tr.stats.distance for tr in stream] min_distance = min(distances) max_distance = max(distances) distance_range = max_distance - min_distance stream_range = distance_range / 10.0 # Normalize data and "shift to distance". stream.normalize() for tr in stream: tr.data *= stream_range tr.data += tr.stats.distance plt.figure(figsize=(18, 10)) for _i, tr in enumerate(stream): plt.plot(times_array, tr.data, label="%s.%s" % (tr.stats.network, tr.stats.station), color=colors[_i]) plt.grid() plt.ylabel("Distance in degree to event") plt.xlabel("Time in seconds since event") plt.legend() dist_min, dist_max = plt.ylim() if plot_travel_times: distances = defaultdict(list) ttimes = defaultdict(list) for i in np.linspace(dist_min, dist_max, 1000): tts = getTravelTimes(i, event_depth_in_km, "ak135") for phase in tts: name = phase["phase_name"] distances[name].append(i) ttimes[name].append(phase["time"]) for key in distances.iterkeys(): min_distance = min(distances[key]) max_distance = max(distances[key]) min_tt_time = min(ttimes[key]) max_tt_time = max(ttimes[key]) if min_tt_time >= times_array[-1] or \ max_tt_time <= times_array[0] or \ (max_distance - min_distance) < 0.8 * (dist_max - dist_min): continue ttime = ttimes[key] dist = distances[key] if max(ttime) > times_array[0] + 0.9 * times_array.ptp(): continue plt.scatter(ttime, dist, s=0.5, zorder=-10, color="black", alpha=0.8) plt.text(max(ttime) + 0.005 * times_array.ptp(), dist_max - 0.02 * (dist_max - dist_min), key) plt.ylim(dist_min, dist_max) plt.xlim(times_array[0], times_array[-1]) plt.title(event.short_str()) plt.show()
def show_distance_plot(stream, event, inventory, starttime, endtime, plot_travel_times=True): """ Plots distance dependent seismogramm sections. :param stream: The waveforms. :type stream: :class:`obspy.core.stream.Stream` :param event: The event. :type event: :class:`obspy.core.event.Event` :param inventory: The station information. :type inventory: :class:`obspy.station.inventory.Inventory` :param starttime: starttime of traces to be plotted :type starttime: UTCDateTime :param endttime: endttime of traces to be plotted :type endttime: UTCDateTime :param plot_travel_times: flag whether phases are marked as traveltime plots in the section obspy.taup is used to calculate the phases :type pot_travel_times: bool """ stream = stream.slice(starttime=starttime, endtime=endtime).copy() event_depth_in_km = event.origins[0].depth / 1000.0 event_time = event.origins[0].time attach_coordinates_to_traces(stream, inventory, event=event) cm = plt.cm.jet stream.traces = sorted(stream.traces, key=lambda x: x.stats.distance)[::-1] # One color for each trace. colors = [cm(_i) for _i in np.linspace(0, 1, len(stream))] # Relative event times. times_array = stream[0].times() + (stream[0].stats.starttime - event_time) distances = [tr.stats.distance for tr in stream] min_distance = min(distances) max_distance = max(distances) distance_range = max_distance - min_distance stream_range = distance_range / 10.0 # Normalize data and "shift to distance". stream.normalize() for tr in stream: tr.data *= stream_range tr.data += tr.stats.distance plt.figure(figsize=(18, 10)) for _i, tr in enumerate(stream): plt.plot(times_array, tr.data, label="%s.%s" % (tr.stats.network, tr.stats.station), color=colors[_i]) plt.grid() plt.ylabel("Distance in degree to event") plt.xlabel("Time in seconds since event") plt.legend() dist_min, dist_max = plt.ylim() if plot_travel_times: distances = defaultdict(list) ttimes = defaultdict(list) for i in np.linspace(dist_min, dist_max, 1000): tts = getTravelTimes(i, event_depth_in_km, "ak135") for phase in tts: name = phase["phase_name"] distances[name].append(i) ttimes[name].append(phase["time"]) for key in distances.iterkeys(): min_distance = min(distances[key]) max_distance = max(distances[key]) min_tt_time = min(ttimes[key]) max_tt_time = max(ttimes[key]) if min_tt_time >= times_array[-1] or \ max_tt_time <= times_array[0] or \ (max_distance - min_distance) < 0.8 * (dist_max - dist_min): continue ttime = ttimes[key] dist = distances[key] if max(ttime) > times_array[0] + 0.9 * times_array.ptp(): continue plt.scatter(ttime, dist, s=0.5, zorder=-10, color="black", alpha=0.8) plt.text( max(ttime) + 0.005 * times_array.ptp(), dist_max - 0.02 * (dist_max - dist_min), key) plt.ylim(dist_min, dist_max) plt.xlim(times_array[0], times_array[-1]) plt.title(event.short_str()) plt.show()