def __toArrival(parser, pick_el, evaluation_mode, pick):
    """
    """
    global CURRENT_TYPE
    arrival = Arrival()
    arrival.resource_id = ResourceIdentifier(prefix="/".join([RESOURCE_ROOT, "arrival"]))
    arrival.pick_id = pick.resource_id
    arrival.phase = parser.xpath2obj('phaseHint', pick_el)
    arrival.azimuth = parser.xpath2obj('azimuth/value', pick_el, float)
    arrival.distance = parser.xpath2obj('epi_dist/value', pick_el, float)
    if arrival.distance is not None:
        arrival.distance = kilometer2degrees(arrival.distance)
    takeoff_angle, _ = __toFloatQuantity(parser, pick_el, "incident/value")
    if takeoff_angle and not math.isnan(takeoff_angle):
        arrival.takeoff_angle = takeoff_angle
    arrival.time_residual = parser.xpath2obj('phase_res/value', pick_el, float)
    arrival.time_weight = parser.xpath2obj('phase_weight/value', pick_el, float)
    return arrival
def __toArrival(parser, pick_el, evaluation_mode, pick):
    """
    """
    global CURRENT_TYPE
    arrival = Arrival()
    arrival.resource_id = ResourceIdentifier(
        prefix="/".join([RESOURCE_ROOT, "arrival"]))
    arrival.pick_id = pick.resource_id
    arrival.phase = parser.xpath2obj('phaseHint', pick_el)
    arrival.azimuth = parser.xpath2obj('azimuth/value', pick_el, float)
    arrival.distance = parser.xpath2obj('epi_dist/value', pick_el, float)
    if arrival.distance is not None:
        arrival.distance = kilometer2degrees(arrival.distance)
    takeoff_angle, _ = __toFloatQuantity(parser, pick_el, "incident/value")
    if takeoff_angle and not math.isnan(takeoff_angle):
        arrival.takeoff_angle = takeoff_angle
    arrival.time_residual = parser.xpath2obj('phase_res/value', pick_el, float)
    arrival.time_weight = parser.xpath2obj('phase_weight/value', pick_el,
                                           float)
    return arrival
Example #3
0
def maps(station_list, origin, strike, dip, rake, plot_file):
    """
    Plotting a map with epicenter and possible stations according to distance
    and a map with with the beachball
    """

    # sets figure's dimensions
    _fig_x = 10
    _fig_y = 10
    fig = plt.figure(figsize=(_fig_x, _fig_y))

    # calculating map space
    _max = max(_station.distance_by_origin for _station in station_list)
    _max = int(round(_max * 1000 * 2))
    _size = _max + int(round(_max / 7.0))
    _diff = kilometer2degrees(round(_size / (2 * 2.0 * 1000)))

    parallels = [
        round(origin.latitude, 2),
        round((origin.latitude - _diff), 2),
        round((origin.latitude + _diff), 2)
    ]
    meridians = [
        round(origin.longitude, 2),
        round((origin.longitude - _diff), 2),
        round((origin.longitude + _diff), 2)
    ]

    m = Basemap(projection='laea',
                lat_0=origin.latitude,
                lon_0=origin.longitude,
                lat_ts=origin.latitude,
                resolution='i',
                area_thresh=0.1,
                width=_size,
                height=_size)
    m.drawparallels(parallels, labels=[1, 0, 0, 0], color='grey', fontsize=10)
    m.drawmeridians(meridians, labels=[0, 0, 0, 1], color='grey', fontsize=10)
    m.drawrivers(color='aqua')
    m.drawcoastlines(color='0.2')
    m.drawcountries(color='0.4')
    m.drawmapboundary(fill_color='aqua')
    m.fillcontinents(color='coral', lake_color='aqua')
    x, y = m(origin.longitude, origin.latitude)

    # epicenter
    m.scatter(x,
              y,
              1,
              color="#FFFF00",
              marker="*",
              zorder=3,
              linewidths=2,
              edgecolor="k")

    # beachball
    ax = plt.gca()
    b = beach.Beach([strike, dip, rake],
                    xy=(x, y),
                    width=35000,
                    linewidth=1,
                    facecolor='r')
    b.set_zorder(10)
    ax.add_collection(b)

    # stations
    for station in station_list:
        x, y = m(station.longitude, station.latitude)
        m.scatter(x,
                  y,
                  150,
                  color="#33CC00",
                  marker="^",
                  zorder=3,
                  linewidths=1,
                  edgecolor="k")
        plt.text(x + 1800,
                 y + 3000,
                 station.code,
                 family="monospace",
                 fontsize=12)

    fig.savefig(plot_file)
