def gather_waveforms(source, network, station, location, channel, starttime, endtime, time_buffer=0, merge_fill_value=0, trim_fill_value=0, remove_response=False, return_failed_stations=False, watc_url=None, watc_username=None, watc_password=None): """ Gather seismic/infrasound waveforms from IRIS or WATC FDSN, or AVO Winston, and output a :class:`~obspy.core.stream.Stream` with station/element coordinates attached. Optionally remove the sensitivity. **NOTE** Usual RTM usage is to specify a starttime/endtime that brackets the estimated source origin time. Then time_buffer is used to download enough extra data to account for the time required for an infrasound signal to propagate to the farthest station. Args: source (str): Which source to gather waveforms from. Options are: * `'IRIS'` – IRIS FDSN * `'WATC'` – WATC FDSN * `'AVO'` – AVO Winston network (str): SEED network code [wildcards (``*``, ``?``) accepted] station (str): SEED station code [wildcards (``*``, ``?``) accepted] location (str): SEED location code [wildcards (``*``, ``?``) accepted] channel (str): SEED channel code [wildcards (``*``, ``?``) accepted] starttime (:class:`~obspy.core.utcdatetime.UTCDateTime`): Start time for data request endtime (:class:`~obspy.core.utcdatetime.UTCDateTime`): End time for data request time_buffer (int or float): Extra amount of data to download after `endtime` [s] merge_fill_value (bool, int, float, str, or None): Controls merging of :class:`~obspy.core.trace.Trace` objects with identical IDs. If `False`, no merging is performed. Otherwise, a merge is performed with the ``fill_value`` provided to this parameter. For details, see the docstring of :meth:`obspy.core.stream.Stream.trim` trim_fill_value (bool, int, float, or None): Controls trimming of the output :class:`~obspy.core.stream.Stream`, useful if precisely uniform start and end times are desired. If `False`, no trimming is performed. Otherwise, a trim is performed with the ``fill_value`` provided to this parameter. For details, see the docstring of :meth:`obspy.core.stream.Stream.merge` remove_response (bool): Toggle response removal via :meth:`~obspy.core.trace.Trace.remove_sensitivity` or a simple scalar multiplication return_failed_stations (bool): If `True`, returns a list of station codes that were requested but not downloaded. This disables the standard failed station warning message watc_url (str): URL for WATC FDSN server watc_username (str): Username for WATC FDSN server watc_password (str): Password for WATC FDSN server Returns: :class:`~obspy.core.stream.Stream` containing gathered waveforms. If `return_failed_stations` is `True`, additionally returns a list containing station codes that were requested but not downloaded """ # Check for issues with fill value args if merge_fill_value is True or trim_fill_value is True: raise ValueError('Cannot provide True to fill value parameters.') print('--------------') print('GATHERING DATA') print('--------------') # IRIS FDSN if source == 'IRIS': client = FDSN_Client('IRIS') print('Reading data from IRIS FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # WATC FDSN elif source == 'WATC': print('Connecting to WATC FDSN...') client = FDSN_Client(base_url=watc_url, user=watc_username, password=watc_password) print('Successfully connected. Reading data from WATC FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # AVO Winston elif source == 'AVO': client = EW_Client('pubavo1.wr.usgs.gov', port=16023) # 16023 is long-term print('Reading data from AVO Winston...') st_out = Stream() # Make empty Stream object to populate # Brute-force "dynamic grid search" over network/station/channel/location codes for nw in _restricted_matching('network', network, client): for sta in _restricted_matching('station', station, client, network=nw): for cha in _restricted_matching('channel', channel, client, network=nw, station=sta): for loc in _restricted_matching('location', location, client, network=nw, station=sta, channel=cha): try: st_out += client.get_waveforms( nw, sta, loc, cha, starttime, endtime + time_buffer) except KeyError: pass else: raise ValueError('Unrecognized source. Valid options are \'IRIS\', ' '\'WATC\', or \'AVO\'.') # Merge, if specified if merge_fill_value is not False: st_out.merge(fill_value=merge_fill_value) # Merge Traces with same ID warnings.warn(f'Merging with "fill_value={merge_fill_value}"', CollectionWarning) st_out.sort() # Check that all requested stations are present in Stream requested_stations = station.split(',') downloaded_stations = [tr.stats.station for tr in st_out] failed_stations = [] for sta in requested_stations: # The below check works with wildcards, but obviously cannot detect if # ALL stations corresponding to a given wildcard (e.g., O??K) were # downloaded. Thus, if careful station selection is desired, specify # each station explicitly and the below check will then be effective. if not fnmatch.filter(downloaded_stations, sta): if not return_failed_stations: # If we're not returning the failed stations, then show this # warning message to alert the user warnings.warn( f'Station {sta} not downloaded from {source} ' 'server for this time period.', CollectionWarning) failed_stations.append(sta) # If the Stream is empty, then we can stop here if st_out.count() == 0: print('No data downloaded.') if return_failed_stations: return st_out, failed_stations else: return st_out # Otherwise, show what the Stream contains print(st_out.__str__(extended=True)) # This syntax prints the WHOLE Stream # Trim, if specified if trim_fill_value is not False: st_out.trim(starttime, endtime + time_buffer, pad=True, fill_value=trim_fill_value) warnings.warn(f'Trimming with "fill_value={trim_fill_value}"', CollectionWarning) print('Assigning coordinates...') # Use IRIS inventory info for AVO data source if source == 'AVO': client = FDSN_Client('IRIS') try: inv = client.get_stations(network=network, station=station, location=location, channel=channel, starttime=starttime, endtime=endtime + time_buffer, level='channel') except FDSNNoDataException: inv = Inventory() # Make an empty inv warnings.warn('Creating empty inventory.', CollectionWarning) for tr in st_out: try: coords = inv.get_coordinates(tr.id) tr.stats.longitude = coords['longitude'] tr.stats.latitude = coords['latitude'] tr.stats.elevation = coords['elevation'] except Exception as e: if str(e) == 'No matching channel metadata found.': warnings.warn(f'No metadata for {tr.id} found in inventory.', CollectionWarning) else: raise # Check if any Trace did NOT get coordinates assigned, and try to use JSON # coordinates if available for tr in st_out: try: tr.stats.longitude, tr.stats.latitude, tr.stats.elevation except AttributeError: try: tr.stats.latitude, tr.stats.longitude,\ tr.stats.elevation = AVO_COORDS[tr.id] warnings.warn(f'Using coordinates from JSON file for {tr.id}.', CollectionWarning) except KeyError: print(f'No coordinates available for {tr.id}. Stopping.') raise # Remove sensitivity if remove_response: print('Removing sensitivity...') for tr in st_out: try: # Just removing sensitivity for now. remove_response() can lead # to errors. This should be sufficient for now. Plus some # IRIS-AVO responses are wonky. tr.remove_sensitivity() except ValueError: # No response information found # This is only set up for infrasound calibration values try: calib = AVO_INFRA_CALIBS[tr.id] tr.data = tr.data * calib warnings.warn( 'Using calibration value from JSON file for ' f'{tr.id}.', CollectionWarning) except KeyError: print(f'No calibration value available for {tr.id}. ' 'Stopping.') raise print('Done') # Return the Stream with coordinates attached (and responses removed if # specified) if return_failed_stations: return st_out, failed_stations else: return st_out
template = tribe[0] event = template.event depth = event.origins[0].depth if depth < 10: origt = event.origins[0].time evlat = event.origins[0].latitude evlon = event.origins[0].longitude tmin = origt - 0.5 tmax = origt + 5.0 name = template.name fname = os.path.join("templates", "waveforms", "%s.mseed" % name) stream = read(fname) for tr in stream: station = tr.stats.station coords = inv.get_coordinates(tr.id, origt) stalat = coords["latitude"] stalon = coords["longitude"] # distdeg = calc_dist(evlat, evlon, stalat, stalon, 6378.1) # distkm = degrees2kilometers(distdeg) dist = gps2dist_azimuth(evlat, evlon, stalat, stalon)[0] tr.stats.distance = dist stream.sort() #stream.traces.sort(key=lambda x: x.stats.distance) stream.trim(starttime=tmin, endtime=tmax).filter("lowpass", freq=60) plot_3cols(stream, event) plt.savefig(os.path.join("templates", "waveforms", "%s.png" % name), dpi=300)