Source code for shakelib.rupture.origin

#!/usr/bin/env python

# stdlib imports
from xml.dom import minidom
import time as time

# third party
from openquake.hazardlib.geo import point

from impactutils.time.ancient_time import HistoricTime
from shakelib.utils.exception import ShakeLibException
from shakelib.rupture import constants


[docs]class Origin(object): """ The purpose of this class is to read/store event origin information, which is usually derived from an xml file called "event.xml". There is also a mechanism to overwrite information in event.xml with a file in the input directory called "source.txt". Note that this class is generally contained within a Rupture instance. Event values are: - eventsourcecode: the event id. - created: file creation time (Unix epoch - seconds since Jan 1, 1970). - lat: hypocenter latitude (decimal degrees; -90 to 90). - lon: hypocenter longitude (decimal degrees; -180 to 180). - depth: hypocenter depth (km, positive down). - locstring: a free-form descriptive string of location. - mag: earthquake magnitude. - year: 4 digit format. - month: 1-12. - day: 1-31. - hour: 0-23. - minute: 0-59. - second: 0-59. - timezone: abbreviation (i.e., GMT, PST, PDT). - mech: a string specifying the rupture mechanism; the accepted types are RS, SS, NM, and ALL, for reverse slip, strike slip, normal, and unspecified ruptures, respectively. For backward-compatibility, we also check for 'type'. If both 'mech' and 'type' are missing (or empty strings) then 'mech' is set to ALL. """ def __init__(self, event): """ Construct an Origin object. Args: event (dict): Dictionary of values. See list above for required keys. Returns: Origin object. """ # --------------------------------------------------------------------- # Check for missing keys # --------------------------------------------------------------------- missing = [] for req in constants.ORIGIN_REQUIRED_KEYS: if req not in list(event.keys()): missing.append(req) if len(missing): raise Exception('Input event dictionary is missing the following ' 'required keys: "%s"' % (','.join(missing))) # --------------------------------------------------------------------- # Check some types, ranges, and defaults # --------------------------------------------------------------------- if not type(event['eventsourcecode']) is str: raise Exception('eventsourcecode must be a string.') if (event['lat'] > 90) or (event['lat'] < -90): raise Exception('lat must be between -90 and 90 degrees.') if (event['lon'] > 180) or (event['lon'] < -180): raise Exception('lat must be between -180 and 180 degrees.') if 'mech' in event.keys(): if event['mech'] == '': event['mech'] = constants.DEFAULT_MECH if not event['mech'] in constants.RAKEDICT.keys(): raise Exception('mech must be SS, NM, RS, or ALL.') elif 'type' in event.keys(): event['mech'] = event['type'] if event['mech'] == '': event['mech'] = constants.DEFAULT_MECH if not event['mech'] in constants.RAKEDICT.keys(): raise Exception('mech must be SS, NM, RS, or ALL.') else: event['mech'] = constants.DEFAULT_MECH # --------------------------------------------------------------------- # Add keys as class attributes # --------------------------------------------------------------------- for k, v in event.items(): if k == 'rake': setattr(self, k, float(v)) else: setattr(self, k, v) # What about rake? if not hasattr(self, 'rake'): if hasattr(self, 'mech'): mech = self.mech self.rake = constants.RAKEDICT[mech] else: self.rake = constants.RAKEDICT['ALL'] if self.rake is None: self.rake = 0.0
[docs] @classmethod def fromFile(cls, eventxmlfile, sourcefile=None): """ Class method to create a Origin object by specifying an event.xml file, a rupture file, and a source.txt file. Args: eventxmlfile (str): Event xml file (see read_event_file()). sourcefile (str): source.txt file (see read_source()). Returns: Origin object. """ event = read_event_file(eventxmlfile) params = None if sourcefile is not None: params = read_source(sourcefile) event.update(params) return cls(event)
[docs] def getHypo(self): """ Returns: Hypocenter as OpenQuake Point instance. """ return point.Point(self.lon, self.lat, self.depth)
[docs] def setMechanism(self, mech=None, rake=None, dip=None): """ Set the earthquake mechanism manually (overriding any values read in from event.xml or source.txt. If rake and dip are not specified, they will be assigned by mechanism as follows: +-------+--------+-----+ | Mech | Rake | Dip | +=======+========+=====+ | RS | 90 | 40 | +-------+--------+-----+ | NM | -90 | 50 | +-------+--------+-----+ | SS | 0 | 90 | +-------+--------+-----+ | ALL | 45 | 90 | +-------+--------+-----+ Args: mech (str): One of 'RS' (reverse), 'NM' (normal), 'SS' (strike slip), or 'ALL' (unknown). rake (float): Value between -360 and 360 degrees. If set, will override default value for mechanism (see table above). dip (float): Value betweeen 0 and 90 degrees. If set, will override default value for mechanism (see table above). Value will be converted to range between -180 and 180 degrees. """ mechs = {'RS': {'rake': 90.0, 'dip': 40.0}, 'NM': {'rake': -90.0, 'dip': 50.0}, 'SS': {'rake': 0.0, 'dip': 90.0}, 'ALL': {'rake': 45.0, 'dip': 90.0}} if mech not in list(mechs.keys()): raise ShakeLibException( 'Mechanism must be one of: %s' % str( list(mechs.keys()))) if dip is not None: if dip < 0 or dip > 90: raise Exception('Dip must be in range 0-90 degrees.') else: dip = mechs[mech]['dip'] if rake is not None: if rake < -180: rake += 360 if rake > 180: rake -= 360 if rake < -180 or rake > 180: raise Exception( 'Rake must be transformable to be in range -180 to 180 ' 'degrees.') else: rake = mechs[mech]['rake'] self.dip = dip self.rake = rake self.mech = mech
[docs]def read_event_file(eventxml): """ Read event.xml file from disk, returning a dictionary of attributes. Input XML format looks like this: .. code-block:: xml <earthquake id="2008ryan " lat="30.9858" lon="103.3639" mag="7.9" year="2008" month="05" day="12" hour="06" minute="28" second="01" timezone="GMT" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" /> Args: eventxml (str): Path to event XML file OR file-like object. Returns: dict: Dictionary with keys: - eventsourcecode Origin network and origin code (i.e., us2017abcd). - eventsource Origin network ("us"). - time Origin time as an HistoricTime object. - lat Origin latitude - lon Origin longitude - depth Origin depth - mag Origin magnitude - created Process time as an HistoricTime object. - locstring Location string - mechanism Moment mechanism, one of: - 'RS' (Reverse) - 'SS' (Strike-Slip) - 'NM' (Normal) - 'ALL' (Undetermined) """ # fill in default values for mechanism, rake and dip # these may be overriden by values in event.xml, source.txt, or by values # passed in after reading input data. # event = {'mech': DEFAULT_MECH, # 'rake': DEFAULT_RAKE, # 'dip': DEFAULT_DIP} if isinstance(eventxml, str): root = minidom.parse(eventxml) else: data = eventxml.read() root = minidom.parseString(data) # Turn XML content into dictionary eq = root.getElementsByTagName('earthquake')[0] xmldict = dict(eq.attributes.items()) root.unlink() eqdict = {} eqdict['eventsourcecode'] = xmldict['id'] if 'network' in xmldict: eqdict['eventsource'] = xmldict['network'] else: eqdict['eventsource'] = 'us' #?? #look for the productcode attribute if 'productcode' in xmldict: eqdict['productcode'] = xmldict['productcode'] # fix eventsourcecode if not specified correctly if not eqdict['eventsourcecode'].startswith(eqdict['eventsource']): eqdict['eventsourcecode'] = eqdict['eventsource'] + eqdict['eventsourcecode'] year = int(xmldict['year']) month = int(xmldict['month']) day = int(xmldict['day']) hour = int(xmldict['hour']) minute = int(xmldict['minute']) second = int(xmldict['second']) microseconds = int((second - int(xmldict['second']))*1e6) eqdict['time'] = HistoricTime(year,month,day,hour,minute,second,microseconds) eqdict['lat'] = float(xmldict['lat']) eqdict['lon'] = float(xmldict['lon']) eqdict['depth'] = float(xmldict['depth']) eqdict['mag'] = float(xmldict['mag']) # make created field in event.xml optional - set to current UTC time if not # supplied. if 'created' in xmldict: eqdict['created'] = HistoricTime.utcfromtimestamp(int(xmldict['created'])) else: eqdict['created'] = HistoricTime.utcnow() eqdict['locstring'] = xmldict['locstring'] if 'mech' in xmldict: eqdict['mech'] = xmldict['mech'] return eqdict
[docs]def read_source(sourcefile): """ Read source.txt file, which has lines like key=value. Args: sourcefile: Path to source.txt file OR file-like object Returns: dict: Dictionary containing key/value pairs from file. """ isFile = False if isinstance(sourcefile, str): f = open(sourcefile, 'rt') else: isFile = True f = sourcefile params = {} for line in f.readlines(): if line.startswith('#'): continue parts = line.split('=') key = parts[0].strip() value = parts[1].strip() # see if this is some sort of number try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass params[key] = value if not isFile: f.close() return params