12 Commits

2 changed files with 78 additions and 75 deletions

View File

@@ -48,7 +48,7 @@ def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max
import logging import logging
from base_logger import getDefaultLogger from base_logger import getDefaultLogger
from timeit import default_timer as timer from timeit import default_timer as timer
from math import ceil, floor from math import ceil, floor, isnan
import numpy as np import numpy as np
import scipy import scipy
import obspy import obspy
@@ -70,6 +70,8 @@ def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator from matplotlib.ticker import MultipleLocator
from matplotlib.contour import ContourSet from matplotlib.contour import ContourSet
import xml.etree.ElementTree as ET
import json
logger = getDefaultLogger('igfash') logger = getDefaultLogger('igfash')
@@ -239,6 +241,9 @@ verbose: {verbose}")
grid_y_max = int(ceil(y_max / grid_dim) * grid_dim) grid_y_max = int(ceil(y_max / grid_dim) * grid_dim)
grid_y_min = int(floor(y_min / grid_dim) * grid_dim) grid_y_min = int(floor(y_min / grid_dim) * grid_dim)
grid_lat_max, grid_lon_max = utm.to_latlon(grid_x_max, grid_y_max, utm_zone_number, utm_zone_letter)
grid_lat_min, grid_lon_min = utm.to_latlon(grid_x_min, grid_y_min, utm_zone_number, utm_zone_letter)
# rectangular grid # rectangular grid
nx = int((grid_x_max - grid_x_min) / grid_dim) + 1 nx = int((grid_x_max - grid_x_min) / grid_dim) + 1
ny = int((grid_y_max - grid_y_min) / grid_dim) + 1 ny = int((grid_y_max - grid_y_min) / grid_dim) + 1
@@ -434,8 +439,11 @@ verbose: {verbose}")
PGA = np.zeros(shape=(nx * ny)) PGA = np.zeros(shape=(nx * ny))
# use dask parallel computing
start = timer() start = timer()
use_pp = False
if use_pp: # use dask parallel computing
pbar = ProgressBar() pbar = ProgressBar()
pbar.register() pbar.register()
# iter = range(0,len(distances)) # iter = range(0,len(distances))
@@ -446,10 +454,24 @@ verbose: {verbose}")
imls = [dask.delayed(compute_IMT_exceedance)(rx_lat[i], rx_lon[i], distances[i].flatten(), fr, p, lambdas, imls = [dask.delayed(compute_IMT_exceedance)(rx_lat[i], rx_lon[i], distances[i].flatten(), fr, p, lambdas,
forecast_len, lambdas_perc, m_range, m_pdf, m_cdf, model, forecast_len, lambdas_perc, m_range, m_pdf, m_cdf, model,
log_level=logging.DEBUG, imt=imt, IMT_min=0.0, IMT_max=2.0, log_level=logging.DEBUG, imt=imt, IMT_min=0.0, IMT_max=2.0, rx_label=i,
rx_label=i) for i in iter] rtol=0.1, use_cython=False) for i in iter]
iml = dask.compute(*imls) iml = dask.compute(*imls)
iml_grid_raw.append(list(iml)) iml_grid_raw.append(list(iml))
else:
iml_grid_raw = []
iter = indices
for imt in products:
iml = []
for i in iter:
iml_i = compute_IMT_exceedance(rx_lat[i], rx_lon[i], distances[i].flatten(), fr, p, lambdas, forecast_len,
lambdas_perc, m_range, m_pdf, m_cdf, model, imt=imt, IMT_min = 0.0,
IMT_max = 2.0, rx_label = i, rtol = 0.1, use_cython=False)
iml.append(iml_i)
logger.info(f"Estimated {imt} at rx {i} is {iml_i}")
iml_grid_raw.append(iml)
end = timer() end = timer()
logger.info(f"Ground motion exceedance computation time: {round(end - start, 1)} seconds") logger.info(f"Ground motion exceedance computation time: {round(end - start, 1)} seconds")
@@ -468,16 +490,6 @@ verbose: {verbose}")
else: else:
iml_grid_prep = iml_grid_raw iml_grid_prep = iml_grid_raw
# TODO Remove. Saving coordinates to
# north, south = lat.max(), lat.min()
# east, west = lon.max(), lon.min()
# bbox_json = {"south": float(south), "west": float(west),
# "north": float(north), "east": float(east)}
# with open("overlay_bounds.json", "w", encoding="utf‑8") as fh:
# json.dump(bbox_json, fh, indent=2)
#
# logger.info(f"Saved bbox to overlay_bounds.json → {bbox_json}")
for j in range(0, len(products)): for j in range(0, len(products)):
vmin = min(x for x in iml_grid_prep[j] if x is not np.nan) vmin = min(x for x in iml_grid_prep[j] if x is not np.nan)
vmax = max(x for x in iml_grid_prep[j] if x is not np.nan) vmax = max(x for x in iml_grid_prep[j] if x is not np.nan)
@@ -491,6 +503,12 @@ verbose: {verbose}")
mode='reflect', anti_aliasing=False) mode='reflect', anti_aliasing=False)
iml_grid_hd[iml_grid_hd == 0.0] = np.nan # change zeroes back to nan iml_grid_hd[iml_grid_hd == 0.0] = np.nan # change zeroes back to nan
# trim edges so the grid is not so blocky
vmin_hd = min(x for x in iml_grid_hd.flatten() if not isnan(x))
vmax_hd = max(x for x in iml_grid_hd.flatten() if not isnan(x))
trim_thresh = vmin
iml_grid_hd[iml_grid_hd < trim_thresh] = np.nan
# generate image overlay # generate image overlay
north, south = lat.max(), lat.min() # Latitude range north, south = lat.max(), lat.min() # Latitude range
east, west = lon.max(), lon.min() # Longitude range east, west = lon.max(), lon.min() # Longitude range
@@ -499,55 +517,27 @@ verbose: {verbose}")
map_center = [np.mean([north, south]), np.mean([east, west])] map_center = [np.mean([north, south]), np.mean([east, west])]
# Create an image from the grid # Create an image from the grid
cmap_name = 'YlOrRd'
cmap = plt.get_cmap(cmap_name)
fig, ax = plt.subplots(figsize=(6, 6)) fig, ax = plt.subplots(figsize=(6, 6))
ax.imshow(iml_grid_hd, origin='lower', cmap='viridis') ax.imshow(iml_grid_hd, origin='lower', cmap=cmap, vmin=vmin, vmax=vmax)
ax.axis('off') ax.axis('off')
# Save the figure # Save the figure
fig.canvas.draw() fig.canvas.draw()
svg_path = f"overlay_{j}.svg" overlay_filename = f"overlay_{j}.svg"
plt.savefig(svg_path, bbox_inches="tight", pad_inches=0, transparent=True) plt.savefig(overlay_filename, bbox_inches="tight", pad_inches=0, transparent=True)
plt.close(fig) plt.close(fig)
# Embed geographic bounding box into the SVG
import xml.etree.ElementTree as ET # <-- TODO place this with other imports map_bounds = dict(zip(("south", "west", "north", "east"),
import json map(float, (grid_lat_min, grid_lon_min, grid_lat_max, grid_lon_max))))
tree = ET.parse(overlay_filename)
# ----------------------------------------- tree.getroot().set("data-map-bounds", json.dumps(map_bounds))
# Inject bounding box (BBOX) metadata into the SVG tree.write(overlay_filename, encoding="utf-8", xml_declaration=True)
# ----------------------------------------- logger.info(f"Saved geographic bounds to SVG metadata (data-map-bounds): {overlay_filename} → {map_bounds}")
north, south = lat.max(), lat.min()
east, west = lon.max(), lon.min()
bbox_dict = {
"south": float(south),
"west": float(west),
"north": float(north),
"east": float(east)
}
tree = ET.parse(svg_path)
root = tree.getroot()
# Remove any existing <metadata> tags
for old_meta in root.findall("{http://www.w3.org/2000/svg}metadata"):
root.remove(old_meta)
# Add new <metadata> element with the bounding box JSON
meta_elem = ET.SubElement(root, "metadata")
meta_elem.text = json.dumps(bbox_dict)
# (Optional) Also store the bounding box as a data attribute for quick access
root.set("data-bbox", json.dumps(bbox_dict))
tree.write(svg_path, encoding="utf-8", xml_declaration=True)
logger.info(f"Embedded bbox into {svg_path} → {bbox_dict}")
# -----------------------------------------
# END Inject bounding box (BBOX) metadata into the SVG
# -----------------------------------------
# Make the color bar # Make the color bar
cmap_name = 'viridis'
width = 50 width = 50
height = 500 height = 500
@@ -555,16 +545,20 @@ verbose: {verbose}")
gradient = np.vstack((gradient, gradient)).T gradient = np.vstack((gradient, gradient)).T
gradient = np.tile(gradient, (1, width)) gradient = np.tile(gradient, (1, width))
colorbar_title = products[j]
if "PGA" in colorbar_title or "SA" in colorbar_title:
colorbar_title = colorbar_title + " (g)"
fig, ax = plt.subplots(figsize=((width + 40) / 100.0, (height + 20) / 100.0), fig, ax = plt.subplots(figsize=((width + 40) / 100.0, (height + 20) / 100.0),
dpi=100) # Increase fig size for labels dpi=100) # Increase fig size for labels
ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(cmap_name), ax.imshow(gradient, aspect='auto', cmap=cmap.reversed(),
extent=[0, 1, vmin, vmax]) # Note: extent order is different for vertical extent=[0, 1, vmin, vmax_hd]) # Note: extent order is different for vertical
ax.set_xticks([]) # Remove x-ticks for vertical colorbar ax.set_xticks([]) # Remove x-ticks for vertical colorbar
num_ticks = 11 # Show more ticks num_ticks = 11 # Show more ticks
tick_positions = np.linspace(vmin, vmax, num_ticks) tick_positions = np.linspace(vmin, vmax_hd, num_ticks)
ax.set_yticks(tick_positions) ax.set_yticks(tick_positions)
ax.set_yticklabels([f"{tick:.2f}" for tick in tick_positions]) # format tick labels ax.set_yticklabels([f"{tick:.2f}" for tick in tick_positions]) # format tick labels
ax.set_title(imt, pad=15) ax.set_title(colorbar_title, loc='right', pad=15)
fig.subplots_adjust(left=0.25, right=0.75, bottom=0.05, top=0.95) # Adjust Layout fig.subplots_adjust(left=0.25, right=0.75, bottom=0.05, top=0.95) # Adjust Layout
fig.savefig("colorbar_" + str(j) + ".svg", bbox_inches='tight') fig.savefig("colorbar_" + str(j) + ".svg", bbox_inches='tight')
plt.close(fig) plt.close(fig)

View File

@@ -23,6 +23,15 @@ from seismic_hazard_forecasting import main as shf
def main(argv): def main(argv):
def str2bool(v):
if v.lower() in ("True", "TRUE", "yes", "true", "t", "y", "1"):
return True
elif v.lower() in ("False", "FALSE", "no", "false", "f", "n", "0"):
return False
else:
raise argparse.ArgumentTypeError("Boolean value expected.")
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("catalog_file", help="Path to input file of type 'catalog'") parser.add_argument("catalog_file", help="Path to input file of type 'catalog'")
@@ -30,23 +39,23 @@ def main(argv):
parser.add_argument("--pdf_file", help="Path to input file of type 'PDF'", type=str, default=None, required=False) parser.add_argument("--pdf_file", help="Path to input file of type 'PDF'", type=str, default=None, required=False)
parser.add_argument("--m_file", help="Path to input file of type 'm'", type=str, default=None, required=False) parser.add_argument("--m_file", help="Path to input file of type 'm'", type=str, default=None, required=False)
parser.add_argument("--m_select", type=bool) parser.add_argument("--m_select", type=str2bool)
parser.add_argument("--mag_label", type=str, default=None, required=False) parser.add_argument("--mag_label", type=str, default=None, required=False)
parser.add_argument("--mc", type=float, default=None, required=False) parser.add_argument("--mc", type=float, default=None, required=False)
parser.add_argument("--m_max", type=float, default=None, required=False) parser.add_argument("--m_max", type=float, default=None, required=False)
parser.add_argument("--m_kde_method", type=str) parser.add_argument("--m_kde_method", type=str)
parser.add_argument("--xy_select", type=bool) parser.add_argument("--xy_select", type=str2bool)
parser.add_argument("--grid_dim", type=int) parser.add_argument("--grid_dim", type=int)
parser.add_argument("--xy_win_method", type=bool) parser.add_argument("--xy_win_method", type=str2bool)
parser.add_argument("--rate_select", type=bool) parser.add_argument("--rate_select", type=str2bool)
parser.add_argument("--time_win_duration", type=float) parser.add_argument("--time_win_duration", type=float)
parser.add_argument("--forecast_select", type=bool) parser.add_argument("--forecast_select", type=str2bool)
parser.add_argument("--custom_rate", type=float, default=None, required=False) parser.add_argument("--custom_rate", type=float, default=None, required=False)
parser.add_argument("--forecast_len", type=float) parser.add_argument("--forecast_len", type=float)
parser.add_argument("--time_unit", type=str) parser.add_argument("--time_unit", type=str)
parser.add_argument("--model", type=str) parser.add_argument("--model", type=str)
parser.add_argument("--products_string", type=str) parser.add_argument("--products_string", type=str)
parser.add_argument("--verbose", type=bool) parser.add_argument("--verbose", type=str2bool)
args = parser.parse_args() args = parser.parse_args()
shf(**vars(args)) shf(**vars(args))