Source code for mth5.utils.fdsn_tools

# -*- coding: utf-8 -*-
"""

Tools for FDSN standards

Created on Wed Sep 30 11:47:01 2020

:author: Jared Peacock

:license: MIT

"""

import logging

logger = logging.getLogger(__name__)

period_code_dict = {
    "F": {"min": 1000, "max": 5000},
    "G": {"min": 1000, "max": 5000},
    "D": {"min": 250, "max": 1000},
    "C": {"min": 250, "max": 1000},
    "E": {"min": 80, "max": 250},
    "S": {"min": 10, "max": 80},
    "H": {"min": 80, "max": 250},
    "B": {"min": 10, "max": 80},
    "M": {"min": 1, "max": 10},
    "L": {"min": 0.95, "max": 1.05},
    "V": {"min": 0.095, "max": 0.105},
    "U": {"min": 0.0095, "max": 0.0105},
    "R": {"min": 0.0001, "max": 0.001},
    "P": {"min": 0.00001, "max": 0.0001},
    "T": {"min": 0.000001, "max": 0.00001},
    "Q": {"min": 0, "max": 0.000001},
}

measurement_code_dict = {
    "tilt": "A",
    "creep": "B",
    "calibration": "C",
    "pressure": "D",
    "magnetics": "F",
    "gravity": "G",
    "humidity": "I",
    "temperature": "K",
    "water_current": "O",
    "electric": "Q",
    "rain_fall": "R",
    "linear_strain": "S",
    "tide": "T",
    "wind": "W",
}

measurement_code_dict_reverse = dict(
    [(v, k) for k, v in measurement_code_dict.items()]
)

orientation_code_dict = {
    "N": {"min": 0, "max": 15},
    "E": {"min": 75, "max": 90},
    "Z": {"min": 0, "max": 15},
    "1": {"min": 15, "max": 45},
    "2": {"min": 45, "max": 75},
    "3": {"min": 15, "max": 75},
}

mt_code_dict = {"magnetics": "h", "electric": "e"}


[docs]def get_location_code(channel_obj): """ Get the location code given the components and channel number :param channel_obj: Channel object :type channel_obj: :class:`~mt_metadata.timeseries.Channel` :return: 2 character location code :rtype: string """ location_code = "{0}{1}".format( channel_obj.component[0].upper(), channel_obj.channel_number % 10, ) return location_code
[docs]def get_period_code(sample_rate): """ Get the SEED sampling rate code given a sample rate :param sample_rate: sample rate in samples per second :type sample_rate: float :return: single character SEED sampling code :rtype: string """ period_code = "A" for key, v_dict in sorted(period_code_dict.items()): if (sample_rate >= v_dict["min"]) and (sample_rate <= v_dict["max"]): period_code = key break return period_code
[docs]def get_measurement_code(measurement): """ get SEED sensor code given the measurement type :param measurement: measurement type, e.g. * temperature * electric * magnetic :type measurement: string :return: single character SEED sensor code, if the measurement type has not been defined yet Y is returned. :rtype: string """ sensor_code = "Y" for key, code in measurement_code_dict.items(): if measurement.lower() in key: sensor_code = code return sensor_code
[docs]def get_orientation_code(azimuth, orientation="horizontal"): """ Get orientation code given angle and orientation. This is a general code and the true azimuth is stored in channel :param azimuth: angel assuming 0 is north, 90 is east, 0 is vertical down :type azimuth: float :return: single character SEED orientation code :rtype: string """ orientation_code = "1" horizontal_keys = ["N", "E", "1", "2"] vertical_keys = ["Z", "3"] azimuth = abs(azimuth) % 91 if orientation == "horizontal": test_keys = horizontal_keys elif orientation == "vertical": test_keys = vertical_keys else: raise ValueError( f"{orientation} not supported must be [ 'horizontal' | 'vertical' ]" ) for key in test_keys: angle_min = orientation_code_dict[key]["min"] angle_max = orientation_code_dict[key]["max"] if (azimuth <= angle_max) and (azimuth >= angle_min): orientation_code = key break return orientation_code
[docs]def make_channel_code(channel_obj): """ Make the 3 character SEED channel code :param channel_obj: Channel metadata :type channel_obj: :class:`~mt_metadata.timeseries.Channel` :return: 3 character channel code :type: string """ period_code = get_period_code(channel_obj.sample_rate) sensor_code = get_measurement_code(channel_obj.type) if "z" in channel_obj.component.lower(): orientation_code = get_orientation_code( channel_obj.measurement_tilt, orientation="vertical" ) else: orientation_code = get_orientation_code( channel_obj.measurement_azimuth ) channel_code = "{0}{1}{2}".format( period_code, sensor_code, orientation_code ) return channel_code
[docs]def read_channel_code(channel_code): """ read FDSN channel code :param channel_code: DESCRIPTION :type channel_code: TYPE :return: DESCRIPTION :rtype: TYPE """ if len(channel_code) != 3: msg = ( "Input FDSN channel code is not proper format, should be 3 letters" ) logger.error(msg) raise ValueError(msg) try: period_range = period_code_dict[channel_code[0].upper()] except KeyError: msg = ( f"Could not find period range for {channel_code[0]}. ", "Setting to 1", ) period_range = {"min": 1, "max": 1} try: component = measurement_code_dict_reverse[channel_code[1].upper()] except KeyError: msg = f"Could not find component for {channel_code[1]}" logger.error(msg) raise ValueError(msg) vertical = False try: orientation = orientation_code_dict[channel_code[2].upper()] if channel_code[2].upper() in ["3", "Z"]: vertical = True except KeyError: msg = ( f"Could not find orientation for {channel_code[2]}. ", "Setting to 0.", ) logger.error(msg) raise ValueError(msg) return { "period": period_range, "component": component, "orientation": orientation, "vertical": vertical, }
[docs]def make_mt_channel(code_dict, angle_tol=15): """ :param code_dict: DESCRIPTION :type code_dict: TYPE :return: DESCRIPTION :rtype: TYPE """ try: mt_comp = mt_code_dict[code_dict["component"]] except KeyError: mt_comp = code_dict["component"] if not code_dict["vertical"]: if ( code_dict["orientation"]["min"] >= 0 and code_dict["orientation"]["max"] <= angle_tol ): mt_dir = "x" elif ( code_dict["orientation"]["min"] >= angle_tol and code_dict["orientation"]["max"] <= 45 ): mt_dir = "1" if ( code_dict["orientation"]["min"] >= (90 - angle_tol) and code_dict["orientation"]["max"] <= 90 ): mt_dir = "y" elif code_dict["orientation"]["min"] >= 45 and code_dict[ "orientation" ]["max"] <= (90 - angle_tol): mt_dir = "2" else: if ( code_dict["orientation"]["min"] >= 0 and code_dict["orientation"]["max"] <= angle_tol ): mt_dir = "z" elif ( code_dict["orientation"]["min"] >= angle_tol and code_dict["orientation"]["max"] <= 90 ): mt_dir = "3" mt_code = f"{mt_comp}{mt_dir}" return mt_code