422 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			422 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
import getopt
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import signal
 | 
						|
 | 
						|
from fractions import Fraction
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
from gempa import CAPS
 | 
						|
 | 
						|
 | 
						|
class Record:
 | 
						|
    net = ""
 | 
						|
    sta = ""
 | 
						|
    loc = ""
 | 
						|
    cha = ""
 | 
						|
    samples = []
 | 
						|
    start = CAPS.Time.GMT()
 | 
						|
    numerator = 0
 | 
						|
    denominator = 1
 | 
						|
    nSamp = 0
 | 
						|
    unit = ""
 | 
						|
 | 
						|
 | 
						|
usage_info = f"""Usage:
 | 
						|
 | 
						|
  {os.path.basename(__file__)} [options]
 | 
						|
 | 
						|
Push data into CAPS. The input and output formats can be easily adjusted.
 | 
						|
 | 
						|
Options:
 | 
						|
  -H, --host                   Host name of CAPS server to connect to. Default: localhost:18003.
 | 
						|
  -h, --help                   Display this help message and exit.
 | 
						|
  -i, --input                  Name of input file. Create a generic signal if not given.
 | 
						|
  -f, --format                 Format of input data. Supported: slist, unavco.
 | 
						|
  -m, --multiplier             Multiplier applied to data samples for generating integers.
 | 
						|
  -n, --network                Network code of data to be sent. Read from input if not given.
 | 
						|
 | 
						|
Examples:
 | 
						|
 | 
						|
Generate generic signal assuming AM network code and send to a caps server with default port 18003
 | 
						|
  {os.path.basename(__file__)} -n AM -H localhost:18003
 | 
						|
 | 
						|
Read data file in slist format, upscale values by 1000000000 and send in RAW format to the default caps server
 | 
						|
  {os.path.basename(__file__)} -i file.slist -f slist -m 1000000000
 | 
						|
 | 
						|
Read data file in unavco 1.0 format and send in RAW format. The network code must be specified.
 | 
						|
  {os.path.basename(__file__)} -f unavco -i unavco-pressure-v1-0.txt -n AB
 | 
						|
 | 
						|
Read data file in unavco 1.1 format and send in RAW format
 | 
						|
  {os.path.basename(__file__)} -f unavco -i unavco-tilt-v1-1.txt
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
def usage(exitcode=0):
 | 
						|
    sys.stderr.write(usage_info)
 | 
						|
    return exitcode
 | 
						|
 | 
						|
 | 
						|
output = CAPS.Plugin("data2caps")
 | 
						|
 | 
						|
 | 
						|
def readSlist(filePath, multiplier=1.0, network=None):
 | 
						|
    # supported formats:
 | 
						|
    # TIMESERIES AM_R1108_00_SHZ_R, 8226 samples, 50 sps, 2018-04-10T10:20:03.862000, SLIST, FLOAT, M/S**2
 | 
						|
    # 0.000134157
 | 
						|
    # ...
 | 
						|
    data = Record()
 | 
						|
    samples = []
 | 
						|
    all_data = []
 | 
						|
    try:
 | 
						|
        with open(filePath, "r", encoding="utf8") as file:
 | 
						|
            all_data = [line.strip() for line in file.readlines()]
 | 
						|
    except FileNotFoundError:
 | 
						|
        print(f"File '{filePath}' not found.", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    if len(all_data) == 0:
 | 
						|
        print(f"No data read from file {filePath}", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    header = all_data[0]
 | 
						|
    dataType = header.split(",")[0].split(" ")[0]
 | 
						|
    if dataType != "TIMESERIES":
 | 
						|
        print(
 | 
						|
            f"Unsupported data type {dataType} in {filePath}. Supported: TIMESERIES",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        return False
 | 
						|
 | 
						|
    if network:
 | 
						|
        data.net = network
 | 
						|
    else:
 | 
						|
        data.net = header.split(",")[0].split(" ")[1].split("_")[0]
 | 
						|
    data.sta = header.split(",")[0].split(" ")[1].split("_")[1]
 | 
						|
    data.loc = header.split(",")[0].split(" ")[1].split("_")[2]
 | 
						|
    data.cha = header.split(",")[0].split(" ")[1].split("_")[3]
 | 
						|
    data.nSamp = int(header.split(",")[1].split(" ")[-2])
 | 
						|
 | 
						|
    try:
 | 
						|
        frac = Fraction(header.split(",")[2].split(" ")[-2]).limit_denominator()
 | 
						|
    except Exception:
 | 
						|
        print("Unknown sample rate", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    data.numerator = frac.numerator
 | 
						|
    data.denominator = frac.denominator
 | 
						|
 | 
						|
    time = header.split(",")[3].strip(" ")
 | 
						|
    data.start = CAPS.Time.FromString(time, "%FT%T.%f")
 | 
						|
    data.unit = header.split(",")[6].strip(" ")
 | 
						|
    samples = [float(x) for x in all_data[1:]]
 | 
						|
    min_abs = min(abs(x) for x in samples)
 | 
						|
    max_abs = max(abs(x) for x in samples)
 | 
						|
    print(
 | 
						|
        f"  + minimum/maximum absolute value found in data: {min_abs}/{max_abs}",
 | 
						|
        file=sys.stderr,
 | 
						|
    )
 | 
						|
    print(f"  + applying multuplier: {multiplier}", file=sys.stderr)
 | 
						|
    for sample in samples:
 | 
						|
        if dataType == "TIMESERIES":
 | 
						|
            data.samples.append(int(round(sample * multiplier, 0)))
 | 
						|
 | 
						|
    min_abs = min(abs(x) for x in data.samples)
 | 
						|
    max_abs = max(abs(x) for x in data.samples)
 | 
						|
    print(
 | 
						|
        "  + minimum/maximum absolute value after applying multiplier: "
 | 
						|
        f"{min_abs}/{max_abs}",
 | 
						|
        file=sys.stderr,
 | 
						|
    )
 | 
						|
    return data
 | 
						|
 | 
						|
 | 
						|
def readUnavco(filePath, multiplier=1.0, network=None):
 | 
						|
    # supported formats:
 | 
						|
    # version 1.1
 | 
						|
    # # PBO Tiltmeter Data, format version 1.1
 | 
						|
    # # http://pboweb.unavco.org/strain_data
 | 
						|
    # #
 | 
						|
    # # B201 coldwt201bwa2007
 | 
						|
    # # Latitude     : +46.3033 degrees (WGS-84)
 | 
						|
    # # Longitude    : -122.2648 degrees (WGS-84)
 | 
						|
    # # Elevation    : 990 meters (WGS-84)
 | 
						|
    # # Sensor Depth : 24.38 meters
 | 
						|
    # # X Orientation: 316 degrees East of North (magnetic)
 | 
						|
    # # SEED Codes   : PB B201 TT LAE
 | 
						|
    # #
 | 
						|
    # # Timestamp (UTC)   Tilt X (microradians)
 | 
						|
    # 2023-10-05T16:00:00.58 162.981
 | 
						|
 | 
						|
    data = Record()
 | 
						|
    version = None
 | 
						|
    versionSupported = ["1.0", "1.1"]
 | 
						|
    all_data = []
 | 
						|
 | 
						|
    try:
 | 
						|
        with open(filePath, "r", encoding="utf8") as file:
 | 
						|
            # read header
 | 
						|
            all_data = [line.strip().split(" ") for line in file.readlines()]
 | 
						|
    except FileNotFoundError:
 | 
						|
        print(f"File '{filePath}' not found.", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    if len(all_data) == 0:
 | 
						|
        print(f"No data read from file {filePath}", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    cnt = 0
 | 
						|
    for line in all_data:
 | 
						|
        if not line[0].startswith("#"):
 | 
						|
            break
 | 
						|
 | 
						|
        try:
 | 
						|
            if "version" in line[-2]:
 | 
						|
                version = line[-1]
 | 
						|
        except IndexError:
 | 
						|
            pass
 | 
						|
 | 
						|
        cnt += 1
 | 
						|
 | 
						|
    if version not in versionSupported:
 | 
						|
        print(
 | 
						|
            f"Unsupported format version '{version}' of file '{filePath}'. "
 | 
						|
            f"Supported are: {versionSupported}",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        return False
 | 
						|
 | 
						|
    print(f"  + unavco format version: {version}", file=sys.stderr)
 | 
						|
 | 
						|
    # extract meta data
 | 
						|
    header = all_data[:cnt]
 | 
						|
    for line in header:
 | 
						|
        # stream code
 | 
						|
        if set(["seed", "codes"]).issubset(set(x.lower() for x in line)):
 | 
						|
            if network:
 | 
						|
                data.net = network
 | 
						|
            else:
 | 
						|
                if version == "1.0":
 | 
						|
                    if not network:
 | 
						|
                        print(
 | 
						|
                            f"Found version {version}: Must provide network code",
 | 
						|
                            file=sys.stderr,
 | 
						|
                        )
 | 
						|
                        return False
 | 
						|
                elif version == "1.1":
 | 
						|
                    data.net = line[-4]
 | 
						|
                else:
 | 
						|
                    print(
 | 
						|
                        f"Unsupported format version {version}. Supported are: {version}",
 | 
						|
                        file=sys.stderr,
 | 
						|
                    )
 | 
						|
                    return False
 | 
						|
            data.sta = line[-3]
 | 
						|
            data.loc = line[-2]
 | 
						|
            data.cha = line[-1]
 | 
						|
 | 
						|
    # physical unit
 | 
						|
    if "Timestamp" in line[1]:
 | 
						|
        unit = line[-1].strip("(").strip(")")
 | 
						|
        if "microradians" in unit:
 | 
						|
            if multiplier == 1000.0:
 | 
						|
                unit = "nRad"
 | 
						|
                print(f"  + converting data in microradians to {unit}", file=sys.stderr)
 | 
						|
 | 
						|
            elif multiplier == 1.0:
 | 
						|
                unit = "nRad"
 | 
						|
                print(
 | 
						|
                    f"  + converting data from microradians to {unit} for high precision",
 | 
						|
                    file=sys.stderr,
 | 
						|
                )
 | 
						|
                multiplier = 1000
 | 
						|
 | 
						|
        if "hPa" in unit:
 | 
						|
            if multiplier == 1.0:
 | 
						|
                multiplier = 100
 | 
						|
                unit = "Pa"
 | 
						|
                print(f"  + converting data from hPa to {unit}", file=sys.stderr)
 | 
						|
 | 
						|
        data.unit = unit
 | 
						|
 | 
						|
    if version == "1.0":
 | 
						|
        time1 = CAPS.Time.FromString(all_data[cnt][0] + all_data[cnt][1], "%F%T.%f")
 | 
						|
        time2 = CAPS.Time.FromString(
 | 
						|
            all_data[cnt + 1][0] + all_data[cnt + 1][1], "%F%T.%f"
 | 
						|
        )
 | 
						|
    elif version == "1.1":
 | 
						|
        time1 = CAPS.Time.FromString(all_data[cnt][0], "%FT%T.%f")
 | 
						|
        time2 = CAPS.Time.FromString(all_data[cnt + 1][0], "%FT%T.%f")
 | 
						|
    else:
 | 
						|
        return False
 | 
						|
 | 
						|
    data.start = time1
 | 
						|
    try:
 | 
						|
        frac = Fraction(1.0 / (time2 - time1).length()).limit_denominator()
 | 
						|
    except Exception:
 | 
						|
        print("Unknown sample rate", file=sys.stderr)
 | 
						|
        return False
 | 
						|
 | 
						|
    data.numerator = frac.numerator
 | 
						|
    data.denominator = frac.denominator
 | 
						|
    # extract time series
 | 
						|
    for i in all_data[cnt:]:
 | 
						|
        if version == "1.0":
 | 
						|
            data.samples.append(int(round(float(i[2]) * multiplier, 0)))
 | 
						|
        elif version == "1.1":
 | 
						|
            data.samples.append(int(round(float(i[1]) * multiplier, 0)))
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
 | 
						|
    data.nSamp = len(data.samples)
 | 
						|
 | 
						|
    return data
 | 
						|
 | 
						|
 | 
						|
def signal_handler(sig, frame):  # pylint: disable=W0613
 | 
						|
    print("Caught Ctrl+C!", file=sys.stderr)
 | 
						|
    output.quit()
 | 
						|
    sys.exit(0)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    inputFormat = None
 | 
						|
    inputFile = None
 | 
						|
    host = "localhost"
 | 
						|
    port = 18003
 | 
						|
    multiplier = 1
 | 
						|
    network = None
 | 
						|
 | 
						|
    try:
 | 
						|
        opts, _ = getopt.getopt(
 | 
						|
            sys.argv[1:],
 | 
						|
            "hH:i:f:m:n:",
 | 
						|
            ["help", "host=", "input=", "format=", "multiplier=", "network="],
 | 
						|
        )
 | 
						|
    except getopt.GetoptError as err:
 | 
						|
        # print help information and exit:
 | 
						|
        print(str(err))  # will print something like "option -a not recognized"
 | 
						|
        return usage(2)
 | 
						|
 | 
						|
    addr = None
 | 
						|
 | 
						|
    signal.signal(signal.SIGINT, signal_handler)
 | 
						|
 | 
						|
    for o, a in opts:
 | 
						|
        if o in ["-h", "--help"]:
 | 
						|
            return usage()
 | 
						|
 | 
						|
        if o in ["-H", "--host"]:
 | 
						|
            addr = a
 | 
						|
        elif o in ["-i", "--input"]:
 | 
						|
            inputFile = a
 | 
						|
        elif o in ["-f", "--format"]:
 | 
						|
            inputFormat = a
 | 
						|
        elif o in ["-m", "--multiplier"]:
 | 
						|
            multiplier = float(a)
 | 
						|
        elif o in ["-n", "--network"]:
 | 
						|
            network = a
 | 
						|
        else:
 | 
						|
            assert False, "unhandled option"
 | 
						|
 | 
						|
    if addr:
 | 
						|
        if ":" in addr:
 | 
						|
            try:
 | 
						|
                host, port = addr.split(":")
 | 
						|
            except BaseException:
 | 
						|
                print(f"Invalid host address given: {addr}\n", file=sys.stderr)
 | 
						|
                return 1
 | 
						|
        else:
 | 
						|
            host = addr
 | 
						|
 | 
						|
    if port:
 | 
						|
        try:
 | 
						|
            port = int(port)
 | 
						|
        except BaseException:
 | 
						|
            print(f"Invalid port given: {port}", file=sys.stderr)
 | 
						|
            return 1
 | 
						|
    else:
 | 
						|
        port = 18003
 | 
						|
 | 
						|
    if not host:
 | 
						|
        host = "localhost"
 | 
						|
 | 
						|
    output.setHost(host)
 | 
						|
    output.setPort(port)
 | 
						|
    output.setBufferSize(1 << 30)
 | 
						|
    output.enableLogging()
 | 
						|
 | 
						|
    res = None
 | 
						|
    print("Summary", file=sys.stderr)
 | 
						|
    print(f"  + input file: {inputFile}", file=sys.stderr)
 | 
						|
    print(f"  + input format: {inputFormat}", file=sys.stderr)
 | 
						|
    print(f"  + caps server: {host}:{port}", file=sys.stderr)
 | 
						|
    if network:
 | 
						|
        print(f"  + set network: {network}", file=sys.stderr)
 | 
						|
 | 
						|
    # generic signal
 | 
						|
    if not inputFormat and not inputFile:
 | 
						|
        startTime = CAPS.Time.GMT()
 | 
						|
        print("Sending generic test data", file=sys.stderr)
 | 
						|
        x = np.array([1, 2], dtype=np.int32)
 | 
						|
 | 
						|
        if not network:
 | 
						|
            network = "AB"
 | 
						|
        print(f"Assuming network '{network}'", file=sys.stderr)
 | 
						|
 | 
						|
        res = output.push(
 | 
						|
            network, "HMA", "", "BHZ", startTime, 1, 1, "m", x, CAPS.DT_INT32
 | 
						|
        )
 | 
						|
 | 
						|
        if res != CAPS.Plugin.Success:
 | 
						|
            print("Failed to send packet", file=sys.stderr)
 | 
						|
 | 
						|
        output.close()
 | 
						|
        return 0
 | 
						|
 | 
						|
    if not inputFile:
 | 
						|
        print("Must provide input file", file=sys.stderr)
 | 
						|
        return 1
 | 
						|
 | 
						|
    # data from input file
 | 
						|
    data = None
 | 
						|
    if inputFormat == "slist":
 | 
						|
        data = readSlist(inputFile, multiplier, network)
 | 
						|
    elif inputFormat == "unavco":
 | 
						|
        data = readUnavco(inputFile, multiplier, network)
 | 
						|
    else:
 | 
						|
        return 1
 | 
						|
 | 
						|
    if not data:
 | 
						|
        print(f"Error parsing {inputFile}", file=sys.stderr)
 | 
						|
        return 1
 | 
						|
 | 
						|
    print(f"Sending data to CAPS server {host}:{port}", file=sys.stderr)
 | 
						|
 | 
						|
    res = output.push(
 | 
						|
        data.net,
 | 
						|
        data.sta,
 | 
						|
        data.loc,
 | 
						|
        data.cha,
 | 
						|
        data.start,
 | 
						|
        data.numerator,
 | 
						|
        data.denominator,
 | 
						|
        data.unit,
 | 
						|
        data.samples,
 | 
						|
        CAPS.DT_INT32,
 | 
						|
    )
 | 
						|
 | 
						|
    if res != CAPS.Plugin.Success:
 | 
						|
        output.close()
 | 
						|
        print("Failed to send packet", file=sys.stderr)
 | 
						|
        return 1
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    sys.exit(main())
 |