def maketime(timestring): outtime = None try: outtime = HistoricTime.strptime(timestring,TIMEFMT1) except: try: outtime = HistoricTime.strptime(timestring,TIMEFMT2) except: try: outtime = HistoricTime.strptime(timestring,DATEFMT) except: raise Exception('Could not parse time or date from %s' % timestring) return outtime
def maketime(timestring): outtime = None try: outtime = HistoricTime.strptime(timestring, TIMEFMT1) except Exception: try: outtime = HistoricTime.strptime(timestring, TIMEFMT2) except Exception: try: outtime = HistoricTime.strptime(timestring, DATEFMT) except Exception: raise Exception("Could not parse time or date from %s" % timestring) return outtime
def rupture_from_dict(d): """ Method returns either a Rupture subclass (QuadRupture, EdgeRupture, or PointRupture) object based on a GeoJSON dictionary. .. seealso:: :func:`rupture_from_dict_and_origin` Args: d (dict): Rupture GeoJSON dictionary, which must contain origin information in the 'metadata' field. Returns: a Rupture subclass. """ validate_json(d) # We don't want to mess with the input just in case it gets used again d = copy.deepcopy(d) try: d['metadata']['time'] = HistoricTime.strptime(d['metadata']['time'], constants.TIMEFMT) except ValueError: d['metadata']['time'] = HistoricTime.strptime(d['metadata']['time'], constants.ALT_TIMEFMT) origin = Origin(d['metadata']) # What type of rupture is this? geo_type = d['features'][0]['geometry']['type'] if geo_type == 'MultiPolygon': valid_quads = is_quadrupture_class(d) if valid_quads is True: rupt = QuadRupture(d, origin) elif 'mesh_dx' in d['metadata']: # EdgeRupture will have 'mesh_dx' in metadata mesh_dx = d['metadata']['mesh_dx'] rupt = EdgeRupture(d, origin, mesh_dx=mesh_dx) else: raise ValueError('Invalid rupture dictionary.') elif geo_type == 'Point': rupt = PointRupture(origin) return rupt
def rupture_from_dict(d): """ Method returns either a Rupture subclass (QuadRupture, EdgeRupture, or PointRupture) object based on a GeoJSON dictionary. .. seealso:: :func:`rupture_from_dict_and_origin` Args: d (dict): Rupture GeoJSON dictionary, which must contain origin information in the 'metadata' field. Returns: a Rupture subclass. """ validate_json(d) d['metadata']['time'] = HistoricTime.strptime(d['metadata']['time'], constants.TIMEFMT) origin = Origin(d['metadata']) # What type of rupture is this? geo_type = d['features'][0]['geometry']['type'] if geo_type == 'MultiPolygon': # EdgeRupture will have 'mesh_dx' in metadata if 'mesh_dx' in d['metadata']: mesh_dx = d['metadata']['mesh_dx'] rupt = EdgeRupture(d, origin, mesh_dx=mesh_dx) else: rupt = QuadRupture(d, origin) elif geo_type == 'Point': rupt = PointRupture(origin) return rupt
def __init__(self, event): """ Construct an Origin object. Args: event (dict): Dictionary of values. See list above for required keys. Returns: Origin object. Raises: ValueError: When input time is not and cannot be converted to HistoricTime 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.') # make sure that time is an HistoricTime instance if 'time' in event: if isinstance(event['time'],str): try: event['time'] = HistoricTime.strptime(event['time'],TIMEFMT) except ValueError: fmt = 'Input time string %s cannot be converted to datetime.' raise ValueError(fmt % event['time']) 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
def read_event_file(eventxml): """Read event.xml file from disk, returning a dictionary of attributes. Input XML format looks like this (all elements are required unless explicitly labeled optional): .. code-block:: xml <earthquake id="2008ryan" netid="us" network="USGS National Network" (required but may be empty string) lat="30.9858" lon="103.3639" mag="7.9" depth="19.0" time="YYYY-mm-ddTHH:MM:SS.ffffffZ" (omitting fractional seconds is also supported) locstring="EASTERN SICHUAN, CHINA" mech='SS' | 'NM' | 'RS' | 'ALL' (optional) reference="Smith et al. 2016" (optional) productcode='us2008ryan' (optional) /> Args: eventxml (str): Path to event XML file OR file-like object. Returns: dict: Dictionary with keys: - id: Origin network and origin code (i.e., us2017abcd). - netid: Origin network ("us"). - network: (A long-form description of the network) - lat: Origin latitude - lon: Origin longitude - mag: Origin magnitude - depth: Origin depth - time: Origin time as an HistoricTime object. - locstring: Location string - mech: (optional) Moment mechanism, one of: - 'RS' (Reverse) - 'SS' (Strike-Slip) - 'NM' (Normal) - 'ALL' (Undetermined) - reference: (optional) A description of the source of the data. - productcode: (optional) This product source's unique code for this particular ShakeMap. Raises: ValueError: If the time string cannot be parsed into a datetime object KeyError: If any of the required attributes are missing from event.xml """ if isinstance(eventxml, str): tree = dET.parse(eventxml) root = tree.getroot() else: data = eventxml.read() root = dET.fromstring(data) # Turn XML content into dictionary if root.tag == 'earthquake': xmldict = dict(root.items()) else: eq = root.find('earthquake') xmldict = dict(eq.items()) eqdict = {} ######################################################### # A Short Primer on PDL-style Identifiers # Because Everybody (Including The Author) Forgets It. # # In PDL, there are 4 identifiers that fully specify a product: # - source The network that generated the *product* (us, ci, etc.). # - code The unique ID string that identifies this product, # usually prepended by *source* (us2008abcd). # - eventsource The network that created the *origin* (us, ci, etc.) # - eventsourcecode The code within that network that uniquely # identifies the event (2008abcd). # # For our purposes, we're storing *source* and *code* as # *productsource* and *productcode* respectively in the # container, in an effort to reduce confusion about their # meaning. Time will tell. ######################################################### # read in the id fields eqdict['id'] = xmldict['id'] # This isn't optional, but maybe it isn't in some old files if 'network' in xmldict: eqdict['network'] = xmldict['network'] else: eqdict['network'] = "" eqdict['netid'] = xmldict['netid'] # look for the productcode attribute in the xml, # otherwise use the event id if 'productcode' in xmldict: eqdict['productcode'] = xmldict['productcode'] elif isinstance(eventxml, str): eqdict['productcode'] = eqdict['id'] else: # It's up to the user of this data how to construct the # product code pass # Support old event file date/times if 'time' in xmldict: try: eqdict['time'] = HistoricTime.strptime(xmldict['time'], constants.TIMEFMT) except ValueError: try: eqdict['time'] = HistoricTime.strptime(xmldict['time'], constants.ALT_TIMEFMT) except ValueError: raise ValueError("Couldn't convert %s to HistoricTime" % xmldict['time']) else: if 'year' not in xmldict or 'month' not in xmldict or \ 'day' not in xmldict or 'hour' not in xmldict or \ 'minute' not in xmldict or 'second' not in xmldict: raise ValueError("Missing date/time elements in event file.") eqdict['time'] = HistoricTime.datetime(xmldict['year'], xmldict['month'], xmldict['day'], xmldict['hour'], xmldict['minute'], xmldict['second']) eqdict['lat'] = float(xmldict['lat']) eqdict['lon'] = float(xmldict['lon']) eqdict['depth'] = float(xmldict['depth']) eqdict['mag'] = float(xmldict['mag']) eqdict['locstring'] = xmldict['locstring'] if 'mech' in xmldict: eqdict['mech'] = xmldict['mech'] # Older files may have "type" instead of "mech" if 'type' in xmldict: eqdict['type'] = xmldict['type'] if 'reference' in xmldict: eqdict['reference'] = xmldict['reference'] return eqdict
def test_origin(): fault_text = """30.979788 103.454422 1 31.691615 104.419160 1 31.723569 104.374760 1 32.532213 105.220821 1 32.641450 105.135050 20 31.846790 104.246202 20 31.942158 104.205286 20 31.290105 103.284388 20 30.979788 103.454422 1""" event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008ryan" lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" netid="us" network="" locstring="EASTERN SICHUAN, CHINA" mech="" />""" source_text = "mech=RS" ffile = io.StringIO(fault_text) # noqa efile = io.StringIO(event_text) sfile = io.StringIO(source_text) origin = Origin.fromFile(efile, sourcefile=sfile) testdict = { 'mag': 7.9, 'id': '2008ryan', 'locstring': 'EASTERN SICHUAN, CHINA', 'mech': 'RS', 'lon': 103.3639, 'lat': 30.9858, 'depth': 19.0 } for key in testdict.keys(): value = eval(f'origin.{key}') if type(value) is str: assert testdict[key] == value if type(value) is float: np.testing.assert_almost_equal(testdict[key], value) assert origin.mech == "RS" origin.setMechanism("SS") assert origin.mech == "SS" origin.setMechanism("SS", rake=10) assert origin.mech == "SS" assert origin.rake == 10.0 assert origin.dip == 90.0 origin.setMechanism("SS", rake=-350) assert origin.rake == 10.0 origin.setMechanism("SS", rake=370) assert origin.rake == 10.0 with pytest.raises(Exception) as a: origin.setMechanism("SS", dip=100) with pytest.raises(Exception) as a: origin.setMechanism("Strike slip") # Rake too large with pytest.raises(Exception) as a: origin.setMechanism("SS", rake=1111) # Lat is greater than 90 with pytest.raises(Exception) as a: event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" lat="91.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" timezone="GMT" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" mech="" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) # Lon is greater than 180 with pytest.raises(Exception) as a: event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" lat="31.9858" lon="183.3639" mag="7.9" time="2008-05-12T06:28:01Z" timezone="GMT" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) # No event id with pytest.raises(Exception) as a: event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" timezone="GMT" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) # Put mech in event keys event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" mech="SS" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) assert origin.mech == 'SS' # Empty mech event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" mech="" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) assert origin.mech == 'ALL' # Mech not acceptable value with pytest.raises(Exception) as a: # noqa event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" lat="31.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" mech="Strike slip" netid="us" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) # Missing keys with pytest.raises(KeyError): event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" locstring="EASTERN SICHUAN, CHINA" created="1211173621" otime="1210573681" type="" network=""/>""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) # Use "type" instead of "mech" event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008ryan" lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" netid="us" network="" locstring="EASTERN SICHUAN, CHINA" type="RS" />""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) assert origin.mech == 'RS' # No rake or mech event_text = """<?xml version="1.0" encoding="US-ASCII" standalone="yes"?> <earthquake id="2008ryan" lat="30.9858" lon="103.3639" mag="7.9" time="2008-05-12T06:28:01Z" depth="19.0" netid="us" network="" locstring="EASTERN SICHUAN, CHINA" reference="Smith, et al. (2019)" />""" efile = io.StringIO(event_text) origin = Origin.fromFile(efile) assert origin.rake == 0.0 hypo = origin.getHypo() assert hypo.x == origin.lon assert hypo.y == origin.lat assert hypo.z == origin.depth # Write the origin to a file event = {} event['id'] = 'us2000ryan' event['netid'] = 'us' event['network'] = 'USGS Network' event['lat'] = 30.9858 event['lon'] = 103.3639 event['depth'] = 19.0 event['mag'] = 7.9 event['time'] = HistoricTime.strptime('2008-05-12T06:28:01.0Z', constants.TIMEFMT) event['locstring'] = "EASTERN SICHUAN, CHINA" event['mech'] = 'RS' event['reference'] = "Smith, et al. (2019)" event['productcode'] = "us2000ryan" tfile = tempfile.NamedTemporaryFile() xmlfile = tfile.name tfile.close() res = write_event_file(event, xmlfile) with open(xmlfile, 'r') as f: fstr = f.read() target_str = ( '<earthquake id="us2000ryan" netid="us" network="USGS Network" lat="30.9858" lon="103.3639" depth="19.0" mag="7.9" time="2008-05-12T06:28:01Z" locstring="EASTERN SICHUAN, CHINA" mech="RS" reference="Smith, et al. (2019)" productcode="us2000ryan" event_type="ACTUAL"/>' ) assert target_str in fstr if res is not None: print(res) assert False origin = Origin.fromFile(xmlfile) os.remove(xmlfile) assert origin.id == event['id'] assert origin.netid == event['netid'] assert origin.network == event['network'] assert origin.time == event['time']
def __init__(self, event): """ Construct an Origin object. Args: event (dict): Dictionary of values. See list above for required keys. Returns: Origin object. Raises: ValueError: When input time is not and cannot be converted to HistoricTime 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.') # make sure that time is an HistoricTime instance if 'time' in event: if isinstance(event['time'], str): try: event['time'] = HistoricTime.strptime( event['time'], TIMEFMT) except ValueError: fmt = 'Input time string %s cannot be converted to datetime.' raise ValueError(fmt % event['time']) 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