Source code for openquake.cat.parsers.gcmt_ndk_parser

# ------------------- The OpenQuake Model Building Toolkit --------------------
# Copyright (C) 2022 GEM Foundation
#           _______  _______        __   __  _______  _______  ___   _
#          |       ||       |      |  |_|  ||  _    ||       ||   | | |
#          |   _   ||   _   | ____ |       || |_|   ||_     _||   |_| |
#          |  | |  ||  | |  ||____||       ||       |  |   |  |      _|
#          |  |_|  ||  |_|  |      |       ||  _   |   |   |  |     |_
#          |       ||      |       | ||_|| || |_|   |  |   |  |    _  |
#          |_______||____||_|      |_|   |_||_______|  |___|  |___| |_|
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding: utf-8


"""
Parser for moment tensor catalogue in GCMT format into a set of GCMT classes
"""

import re
import datetime
import numpy as np
from math import floor, fabs
from linecache import getlines
import openquake.cat.gcmt_utils as utils
from openquake.cat.gcmt_catalogue import (GCMTHypocentre, GCMTCentroid,
                                          GCMTPrincipalAxes, GCMTNodalPlanes,
                                          GCMTMomentTensor, GCMTEvent,
                                          GCMTCatalogue)


def _read_date_from_string(str1):
    """
    Reads the date from a string in the format YYYY/MM/DD and returns
    :class: datetime.date
    """
    full_date = list(map(int, str1.split('/')))
    return datetime.date(full_date[0], full_date[1], full_date[2])


def _read_time_from_string(str1):
    """
    Reads the time from a string in the format HH:MM:SS.S and returns
    :class: datetime.time
    """
    full_time = list(map(float, str1.split(':')))
    hour = int(full_time[0])
    minute = int(full_time[1])
    if full_time[2] > 59.99:
        minute += 1
        second = 0
    else:
        second = int(full_time[2])

    microseconds = int((full_time[2] - floor(full_time[2])) * 1000000)
    return datetime.time(hour, minute, second, microseconds)


def _read_moment_tensor_from_ndk_string(ndk_string, system='USE'):
    """
    Reads the moment tensor from the ndk_string representation
    ndk_string = [Mrr, sigMrr, Mtt, sigMtt, Mpp, sigMpp, Mrt, sigMrt, Mrp,
        sigMrp, Mtp, sigMtp]
    Output tensors should be of format:
        expected = [[Mtt, Mtp, Mtr],
                    [Mtp, Mpp, Mpr],
                    [Mtr, Mpr, Mrr]]
        sigma = [[sigMtt, sigMtp, sigMtr],
                 [sigMtp, sigMpp, sigMpr],
                 [sigMtr, sigMpr, sigMrr]]
    Exponent returned in Nm

    :param str ndk_string:
        String of data in ndk format (line 4 of event)
    :param str system:
        Reference frame of tensor Up, South, East {USE} or North, East, Down
        (NED)
    """
    exponent = float(ndk_string[0:2]) - 7.
    mkr = np.array([2, 9, 15], dtype=int)
    vector = []
    for i in range(0, 6):
        vector.extend([float(ndk_string[mkr[0]:mkr[1]]),
                       float(ndk_string[mkr[1]:mkr[2]])])
        mkr = mkr + 13
    vector = np.array(vector)
    mrr, mtt, mpp, mrt, mrp, mtp = tuple(vector[np.arange(0, 12, 2)])
    sig_mrr, sig_mtt, sig_mpp, sig_mrt, sig_mrp, sig_mtp = \
        tuple(vector[np.arange(1, 13, 2)])

    tensor = utils.COORD_SYSTEM[system](mrr, mtt, mpp, mrt, mrp, mtp)
    tensor = (10. ** exponent) * tensor

    sigma = utils.COORD_SYSTEM[system](sig_mrr, sig_mtt, sig_mpp,
                                       sig_mrt, sig_mrp, sig_mtp)
    sigma = (10. ** exponent) * sigma

    return tensor, sigma, exponent


