# stdlib imports
import os.path
import logging
from datetime import datetime
import re
from collections import OrderedDict
# third party imports
import numpy as np
from impactutils.io.smcontainers import ShakeMapOutputContainer
from impactutils.rupture import constants
from mapio.shake import ShakeGrid
from mapio.geodict import GeoDict
# local imports
from .base import CoreModule, Contents
from shakemap.utils.config import get_config_paths
import shakemap
def _oq_to_gridxml(oqimt):
"""
Convert openquake IMT nomenclature to grid.xml friendly form.
Note: The grid.xml form only handles periods up to 9.9, after
that there is no way to tell the difference between 10.0 and 1.0.
Examples:
SA(1.0) (Spectral Acceleration at 1 second) -> PSA10
SA(0.3) (Spectral Acceleration at 0.3 second) -> PSA03
SA(15.0) (Spectral Acceleration at 15 seconds) -> NOT SUPPORTED
SA(3) (Spectral Acceleration at 3 seconds) -> PSA30
SA(.5) (Spectral Acceleration at 0.5 seconds) -> PSA05
Args:
oqimt (str): Openquake IMT nomenclature string.
Returns:
str: grid.xml friendly IMT string.
Raises:
ValueError: when there is no corresponding filename-friendly
IMT representation, or when frequency exceeds 9.9.
"""
if oqimt in ["PGA", "PGV", "MMI"]:
return oqimt
float_pattern = r"[-+]?\d*\.\d+|\d+"
periods = re.findall(float_pattern, oqimt)
if not len(periods):
fmt = 'IMT string "%s" has no file-name friendly representation.'
raise ValueError(fmt % oqimt)
period = periods[0]
if period.find(".") < 0:
integer = period
fraction = "0"
else:
integer, fraction = period.split(".")
if not len(integer):
integer = "0"
if int(integer) >= 10:
raise ValueError("Periods >= than 10 seconds not supported.")
fileimt = f"PSA{integer}{fraction}"
return fileimt
[docs]class GridXMLModule(CoreModule):
"""
gridxml -- Create grid.xml and uncertainty.xml files from shake_result.hdf.
"""
command_name = "gridxml"
targets = [r"products/grid\.xml", r"products/uncertainty\.xml"]
dependencies = [("products/shake_result.hdf", True)]
def __init__(self, eventid):
super(GridXMLModule, self).__init__(eventid)
self.contents = Contents("XML Grids", "gridxml", eventid)
[docs] def execute(self):
"""Create grid.xml and uncertainty.xml files.
Raises:
NotADirectoryError: When the event data directory does not exist.
FileNotFoundError: When the the shake_result HDF file does not
exist.
"""
logger = logging.getLogger(__name__)
install_path, data_path = get_config_paths()
datadir = os.path.join(data_path, self._eventid, "current", "products")
if not os.path.isdir(datadir):
raise NotADirectoryError(f"{datadir} is not a valid directory.")
datafile = os.path.join(datadir, "shake_result.hdf")
if not os.path.isfile(datafile):
raise FileNotFoundError(f"{datafile} does not exist.")
# Open the ShakeMapOutputContainer and extract the data
container = ShakeMapOutputContainer.load(datafile)
# get all of the grid layers and the geodict
if container.getDataType() != "grid":
raise NotImplementedError(
"gridxml module can only function on "
"gridded data, not sets of points"
)
components = container.getComponents()
for component in components:
xml_types = ["grid", "uncertainty"]
for xml_type in xml_types:
layers = OrderedDict()
field_keys = OrderedDict()
gridnames = container.getIMTs(component)
for gridname in gridnames:
imt_field = _oq_to_gridxml(gridname)
imtdict = container.getIMTGrids(gridname, component)
if xml_type == "grid":
grid_data = imtdict["mean"]
metadata = imtdict["mean_metadata"]
elif xml_type == "uncertainty":
grid_data = imtdict["std"]
metadata = imtdict["std_metadata"]
units = metadata["units"]
digits = metadata["digits"]
# convert from HDF units to legacy grid.xml units
if xml_type == "grid":
if units == "ln(cm/s)":
grid_data = np.exp(grid_data)
units = "cm/s"
elif units == "ln(g)":
grid_data = np.exp(grid_data) * 100
units = "%g"
else:
pass
if xml_type == "grid":
layers[imt_field] = grid_data
field_keys[imt_field] = (units, digits)
else:
layers["STD" + imt_field] = grid_data
field_keys["STD" + imt_field] = (units, digits)
if xml_type == "grid":
grid_data, _ = container.getArray([], "vs30")
units = "m/s"
digits = metadata["digits"]
layers["SVEL"] = grid_data
field_keys["SVEL"] = (units, digits)
geodict = GeoDict(metadata)
config = container.getConfig()
# event dictionary
info = container.getMetadata()
event_info = info["input"]["event_information"]
event_dict = {}
event_dict["event_id"] = event_info["event_id"]
event_dict["magnitude"] = float(event_info["magnitude"])
event_dict["depth"] = float(event_info["depth"])
event_dict["lat"] = float(event_info["latitude"])
event_dict["lon"] = float(event_info["longitude"])
try:
event_dict["event_timestamp"] = datetime.strptime(
event_info["origin_time"], constants.TIMEFMT
)
except ValueError:
event_dict["event_timestamp"] = datetime.strptime(
event_info["origin_time"], constants.ALT_TIMEFMT
)
event_dict["event_description"] = event_info["location"]
event_dict["event_network"] = info["input"]["event_information"][
"eventsource"
]
event_dict["intensity_observations"] = info["input"][
"event_information"
]["intensity_observations"]
event_dict["seismic_stations"] = info["input"]["event_information"][
"seismic_stations"
]
if info["input"]["event_information"]["fault_ref"] == "Origin":
event_dict["point_source"] = "True"
else:
event_dict["point_source"] = "False"
# shake dictionary
shake_dict = {}
shake_dict["event_id"] = event_dict["event_id"]
shake_dict["shakemap_id"] = event_dict["event_id"]
shake_dict["shakemap_version"] = info["processing"][
"shakemap_versions"
]["map_version"]
shake_dict["code_version"] = shakemap.__version__
ptime = info["processing"]["shakemap_versions"]["process_time"]
try:
shake_dict["process_timestamp"] = datetime.strptime(
ptime, constants.TIMEFMT
)
except ValueError:
shake_dict["process_timestamp"] = datetime.strptime(
ptime, constants.ALT_TIMEFMT
)
shake_dict["shakemap_originator"] = config["system"]["source_network"]
shake_dict["map_status"] = config["system"]["map_status"]
shake_dict["shakemap_event_type"] = "ACTUAL"
if event_dict["event_id"].endswith("_se"):
shake_dict["shakemap_event_type"] = "SCENARIO"
shake_grid = ShakeGrid(
layers, geodict, event_dict, shake_dict, {}, field_keys=field_keys
)
if component == "GREATER_OF_TWO_HORIZONTAL":
fname = os.path.join(datadir, f"{xml_type}.xml")
else:
fname = os.path.join(datadir, f"{xml_type}_{component}.xml")
logger.debug(f"Saving IMT grids to {fname}")
shake_grid.save(fname) # TODO - set grid version number
cname = os.path.split(fname)[1]
if xml_type == "grid":
self.contents.addFile(
"xmlGrids",
"XML Grid",
f"XML grid of {component} ground motions",
cname,
"text/xml",
)
else:
self.contents.addFile(
"uncertaintyGrids",
"Uncertainty Grid",
f"XML grid of {component} uncertainties",
cname,
"text/xml",
)
container.close()