def __toOrigin(parser, origin_el):
    """
    Parses a given origin etree element.

    :type parser: :class:`~obspy.core.util.xmlwrapper.XMLParser`
    :param parser: Open XMLParser object.
    :type origin_el: etree.element
    :param origin_el: origin element to be parsed.
    :return: A ObsPy :class:`~obspy.core.event.Origin` object.
    """
    global CURRENT_TYPE

    origin = Origin()
    origin.resource_id = ResourceIdentifier(prefix="/".join([RESOURCE_ROOT, "origin"]))

    # I guess setting the program used as the method id is fine.
    origin.method_id = "%s/location_method/%s/1" % (RESOURCE_ROOT,
        parser.xpath2obj('program', origin_el))
    if str(origin.method_id).lower().endswith("none"):
        origin.method_id = None

    # Standard parameters.
    origin.time, origin.time_errors = \
        __toTimeQuantity(parser, origin_el, "time")
    origin.latitude, origin_latitude_error = \
        __toFloatQuantity(parser, origin_el, "latitude")
    origin.longitude, origin_longitude_error = \
        __toFloatQuantity(parser, origin_el, "longitude")
    origin.depth, origin.depth_errors = \
        __toFloatQuantity(parser, origin_el, "depth")

    if origin_longitude_error:
        origin_longitude_error = origin_longitude_error["uncertainty"]
    if origin_latitude_error:
        origin_latitude_error = origin_latitude_error["uncertainty"]

    # Figure out the depth type.
    depth_type = parser.xpath2obj("depth_type", origin_el)

    # Map Seishub specific depth type to the QuakeML depth type.
    if depth_type == "from location program":
        depth_type = "from location"
    if depth_type is not None:
        origin.depth_type = depth_type

    # XXX: CHECK DEPTH ORIENTATION!!

    if CURRENT_TYPE == "seiscomp3":
        origin.depth *= 1000
        if origin.depth_errors.uncertainty:
            origin.depth_errors.uncertainty *= 1000
    else:
        # Convert to m.
        origin.depth *= -1000
        if origin.depth_errors.uncertainty:
            origin.depth_errors.uncertainty *= 1000

    # Earth model.
    earth_mod = parser.xpath2obj('earth_mod', origin_el, str)
    if earth_mod:
        earth_mod = earth_mod.split()
        earth_mod = ",".join(earth_mod)
        origin.earth_model_id = "%s/earth_model/%s/1" % (RESOURCE_ROOT,
            earth_mod)

    if (origin_latitude_error is None or origin_longitude_error is None) and \
        CURRENT_TYPE not in ["seiscomp3", "toni"]:
        print "AAAAAAAAAAAAA"
        raise Exception

    if origin_latitude_error and origin_latitude_error:
        if CURRENT_TYPE in ["baynet", "obspyck"]:
            uncert = OriginUncertainty()
            if origin_latitude_error > origin_longitude_error:
                uncert.azimuth_max_horizontal_uncertainty = 0
            else:
                uncert.azimuth_max_horizontal_uncertainty = 90
            uncert.min_horizontal_uncertainty, \
                uncert.max_horizontal_uncertainty = \
                sorted([origin_longitude_error, origin_latitude_error])
            uncert.min_horizontal_uncertainty *= 1000.0
            uncert.max_horizontal_uncertainty *= 1000.0
            uncert.preferred_description = "uncertainty ellipse"
            origin.origin_uncertainty = uncert
        elif CURRENT_TYPE == "earthworm":
            uncert = OriginUncertainty()
            uncert.horizontal_uncertainty = origin_latitude_error
            uncert.horizontal_uncertainty *= 1000.0
            uncert.preferred_description = "horizontal uncertainty"
            origin.origin_uncertainty = uncert
        elif CURRENT_TYPE in ["seiscomp3", "toni"]:
            pass
        else:
            raise Exception

    # Parse the OriginQuality if applicable.
    if not origin_el.xpath("originQuality"):
        return origin

    origin_quality_el = origin_el.xpath("originQuality")[0]
    origin.quality = OriginQuality()
    origin.quality.associated_phase_count = \
        parser.xpath2obj("associatedPhaseCount", origin_quality_el, int)
    # QuakeML does apparently not distinguish between P and S wave phase
    # count. Some Seishub event files do.
    p_phase_count = parser.xpath2obj("P_usedPhaseCount", origin_quality_el,
                                     int)
    s_phase_count = parser.xpath2obj("S_usedPhaseCount", origin_quality_el,
                                     int)
    # Use both in case they are set.
    if p_phase_count is not None and s_phase_count is not None:
        phase_count = p_phase_count + s_phase_count
        # Also add two Seishub element file specific elements.
        origin.quality.p_used_phase_count = p_phase_count
        origin.quality.s_used_phase_count = s_phase_count
    # Otherwise the total usedPhaseCount should be specified.
    else:
        phase_count = parser.xpath2obj("usedPhaseCount",
                                       origin_quality_el, int)
    if p_phase_count is not None:
        origin.quality.setdefault("extra", AttribDict())
        origin.quality.extra.usedPhaseCountP = {'value': p_phase_count,
                                                'namespace': NAMESPACE}
    if s_phase_count is not None:
        origin.quality.setdefault("extra", AttribDict())
        origin.quality.extra.usedPhaseCountS = {'value': s_phase_count,
                                                'namespace': NAMESPACE}
    origin.quality.used_phase_count = phase_count

    associated_station_count = \
        parser.xpath2obj("associatedStationCount", origin_quality_el, int)
    used_station_count = parser.xpath2obj("usedStationCount",
        origin_quality_el, int)
    depth_phase_count = parser.xpath2obj("depthPhaseCount", origin_quality_el,
        int)
    standard_error = parser.xpath2obj("standardError", origin_quality_el,
        float)
    azimuthal_gap = parser.xpath2obj("azimuthalGap", origin_quality_el, float)
    secondary_azimuthal_gap = \
        parser.xpath2obj("secondaryAzimuthalGap", origin_quality_el, float)
    ground_truth_level = parser.xpath2obj("groundTruthLevel",
        origin_quality_el, str)
    minimum_distance = parser.xpath2obj("minimumDistance", origin_quality_el,
        float)
    maximum_distance = parser.xpath2obj("maximumDistance", origin_quality_el,
        float)
    median_distance = parser.xpath2obj("medianDistance", origin_quality_el,
        float)
    if minimum_distance is not None:
        minimum_distance = kilometer2degrees(minimum_distance)
    if maximum_distance is not None:
        maximum_distance = kilometer2degrees(maximum_distance)
    if median_distance is not None:
        median_distance = kilometer2degrees(median_distance)

    if associated_station_count is not None:
        origin.quality.associated_station_count = associated_station_count
    if used_station_count is not None:
        origin.quality.used_station_count = used_station_count
    if depth_phase_count is not None:
        origin.quality.depth_phase_count = depth_phase_count
    if standard_error is not None and not math.isnan(standard_error):
        origin.quality.standard_error = standard_error
    if azimuthal_gap is not None:
        origin.quality.azimuthal_gap = azimuthal_gap
    if secondary_azimuthal_gap is not None:
        origin.quality.secondary_azimuthal_gap = secondary_azimuthal_gap
    if ground_truth_level is not None:
        origin.quality.ground_truth_level = ground_truth_level
    if minimum_distance is not None:
        origin.quality.minimum_distance = minimum_distance
    if maximum_distance is not None:
        origin.quality.maximum_distance = maximum_distance
    if median_distance is not None and not math.isnan(median_distance):
        origin.quality.median_distance = median_distance

    return origin