[docs] class ParseNDKtoGCMT(object): """ Implements the parser to read a file in ndk format to the GCMT catalogue """ def __init__(self, filename): ''' :param str filename: Name of the catalogue file in ndk format ''' self.filename = filename
[docs] def read_file(self, start_year=None, end_year=None): ''' Reads the file ''' raw_data = getlines(self.filename) num_lines = len(raw_data) if ((float(num_lines) / 5.) - float(num_lines // 5)) > 1E-9: raise IOError('GCMT represented by 5 lines - number in file not' ' a multiple of 5!') number_gcmts = num_lines // 5 # Pre-allocates list data_gcmts = [None for i in range(number_gcmts)] id0 = 0 print('Parsing catalogue ...') for iloc in range(number_gcmts): data_gcmts[iloc] = self.read_ndk_event(raw_data, id0) id0 += 5 print('complete. Contains %s moment tensors' % len(data_gcmts)) if not start_year: start_year = data_gcmts[0].centroid.date.year if not end_year: end_year = data_gcmts[-1].centroid.date.year return GCMTCatalogue(start_year, end_year, data_gcmts)
[docs] def read_ndk_event(self, raw_data, id0): """ Reads a 5-line batch of data into a set of GCMTs """ gcmt = GCMTEvent() # Get hypocentre ndkstring = raw_data[id0].rstrip('\n') gcmt.hypocentre = self._read_hypocentre_from_ndk_string(ndkstring) # GCMT metadata ndkstring = raw_data[id0 + 1].rstrip('\n') gcmt = self._get_metadata_from_ndk_string(gcmt, ndkstring) # Get Centroid ndkstring = raw_data[id0 + 2].rstrip('\n') gcmt.centroid = self._read_centroid_from_ndk_string(ndkstring, gcmt.hypocentre) # Get Moment Tensor ndkstring = raw_data[id0 + 3].rstrip('\n') gcmt.moment_tensor = self._get_moment_tensor_from_ndk_string(ndkstring) # Get principal axes ndkstring = raw_data[id0 + 4].rstrip('\n') gcmt.principal_axes = self._get_principal_axes_from_ndk_string( ndkstring[3:48], exponent=gcmt.moment_tensor.exponent) # Get Nodal Planes gcmt.nodal_planes = self._get_nodal_planes_from_ndk_string( ndkstring[57:]) # Get Moment and Magnitude gcmt.moment, gcmt.version, gcmt.magnitude = \ self._get_moment_from_ndk_string(ndkstring, gcmt.moment_tensor.exponent) return gcmt
def _read_hypocentre_from_ndk_string(self, linestring): """ Reads the hypocentre data from the ndk string to return an instance of the GCMTHypocentre class """ hypo = GCMTHypocentre() hypo.source = linestring[0:4] hypo.date = _read_date_from_string(linestring[5:15]) hypo.time = _read_time_from_string(linestring[16:26]) hypo.latitude = float(linestring[27:33]) hypo.longitude = float(linestring[34:41]) hypo.depth = float(linestring[42:47]) magnitudes = list(map(float, (linestring[48:55]).split(' '))) if magnitudes[0] > 0.: hypo.m_b = magnitudes[0] if magnitudes[1] > 0.: hypo.m_s = magnitudes[1] hypo.location = linestring[56:].strip() return hypo def _get_metadata_from_ndk_string(self, gcmt, ndk_string): """ Reads the GCMT metadata from line 2 of the ndk batch """ gcmt.identifier = ndk_string[:16].strip() inversion_data = re.split('[A-Z:]+', ndk_string[17:61]) gcmt.metadata['BODY'] = list(map(float, inversion_data[1].split())) gcmt.metadata['SURFACE'] = list(map(float, inversion_data[2].split())) gcmt.metadata['MANTLE'] = list(map(float, inversion_data[3].split())) further_meta = re.split('[: ]+', ndk_string[62:]) gcmt.metadata['CMT'] = int(further_meta[1]) gcmt.metadata['FUNCTION'] = {'TYPE': further_meta[2], 'DURATION': float(further_meta[3])} return gcmt def _read_centroid_from_ndk_string(self, ndk_string, hypocentre): """ Reads the centroid data from the ndk string to return an instance of the GCMTCentroid class :param str ndk_string: String of data (line 3 of ndk format) :param hypocentre: Instance of the GCMTHypocentre class """ centroid = GCMTCentroid(hypocentre.date, hypocentre.time) data = ndk_string[:58].split() centroid.centroid_type = data[0].rstrip(':') data = list(map(float, data[1:])) time_diff = data[0] if fabs(time_diff) > 1E-6: centroid._get_centroid_time(time_diff) centroid.time_error = data[1] centroid.latitude = data[2] centroid.latitude_error = data[3] centroid.longitude = data[4] centroid.longitude_error = data[5] centroid.depth = data[6] centroid.depth_error = data[7] centroid.depth_type = ndk_string[59:63] centroid.centroid_id = ndk_string[64:].strip() return centroid def _get_moment_tensor_from_ndk_string(self, ndk_string): """ Reads the moment tensor from the ndk_string and returns an instance of the GCMTMomentTensor class. By default the ndk format uses the Up, South, East (USE) reference system. """ moment_tensor = GCMTMomentTensor('USE') tensor_data = _read_moment_tensor_from_ndk_string(ndk_string, 'USE') moment_tensor.tensor = tensor_data[0] moment_tensor.tensor_sigma = tensor_data[1] moment_tensor.exponent = tensor_data[2] return moment_tensor def _get_principal_axes_from_ndk_string(self, ndk_string, exponent): """ Gets the principal axes from the ndk string and returns an instance of the GCMTPrincipalAxes class """ axes = GCMTPrincipalAxes() # The principal axes is defined in characters 3:48 of the 5th line exponent = 10. ** exponent axes.t_axis = {'eigenvalue': exponent * float(ndk_string[0:8]), 'plunge': float(ndk_string[8:11]), 'azimuth': float(ndk_string[11:15])} axes.b_axis = {'eigenvalue': exponent * float(ndk_string[15:23]), 'plunge': float(ndk_string[23:26]), 'azimuth': float(ndk_string[26:30])} axes.p_axis = {'eigenvalue': exponent * float(ndk_string[30:38]), 'plunge': float(ndk_string[38:41]), 'azimuth': float(ndk_string[41:])} return axes def _get_nodal_planes_from_ndk_string(self, ndk_string): ''' Reads the nodal plane information (represented by 5th line [57:] of the tensor representation) and returns an instance of the GCMTNodalPlanes class ''' planes = GCMTNodalPlanes() planes.nodal_plane_1 = {'strike': float(ndk_string[0:3]), 'dip': float(ndk_string[3:6]), 'rake': float(ndk_string[6:11])} planes.nodal_plane_2 = {'strike': float(ndk_string[11:15]), 'dip': float(ndk_string[15:18]), 'rake': float(ndk_string[18:])} return planes def _get_moment_from_ndk_string(self, ndk_string, exponent): """ Gets the moment and the moment magnitude """ moment = float(ndk_string[49:56]) * (10. ** exponent) version = ndk_string[:3] magnitude = utils.moment_magnitude_scalar(moment) return moment, version, magnitude