#!/usr/bin/env python # -*- coding: utf-8 -*- # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright (C) 2014-2017 GEM Foundation and G. Weatherill # # OpenQuake 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. # # OpenQuake 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 OpenQuake. If not, see <http://www.gnu.org/licenses/>. """ ...configure implement SourceConfigure, the class to set-up the source and site configuration for comparing the trellis plots """ import numpy as np from math import sqrt, pi, sin, cos, fabs from copy import deepcopy import matplotlib.pyplot as plt from openquake.hazardlib.geo import Point, Line, Polygon, Mesh, PlanarSurface from openquake.hazardlib.scalerel.wc1994 import WC1994 from openquake.hazardlib.site import Site, SiteCollection from openquake.hazardlib.source.rupture import BaseRupture as Rupture from openquake.hazardlib.source.point import PointSource from openquake.hazardlib.gsim.base import ( SitesContext, RuptureContext, DistancesContext) from smtk.sm_utils import _save_image from openquake.hazardlib.contexts import get_distances TO_RAD = pi / 180. FROM_RAD = 180. / pi # Default point - some random location on Earth DEFAULT_POINT = Point(45.18333, 9.15, 0.) def create_planar_surface(top_centroid, strike, dip, area, aspect): """ Given a central location, create a simple planar rupture :param top_centroid: Centroid of trace of the rupture, as instance of :class: openquake.hazardlib.geo.point.Point :param float strike: Strike of rupture(Degrees) :param float dip: Dip of rupture (degrees) :param float area: Area of rupture (km^2) :param float aspect: Aspect ratio of rupture :returns: Rupture as an instance of the :class: openquake.hazardlib.geo.surface.planar.PlanarSurface """ rad_dip = dip * pi / 180. width = sqrt(area / aspect) length = aspect * width # Get end points by moving the top_centroid along strike top_right = top_centroid.point_at(length / 2., 0., strike) top_left = top_centroid.point_at(length / 2., 0., (strike + 180.) % 360.) # Along surface width surface_width = width * cos(rad_dip) vertical_depth = width * sin(rad_dip) dip_direction = (strike + 90.) % 360. bottom_right = top_right.point_at(surface_width, vertical_depth, dip_direction) bottom_left = top_left.point_at(surface_width, vertical_depth, dip_direction) # Create the rupture return PlanarSurface(strike, dip, top_left, top_right, bottom_right, bottom_left) def get_hypocentre_on_planar_surface(plane, hypo_loc=None): """ Determines the location of the hypocentre within the plane :param plane: Rupture plane as instance of :class: openquake.hazardlib.geo.surface.planar.PlanarSurface :param tuple hypo_loc: Hypocentre location as fraction of rupture plane, as a tuple of (Along Strike, Down Dip), e.g. a hypocentre located in the centroid of the rupture plane would be input as (0.5, 0.5), whereas a hypocentre located in a position 3/4 along the length, and 1/4 of the way down dip of the rupture plane would be entered as (0.75, 0.25) :returns: Hypocentre location as instance of :class: openquake.hazardlib.geo.point.Point """ centroid = plane.get_middle_point() if hypo_loc is None: return centroid along_strike_dist = (hypo_loc[0] * plane.length) - (0.5 * plane.length) down_dip_dist = (hypo_loc[1] * plane.width) - (0.5 * plane.width) if along_strike_dist >= 0.: along_strike_azimuth = plane.strike else: along_strike_azimuth = (plane.strike + 180.) % 360. along_strike_dist = (0.5 - hypo_loc[0]) * plane.length # Translate along strike hypocentre = centroid.point_at(along_strike_dist, 0., along_strike_azimuth) # Translate down dip horizontal_dist = down_dip_dist * cos(TO_RAD * plane.dip) vertical_dist = down_dip_dist * sin(TO_RAD * plane.dip) if down_dip_dist >= 0.: down_dip_azimuth = (plane.strike + 90.) % 360. else: down_dip_azimuth = (plane.strike - 90.) % 360. down_dip_dist = (0.5 - hypo_loc[1]) * plane.width horizontal_dist = down_dip_dist * cos(TO_RAD * plane.dip) return hypocentre.point_at(horizontal_dist, vertical_dist, down_dip_azimuth) def vs30_to_z1pt0_as08(vs30): """ Extracts a depth to 1.0 km/s velocity layer using the relationship proposed in Abrahamson & Silva 2008 :param float vs30: Input Vs30 (m/s) """ if vs30 < 180.: return np.exp(6.745) elif vs30 > 500.: return np.exp(5.394 - 4.48 * np.log(vs30 / 500.)) else: return np.exp(6.745 - 1.35 * np.log(vs30 / 180.)) def vs30_to_z1pt0_cy08(vs30): """ Extracts a depth to 1.0 km/s velocity layer using the relationship proposed in Chiou & Youngs 2008 :param float vs30: Input Vs30 (m/s) """ return np.exp(28.5 - (3.82 / 8.) * np.log((vs30 ** 8.) + (378.7 ** 8.))) def z1pt0_to_z2pt5(z1pt0): """ Calculates the depth to 2.5 km/s layer (km /s) using the model presented in Campbell & Bozorgnia (2007) :param float z1pt0: Depth (m) to the 1.0 km/s layer :returns: Depth (km) to 2.5 km/s layer """ return 0.519 + 3.595 * (z1pt0 / 1000.) def vs30_to_z1pt0_cy14(vs30, japan=False): """ Returns the estimate depth to the 1.0 km/s velocity layer based on Vs30 from Chiou & Youngs (2014) California model :param numpy.ndarray vs30: Input Vs30 values in m/s :param bool japan: If true returns the Japan model, otherwise the California model :returns: Z1.0 in m """ if japan: c1 = 412. ** 2. c2 = 1360.0 ** 2. return np.exp((-5.23 / 2.0) * np.log((np.power(vs30,2.) + c1) / (c2 + c1))) else: c1 = 571 ** 4. c2 = 1360.0 ** 4. return np.exp((-7.15 / 4.0) * np.log((vs30 ** 4. + c1) / (c2 + c1))) def vs30_to_z2pt5_cb14(vs30, japan=False): """ Converts vs30 to depth to 2.5 km/s interface using model proposed by Campbell & Bozorgnia (2014) :param vs30: Vs30 values (numpy array or float) :param bool japan: Use Japan formula (True) or California formula (False) :returns: Z2.5 in km """ if japan: return np.exp(5.359 - 1.102 * np.log(vs30)) else: return np.exp(7.089 - 1.144 * np.log(vs30)) def _setup_site_peripherals(azimuth, origin_point, vs30, z1pt0, z2pt5, strike, surface): """ For a given configuration determine the site periferal values """ if not z1pt0: z1pt0 = vs30_to_z1pt0_cy14(vs30) if not z2pt5: z2pt5 = vs30_to_z2pt5_cb14(vs30) azimuth = (strike + azimuth) % 360. origin_location = get_hypocentre_on_planar_surface(surface, origin_point) origin_location.depth = 0.0 return azimuth, origin_location, z1pt0, z2pt5 def _rup_to_point(distance, surface, origin, azimuth, distance_type='rjb', iter_stop=1E-3, maxiter=1000): """ Place a point at a given distance from a rupture along a specified azimuth """ pt0 = origin pt1 = origin.point_at(distance, 0., azimuth) r_diff = np.inf dip = surface.dip sin_dip = np.sin(np.radians(dip)) dist_sin_dip = distance / sin_dip iterval = 0 while (np.fabs(r_diff) >= iter_stop) and (iterval <= maxiter): pt1mesh = Mesh(np.array([pt1.longitude]), np.array([pt1.latitude]), None) if distance_type == 'rjb' or np.fabs(dip - 90.0) < 1.0E-3: r_diff = (distance - surface.get_joyner_boore_distance(pt1mesh)).flatten() pt0 = Point(pt1.longitude, pt1.latitude) if r_diff > 0.: pt1 = pt0.point_at(r_diff, 0., azimuth) else: pt1 = pt0.point_at(np.fabs(r_diff), 0., (azimuth + 180.) % 360.) elif distance_type == 'rrup': rrup = surface.get_min_distance(pt1mesh).flatten() if azimuth >= 0.0 and azimuth <= 180.0: # On hanging wall r_diff = dist_sin_dip - (rrup / sin_dip) else: # On foot wall r_diff = distance - rrup pt0 = Point(pt1.longitude, pt1.latitude) if r_diff > 0.: pt1 = pt0.point_at(r_diff, 0., azimuth) else: pt1 = pt0.point_at(np.fabs(r_diff), 0., (azimuth + 180.) % 360.) else: raise ValueError('Distance type must be rrup or rjb!') iterval += 1 return pt1 class PointAtDistance(object): """ Abstract Base class to implement set of methods for rendering a point at a given distance from the rupture """ def point_at_distance(self, model, distance, vs30, line_azimuth=90., origin_point=(0.5, 0.), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ """ raise NotImplementedError class PointAtRuptureDistance(PointAtDistance): """ Locate a point at a given Joyner-Boore distance """ def point_at_distance(self, model, distance, vs30, line_azimuth=90., origin_point=(0.5, 0.), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Generates a site given a specified rupture distance from the rupture surface """ azimuth, origin_location, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, model.strike, model.surface) selected_point = _rup_to_point(distance, model.surface, origin_location, azimuth, 'rrup') target_sites = SiteCollection([Site(selected_point, vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)]) return target_sites class PointAtJoynerBooreDistance(PointAtDistance): """ Locate a point at a given Joyner-Boore distance """ def point_at_distance(self, model, distance, vs30, line_azimuth=90., origin_point=(0.5, 0.), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Generates a site given a specified rupture distance from the rupture surface """ azimuth, origin_location, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, model.strike, model.surface) selected_point = _rup_to_point(distance, model.surface, origin_location, azimuth, 'rjb') target_sites = SiteCollection([Site(selected_point, vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)]) return target_sites class PointAtEpicentralDistance(PointAtDistance): """ Locate at point at a given epicentral distance from a source """ def point_at_distance(self, model, distance, vs30, line_azimuth=90., origin_point=(0.5, 0.), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Generates a point at a given epicentral distance """ azimuth, origin_point, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, model.strike, model.surface) return SiteCollection([Site( model.hypocentre.point_at(distance, 0., line_azimuth), vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)]) class PointAtHypocentralDistance(PointAtDistance): """ Locate a point at a given hypocentral distance from a source """ def point_at_distance(self, model, distance, vs30, line_azimuth=90., origin_point=(0.5, 0.), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Generates a point at a given hypocentral distance """ azimuth, origin_point, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, model.strike, model.surface) xdist = sqrt(distance ** 2. - model.hypocentre.depth ** 2.) return SiteCollection([Site( model.hypocentre.point_at(xdist, -model.hypocentre.depth, azimuth), vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)]) POINT_AT_MAPPING = {'rrup': PointAtRuptureDistance(), 'rjb': PointAtJoynerBooreDistance(), 'repi': PointAtEpicentralDistance(), 'rhypo': PointAtHypocentralDistance()} class GSIMRupture(object): """ Defines a rupture plane consistent with the properties specified for the trellis plotting. Also contains methods for configuring the site locations """ def __init__(self, magnitude, dip, aspect, tectonic_region='Active Shallow Crust', rake=0., ztor=0., strike=0., msr=WC1994(), initial_point=DEFAULT_POINT, hypocentre_location=None): """ Instantiate the rupture - requires a minimum of a magnitude, dip and aspect ratio """ self.magnitude = magnitude self.dip = dip self.aspect = aspect self.rake = rake self.strike = strike self.location = initial_point self.ztor = ztor self.trt = tectonic_region self.hypo_loc = hypocentre_location # If the top of rupture depth in the initial if fabs(self.location.depth - self.ztor) > 1E-9: self.location.depth = ztor self.msr = msr self.area = self.msr.get_median_area(self.magnitude, self.rake) self.surface = create_planar_surface(self.location, self.strike, self.dip, self.area, self.aspect) self.hypocentre = get_hypocentre_on_planar_surface(self.surface, self.hypo_loc) self.rupture = self.get_rupture() self.target_sites_config = None self.target_sites = None def get_rupture(self): """ Returns the rupture as an instance of the openquake.hazardlib.source.rupture.Rupture class """ return Rupture(self.magnitude, self.rake, self.trt, self.hypocentre, self.surface, PointSource) def get_gsim_contexts(self): """ Returns a comprehensive set of GMPE contecxt objects """ assert isinstance(self.rupture, Rupture) assert isinstance(self.target_sites, SiteCollection) # Distances dctx = DistancesContext() # Rupture distance setattr(dctx, 'rrup', self.rupture.surface.get_min_distance(self.target_sites.mesh)) # Rx setattr(dctx, 'rx', self.rupture.surface.get_rx_distance(self.target_sites.mesh)) # Rjb setattr(dctx, 'rjb', self.rupture.surface.get_joyner_boore_distance( self.target_sites.mesh)) # Rhypo setattr(dctx, 'rhypo', self.rupture.hypocenter.distance_to_mesh( self.target_sites.mesh)) # Repi setattr(dctx, 'repi', self.rupture.hypocenter.distance_to_mesh( self.target_sites.mesh, with_depths=False)) # Ry0 setattr(dctx, 'ry0', self.rupture.surface.get_ry0_distance(self.target_sites.mesh)) # Rcdpp - ignored at present setattr(dctx, 'rcdpp', None) # Azimuth setattr(dctx, 'azimuth', get_distances(self.rupture, self.target_sites.mesh, 'azimuth')) setattr(dctx, 'hanging_wall', None) # Rvolc setattr(dctx, "rvolc", np.zeros_like(self.target_sites.mesh.lons)) # Sites sctx = SitesContext(slots=self.target_sites.array.dtype.names) for key in sctx._slots_: setattr(sctx, key, self.target_sites.array[key]) # Rupture rctx = RuptureContext() setattr(rctx, 'mag', self.magnitude) setattr(rctx, 'strike', self.strike) setattr(rctx, 'dip', self.dip) setattr(rctx, 'rake', self.rake) setattr(rctx, 'ztor', self.ztor) setattr(rctx, 'hypo_depth', self.rupture.hypocenter.depth) setattr(rctx, 'hypo_lat', self.rupture.hypocenter.latitude) setattr(rctx, 'hypo_lon', self.rupture.hypocenter.longitude) setattr(rctx, 'hypo_loc', self.hypo_loc) setattr(rctx, 'width', self.rupture.surface.get_width()) return sctx, rctx, dctx def get_target_sites_mesh(self, maximum_distance, spacing, vs30, vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Renders a two dimensional mesh of points over the rupture surface """ # Get bounding box of dilated rupture lowx, highx, lowy, highy = self._get_limits_maximum_rjb( maximum_distance) # Create bounding box lines and then resample at spacing ewline = Line([Point(lowx, highy, 0.), Point(highx, highy, 0.)]) nsline = Line([Point(lowx, highy, 0.), Point(lowx, lowy, 0.)]) ewline = ewline.resample(spacing) nsline = nsline.resample(spacing) xvals = np.array([pnt.longitude for pnt in ewline.points]) yvals = np.array([pnt.latitude for pnt in nsline.points]) gridx, gridy = np.meshgrid(xvals, yvals) numx, numy = np.shape(gridx) npts = numx * numy gridx = (np.reshape(gridx, npts, 1)).flatten() gridy = (np.reshape(gridy, npts, 1)).flatten() site_list = [] if not z1pt0: # z1pt0 = vs30_to_z1pt0_as08(vs30) z1pt0 = vs30_to_z1pt0_cy14(vs30) if not z2pt5: # z2pt5 = z1pt0_to_z2pt5(z1pt0) z2pt5 = vs30_to_z2pt5_cb14(vs30) for iloc in range(0, npts): site_list.append(Site(Point(gridx[iloc], gridy[iloc], 0.), vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)) self.target_sites = SiteCollection(site_list) self.target_sites_config = { "TYPE": "Mesh", "RMAX": maximum_distance, "SPACING": spacing, "VS30": vs30, "VS30MEASURED": vs30measured, "Z1.0": z1pt0, "Z2.5": z2pt5, "BACKARC": backarc} return self.target_sites def get_target_sites_line(self, maximum_distance, spacing, vs30, line_azimuth=90., origin_point=(0.5, 0.5), as_log=False, vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Defines the target sites along a line with respect to the rupture :param maximum_distance: Maximum distance to be considered [km] :param spacing: Sampling distance for the reference line [km] :param vs30: Time averaged shear wave velocity within the uppermost 30m [m/s] :param line_azimuth: Azimuth of the reference line [degrees] :param origin_point: Coordinates of the origin point of the reference line :param bool as_log: When True scales the distances logarithmically :param bool vs30measured: A boolean defining the method used to determine the vs30 value :param z1pt0: Depth to the 1km/s interface [km] :param z2pt5: Depth to the 2.5km/s interface [km] :param bool backarc: When True the sites are considered in the backarc region """ azimuth, origin_location, z1pt0, z2pt5 = \ self._define_origin_target_site(vs30, line_azimuth, origin_point, vs30measured, z1pt0, z2pt5, backarc) spacings = self._define_line_spacing(maximum_distance, spacing, as_log) target_sites = \ self._append_target_sites(spacings, azimuth, origin_location, vs30, line_azimuth, origin_point, as_log, vs30measured, z1pt0, z2pt5, backarc) # let's be picky and replace inferred values of # self.target_sites_config with the values provided here: self.target_sites_config.update({"RMAX": maximum_distance, "SPACING": spacing}) return target_sites def _define_line_spacing(self, maximum_distance, spacing, as_log=False): """ The user may wish to define the line spacing in either log or linear space """ nvals = int(maximum_distance / spacing) + 1 if as_log: spacings = np.logspace(-3., np.log10(maximum_distance), nvals) spacings[0] = 0.0 else: spacings = np.linspace(0.0, maximum_distance, nvals) if spacings[-1] < (maximum_distance - 1.0E-7): spacings = np.hstack([spacings, maximum_distance]) return spacings def get_target_sites_line_from_given_distances(self, distances, vs30, line_azimuth=90., origin_point=(0.5, 0.5), as_log=False, vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Defines the target sites along a line with respect to the rupture from a given numeric array of distances """ azimuth, origin_location, z1pt0, z2pt5 = \ self._define_origin_target_site(vs30, line_azimuth, origin_point, vs30measured, z1pt0, z2pt5, backarc) distances = self._convert_distances(distances, as_log) return self._append_target_sites(distances, azimuth, origin_location, vs30, line_azimuth, origin_point, as_log, vs30measured, z1pt0, z2pt5, backarc) @staticmethod def _convert_distances(distances, as_log=False): '''assures distances is a numpy numeric array, sorts it and converts its value to a logaritmic scale preserving the array bounds (min and max)''' dist = np.asarray(distances) dist.sort() if as_log: oldmin, oldmax = dist[0], dist[-1] dist = np.log1p(dist) # avoid -inf @ zero in case newmin, newmax = dist[0], dist[-1] # re-map the space to be logarithmic between oldmin and oldmax: dist = oldmin + (oldmax-oldmin)*(dist - newmin)/(newmax - newmin) return dist def _define_origin_target_site(self, vs30, line_azimuth=90., origin_point=(0.5, 0.5), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Defines the target site from an origin point """ azimuth, origin_location, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, self.strike, self.surface) self.target_sites = [Site(origin_location, vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)] return azimuth, origin_location, z1pt0, z2pt5 def _append_target_sites(self, distances, azimuth, origin_location, vs30, line_azimuth=90., origin_point=(0.5, 0.5), as_log=False, vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Appends the target sites along a line with respect to the rupture, given an already set origin target site """ for offset in distances: target_loc = origin_location.point_at(offset, 0., azimuth) self.target_sites.append(Site(target_loc, vs30, z1pt0, z2pt5, vs30measured=vs30measured, backarc=backarc)) self.target_sites_config = { "TYPE": "Line", "RMAX": distances[-1], "SPACING": np.nan if len(distances) < 2 else distances[1] - distances[0], # FIXME does it make sense? "AZIMUTH": line_azimuth, "ORIGIN": origin_point, "AS_LOG": as_log, "VS30": vs30, "VS30MEASURED": vs30measured, "Z1.0": z1pt0, "Z2.5": z2pt5, "BACKARC": backarc} self.target_sites = SiteCollection(self.target_sites) return self.target_sites def get_target_sites_point( self, distance, distance_type, vs30, line_azimuth=90, origin_point=(0.5, 0.5), vs30measured=True, z1pt0=None, z2pt5=None, backarc=False): """ Returns a single target site at a fixed distance from the source, with distance defined according to a specific typology :param float distance: Distance (km) from the point to the source :param str distance_type: Type of distance {'rrup', 'rjb', 'repi', 'rhyp'} :param float vs30: Vs30 (m / s) :param float line_azimuth: Aziumth of the source-to-site line :param tuple origin point: Location (along strike, down dip) of the origin of the source-site line within the rupture :param bool vs30measured: Is vs30 measured (True) or inferred (False) :param float z1pt0: Depth to 1 km/s interface :param floar z2pt5: Depth to 2.5 km/s interface """ if not distance_type in list(POINT_AT_MAPPING.keys()): raise ValueError("Distance type must be one of: Rupture ('rrup'), " "Joyner-Boore ('rjb'), Epicentral ('repi') or " "Hypocentral ('rhyp')") #input_origin_point = deepcopy(origin_point) azimuth, origin_location, z1pt0, z2pt5 = _setup_site_peripherals( line_azimuth, origin_point, vs30, z1pt0, z2pt5, self.strike, self.surface) self.target_sites_config = { "TYPE": "Point", "R": distance, "RTYPE": distance_type, "AZIMUTH": line_azimuth, "ORIGIN": origin_point, "VS30": vs30, "VS30MEASURED": vs30measured, "Z1.0": z1pt0, "Z2.5": z2pt5, "BACKARC": backarc} self.target_sites = POINT_AT_MAPPING[distance_type].point_at_distance( self, distance, vs30, line_azimuth, origin_point, vs30measured, z1pt0, z2pt5, backarc=backarc) def _get_limits_maximum_rjb(self, maximum_distance): """ Returns the bounding box of a polyon representing the locations of maximum distance from the rupture """ top_left = deepcopy(self.surface.top_left) top_left.depth = 0. top_right = deepcopy(self.surface.top_right) top_right.depth = 0. bottom_left = deepcopy(self.surface.bottom_left) bottom_left.depth = 0. bottom_right = deepcopy(self.surface.bottom_right) bottom_right.depth = 0. surface_projection = Polygon([top_left, top_right, bottom_right, bottom_left]) dilated_projection = surface_projection.dilate(maximum_distance) return (np.min(dilated_projection.lons), np.max(dilated_projection.lons), np.min(dilated_projection.lats), np.max(dilated_projection.lats)) def filter_hanging_wall(self, filter_type=None): """ Opt to consider only hanging wall or footwall sites """ if not filter_type: # Considers both footwall and hanging wall return self.target_sites elif not filter_type in ('HW', 'FW'): raise ValueError('Hanging wall filter must be either "HW" or "FW"') else: pass # Gets the Rx distance r_x = self.surface.get_rx_distance(self.target_sites.mesh) selected_sites = [] if filter_type == "HW": # Only hanging wall idx = np.where(r_x >= 0.)[0] else: # Only footwall idx = np.where(r_x < 0.)[0] for val in idx: selected_sites.append(self.target_sites.sites[val]) self.target_sites = SiteCollection(selected_sites) return self.target_sites def _site_collection_to_mesh(self): """ Returns a collection of sites as an instance of the :class: `openquake.hazardlib.geo.Mesh` """ if isinstance(self.target_sites, SiteCollection): locations = np.array([len(self.target_sites.sites), 3], dtype=float) for iloc, site in enumerate(self.target_sites.sites): locations[iloc, 0] = site.location.longitude locations[iloc, 1] = site.location.latitude locations[iloc, 2] = site.location.depth return Mesh(locations[:, 0], locations[:, 1], locations[:, 2]) else: raise ValueError('Target sites must be an instance of ' 'openquake.hazardlib.site.SiteCollection') def plot_distance_comparisons(self, distance1, distance2, logaxis=False, figure_size=(7, 5), filename=None, filetype="png", dpi=300): """ Creates a plot comparing different distance metrics for the specific rupture and target site combination """ xdist = self._calculate_distance(distance1) ydist = self._calculate_distance(distance2) plt.figure(figsize=figure_size) if logaxis: plt.loglog(xdist, ydist, color='b', marker='o', linestyle='None') else: plt.plot(xdist, ydist, color='b', marker='o', linestyle='None') plt.xlabel("%s (km)" % distance1, size='medium') plt.ylabel("%s (km)" % distance2, size='medium') plt.title('Rupture: M=%6.1f, Dip=%3.0f, Ztor=%4.1f, Aspect=%5.2f' % (self.magnitude, self.dip, self.ztor, self.aspect)) _save_image(filename, filetype, dpi) plt.show() def _calculate_distance(self, distance_type): """ Calculates the rupture to target site distances for the present rupture and target site configuration """ if distance_type == 'rrup': return self.surface.get_min_distance(self.target_sites.mesh) elif distance_type == 'rjb': return self.surface.get_joyner_boore_distance( self.target_sites.mesh) elif distance_type == 'rx': return self.surface.get_rx_distance(self.target_sites.mesh) elif distance_type == 'rhypo': return self.hypocentre.distance_to_mesh(self.target_sites.mesh) elif distance_type == 'repi': return self.hypocentre.distance_to_mesh(self.target_sites.mesh, with_depths=False) else: raise ValueError('Unsupported Distance Measure: %s' % distance_type) def plot_model_configuration(self, marker_style="o", figure_size=(7, 5), filename=None, filetype="png", dpi=300): """ Produces a 3D plot of the current model configuration """ fig = plt.figure(figsize=figure_size) ax = fig.add_subplot(111, projection='3d') # Wireframe rupture surface mesh lons = [] lats = [] depths = [] for pnt in [self.surface.top_left, self.surface.top_right, self.surface.bottom_right, self.surface.bottom_left, self.surface.top_left]: lons.append(pnt.longitude) lats.append(pnt.latitude) depths.append(-pnt.depth) ax.plot(lons, lats, depths, "k-", lw=2) # Scatter the target sites ax.scatter(self.target_sites.mesh.lons, self.target_sites.mesh.lats, np.ones_like(self.target_sites.mesh.lons), c='r', marker=marker_style) ax.set_xlabel('Longitude') ax.set_ylabel('Latitude') ax.set_zlabel('Depth (km)') ax.set_zlim(np.floor(min(depths)), 0.0) _save_image(filename, filetype, dpi) plt.show()