Example #5
0
def PyNASTF(**kwargs):
    """
    PyNASTF: Python Neighbourhood Algorithm STF
    """ 
    #----------------------------- input handler -----------------------------
    config = ConfigParser.RawConfigParser()
    inputpath = 'in.na.cfg'

    class input_handler:
        def __init__(self, inputpath):
            self.inpath = inputpath
            self.config = config.read(os.path.join(os.getcwd(), self.inpath))
            self.event_address = config.get('General', 'event_address')
            self.remote_address = config.get('General', 'remote_address')
            self.network = config.get('General', 'network')
            self.station = config.get('General', 'station')
            self.location = config.get('General', 'location')
            self.channel = config.get('General', 'channel')
            self.filter = eval(config.get('General', 'filter'))
            self.lfreq = float(config.get('General', 'lfreq'))
            self.hfreq = float(config.get('General', 'hfreq'))
            self.resample = eval(config.get('General', 'resample'))
            self.sampling_rate = int(config.get('General', 'sampling_rate'))
            self.min_dist = eval(config.get('General', 'min_dist'))
            self.max_dist = eval(config.get('General', 'max_dist'))
            self.bg_model = config.get('General', 'bg_model')
            self.SNR_limit = eval(config.get('General', 'SNR_limit'))
            self.plot_ph_no = eval(config.get('General', 'plot_phase_noise'))
            self.map = eval(config.get('General', 'map'))
            self.plot_azi = eval(config.get('General', 'plot_azi'))

    # create the input class
    inp = input_handler(inputpath)

    # modifying the input objects in a way to be usable in the next steps!
    inp.network = inp.network.split(',')
    for _i in xrange(len(inp.network)):
        inp.network[_i] = inp.network[_i].strip()
    
    inp.channel = inp.channel.split(',')
    for _i in xrange(len(inp.channel)):
        inp.channel[_i] = inp.channel[_i].strip()
    
    # s_tb: Signal time before, s_ta: Signal time after
    # n_tb: Noise Time before, n_ta: Noise time after
    s_tb=-3; s_ta=9
    n_tb=-150; n_ta=-30
    
    ev_name, ev_lat, ev_lon, ev_dp, ev_date = \
            pdata_reader(address = inp.event_address, remote_address=inp.remote_address)
    for _i in range(len(ev_name)):
        ev_name[_i] = os.path.join(ev_name[_i], 'BH')
    for ev_enum in xrange(len(ev_name)):
        e_add = ev_name[ev_enum]
        print '\n==========='
        print 'Event %s/%s: \n%s' %(ev_enum+1, len(ev_name), e_add)
        print '==========='
        if not os.path.isdir(os.path.join(e_add.split('/')[-2], 'infiles')): 
            os.makedirs(os.path.join(e_add.split('/')[-2], 'infiles')) 
        metadata = []
        msg_header = 'Event information; Lat, Lon, Depth\n'
        msg_header += '%.6f %.6f %.6f\n' %(ev_lat[ev_enum],ev_lon[ev_enum], ev_dp[ev_enum])
        msg_p = 'P-wave data ' + 17*'*' + '\n'
        msg_sh = 'SH-wave data ' + 17*'*' + '\n'
        all_p_data = []; all_sh_data = []
        all_sta_add = glob.glob(os.path.join(e_add, '*.*.*.*'))
        all_sta_add.sort()
        print len(all_sta_add)
        for sta_add in all_sta_add:
            print '.',
            try:
                tr = read(sta_add)[0]
            except Exception, e:
                print e
                continue
            if not inp.network == ['*']:
                if not tr.stats.network in inp.network: continue
            if not tr.stats.channel in inp.channel: continue
            #epi_dist_prev = locations2degrees(tr.stats.sac.evla, tr.stats.sac.evlo,
            #            tr.stats.sac.stla, tr.stats.sac.stlo)
            epi_km = gps2DistAzimuth(tr.stats.sac.evla, tr.stats.sac.evlo,
                        tr.stats.sac.stla, tr.stats.sac.stlo)[0]
            epi_dist = kilometer2degrees(epi_km/1000.)
            # XXX for testing!
            #epi_dist = tr.stats.sac.gcarc
            if not inp.min_dist<=epi_dist<=inp.max_dist: continue
            if 'Z' in tr.stats.channel:
                tr_tw = time_window(tr, model=inp.bg_model)
                ph_arr = tr_tw.arr_time(epi_dist, req_phase='P')
                # XXX for testing
                #ph_arr = tr.stats.sac.t0
                if ph_arr == -12345.0: continue
                tr = preproc(tr, filter=inp.filter, hfreq=inp.hfreq, lfreq=inp.lfreq, 
                            resample=inp.resample, sampling_rate=inp.sampling_rate)
                SNR, l1_noise, l2_noise, p_data, flag_exist = \
                        SNR_calculator(tr, ev_date[ev_enum], 
                        ph_arr, s_tb=s_tb, s_ta=s_ta, n_tb=n_tb, n_ta=n_ta, method='squared',
                        plot_ph_no=inp.plot_ph_no,
                        address=os.path.join(e_add.split('/')[-2], 'infiles'))
                if not flag_exist: continue
                if SNR < inp.SNR_limit: continue
                innastats_str = '%s\n%.6f %.6f %.6f\n%.6f %.6f %.6f\n' %(tr.stats.station,
                            tr.stats.sac.stla, tr.stats.sac.stlo, ph_arr+s_tb, 
                            SNR, l1_noise, l2_noise)
                az, ba = azbackaz(tr)
                all_p_data.append([tr.stats.station, tr.stats.location, SNR, az, 
                                        p_data, innastats_str]) 
            if 'N' in tr.stats.channel:
                try:
                    tr_E = read(sta_add[:-1] + 'E')[0]
                except Exception, e:
                    print 'Cannot read: \n%s' %(sta_add[:-1] + 'E')
                    continue
                tr = preproc(tr, filter=inp.filter, hfreq=inp.hfreq, lfreq=inp.lfreq, 
                            resample=inp.resample, sampling_rate=inp.sampling_rate)
                tr_E = preproc(tr_E, filter=inp.filter, hfreq=inp.hfreq, lfreq=inp.lfreq, 
                            resample=inp.resample, sampling_rate=inp.sampling_rate)
                tr_sh = tr.copy() 
                az, tr_sh.data = rotater(tr, tr_E)
                if not az: continue 
                tr_tw = time_window(tr_sh, model=inp.bg_model)
                ph_arr = tr_tw.arr_time(epi_dist, req_phase='S')
                if ph_arr == -12345.0: continue
                SNR, l1_noise, l2_noise, sh_data, flag_exist = \
                        SNR_calculator(tr_sh, ev_date[ev_enum], 
                        ph_arr, s_tb=s_tb, s_ta=s_ta, n_tb=n_tb, n_ta=n_ta, method='squared',
                        plot_ph_no=inp.plot_ph_no,
                        address=os.path.join(e_add.split('/')[-2], 'infiles'))
                if not flag_exist: continue
                if SNR < inp.SNR_limit: continue
                innastats_str = '%s\n%.6f %.6f %.6f\n%.6f %.6f %.6f\n' %(tr_sh.stats.station,
                            tr_sh.stats.sac.stla, tr_sh.stats.sac.stlo, ph_arr+s_tb, 
                            SNR, l1_noise, l2_noise)
                all_sh_data.append([tr_sh.stats.station, tr_sh.stats.location, SNR, az, 
                                            sh_data, innastats_str])
