def _parse_record_m(self, line, event, pick): """ Parses the 'surface wave record' M """ # unused: Z_comp = line[7] z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # z_amplitude includes that column z_amplitude = self._float(line[13:21]) # micrometers # TODO: N_comp and E_comp seems to be never there msz_mag = line[49:52] ms_mag = self._float(line[53:56]) # unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split('/')[-1] station_string = \ pick.waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() amplitude = None if z_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = z_amplitude * 1E-6 amplitude.unit = 'm' amplitude.period = z_period amplitude.type = 'AS' amplitude.magnitude_hint = 'Ms' amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if msz_mag is not None: station_magnitude = StationMagnitude() prefix = '/'.join((res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = ms_mag station_magnitude.station_magnitude_type = 'Ms' if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
def _parseRecordM(self, line, event, pick): """ Parses the 'surface wave record' M """ #unused: Z_comp = line[7] Z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # Z_amplitude includes that column Z_amplitude = self._float(line[13:21]) # micrometers #TODO: N_comp and E_comp seems to be never there MSZ_mag = line[49:52] Ms_mag = self._float(line[53:56]) #unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split('/')[-1] station_string =\ pick.waveform_id.getSEEDString()\ .replace(' ', '-').replace('.', '_').lower() amplitude = None if Z_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = Z_amplitude * 1E-6 amplitude.unit = 'm' amplitude.period = Z_period amplitude.type = 'AS' amplitude.magnitude_hint = 'Ms' amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if MSZ_mag is not None: station_magnitude = StationMagnitude() prefix = '/'.join( (res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = Ms_mag station_magnitude.station_magnitude_type = 'Ms' if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
def _parseRecordM(self, line, event, pick): """ Parses the 'surface wave record' M """ # unused: Z_comp = line[7] Z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # Z_amplitude includes that column Z_amplitude = self._float(line[13:21]) # micrometers # TODO: N_comp and E_comp seems to be never there MSZ_mag = line[49:52] Ms_mag = self._float(line[53:56]) # unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split("/")[-1] station_string = pick.waveform_id.getSEEDString().replace(" ", "-").replace(".", "_").lower() amplitude = None if Z_amplitude is not None: amplitude = Amplitude() prefix = "/".join((res_id_prefix, "amp", evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = Z_amplitude * 1e-6 amplitude.unit = "m" amplitude.period = Z_period amplitude.type = "AS" amplitude.magnitude_hint = "Ms" amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if MSZ_mag is not None: station_magnitude = StationMagnitude() prefix = "/".join((res_id_prefix, "stationmagntiude", evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = Ms_mag station_magnitude.station_magnitude_type = "Ms" if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
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 _parseRecordP(self, line, event): """ Parses the 'primary phase record' P The primary phase is the first phase of the reading, regardless its type. """ station = line[2:7].strip() phase = line[7:15] arrival_time = line[15:24] residual = self._float(line[25:30]) #unused: residual_flag = line[30] distance = self._float(line[32:38]) # degrees azimuth = self._float(line[39:44]) backazimuth = round(azimuth % -360 + 180, 1) mb_period = self._float(line[44:48]) mb_amplitude = self._float(line[48:55]) # nanometers mb_magnitude = self._float(line[56:59]) #unused: mb_usage_flag = line[59] origin = event.origins[0] evid = event.resource_id.id.split('/')[-1] waveform_id = WaveformStreamID() waveform_id.station_code = station #network_code is required for QuakeML validation waveform_id.network_code = ' ' station_string =\ waveform_id.getSEEDString()\ .replace(' ', '-').replace('.', '_').lower() prefix = '/'.join( (res_id_prefix, 'waveformstream', evid, station_string)) waveform_id.resource_uri = ResourceIdentifier(prefix=prefix) pick = Pick() prefix = '/'.join((res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) #Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = waveform_id pick.backazimuth = backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) if mb_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = mb_amplitude * 1E-9 amplitude.unit = 'm' amplitude.period = mb_period amplitude.type = 'AB' amplitude.magnitude_hint = 'Mb' amplitude.pick_id = pick.resource_id amplitude.waveform_id = pick.waveform_id event.amplitudes.append(amplitude) station_magnitude = StationMagnitude() prefix = '/'.join( (res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = origin.resource_id station_magnitude.mag = mb_magnitude #station_magnitude.mag_errors['uncertainty'] = 0.0 station_magnitude.station_magnitude_type = 'Mb' station_magnitude.amplitude_id = amplitude.resource_id station_magnitude.waveform_id = pick.waveform_id res_id = '/'.join( (res_id_prefix, 'magnitude/generic/body_wave_magnitude')) station_magnitude.method_id =\ ResourceIdentifier(id=res_id) event.station_magnitudes.append(station_magnitude) arrival = Arrival() prefix = '/'.join((res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = azimuth arrival.distance = distance arrival.time_residual = residual res_id = '/'.join((res_id_prefix, 'earthmodel/ak135')) arrival.earth_model_id = ResourceIdentifier(id=res_id) origin.arrivals.append(arrival) origin.quality.minimum_distance = min( d for d in (arrival.distance, origin.quality.minimum_distance) if d is not None) origin.quality.maximum_distance =\ max(arrival.distance, origin.quality.minimum_distance) origin.quality.associated_phase_count += 1 return pick, arrival
def __toAmplitude(parser, el): """ """ amp = Amplitude() amp.resource_id = ResourceIdentifier( prefix="/".join([RESOURCE_ROOT, "amplitude"])) if CURRENT_TYPE == "obspyck": amp.method_id = "%s/amplitude_method/obspyck/1" % RESOURCE_ROOT else: msg = "encountered non-obspyck amplitude!" raise Exception(msg) amp.generic_amplitude, amp.generic_amplitude_errors = \ __toFloatQuantity(parser, el, "genericAmplitude") amp.unit = parser.xpath2obj('unit', el, str) waveform = el.xpath("waveform")[0] network = waveform.get("networkCode") station = fix_station_name(waveform.get("stationCode")) # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] if not network: network = NETWORK_DICT[station] location = waveform.get("locationCode") or "" channel = waveform.get("channelCode") or "" amp.waveform_id = WaveformStreamID( network_code=network, station_code=station, channel_code=channel, location_code=location) # Amplitudes without generic_amplitude are not quakeml conform if amp.generic_amplitude is None: print ("Amplitude has no generic_amplitude value and is " "ignored: %s" % station) return None # generate time_window amp.time_window = TimeWindow() t_min = parser.xpath2obj('timeWindow/reference', el, UTCDateTime) t_max = t_min + parser.xpath2obj('timeWindow/end', el, float) dt_abs = abs(t_max - t_min) amp.time_window.reference = t_min if t_min < t_max: amp.time_window.begin = 0.0 amp.time_window.end = dt_abs else: amp.time_window.begin = dt_abs amp.time_window.end = 0.0 # we have so far saved frequency in Hz as "period" tag # use two times dt instead ##amp.period = 1.0 / parser.xpath2obj('period', el, float) amp.period = 2.0 * dt_abs t = ("PGV; reference time is time of minimum amplitude. if minimum comes " "before maximum, start is 0 and end is relative time of maximum. " "otherwise end is 0, start is relative time of minimum.") comment = Comment(force_resource_id=False, resource_id=None, text=t) amp.comments.append(comment) return amp
def _parse_record_p(self, line, event): """ Parses the 'primary phase record' P The primary phase is the first phase of the reading, regardless its type. """ station = line[2:7].strip() phase = line[7:15] arrival_time = line[15:24] residual = self._float(line[25:30]) # unused: residual_flag = line[30] distance = self._float(line[32:38]) # degrees azimuth = self._float(line[39:44]) backazimuth = round(azimuth % -360 + 180, 1) mb_period = self._float(line[44:48]) mb_amplitude = self._float(line[48:55]) # nanometers mb_magnitude = self._float(line[56:59]) # unused: mb_usage_flag = line[59] origin = event.origins[0] evid = event.resource_id.id.split('/')[-1] waveform_id = WaveformStreamID() waveform_id.station_code = station # network_code is required for QuakeML validation waveform_id.network_code = ' ' station_string = \ waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() prefix = '/'.join((res_id_prefix, 'waveformstream', evid, station_string)) waveform_id.resource_uri = ResourceIdentifier(prefix=prefix) pick = Pick() prefix = '/'.join((res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) # Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = waveform_id pick.backazimuth = backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) if mb_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = mb_amplitude * 1E-9 amplitude.unit = 'm' amplitude.period = mb_period amplitude.type = 'AB' amplitude.magnitude_hint = 'Mb' amplitude.pick_id = pick.resource_id amplitude.waveform_id = pick.waveform_id event.amplitudes.append(amplitude) station_magnitude = StationMagnitude() prefix = '/'.join((res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = origin.resource_id station_magnitude.mag = mb_magnitude # station_magnitude.mag_errors['uncertainty'] = 0.0 station_magnitude.station_magnitude_type = 'Mb' station_magnitude.amplitude_id = amplitude.resource_id station_magnitude.waveform_id = pick.waveform_id res_id = '/'.join( (res_id_prefix, 'magnitude/generic/body_wave_magnitude')) station_magnitude.method_id = \ ResourceIdentifier(id=res_id) event.station_magnitudes.append(station_magnitude) arrival = Arrival() prefix = '/'.join((res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = azimuth arrival.distance = distance arrival.time_residual = residual res_id = '/'.join((res_id_prefix, 'earthmodel/ak135')) arrival.earth_model_id = ResourceIdentifier(id=res_id) origin.arrivals.append(arrival) origin.quality.minimum_distance = min( d for d in (arrival.distance, origin.quality.minimum_distance) if d is not None) origin.quality.maximum_distance = \ max(arrival.distance, origin.quality.minimum_distance) origin.quality.associated_phase_count += 1 return pick, arrival
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 __toAmplitude(parser, el): """ """ amp = Amplitude() amp.resource_id = ResourceIdentifier( prefix="/".join([RESOURCE_ROOT, "amplitude"])) if CURRENT_TYPE == "obspyck": amp.method_id = "%s/amplitude_method/obspyck/1" % RESOURCE_ROOT else: msg = "encountered non-obspyck amplitude!" raise Exception(msg) amp.generic_amplitude, amp.generic_amplitude_errors = \ __toFloatQuantity(parser, el, "genericAmplitude") amp.unit = parser.xpath2obj('unit', el, str) waveform = el.xpath("waveform")[0] network = waveform.get("networkCode") station = fix_station_name(waveform.get("stationCode")) # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] if not network: network = NETWORK_DICT[station] location = waveform.get("locationCode") or "" channel = waveform.get("channelCode") or "" amp.waveform_id = WaveformStreamID(network_code=network, station_code=station, channel_code=channel, location_code=location) # Amplitudes without generic_amplitude are not quakeml conform if amp.generic_amplitude is None: print( "Amplitude has no generic_amplitude value and is " "ignored: %s" % station) return None # generate time_window amp.time_window = TimeWindow() t_min = parser.xpath2obj('timeWindow/reference', el, UTCDateTime) t_max = t_min + parser.xpath2obj('timeWindow/end', el, float) dt_abs = abs(t_max - t_min) amp.time_window.reference = t_min if t_min < t_max: amp.time_window.begin = 0.0 amp.time_window.end = dt_abs else: amp.time_window.begin = dt_abs amp.time_window.end = 0.0 # we have so far saved frequency in Hz as "period" tag # use two times dt instead ##amp.period = 1.0 / parser.xpath2obj('period', el, float) amp.period = 2.0 * dt_abs t = ("PGV; reference time is time of minimum amplitude. if minimum comes " "before maximum, start is 0 and end is relative time of maximum. " "otherwise end is 0, start is relative time of minimum.") comment = Comment(force_resource_id=False, resource_id=None, text=t) amp.comments.append(comment) return amp
def _read_single_event(event_file, locate_dir, units, local_mag_ph): """ Parse an event file from QuakeMigrate into an obspy Event object. Parameters ---------- event_file : `pathlib.Path` object Path to .event file to read. locate_dir : `pathlib.Path` object Path to locate directory (contains "events", "picks" etc. directories). units : {"km", "m"} Grid projection coordinates for QM LUT (determines units of depths and uncertainties in the .event files). local_mag_ph : {"S", "P"} Amplitude measurement used to calculate local magnitudes. Returns ------- event : `obspy.Event` object Event object populated with all available information output by :class:`~quakemigrate.signal.scan.locate()`, including event locations and uncertainties, picks, and amplitudes and magnitudes if available. """ # Parse information from event file event_info = pd.read_csv(event_file).iloc[0] event_uid = str(event_info["EventID"]) # Set distance conversion factor (from units of QM LUT projection units). if units == "km": factor = 1e3 elif units == "m": factor = 1 else: raise AttributeError(f"units must be 'km' or 'm'; not {units}") # Create event object to store origin and pick information event = Event() event.extra = AttribDict() event.resource_id = str(event_info["EventID"]) event.creation_info = CreationInfo(author="QuakeMigrate", version=quakemigrate.__version__) # Add COA info to extra event.extra.coa = {"value": event_info["COA"], "namespace": ns} event.extra.coa_norm = {"value": event_info["COA_NORM"], "namespace": ns} event.extra.trig_coa = {"value": event_info["TRIG_COA"], "namespace": ns} event.extra.dec_coa = {"value": event_info["DEC_COA"], "namespace": ns} event.extra.dec_coa_norm = { "value": event_info["DEC_COA_NORM"], "namespace": ns } # Determine location of cut waveform data - add to event object as a # custom extra attribute. mseed = locate_dir / "raw_cut_waveforms" / event_uid event.extra.cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } if (locate_dir / "real_cut_waveforms").exists(): mseed = locate_dir / "real_cut_waveforms" / event_uid event.extra.real_cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } if (locate_dir / "wa_cut_waveforms").exists(): mseed = locate_dir / "wa_cut_waveforms" / event_uid event.extra.wa_cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } # Create origin with spline location and set to preferred event origin. origin = Origin() origin.method_id = "spline" origin.longitude = event_info["X"] origin.latitude = event_info["Y"] origin.depth = event_info["Z"] * factor origin.time = UTCDateTime(event_info["DT"]) event.origins = [origin] event.preferred_origin_id = origin.resource_id # Create origin with gaussian location and associate with event origin = Origin() origin.method_id = "gaussian" origin.longitude = event_info["GAU_X"] origin.latitude = event_info["GAU_Y"] origin.depth = event_info["GAU_Z"] * factor origin.time = UTCDateTime(event_info["DT"]) event.origins.append(origin) ouc = OriginUncertainty() ce = ConfidenceEllipsoid() ce.semi_major_axis_length = event_info["COV_ErrY"] * factor ce.semi_intermediate_axis_length = event_info["COV_ErrX"] * factor ce.semi_minor_axis_length = event_info["COV_ErrZ"] * factor ce.major_axis_plunge = 0 ce.major_axis_azimuth = 0 ce.major_axis_rotation = 0 ouc.confidence_ellipsoid = ce ouc.preferred_description = "confidence ellipsoid" # Set uncertainties for both as the gaussian uncertainties for origin in event.origins: origin.longitude_errors.uncertainty = kilometer2degrees( event_info["GAU_ErrX"] * factor / 1e3) origin.latitude_errors.uncertainty = kilometer2degrees( event_info["GAU_ErrY"] * factor / 1e3) origin.depth_errors.uncertainty = event_info["GAU_ErrZ"] * factor origin.origin_uncertainty = ouc # Add OriginQuality info to each origin? for origin in event.origins: origin.origin_type = "hypocenter" origin.evaluation_mode = "automatic" # --- Handle picks file --- pick_file = locate_dir / "picks" / event_uid if pick_file.with_suffix(".picks").is_file(): picks = pd.read_csv(pick_file.with_suffix(".picks")) else: return None for _, pickline in picks.iterrows(): station = str(pickline["Station"]) phase = str(pickline["Phase"]) wid = WaveformStreamID(network_code="", station_code=station) for method in ["modelled", "autopick"]: pick = Pick() pick.extra = AttribDict() pick.waveform_id = wid pick.method_id = method pick.phase_hint = phase if method == "autopick" and str(pickline["PickTime"]) != "-1": pick.time = UTCDateTime(pickline["PickTime"]) pick.time_errors.uncertainty = float(pickline["PickError"]) pick.extra.snr = { "value": float(pickline["SNR"]), "namespace": ns } elif method == "modelled": pick.time = UTCDateTime(pickline["ModelledTime"]) else: continue event.picks.append(pick) # --- Handle amplitudes file --- amps_file = locate_dir / "amplitudes" / event_uid if amps_file.with_suffix(".amps").is_file(): amps = pd.read_csv(amps_file.with_suffix(".amps")) i = 0 for _, ampsline in amps.iterrows(): wid = WaveformStreamID(seed_string=ampsline["id"]) noise_amp = ampsline["Noise_amp"] / 1000 # mm to m for phase in ["P_amp", "S_amp"]: amp = Amplitude() if pd.isna(ampsline[phase]): continue amp.generic_amplitude = ampsline[phase] / 1000 # mm to m amp.generic_amplitude_errors.uncertainty = noise_amp amp.unit = "m" amp.type = "AML" amp.method_id = phase amp.period = 1 / ampsline[f"{phase[0]}_freq"] amp.time_window = TimeWindow( reference=UTCDateTime(ampsline[f"{phase[0]}_time"])) # amp.pick_id = ? amp.waveform_id = wid # amp.filter_id = ? amp.magnitude_hint = "ML" amp.evaluation_mode = "automatic" amp.extra = AttribDict() try: amp.extra.filter_gain = { "value": ampsline[f"{phase[0]}_filter_gain"], "namespace": ns } amp.extra.avg_amp = { "value": ampsline[f"{phase[0]}_avg_amp"] / 1000, # m "namespace": ns } except KeyError: pass if phase[0] == local_mag_ph and not pd.isna(ampsline["ML"]): i += 1 stat_mag = StationMagnitude() stat_mag.extra = AttribDict() # stat_mag.origin_id = ? local_mag_loc stat_mag.mag = ampsline["ML"] stat_mag.mag_errors.uncertainty = ampsline["ML_Err"] stat_mag.station_magnitude_type = "ML" stat_mag.amplitude_id = amp.resource_id stat_mag.extra.picked = { "value": ampsline["is_picked"], "namespace": ns } stat_mag.extra.epi_dist = { "value": ampsline["epi_dist"], "namespace": ns } stat_mag.extra.z_dist = { "value": ampsline["z_dist"], "namespace": ns } event.station_magnitudes.append(stat_mag) event.amplitudes.append(amp) mag = Magnitude() mag.extra = AttribDict() mag.mag = event_info["ML"] mag.mag_errors.uncertainty = event_info["ML_Err"] mag.magnitude_type = "ML" # mag.origin_id = ? mag.station_count = i mag.evaluation_mode = "automatic" mag.extra.r2 = {"value": event_info["ML_r2"], "namespace": ns} event.magnitudes = [mag] event.preferred_magnitude_id = mag.resource_id return event