def __toOrigin(parser, origin_el):
    """
    Parses a given origin etree element.

    :type parser: :class:`~obspy.core.util.xmlwrapper.XMLParser`
    :param parser: Open XMLParser object.
    :type origin_el: etree.element
    :param origin_el: origin element to be parsed.
    :return: A ObsPy :class:`~obspy.core.event.Origin` object.
    """
    global CURRENT_TYPE

    origin = Origin()
    origin.resource_id = ResourceIdentifier(
        prefix="/".join([RESOURCE_ROOT, "origin"]))

    # I guess setting the program used as the method id is fine.
    origin.method_id = "%s/location_method/%s/1" % (
        RESOURCE_ROOT, parser.xpath2obj('program', origin_el))
    if str(origin.method_id).lower().endswith("none"):
        origin.method_id = None

    # Standard parameters.
    origin.time, origin.time_errors = \
        __toTimeQuantity(parser, origin_el, "time")
    origin.latitude, origin_latitude_error = \
        __toFloatQuantity(parser, origin_el, "latitude")
    origin.longitude, origin_longitude_error = \
        __toFloatQuantity(parser, origin_el, "longitude")
    origin.depth, origin.depth_errors = \
        __toFloatQuantity(parser, origin_el, "depth")

    if origin_longitude_error:
        origin_longitude_error = origin_longitude_error["uncertainty"]
    if origin_latitude_error:
        origin_latitude_error = origin_latitude_error["uncertainty"]

    # Figure out the depth type.
    depth_type = parser.xpath2obj("depth_type", origin_el)

    # Map Seishub specific depth type to the QuakeML depth type.
    if depth_type == "from location program":
        depth_type = "from location"
    if depth_type is not None:
        origin.depth_type = depth_type

    # XXX: CHECK DEPTH ORIENTATION!!

    if CURRENT_TYPE == "seiscomp3":
        origin.depth *= 1000
        if origin.depth_errors.uncertainty:
            origin.depth_errors.uncertainty *= 1000
    else:
        # Convert to m.
        origin.depth *= -1000
        if origin.depth_errors.uncertainty:
            origin.depth_errors.uncertainty *= 1000

    # Earth model.
    earth_mod = parser.xpath2obj('earth_mod', origin_el, str)
    if earth_mod:
        earth_mod = earth_mod.split()
        earth_mod = ",".join(earth_mod)
        origin.earth_model_id = "%s/earth_model/%s/1" % (RESOURCE_ROOT,
                                                         earth_mod)

    if (origin_latitude_error is None or origin_longitude_error is None) and \
        CURRENT_TYPE not in ["seiscomp3", "toni"]:
        print "AAAAAAAAAAAAA"
        raise Exception

    if origin_latitude_error and origin_latitude_error:
        if CURRENT_TYPE in ["baynet", "obspyck"]:
            uncert = OriginUncertainty()
            if origin_latitude_error > origin_longitude_error:
                uncert.azimuth_max_horizontal_uncertainty = 0
            else:
                uncert.azimuth_max_horizontal_uncertainty = 90
            uncert.min_horizontal_uncertainty, \
                uncert.max_horizontal_uncertainty = \
                sorted([origin_longitude_error, origin_latitude_error])
            uncert.min_horizontal_uncertainty *= 1000.0
            uncert.max_horizontal_uncertainty *= 1000.0
            uncert.preferred_description = "uncertainty ellipse"
            origin.origin_uncertainty = uncert
        elif CURRENT_TYPE == "earthworm":
            uncert = OriginUncertainty()
            uncert.horizontal_uncertainty = origin_latitude_error
            uncert.horizontal_uncertainty *= 1000.0
            uncert.preferred_description = "horizontal uncertainty"
            origin.origin_uncertainty = uncert
        elif CURRENT_TYPE in ["seiscomp3", "toni"]:
            pass
        else:
            raise Exception

    # Parse the OriginQuality if applicable.
    if not origin_el.xpath("originQuality"):
        return origin

    origin_quality_el = origin_el.xpath("originQuality")[0]
    origin.quality = OriginQuality()
    origin.quality.associated_phase_count = \
        parser.xpath2obj("associatedPhaseCount", origin_quality_el, int)
    # QuakeML does apparently not distinguish between P and S wave phase
    # count. Some Seishub event files do.
    p_phase_count = parser.xpath2obj("P_usedPhaseCount", origin_quality_el,
                                     int)
    s_phase_count = parser.xpath2obj("S_usedPhaseCount", origin_quality_el,
                                     int)
    # Use both in case they are set.
    if p_phase_count is not None and s_phase_count is not None:
        phase_count = p_phase_count + s_phase_count
        # Also add two Seishub element file specific elements.
        origin.quality.p_used_phase_count = p_phase_count
        origin.quality.s_used_phase_count = s_phase_count
    # Otherwise the total usedPhaseCount should be specified.
    else:
        phase_count = parser.xpath2obj("usedPhaseCount", origin_quality_el,
                                       int)
    if p_phase_count is not None:
        origin.quality.setdefault("extra", AttribDict())
        origin.quality.extra.usedPhaseCountP = {
            'value': p_phase_count,
            'namespace': NAMESPACE
        }
    if s_phase_count is not None:
        origin.quality.setdefault("extra", AttribDict())
        origin.quality.extra.usedPhaseCountS = {
            'value': s_phase_count,
            'namespace': NAMESPACE
        }
    origin.quality.used_phase_count = phase_count

    associated_station_count = \
        parser.xpath2obj("associatedStationCount", origin_quality_el, int)
    used_station_count = parser.xpath2obj("usedStationCount",
                                          origin_quality_el, int)
    depth_phase_count = parser.xpath2obj("depthPhaseCount", origin_quality_el,
                                         int)
    standard_error = parser.xpath2obj("standardError", origin_quality_el,
                                      float)
    azimuthal_gap = parser.xpath2obj("azimuthalGap", origin_quality_el, float)
    secondary_azimuthal_gap = \
        parser.xpath2obj("secondaryAzimuthalGap", origin_quality_el, float)
    ground_truth_level = parser.xpath2obj("groundTruthLevel",
                                          origin_quality_el, str)
    minimum_distance = parser.xpath2obj("minimumDistance", origin_quality_el,
                                        float)
    maximum_distance = parser.xpath2obj("maximumDistance", origin_quality_el,
                                        float)
    median_distance = parser.xpath2obj("medianDistance", origin_quality_el,
                                       float)
    if minimum_distance is not None:
        minimum_distance = kilometer2degrees(minimum_distance)
    if maximum_distance is not None:
        maximum_distance = kilometer2degrees(maximum_distance)
    if median_distance is not None:
        median_distance = kilometer2degrees(median_distance)

    if associated_station_count is not None:
        origin.quality.associated_station_count = associated_station_count
    if used_station_count is not None:
        origin.quality.used_station_count = used_station_count
    if depth_phase_count is not None:
        origin.quality.depth_phase_count = depth_phase_count
    if standard_error is not None and not math.isnan(standard_error):
        origin.quality.standard_error = standard_error
    if azimuthal_gap is not None:
        origin.quality.azimuthal_gap = azimuthal_gap
    if secondary_azimuthal_gap is not None:
        origin.quality.secondary_azimuthal_gap = secondary_azimuthal_gap
    if ground_truth_level is not None:
        origin.quality.ground_truth_level = ground_truth_level
    if minimum_distance is not None:
        origin.quality.minimum_distance = minimum_distance
    if maximum_distance is not None:
        origin.quality.maximum_distance = maximum_distance
    if median_distance is not None and not math.isnan(median_distance):
        origin.quality.median_distance = median_distance

    return origin