def __computeD(self, i): """Compute d for the i-th quad/segment. Y = d/W, where d is the portion (in km) of the width of the fault which ruptures up-dip from the hypocenter to the top of the fault. Args: i (int): index of segment for which d is to be computed. """ hyp_ecef = self.phyp[i] # already in ECEF hyp_col = np.array([[hyp_ecef.x], [hyp_ecef.y], [hyp_ecef.z]]) # First compute "updip" vector P0, P1, P2 = self._rup.getQuadrilaterals()[i][0:3] p1 = Vector.fromPoint(P1) # convert to ECEF p2 = Vector.fromPoint(P2) e21 = p1 - p2 e21norm = e21.norm() hp1 = p1 - hyp_ecef # convert to km (used as max later) udip_len = Vector.dot(hp1, e21norm) / 1000.0 udip_col = np.array([[e21norm.x], [e21norm.y], [e21norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([ np.reshape(site_ecef_x, (-1, )), np.reshape(site_ecef_y, (-1, )), np.reshape(site_ecef_z, (-1, )) ]) # Hypocenter-to-site matrix h2s_mat = site_mat - hyp_col # in ECEF # Dot hypocenter-to-site with updip vector d_raw = np.abs(np.sum(h2s_mat * udip_col, axis=0)) / \ 1000.0 # convert to km d_raw = np.reshape(d_raw, self._lat.shape) self.d = d_raw.clip(min=1.0, max=udip_len)
def __computeD(self, i): """Compute d for the i-th quad/segment. Y = d/W, where d is the portion (in km) of the width of the fault which ruptures up-dip from the hypocenter to the top of the fault. Args: i (int): index of segment for which d is to be computed. """ hyp_ecef = self.phyp[i] # already in ECEF hyp_col = np.array([[hyp_ecef.x], [hyp_ecef.y], [hyp_ecef.z]]) # First compute "updip" vector P0, P1, P2 = self._rup.getQuadrilaterals()[i][0:3] p1 = Vector.fromPoint(P1) # convert to ECEF p2 = Vector.fromPoint(P2) e21 = p1 - p2 e21norm = e21.norm() hp1 = p1 - hyp_ecef # convert to km (used as max later) udip_len = Vector.dot(hp1, e21norm) / 1000.0 udip_col = np.array( [[e21norm.x], [e21norm.y], [e21norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([np.reshape(site_ecef_x, (-1,)), np.reshape(site_ecef_y, (-1,)), np.reshape(site_ecef_z, (-1,))]) # Hypocenter-to-site matrix h2s_mat = site_mat - hyp_col # in ECEF # Dot hypocenter-to-site with updip vector d_raw = np.abs(np.sum(h2s_mat * udip_col, axis=0)) / \ 1000.0 # convert to km d_raw = np.reshape(d_raw, self._lat.shape) self.d = d_raw.clip(min=1.0, max=udip_len)
def get_quad_strike_vector(q): """ Compute the unit vector pointing in the direction of strike for a quadrilateral in ECEF coordinates. Top edge assumed to be horizontal. Args: q (list): A quadrilateral; list of four points. Returns: Vector: The unit vector pointing in strike direction in ECEF coords. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) v1 = (p1 - p0).norm() return v1
def get_quad_length(q): """ Length of top eduge of a quadrilateral. Args: q (list): A quadrilateral; list of four points. Returns: float: Length of quadrilateral in km. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) qlength = (p1 - p0).mag() / 1000 return qlength
def getIndividualTopLengths(self): """ Return an array of rupture lengths along top edge (km), one for each quadrilateral defined for the rupture. :returns: Array of lengths in km of top edge of quadrilaterals. """ nquad = self.getNumQuads() lengths = np.zeros(nquad) for i in range(nquad): P0, P1, P2, P3 = self._quadrilaterals[i] p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) lengths[i] = (p1 - p0).mag() / 1000.0 return lengths
def is_quad(q): """ Checks that an individual quad is coplanar. Args: q (list): A quadrilateral; list of four OQ Points. Returns: tuple: Bool for whether or not the points are planar within tolerance; and also the corrected quad where p2 is adjusted to be on the same plane as the other points. """ P0, P1, P2, P3 = q # Convert points to ECEF p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Unit vector along top edge v0 = (p1 - p0).norm() # Distance along bottom edge d = (p3 - p2).mag() # New location for p2 by extending from p3 the same distance and # direction that p1 is from p0: new_p2 = p3 + v0 * d # How far off of the plane is the origin p2? planepoints = [p0, p1, p2] dist = get_distance_to_plane(planepoints, p2) # Is it close enough? if dist / d > constants.OFFPLANE_TOLERANCE: on_plane = False else: on_plane = True # Fixed quad fquad = [p0.toPoint(), p1.toPoint(), new_p2.toPoint(), p3.toPoint()] return (on_plane, fquad)
def get_quad_down_dip_vector(q): """ Compute the unit vector pointing down dip for a quadrilateral in ECEF coordinates. Args: q (list): A quadrilateral; list of four points. Returns: Vector: The unit vector pointing down dip in ECEF coords. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) p0p1 = p1 - p0 qnv = get_quad_normal(q) ddv = Vector.cross(p0p1, qnv).norm() return ddv
def get_quad_normal(q): """ Compute the unit normal vector for a quadrilateral in ECEF coordinates. Args: q (list): A quadrilateral; list of four points. Returns: Vector: Normalized normal vector for the quadrilateral in ECEF coords. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) p3 = Vector.fromPoint(P3) v1 = p1 - p0 v2 = p3 - p0 vn = Vector.cross(v2, v1).norm() return vn
def get_vertical_vector(q): """ Compute the vertical unit vector for a quadrilateral in ECEF coordinates. Args: q (list): A quadrilateral; list of four points. Returns: Vector: Normalized vertical vector for the quadrilateral in ECEF coords. """ P0, P1, P2, P3 = q P0_up = copy.deepcopy(P0) P0_up.depth = P0_up.depth - 1.0 p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P0_up) v1 = (p1 - p0).norm() return v1
def __computeWrup(self): """ Compute the the portion (in km) of the width of the rupture which ruptures up-dip from the hypocenter to the top of the rupture. Wrup is the portion (in km) of the width of the rupture which ruptures up-dip from the hypocenter to the top of the rupture. * This is ambiguous for ruptures with varible top of rupture (not allowed in NGA). For now, lets just compute this for the quad where the hypocenter is located. * Alternative is to compute max Wrup for the different quads. """ nquad = len(self._rup.getQuadrilaterals()) # --------------------------------------------------------------------- # First find which quad the hypocenter is on # --------------------------------------------------------------------- x, y, z = latlon2ecef(self._hyp.latitude, self._hyp.longitude, self._hyp.depth) hyp_ecef = np.array([[x, y, z]]) qdist = np.zeros(nquad) for i in range(0, nquad): qdist[i] = utils._quad_distance(self._rup.getQuadrilaterals()[i], hyp_ecef) ind = int(np.where(qdist == np.min(qdist))[0][0]) # *** check that this doesn't break with more than one quad q = self._rup.getQuadrilaterals()[ind] # --------------------------------------------------------------------- # Compute Wrup on that quad # --------------------------------------------------------------------- pp0 = Vector.fromPoint( geo.point.Point(q[0].longitude, q[0].latitude, q[0].depth)) hyp_ecef = Vector.fromPoint( geo.point.Point(self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) hp0 = hyp_ecef - pp0 ddv = utils.get_quad_down_dip_vector(q) self._Wrup = Vector.dot(ddv, hp0) / 1000
def __computeWrup(self): """ Compute the the portion (in km) of the width of the rupture which ruptures up-dip from the hypocenter to the top of the rupture. Wrup is the portion (in km) of the width of the rupture which ruptures up-dip from the hypocenter to the top of the rupture. * This is ambiguous for ruptures with varible top of rupture (not allowed in NGA). For now, lets just compute this for the quad where the hypocenter is located. * Alternative is to compute max Wrup for the different quads. """ nquad = len(self._rup.getQuadrilaterals()) # --------------------------------------------------------------------- # First find which quad the hypocenter is on # --------------------------------------------------------------------- x, y, z = latlon2ecef( self._hyp.latitude, self._hyp.longitude, self._hyp.depth) hyp_ecef = np.array([[x, y, z]]) qdist = np.zeros(nquad) for i in range(0, nquad): qdist[i] = utils._quad_distance( self._rup.getQuadrilaterals()[i], hyp_ecef) ind = int(np.where(qdist == np.min(qdist))[0][0]) # *** check that this doesn't break with more than one quad q = self._rup.getQuadrilaterals()[ind] # --------------------------------------------------------------------- # Compute Wrup on that quad # --------------------------------------------------------------------- pp0 = Vector.fromPoint(geo.point.Point( q[0].longitude, q[0].latitude, q[0].depth)) hyp_ecef = Vector.fromPoint(geo.point.Point( self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) hp0 = hyp_ecef - pp0 ddv = utils.get_quad_down_dip_vector(q) self._Wrup = Vector.dot(ddv, hp0) / 1000
def _fixStrikeDirection(quad): P0, P1, P2, P3 = quad eps = 1e-6 p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p1p0 = p1 - p0 p2p0 = p2 - p0 qnv = Vector.cross(p2p0, p1p0).norm() tmp = p0 + qnv tmplat, tmplon, tmpz = ecef2latlon(tmp.x, tmp.y, tmp.z) if (tmpz - P0.depth) < eps: # If True then do nothing fixed = quad else: newP0 = copy.deepcopy(P1) newP1 = copy.deepcopy(P0) newP2 = copy.deepcopy(P3) newP3 = copy.deepcopy(P2) fixed = [newP0, newP1, newP2, newP3] return fixed
def get_quad_width(q): """ Return width of an individual planar trapezoid, where the p0-p1 distance represents the long side. Args: q (list): A quadrilateral; list of four points. Returns: float: Width of planar trapezoid. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p3 = Vector.fromPoint(P3) AB = p0 - p1 AC = p0 - p3 t1 = (AB.cross(AC).cross(AB)).norm() width = t1.dot(AC) return width
def get_quad_width(q): """ Return width of an individual planar trapezoid, where the p0-p1 distance represents the long side. Args: q (list): A quadrilateral; list of four points. Returns: float: Width of planar trapezoid in km. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p3 = Vector.fromPoint(P3) AB = p0 - p1 AC = p0 - p3 t1 = (AB.cross(AC).cross(AB)).norm() width = t1.dot(AC) / 1000.0 return width
def __setPseudoHypocenters(self): """ Set a pseudo-hypocenter. Adapted from ShakeMap 3.5 src/contour/directivity.c From Bayless and Somerville: "Define the pseudo-hypocenter for rupture of successive segments as the point on the side edge of the rupture segment that is closest to the side edge of the previous segment, and that lies half way between the top and bottom of the rupture. We assume that the rupture is segmented along strike, not updip. All geometric parameters are computed relative to the pseudo-hypocenter." """ hyp_ecef = Vector.fromPoint(geo.point.Point( self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) # Loop over each quad self.phyp = [None] * self._nq for i in range(self._nq): P0, P1, P2, P3 = self._rup.getQuadrilaterals()[i] p0 = Vector.fromPoint(P0) # convert to ECEF p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Create 4 planes with normals pointing outside rectangle hpnp = Vector.cross(p1 - p0, p2 - p0).norm() hpp = -hpnp.x * p0.x - hpnp.y * p0.y - hpnp.z * p0.z n0 = Vector.cross(p1 - p0, hpnp) n1 = Vector.cross(p2 - p1, hpnp) n2 = Vector.cross(p3 - p2, hpnp) n3 = Vector.cross(p0 - p3, hpnp) # ----------------------------------------------------------------- # Is the hypocenter inside the projected rectangle? # Dot products show which side the origin is on. # If origin is on same side of all the planes, then it is 'inside' # ----------------------------------------------------------------- sgn0 = np.signbit(Vector.dot(n0, p0 - hyp_ecef)) sgn1 = np.signbit(Vector.dot(n1, p1 - hyp_ecef)) sgn2 = np.signbit(Vector.dot(n2, p2 - hyp_ecef)) sgn3 = np.signbit(Vector.dot(n3, p3 - hyp_ecef)) if (sgn0 == sgn1) and (sgn1 == sgn2) and (sgn2 == sgn3): # Origin is inside. Use distance-to-plane formula. d = Vector.dot(hpnp, hyp_ecef) + hpp d = d * d # Put the pseudo hypocenter on the plane D = Vector.dot(hpnp, hyp_ecef) + hpp self.phyp[i] = hyp_ecef - hpnp * D else: # Origin is outside. Find distance to edges p0p = np.reshape(p0.getArray() - hyp_ecef.getArray(), [1, 3]) p1p = np.reshape(p1.getArray() - hyp_ecef.getArray(), [1, 3]) p2p = np.reshape(p2.getArray() - hyp_ecef.getArray(), [1, 3]) p3p = np.reshape(p3.getArray() - hyp_ecef.getArray(), [1, 3]) s1 = _distance_sq_to_segment(p1p, p2p) s3 = _distance_sq_to_segment(p3p, p0p) # Assuming that the rupture is segmented along strike and not # updip (as described by Bayless and somerville), we only # need to consider s1 and s3: if s1 > s3: e30 = p0 - p3 e30norm = e30.norm() mag = e30.mag() self.phyp[i] = p3 + e30norm * (0.5 * mag) else: e21 = p1 - p2 e21norm = e21.norm() mag = e21.mag() self.phyp[i] = p2 + e21norm * (0.5 * mag)
def _computeGC2(rupture, lon, lat, depth): """ Method for computing GC2 from a ShakeMap Rupture instance. Args: rupture (Rupture): ShakeMap rupture object. lon (array): Numpy array of site longitudes. lat (array): Numpy array of site latitudes. depth (array): Numpy array of site depths. Returns: dict: Dictionary of GC2 distances. Keys include "T", "U", "rx" "ry", "ry0". """ quadlist = rupture.getQuadrilaterals() quadgc2 = copy.deepcopy(quadlist) oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) # ------------------------------------------------------------------------- # Define a projection that spans sites and rupture # ------------------------------------------------------------------------- all_lat = np.append(lat, rupture.lats) all_lon = np.append(lon, rupture.lons) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = OrthographicProjection(west, east, north, south) totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) # ------------------------------------------------------------------------- # First sort out strike discordance and nominal strike prior to # starting the loop if there is more than one group/trace. # ------------------------------------------------------------------------- group_ind = rupture._getGroupIndex() # Need group_ind as numpy array for sensible indexing... group_ind_np = np.array(group_ind) uind = np.unique(group_ind_np) n_groups = len(uind) if n_groups > 1: # --------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is "selected from the two endpoints most # distant from each other." # --------------------------------------------------------------------- # Need to get index of first and last quad # for each segment iq0 = np.zeros(n_groups, dtype='int16') iq1 = np.zeros(n_groups, dtype='int16') for k in uind: ii = [i for i, j in enumerate(group_ind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) # --------------------------------------------------------------------- # This is an iterator for each possible combination of traces # including trace orientations (i.e., flipped). # --------------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the trace pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 # --------------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". # --------------------------------------------------------------------- # This goofy while-loop is to adjust the side of the rupture where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(n_groups) b_prime = [None] * n_groups for j in range(n_groups): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(n_groups): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.deepcopy(A0) A0 = copy.deepcopy(A1) A1 = tmpA0 # --------------------------------------------------------------------- # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # --------------------------------------------------------------------- # 1) flip quads for i in range(len(quadgc2)): if dc[group_ind[i]] < 0: quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist order qind = np.arange(len(quadgc2)) for i in range(n_groups): qsel = qind[group_ind_np == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[group_ind_np == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] # End of if-statement for adjusting group discordancy s_i = 0.0 l_i = np.zeros(len(quadgc2)) for i in range(len(quadgc2)): G0, G1, G2, G3 = quadgc2[i] # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length (top edge) l_i[i] = get_quad_length(quadgc2[i]) # --------------------------------------------------------------------- # Weight of segment, three cases # --------------------------------------------------------------------- # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # To avoid division by zero in totweight later on: ix = (t_i == 0) & (0 <= u_i) & (u_i <= l_i[i]) totweight[ix] = 1.0 # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan( (l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if n_groups == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadgc2))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(group_ind_np == group_ind_np[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) # First endpoint in the current 'group' (or 'trace' in GC2 terms) p1 = Vector.fromPoint(quadgc2[iq0[group_ind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E) * ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] GC2T = GC2T / totweight GC2U = GC2U / totweight # Dictionary for holding the distances distdict = dict() distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if n_groups > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 return distdict
def get_hypo(edges, args): """ Args: edges (list): A list of two lists of points; the first list corresponds to the top edge and the second is the bottom edge. args (ArgumentParser): argparse object. Returns: tuple: Hypocenter (lat, lon depth). """ top = copy.deepcopy(edges[0]) bot = copy.deepcopy(edges[1]) # Along strike and along dip distance (0-1) # NOTE: This could also be made a function of mechanism if args.dirind == -1: # no directivity dxp = 0.5 # strike dyp = 0.6 # dip elif args.dirind == 0: # first unilateral dxp = 0.05 # strike dyp = 0.6 # dip elif args.dirind == 2: # second unilateral dxp = 0.95 # strike dyp = 0.6 # dip elif args.dirind == 1: # bilateral dxp = 0.5 # strike dyp = 0.6 # dip # Convert to ECEF topxy = [ Vector.fromPoint(geo.point.Point(p.longitude, p.latitude, p.depth)) for p in top ] botxy = [ Vector.fromPoint(geo.point.Point(p.longitude, p.latitude, p.depth)) for p in bot ] # Compute distances along edges for each vertex t0 = topxy[0] b0 = botxy[0] topdist = np.array([t0.distance(p) for p in topxy]) botdist = np.array([b0.distance(p) for p in botxy]) # Normalize distance from 0 to 1 topdist = topdist / np.max(topdist) botdist = botdist / np.max(botdist) #--------------------------------------------------------------------------- # Find points of surrounding quad #--------------------------------------------------------------------------- tix0 = np.amax(np.where(topdist < dxp)) tix1 = np.amin(np.where(topdist > dxp)) bix0 = np.amax(np.where(botdist < dxp)) bix1 = np.amin(np.where(botdist > dxp)) # top left pp0 = topxy[tix0] # top right pp1 = topxy[tix1] # bottom right pp2 = botxy[bix0] # bottom left pp3 = botxy[bix1] # How far from pp0 to pp1, and pp2 to pp3? dxt = (dxp - topdist[tix0]) / (topdist[tix1] - topdist[tix0]) dxb = (dxp - botdist[bix0]) / (botdist[bix1] - botdist[bix0]) mp0 = pp0 + (pp1 - pp0) * dxt mp1 = pp3 + (pp2 - pp3) * dxb rp = mp0 + (mp1 - mp0) * dyp hlat, hlon, hdepth = ecef2latlon(rp.x, rp.y, rp.z) return hlat, hlon, hdepth
def __computeThetaAndS(self, i): """ Args: i (int): Compute d for the i-th quad/segment. """ # self.phyp is in ECEF tmp = ecef.ecef2latlon(self.phyp[i].x, self.phyp[i].y, self.phyp[i].z) epi_ecef = Vector.fromPoint(geo.point.Point(tmp[1], tmp[0], 0.0)) epi_col = np.array([[epi_ecef.x], [epi_ecef.y], [epi_ecef.z]]) # First compute along strike vector P0, P1, P2, P3 = self._rup.getQuadrilaterals()[i] p0 = Vector.fromPoint(P0) # convert to ECEF p1 = Vector.fromPoint(P1) e01 = p1 - p0 e01norm = e01.norm() hp0 = p0 - epi_ecef hp1 = p1 - epi_ecef strike_min = Vector.dot(hp0, e01norm) / 1000.0 # convert to km strike_max = Vector.dot(hp1, e01norm) / 1000.0 # convert to km strike_col = np.array([[e01norm.x], [e01norm.y], [e01norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([ np.reshape(site_ecef_x, (-1, )), np.reshape(site_ecef_y, (-1, )), np.reshape(site_ecef_z, (-1, )) ]) # Epicenter-to-site matrix e2s_mat = site_mat - epi_col # in ECEF mag = np.sqrt(np.sum(e2s_mat * e2s_mat, axis=0)) # Avoid division by zero mag[mag == 0] = 1e-12 e2s_norm = e2s_mat / mag # Dot epicenter-to-site with along-strike vector s_raw = np.sum(e2s_mat * strike_col, axis=0) / 1000.0 # conver to km # Put back into a 2d array s_raw = np.reshape(s_raw, self._lat.shape) self.s = np.abs(s_raw.clip(min=strike_min, max=strike_max)).clip(min=np.exp(1)) # Compute theta sdots = np.sum(e2s_norm * strike_col, axis=0) theta_raw = np.arccos(sdots) # But theta is defined to be the reference angle # (i.e., the equivalent angle between 0 and 90 deg) sintheta = np.abs(np.sin(theta_raw)) costheta = np.abs(np.cos(theta_raw)) theta = np.arctan2(sintheta, costheta) self.theta = np.reshape(theta, self._lat.shape)
def test_so6(): magnitude = 7.2 dip = np.array([70]) rake = 135 width = np.array([15]) L = 80 rupx = np.array([0, 0]) rupy = np.array([0, L]) zp = np.array([0]) # Convert to lat/lon proj = geo.utils.get_orthographic_projection(-122, -120, 39, 37) tlon, tlat = proj(rupx, rupy, reverse=True) # Dummy origin origin = Origin({ 'lat': 0, 'lon': 0, 'depth': 0, 'mag': 0, 'eventsourcecode': 'so6', 'rake': rake }) # Rupture rup = QuadRupture.fromTrace(np.array([tlon[0]]), np.array([tlat[0]]), np.array([tlon[1]]), np.array([tlat[1]]), zp, width, dip, origin, reference='rv4') # Sites x = np.linspace(-80, 80, 21) y = np.linspace(-50, 130, 21) site_x, site_y = np.meshgrid(x, y) slon, slat = proj(site_x, site_y, reverse=True) sdepth = np.zeros_like(slon) # Fix origin tmp = rup.getQuadrilaterals()[0] pp0 = Vector.fromPoint( point.Point(tmp[0].longitude, tmp[0].latitude, tmp[0].depth)) pp1 = Vector.fromPoint( point.Point(tmp[1].longitude, tmp[1].latitude, tmp[1].depth)) pp2 = Vector.fromPoint( point.Point(tmp[2].longitude, tmp[2].latitude, tmp[2].depth)) pp3 = Vector.fromPoint( point.Point(tmp[3].longitude, tmp[3].latitude, tmp[3].depth)) dxp = 10 / L dyp = (width - 5) / width mp0 = pp0 + (pp1 - pp0) * dxp mp1 = pp3 + (pp2 - pp3) * dxp rp = mp0 + (mp1 - mp0) * dyp epilat, epilon, epidepth = ecef2latlon(rp.x, rp.y, rp.z) epix, epiy = proj(epilon, epilat, reverse=False) origin = Origin({ 'lat': epilat, 'lon': epilon, 'depth': epidepth, 'mag': magnitude, 'eventsourcecode': 'so6', 'rake': rake }) ruplat = [a.latitude for a in rup.getQuadrilaterals()[0]] ruplon = [a.longitude for a in rup.getQuadrilaterals()[0]] ruplat = np.append(ruplat, ruplat[0]) ruplon = np.append(ruplon, ruplon[0]) rupx, rupy = proj(ruplon, ruplat, reverse=False) test1 = Bayless2013(origin, rup, slat, slon, sdepth, T=5) fd = test1.getFd() fd_test = np.array([ [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -8.92879772e-03, -1.74526918e-02, -2.22981746e-02, -2.34350450e-02, -2.13620062e-02, -1.72712346e-02, -1.29509613e-02, -1.02545064e-02, -1.03010185e-02, -1.28847597e-02, -1.66274727e-02, -1.96984070e-02, -2.05377743e-02, -1.81831337e-02, -1.21881814e-02, -2.64862879e-03, 0.00000000e+00, 0.00000000e+00 ], [ 0.00000000e+00, 0.00000000e+00, -8.73221519e-03, -2.21421374e-02, -3.18438939e-02, -3.71488270e-02, -3.76239913e-02, -3.35015951e-02, -2.61748968e-02, -1.83864728e-02, -1.34793002e-02, -1.36687799e-02, -1.85727143e-02, -2.55527671e-02, -3.14227568e-02, -3.38933995e-02, -3.19289607e-02, -2.53396980e-02, -1.45943649e-02, -3.71405488e-04, 0.00000000e+00 ], [ 0.00000000e+00, -2.54621422e-03, -2.11428566e-02, -3.68609103e-02, -4.87464747e-02, -5.56539037e-02, -5.64419387e-02, -5.05331157e-02, -3.52919381e-02, -2.18782050e-02, -1.40858125e-02, -1.47354546e-02, -2.35727189e-02, -3.74838465e-02, -4.75915414e-02, -5.13000399e-02, -4.87882409e-02, -4.05716321e-02, -2.77368254e-02, -1.13542729e-02, 0.00000000e+00 ], [ 0.00000000e+00, -1.21642958e-02, -3.33747360e-02, -5.21661817e-02, -6.74724509e-02, -7.77628842e-02, -8.00243748e-02, -6.42496853e-02, -4.38124530e-02, -1.97027426e-02, -1.45897731e-02, -1.07427056e-02, -3.08235222e-02, -4.82656988e-02, -6.67692677e-02, -7.35152908e-02, -6.85574283e-02, -5.71811573e-02, -4.12138780e-02, -2.20396726e-02, -6.24121310e-04 ], [ 0.00000000e+00, -2.00643401e-02, -4.39827328e-02, -6.62722434e-02, -8.60268414e-02, -1.01730306e-01, -9.86277741e-02, -9.82914922e-02, -5.22335876e-02, -1.54622435e-02, -1.57487554e-02, -3.06190808e-03, -4.81481586e-02, -8.92480491e-02, -8.63776477e-02, -9.98130440e-02, -8.95491230e-02, -7.33553695e-02, -5.34401725e-02, -3.11601812e-02, -7.33715103e-03 ], [ 0.00000000e+00, -2.50053614e-02, -5.11695772e-02, -7.65997026e-02, -1.00809054e-01, -1.22877573e-01, -1.18738178e-01, -1.55236782e-01, -7.45388001e-02, 1.92779182e-03, -1.94380016e-02, 1.94922939e-02, -7.66669920e-02, -1.53909722e-01, -1.10846875e-01, -1.19746768e-01, -1.07680300e-01, -8.59905101e-02, -6.22042294e-02, -3.71802472e-02, -1.13867485e-02 ], [ 0.00000000e+00, -2.63645827e-02, -5.37984901e-02, -8.11337022e-02, -1.08298371e-01, -1.35146441e-01, -1.34825430e-01, -1.85836050e-01, -1.10730875e-01, -3.18861095e-02, 4.14395701e-02, -1.52711946e-02, -1.31840763e-01, -1.96794707e-01, -1.33453212e-01, -1.34989129e-01, -1.17922385e-01, -9.21637323e-02, -6.58369237e-02, -3.91646838e-02, -1.22685698e-02 ], [ 0.00000000e+00, -2.64622244e-02, -5.40483999e-02, -8.16190336e-02, -1.09162854e-01, -1.36656677e-01, -1.37081504e-01, -1.89522811e-01, -1.17723634e-01, -4.88765748e-02, -5.04529015e-03, -5.76414497e-02, -1.45712183e-01, -2.03062804e-01, -1.36859828e-01, -1.37107390e-01, -1.19124650e-01, -9.28263279e-02, -6.61800709e-02, -3.93088682e-02, -1.22842049e-02 ], [ 0.00000000e+00, -2.58466495e-02, -5.24858827e-02, -7.86086164e-02, -1.03856343e-01, -1.27529509e-01, -1.23794779e-01, -1.68810613e-01, -8.22602627e-02, 1.74236964e-02, 9.38708725e-02, 4.23208284e-02, -8.46343723e-02, -1.70476759e-01, -1.17547884e-01, -1.24569752e-01, -1.11518670e-01, -8.84736806e-02, -6.38037151e-02, -3.81874381e-02, -1.19867610e-02 ], [ 0.00000000e+00, -2.42186547e-02, -4.84175525e-02, -7.09428614e-02, -9.07754575e-02, -1.06117824e-01, -9.50228292e-02, -1.29781980e-01, -3.08573454e-02, 7.39058739e-02, 1.30478117e-01, 8.28181149e-02, -2.70389535e-02, -1.20837502e-01, -8.02081725e-02, -9.70274506e-02, -9.35853383e-02, -7.77422806e-02, -5.77817530e-02, -3.53067886e-02, -1.12414659e-02 ], [ 0.00000000e+00, -2.16818717e-02, -4.22363856e-02, -5.96909893e-02, -7.24805224e-02, -7.81867829e-02, -6.11838569e-02, -9.05679744e-02, 9.95934969e-03, 1.07503875e-01, 1.52073917e-01, 1.05894634e-01, 8.68652263e-03, -7.98571818e-02, -4.16548658e-02, -6.40511838e-02, -6.99337160e-02, -6.26305633e-02, -4.89098800e-02, -3.09284566e-02, -1.00919381e-02 ], [ 0.00000000e+00, -1.84940182e-02, -3.47054606e-02, -4.65278129e-02, -5.22037664e-02, -4.93977115e-02, -2.95395230e-02, -5.82421092e-02, 3.91025654e-02, 1.29337956e-01, 1.67436703e-01, 1.21969296e-01, 3.20823547e-02, -5.00287386e-02, -9.22993907e-03, -3.27186625e-02, -4.52706958e-02, -4.57409325e-02, -3.84701291e-02, -2.55751405e-02, -8.64950254e-03 ], [ 0.00000000e+00, -1.49431380e-02, -2.65887341e-02, -3.29162158e-02, -3.22994323e-02, -2.29081781e-02, -2.60259636e-03, -3.29856530e-02, 6.02631314e-02, 1.45003704e-01, 1.79361264e-01, 1.34292814e-01, 4.88007115e-02, -2.82328554e-02, 1.64212421e-02, -5.72391847e-03, -2.23438861e-02, -2.90246794e-02, -2.76054402e-02, -1.97779758e-02, -7.03945406e-03 ], [ 0.00000000e+00, -1.12771143e-02, -1.84737590e-02, -1.98228664e-02, -1.40092305e-02, 1.84580818e-04, 1.95817303e-02, -1.32608487e-02, 7.62783168e-02, 1.57076433e-01, 1.89083905e-01, 1.44259188e-01, 6.15722813e-02, -1.17505212e-02, 3.65938109e-02, 1.66937711e-02, -2.18970818e-03, -1.35507683e-02, -1.70890527e-02, -1.39519424e-02, -5.37036892e-03 ], [ 0.00000000e+00, -7.67615215e-03, -1.07348257e-02, -7.75276739e-03, 2.22351695e-03, 1.98662250e-02, 3.77611177e-02, 2.42018661e-03, 8.89036172e-02, 1.66855206e-01, 1.97260700e-01, 1.52590263e-01, 7.17981256e-02, 1.18005972e-03, 5.26852303e-02, 3.51638855e-02, 1.51012176e-02, 2.69654076e-04, -7.33815554e-03, -8.36639665e-03, -3.72176313e-03 ], [ 0.00000000e+00, -4.50552324e-03, -4.32262850e-03, 1.73559158e-03, 1.42670366e-02, 3.35040699e-02, 4.97279358e-02, 1.85410528e-02, 9.39950666e-02, 1.46646579e-01, 9.13474746e-02, 1.37004651e-01, 7.74648339e-02, 1.59777072e-02, 6.25334939e-02, 4.74577418e-02, 2.72155518e-02, 1.06174952e-02, 3.94103899e-04, -3.68465400e-03, -2.19830733e-03 ], [ 0.00000000e+00, -1.74629916e-03, 5.44471813e-04, 8.22933499e-03, 2.15699287e-02, 4.04232250e-02, 5.69678048e-02, 5.52408259e-02, 9.04381272e-02, 1.08204635e-01, 9.14439984e-02, 1.06884511e-01, 8.17241884e-02, 5.55282924e-02, 6.78528399e-02, 5.47188925e-02, 3.35251483e-02, 1.69615982e-02, 5.72048628e-03, -8.81437278e-05, -7.36518436e-04 ], [ 0.00000000e+00, 4.07838765e-05, 3.63933766e-03, 1.20080876e-02, 2.51274691e-02, 4.25687176e-02, 6.25685606e-02, 7.33480475e-02, 8.37515545e-02, 9.52500287e-02, 9.15135660e-02, 9.66442834e-02, 8.66659913e-02, 8.10325633e-02, 7.18836713e-02, 5.45548434e-02, 3.55884875e-02, 2.00142359e-02, 8.71200201e-03, 2.04407846e-03, -6.53680674e-06 ], [ 0.00000000e+00, 2.40054729e-04, 4.44975227e-03, 1.27572519e-02, 2.49362989e-02, 4.03831326e-02, 5.80039988e-02, 7.61280192e-02, 8.37404162e-02, 8.89634569e-02, 9.15651607e-02, 9.13586235e-02, 8.83589144e-02, 8.27804032e-02, 6.75666471e-02, 5.00483249e-02, 3.36733366e-02, 1.96758691e-02, 9.00603204e-03, 2.18370401e-03, 0.00000000e+00 ], [ 0.00000000e+00, 0.00000000e+00, 2.78776980e-03, 1.05086036e-02, 2.13238822e-02, 3.45577738e-02, 4.91570145e-02, 6.36787133e-02, 7.63710088e-02, 8.54072310e-02, 8.92960200e-02, 8.75702197e-02, 8.07095447e-02, 6.97999389e-02, 5.63787286e-02, 4.20734776e-02, 2.83073312e-02, 1.61614525e-02, 6.56194125e-03, 1.00721924e-04, 0.00000000e+00 ], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 5.49667845e-03, 1.47563319e-02, 2.57955743e-02, 3.76689418e-02, 4.91861917e-02, 5.90108907e-02, 6.58478416e-02, 6.87018515e-02, 6.73174642e-02, 6.20270643e-02, 5.35456385e-02, 4.29400416e-02, 3.14129728e-02, 2.00795162e-02, 9.84001885e-03, 1.53992995e-03, 0.00000000e+00, 0.00000000e+00 ] ]) np.testing.assert_allclose(fd, fd_test, rtol=1e-4)
def _computeStikeDip(self): """ Loop over all triangles and get the average normal, north, and up vectors in ECEF. Use these to compute a representative strike and dip. """ seg = self._group_index groups = np.unique(seg) ng = len(groups) norm_vec = Vector(0, 0, 0) north_vec = Vector(0, 0, 0) up_vec = Vector(0, 0, 0) for i in range(ng): group_segments = np.where(groups[i] == seg)[0] nseg = len(group_segments) - 1 for j in range(nseg): ind = group_segments[j] P0 = Point(self._toplons[ind], self._toplats[ind], self._topdeps[ind]) P1 = Point(self._toplons[ind + 1], self._toplats[ind + 1], self._topdeps[ind + 1]) P2 = Point(self._botlons[ind + 1], self._botlats[ind + 1], self._botdeps[ind + 1]) P3 = Point(self._botlons[ind], self._botlats[ind], self._botdeps[ind]) P1up = Point(self._toplons[ind + 1], self._toplats[ind + 1], self._topdeps[ind + 1] - 1.0) P1N = Point(self._toplons[ind + 1], self._toplats[ind + 1] + 0.001, self._topdeps[ind + 1]) P3up = Point(self._botlons[ind], self._botlats[ind], self._botdeps[ind] - 1.0) P3N = Point(self._botlons[ind], self._botlats[ind] + 0.001, self._botdeps[ind]) p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) p1up = Vector.fromPoint(P1up) p1N = Vector.fromPoint(P1N) p3up = Vector.fromPoint(P3up) p3N = Vector.fromPoint(P3N) # Sides s01 = p1 - p0 s02 = p2 - p0 s03 = p3 - p0 s21 = p1 - p2 s23 = p3 - p2 # First triangle t1norm = (s02.cross(s01)).norm() a = s01.mag() b = s02.mag() c = s21.mag() s = (a + b + c) / 2 A1 = np.sqrt(s * (s - a) * (s - b) * (s - c)) / 1000 # Second triangle t2norm = (s03.cross(s02)).norm() a = s03.mag() b = s23.mag() c = s02.mag() s = (a + b + c) / 2 A2 = np.sqrt(s * (s - a) * (s - b) * (s - c)) / 1000 # Up and North p1up = (p1up - p1).norm() p3up = (p3up - p3).norm() p1N = (p1N - p1).norm() p3N = (p3N - p3).norm() # Combine norm_vec = norm_vec + A1 * t1norm + A2 * t2norm north_vec = north_vec + A1 * p1N + A2 * p3N up_vec = up_vec + A1 * p1up + A2 * p3up norm_vec = norm_vec.norm() north_vec = north_vec.norm() up_vec = up_vec.norm() # Do I need to flip the vector because it is pointing down (i.e., # right-hand rule is violated)? flip = np.sign(up_vec.dot(norm_vec)) norm_vec = flip * norm_vec # Angle between up_vec and norm_vec is dip self._dip = np.arcsin(up_vec.cross(norm_vec).mag()) * 180 / np.pi # Normal vector projected to horizontal plane nvph = (norm_vec - up_vec.dot(norm_vec) * up_vec).norm() # Dip direction is angle between nvph and north; strike is orthogonal. cp = nvph.cross(north_vec) sign = np.sign(cp.dot(up_vec)) dp = nvph.dot(north_vec) strike = np.arctan2(sign * cp.mag(), dp) * 180 / np.pi - 90 if strike < -180: strike = strike + 360 self._strike = strike
def test_rv4(): magnitude = 7.0 rake = 90.0 width = np.array([28]) rupx = np.array([0, 0]) rupy = np.array([0, 32]) zp = np.array([0]) dip = np.array([30]) # Convert to lat/lon proj = geo.utils.get_orthographic_projection(-122, -120, 39, 37) tlon, tlat = proj(rupx, rupy, reverse=True) # Dummy Origin origin = Origin({'lat': 0, 'lon': 0, 'depth': 0, 'mag': 0, 'eventsourcecode': 'rv4', 'rake': rake}) # Rupture rup = QuadRupture.fromTrace( np.array([tlon[0]]), np.array([tlat[0]]), np.array([tlon[1]]), np.array([tlat[1]]), zp, width, dip, origin, reference='') L = rup.getLength() # Figure out epicenter tmp = rup.getQuadrilaterals()[0] pp0 = Vector.fromPoint(point.Point( tmp[0].longitude, tmp[0].latitude, tmp[0].depth)) pp1 = Vector.fromPoint(point.Point( tmp[1].longitude, tmp[1].latitude, tmp[1].depth)) pp2 = Vector.fromPoint(point.Point( tmp[2].longitude, tmp[2].latitude, tmp[2].depth)) pp3 = Vector.fromPoint(point.Point( tmp[3].longitude, tmp[3].latitude, tmp[3].depth)) dxp = 6 / L dyp = (width - 8) / width mp0 = pp0 + (pp1 - pp0) * dxp mp1 = pp3 + (pp2 - pp3) * dxp rp = mp0 + (mp1 - mp0) * dyp epilat, epilon, epidepth = ecef2latlon(rp.x, rp.y, rp.z) # Fix Origin: origin = Origin({'lat': epilat, 'lon': epilon, 'depth': epidepth, 'mag': magnitude, 'eventsourcecode': 'rv4', 'rake': rake}) x = np.linspace(-50, 50, 11) y = np.linspace(-50, 50, 11) site_x, site_y = np.meshgrid(x, y) slon, slat = proj(site_x, site_y, reverse=True) deps = np.zeros_like(slon) test1 = Bayless2013(origin, rup, slat, slon, deps, T=2.0) # Test fd fd = test1.getFd() fd_test = np.array( [[0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.72143257e-03, 1.34977260e-03, 4.33616224e-15, 1.24446253e-03, 1.16142357e-03, 2.25464716e-03, 7.05281751e-04, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 7.62610242e-03, 1.25133844e-02, 5.61896104e-03, 7.63126014e-15, 4.52266194e-03, 4.67970900e-03, 1.02820316e-02, 5.13160096e-03, -6.13926251e-03], [0.00000000e+00, 4.00495234e-03, 2.37608386e-02, 2.37139333e-02, 9.55224050e-03, 5.66364910e-15, 7.70344813e-03, 7.36466362e-03, 1.48239704e-02, 8.40388145e-03, -1.58592485e-02], [8.08385547e-19, 9.38150101e-03, 3.38610620e-02, 3.85351492e-02, 1.91044918e-02, 3.98697802e-15, 1.54321666e-02, 1.21913760e-02, 2.04435166e-02, 1.04931859e-02, -1.85935894e-02], [2.12025421e-18, 1.37316085e-02, 4.40193799e-02, 6.16562477e-02, 4.77612496e-02, 2.60257085e-15, 3.86322888e-02, 1.97965887e-02, 2.64882038e-02, 1.23335908e-02, -2.07389932e-02], [2.64338576e-18, 1.45898292e-02, 4.89104213e-02, 7.70703166e-02, 9.55225258e-02, 1.01875104e-01, 7.73459329e-02, 2.50275508e-02, 2.93537540e-02, 1.30949577e-02, -2.15685454e-02], [2.64330042e-18, 1.45898262e-02, 4.89104186e-02, 7.70703146e-02, 9.55225248e-02, 1.01910945e-01, 7.74050835e-02, 2.52307946e-02, 2.92970736e-02, 1.30880504e-02, -2.15685424e-02], [2.64318867e-18, 1.45898259e-02, 4.89104184e-02, 7.70703144e-02, 9.55225247e-02, 1.01933432e-01, 7.74421258e-02, 2.53572923e-02, 2.92615130e-02, 1.30837284e-02, -2.15685422e-02], [2.64305117e-18, 1.45898284e-02, 4.89104206e-02, 7.70703161e-02, 9.55225256e-02, 1.01942593e-01, 7.74571359e-02, 2.54081640e-02, 2.92472117e-02, 1.30819985e-02, -2.15685446e-02], [2.30141673e-18, 1.40210825e-02, 4.56205547e-02, 6.63109661e-02, 5.79266964e-02, 2.33044622e-15, 4.69672564e-02, 2.18401553e-02, 2.72864925e-02, 1.25728575e-02, -2.10227772e-02], [1.10672535e-18, 1.04777076e-02, 3.59041065e-02, 4.24614318e-02, 2.24217216e-02, 3.66914762e-15, 1.81728517e-02, 1.39301504e-02, 2.14956836e-02, 1.08711460e-02, -1.90802849e-02]] ) np.testing.assert_allclose(fd, fd_test, rtol=2e-4)
def _computeGC2(rupture, lon, lat, depth): """ Method for computing GC2 from a ShakeMap Rupture instance. Args: rupture (Rupture): ShakeMap rupture object. lon (array): Numpy array of site longitudes. lat (array): Numpy array of site latitudes. depth (array): Numpy array of site depths. Returns: dict: Dictionary of GC2 distances. Keys include "T", "U", "rx" "ry", "ry0". """ quadlist = rupture.getQuadrilaterals() quadgc2 = copy.deepcopy(quadlist) oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) # ------------------------------------------------------------------------- # Define a projection that spans sites and rupture # ------------------------------------------------------------------------- all_lat = np.append(lat, rupture.lats) all_lon = np.append(lon, rupture.lons) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) # ------------------------------------------------------------------------- # First sort out strike discordance and nominal strike prior to # starting the loop if there is more than one group/trace. # ------------------------------------------------------------------------- group_ind = rupture._getGroupIndex() # Need group_ind as numpy array for sensible indexing... group_ind_np = np.array(group_ind) uind = np.unique(group_ind_np) n_groups = len(uind) if n_groups > 1: # --------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is "selected from the two endpoints most # distant from each other." # --------------------------------------------------------------------- # Need to get index of first and last quad # for each segment iq0 = np.zeros(n_groups, dtype='int16') iq1 = np.zeros(n_groups, dtype='int16') for k in uind: ii = [i for i, j in enumerate(group_ind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) # --------------------------------------------------------------------- # This is an iterator for each possible combination of traces # including trace orientations (i.e., flipped). # --------------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the trace pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 # --------------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". # --------------------------------------------------------------------- # This goofy while-loop is to adjust the side of the rupture where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(n_groups) b_prime = [None] * n_groups for j in range(n_groups): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(n_groups): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.deepcopy(A0) tmpA1 = copy.deepcopy(A1) A0 = tmpA1 A1 = tmpA0 # --------------------------------------------------------------------- # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # --------------------------------------------------------------------- # 1) flip quads for i in range(len(quadgc2)): if dc[group_ind[i]] < 0: quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist order qind = np.arange(len(quadgc2)) for i in range(n_groups): qsel = qind[group_ind_np == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[group_ind_np == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] # End of if-statement for adjusting group discordancy s_i = 0.0 l_i = np.zeros(len(quadgc2)) for i in range(len(quadgc2)): G0, G1, G2, G3 = quadgc2[i] # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length (top edge) l_i[i] = get_quad_length(quadgc2[i]) # --------------------------------------------------------------------- # Weight of segment, three cases # --------------------------------------------------------------------- # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan((l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if n_groups == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadgc2))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(group_ind_np == group_ind_np[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) # First endpoint in the current 'group' (or 'trace' in GC2 terms) p1 = Vector.fromPoint(quadgc2[iq0[group_ind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E) * ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] GC2T = GC2T / totweight GC2U = GC2U / totweight # Dictionary for holding the distances distdict = dict() distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if n_groups > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 return distdict
def get_quad_mesh(q, dx): """ Create a mesh from a quadrilateal. Args: q (list): A quadrilateral; list of four points. dx (float): Target dx in km; used to get nx and ny of mesh, but mesh snaps to edges of rupture so actual dx/dy will not actually equal this value in general. Returns: dict: Mesh dictionary, which includes numpy arrays: - llx: lower left x coordinate in ECEF coords. - lly: lower left y coordinate in ECEF coords. - llz: lower left z coordinate in ECEF coords. - ulx: upper left x coordinate in ECEF coords. - etc. """ P0, P1, P2, P3 = q p0 = Vector.fromPoint(P0) # fromPoint converts to ECEF p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Get nx based on length of top edge, minimum allowed is 2 toplen_km = get_quad_length(q) nx = int(np.max([round(toplen_km / dx, 0) + 1, 2])) # Get array of points along top and bottom edges xfac = np.linspace(0, 1, nx) topp = [p0 + (p1 - p0) * a for a in xfac] botp = [p3 + (p2 - p3) * a for a in xfac] # Get ny based on mean length of vectors connecting top and bottom points ylen_km = np.ones(nx) for i in range(nx): ylen_km[i] = (topp[i] - botp[i]).mag() / 1000 ny = int(np.max([round(np.mean(ylen_km) / dx, 0) + 1, 2])) yfac = np.linspace(0, 1, ny) # Build mesh: dict of ny by nx arrays (x, y, z): mesh = {'x': np.zeros([ny, nx]), 'y': np.zeros( [ny, nx]), 'z': np.zeros([ny, nx])} for i in range(nx): mpts = [topp[i] + (botp[i] - topp[i]) * a for a in yfac] mesh['x'][:, i] = [a.x for a in mpts] mesh['y'][:, i] = [a.y for a in mpts] mesh['z'][:, i] = [a.z for a in mpts] # Make arrays of pixel corners mesh['llx'] = mesh['x'][1:, 0:-1] mesh['lrx'] = mesh['x'][1:, 1:] mesh['ulx'] = mesh['x'][0:-1, 0:-1] mesh['urx'] = mesh['x'][0:-1, 1:] mesh['lly'] = mesh['y'][1:, 0:-1] mesh['lry'] = mesh['y'][1:, 1:] mesh['uly'] = mesh['y'][0:-1, 0:-1] mesh['ury'] = mesh['y'][0:-1, 1:] mesh['llz'] = mesh['z'][1:, 0:-1] mesh['lrz'] = mesh['z'][1:, 1:] mesh['ulz'] = mesh['z'][0:-1, 0:-1] mesh['urz'] = mesh['z'][0:-1, 1:] mesh['cpx'] = np.zeros_like(mesh['llx']) mesh['cpy'] = np.zeros_like(mesh['llx']) mesh['cpz'] = np.zeros_like(mesh['llx']) # i and j are indices over subruptures ni, nj = mesh['llx'].shape for i in range(0, ni): for j in range(0, nj): # Rupture corner points pp0 = Vector( mesh['ulx'][i, j], mesh['uly'][i, j], mesh['ulz'][i, j]) pp1 = Vector( mesh['urx'][i, j], mesh['ury'][i, j], mesh['urz'][i, j]) pp2 = Vector( mesh['lrx'][i, j], mesh['lry'][i, j], mesh['lrz'][i, j]) pp3 = Vector( mesh['llx'][i, j], mesh['lly'][i, j], mesh['llz'][i, j]) # Find center of quad mp0 = pp0 + (pp1 - pp0) * 0.5 mp1 = pp3 + (pp2 - pp3) * 0.5 cp = mp0 + (mp1 - mp0) * 0.5 mesh['cpx'][i, j] = cp.x mesh['cpy'][i, j] = cp.y mesh['cpz'][i, j] = cp.z return mesh
def __computeXiPrime(self): """ Computes the xi' value. """ hypo_ecef = Vector.fromPoint(geo.point.Point( self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) slat = self._lat slon = self._lon # Convert site to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([np.reshape(site_ecef_x, (-1,)), np.reshape(site_ecef_y, (-1,)), np.reshape(site_ecef_z, (-1,))]) xi_prime_unscaled = np.zeros_like(slat) # Normalize by total number of subruptures. For mtype == 1, the number # of subruptures will vary with site and be different for xi_s and # xi_p, so keep two variables and sum them for each quad. nsubs = np.zeros(np.product(slat.shape)) nsubp = np.zeros(np.product(slat.shape)) xi_prime_s = np.zeros(np.product(slat.shape)) xi_prime_p = np.zeros(np.product(slat.shape)) for k in range(len(self._rup.getQuadrilaterals())): # Select a quad q = self._rup.getQuadrilaterals()[k] # Quad mesh (ECEF coords) mesh = utils.get_quad_mesh(q, self._dx) # Rupture plane normal vector (ECEF coords) rpnv = utils.get_quad_normal(q) rpnvcol = np.array([[rpnv.x], [rpnv.y], [rpnv.z]]) cp_mat = np.array([np.reshape(mesh['cpx'], (-1,)), np.reshape(mesh['cpy'], (-1,)), np.reshape(mesh['cpz'], (-1,))]) # Compute matrix of p vectors hypcol = np.array([[hypo_ecef.x], [hypo_ecef.y], [hypo_ecef.z]]) pmat = cp_mat - hypcol # Project pmat onto quad ndotp = np.sum(pmat * rpnvcol, axis=0) pmat = pmat - ndotp * rpnvcol mag = np.sqrt(np.sum(pmat * pmat, axis=0)) pmatnorm = pmat / mag # like r1 # According to Rowshandel: # "The choice of the +/- sign in the above equations # depends on the (along-the-strike and across-the-dip) # location of the rupturing sub-fault relative to the # location of the hypocenter." # and: # "for the along the strike component of the slip unit # vector, the choice of the sign should result in the # slip unit vector (s) being exactly the same as the # rupture unit vector (p) for a pure strike-slip case" # Strike slip and dip slip components of unit slip vector # (ECEF coords) ds_mat, ss_mat = _get_quad_slip_ds_ss( q, self._rake, cp_mat, pmatnorm) slpmat = (ds_mat + ss_mat) mag = np.sqrt(np.sum(slpmat * slpmat, axis=0)) slpmatnorm = slpmat / mag # Loop over sites for i in range(site_mat.shape[1]): sitecol = np.array([[site_mat[0, i]], [site_mat[1, i]], [site_mat[2, i]]]) qmat = sitecol - cp_mat # 3x(ni*nj), like r2 mag = np.sqrt(np.sum(qmat * qmat, axis=0)) qmatnorm = qmat / mag # Propagation dot product pdotqraw = np.sum(pmatnorm * qmatnorm, axis=0) # Slip vector dot product sdotqraw = np.sum(slpmatnorm * qmatnorm, axis=0) if self._mtype == 1: # Only sum over (+) directivity effect subruptures # xi_p_prime pdotq = pdotqraw.clip(min=0) nsubp[i] = nsubp[i] + np.sum(pdotq > 0) # xi_s_prime sdotq = sdotqraw.clip(min=0) nsubs[i] = nsubs[i] + np.sum(sdotq > 0) elif self._mtype == 2: # Sum over contributing subruptures # xi_p_prime pdotq = pdotqraw nsubp[i] = nsubp[i] + cp_mat.shape[1] # xi_s_prime sdotq = sdotqraw nsubs[i] = nsubs[i] + cp_mat.shape[1] # Normalize by n sub ruptures later xi_prime_s[i] = xi_prime_s[i] + np.sum(sdotq) xi_prime_p[i] = xi_prime_p[i] + np.sum(pdotq) # Apply a water level to nsubp and nsubs to avoid division by # zero. This should only occur when the numerator is also zero # and so the resulting value should be zero. nsubs = np.maximum(nsubs, 1) nsubp = np.maximum(nsubp, 1) # We are outside the 'k' loop over nquads. # o Normalize xi_prime_s and xi_prime_p # o Reshape them # o Add them together with the 'a' weights xi_prime_tmp = (self._a_weight) * (xi_prime_s / nsubs) + \ (1 - self._a_weight) * (xi_prime_p / nsubp) xi_prime_unscaled = xi_prime_unscaled + \ np.reshape(xi_prime_tmp, slat.shape) # Scale so that xi_prime has range (0, 1) if self._mtype == 1: xi_prime = xi_prime_unscaled elif self._mtype == 2: xi_prime = 0.5 * (xi_prime_unscaled + 1) self._xi_prime = xi_prime
def __computeXiPrime(self): """ Computes the xi' value. """ hypo_ecef = Vector.fromPoint( geo.point.Point(self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) slat = self._lat slon = self._lon # Convert site to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([ np.reshape(site_ecef_x, (-1, )), np.reshape(site_ecef_y, (-1, )), np.reshape(site_ecef_z, (-1, )) ]) xi_prime_unscaled = np.zeros_like(slat) # Normalize by total number of subruptures. For mtype == 1, the number # of subruptures will vary with site and be different for xi_s and # xi_p, so keep two variables and sum them for each quad. nsubs = np.zeros(np.product(slat.shape)) nsubp = np.zeros(np.product(slat.shape)) xi_prime_s = np.zeros(np.product(slat.shape)) xi_prime_p = np.zeros(np.product(slat.shape)) for k in range(len(self._rup.getQuadrilaterals())): # Select a quad q = self._rup.getQuadrilaterals()[k] # Quad mesh (ECEF coords) mesh = utils.get_quad_mesh(q, self._dx) # Rupture plane normal vector (ECEF coords) rpnv = utils.get_quad_normal(q) rpnvcol = np.array([[rpnv.x], [rpnv.y], [rpnv.z]]) cp_mat = np.array([ np.reshape(mesh['cpx'], (-1, )), np.reshape(mesh['cpy'], (-1, )), np.reshape(mesh['cpz'], (-1, )) ]) # Compute matrix of p vectors hypcol = np.array([[hypo_ecef.x], [hypo_ecef.y], [hypo_ecef.z]]) pmat = cp_mat - hypcol # Project pmat onto quad ndotp = np.sum(pmat * rpnvcol, axis=0) pmat = pmat - ndotp * rpnvcol mag = np.sqrt(np.sum(pmat * pmat, axis=0)) pmatnorm = pmat / mag # like r1 # According to Rowshandel: # "The choice of the +/- sign in the above equations # depends on the (along-the-strike and across-the-dip) # location of the rupturing sub-fault relative to the # location of the hypocenter." # and: # "for the along the strike component of the slip unit # vector, the choice of the sign should result in the # slip unit vector (s) being exactly the same as the # rupture unit vector (p) for a pure strike-slip case" # Strike slip and dip slip components of unit slip vector # (ECEF coords) ds_mat, ss_mat = _get_quad_slip_ds_ss(q, self._rake, cp_mat, pmatnorm) slpmat = (ds_mat + ss_mat) mag = np.sqrt(np.sum(slpmat * slpmat, axis=0)) slpmatnorm = slpmat / mag # Loop over sites for i in range(site_mat.shape[1]): sitecol = np.array([[site_mat[0, i]], [site_mat[1, i]], [site_mat[2, i]]]) qmat = sitecol - cp_mat # 3x(ni*nj), like r2 mag = np.sqrt(np.sum(qmat * qmat, axis=0)) qmatnorm = qmat / mag # Propagation dot product pdotqraw = np.sum(pmatnorm * qmatnorm, axis=0) # Slip vector dot product sdotqraw = np.sum(slpmatnorm * qmatnorm, axis=0) if self._mtype == 1: # Only sum over (+) directivity effect subruptures # xi_p_prime pdotq = pdotqraw.clip(min=0) nsubp[i] = nsubp[i] + np.sum(pdotq > 0) # xi_s_prime sdotq = sdotqraw.clip(min=0) nsubs[i] = nsubs[i] + np.sum(sdotq > 0) elif self._mtype == 2: # Sum over contributing subruptures # xi_p_prime pdotq = pdotqraw nsubp[i] = nsubp[i] + cp_mat.shape[1] # xi_s_prime sdotq = sdotqraw nsubs[i] = nsubs[i] + cp_mat.shape[1] # Normalize by n sub ruptures later xi_prime_s[i] = xi_prime_s[i] + np.sum(sdotq) xi_prime_p[i] = xi_prime_p[i] + np.sum(pdotq) # Apply a water level to nsubp and nsubs to avoid division by # zero. This should only occur when the numerator is also zero # and so the resulting value should be zero. nsubs = np.maximum(nsubs, 1) nsubp = np.maximum(nsubp, 1) # We are outside the 'k' loop over nquads. # o Normalize xi_prime_s and xi_prime_p # o Reshape them # o Add them together with the 'a' weights xi_prime_tmp = (self._a_weight) * (xi_prime_s / nsubs) + \ (1 - self._a_weight) * (xi_prime_p / nsubp) xi_prime_unscaled = xi_prime_unscaled + \ np.reshape(xi_prime_tmp, slat.shape) # Scale so that xi_prime has range (0, 1) if self._mtype == 1: xi_prime = xi_prime_unscaled elif self._mtype == 2: xi_prime = 0.5 * (xi_prime_unscaled + 1) self._xi_prime = xi_prime
def _quad_distance(q, points, horizontal=False): """ Calculate the shortest distance from a set of points to a rupture surface. Args: q (list): A quadrilateral; list of four points. points (array): Numpy array Nx3 of points (ECEF) to calculate distance from. horizontal: Boolean indicating whether to treat points inside quad as 0 distance. Returns: float: Array of size N of distances (in km) from input points to rupture surface. """ P0, P1, P2, P3 = q if horizontal: # project this quad to the surface P0 = Point(P0.x, P0.y, 0) P1 = Point(P1.x, P1.y, 0) P2 = Point(P2.x, P2.y, 0) P3 = Point(P3.x, P3.y, 0) # Convert to ecef p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Make a unit vector normal to the plane normalVector = (p1 - p0).cross(p2 - p0).norm() dist = np.ones_like(points[:, 0]) * np.nan p0d = p0.getArray() - points p1d = p1.getArray() - points p2d = p2.getArray() - points p3d = p3.getArray() - points # Create 4 planes with normals pointing outside rectangle n0 = (p1 - p0).cross(normalVector).getArray() n1 = (p2 - p1).cross(normalVector).getArray() n2 = (p3 - p2).cross(normalVector).getArray() n3 = (p0 - p3).cross(normalVector).getArray() sgn0 = np.signbit(np.sum(n0 * p0d, axis=1)) sgn1 = np.signbit(np.sum(n1 * p1d, axis=1)) sgn2 = np.signbit(np.sum(n2 * p2d, axis=1)) sgn3 = np.signbit(np.sum(n3 * p3d, axis=1)) inside_idx = (sgn0 == sgn1) & (sgn1 == sgn2) & (sgn2 == sgn3) if horizontal: dist[inside_idx] = 0.0 else: dist[inside_idx] = np.power(np.abs( np.sum(p0d[inside_idx, :] * normalVector.getArray(), axis=1)), 2) outside_idx = np.logical_not(inside_idx) s0 = _distance_sq_to_segment(p0d, p1d) s1 = _distance_sq_to_segment(p1d, p2d) s2 = _distance_sq_to_segment(p2d, p3d) s3 = _distance_sq_to_segment(p3d, p0d) smin = np.minimum(np.minimum(s0, s1), np.minimum(s2, s3)) dist[outside_idx] = smin[outside_idx] dist = np.sqrt(dist) / 1000.0 shp = dist.shape if len(shp) == 1: dist.shape = (shp[0], 1) if np.any(np.isnan(dist)): raise Exception("Could not calculate some distances!") dist = np.fliplr(dist) return dist
def __computeThetaAndS(self, i): """ Args: i (int): Compute d for the i-th quad/segment. """ # self.phyp is in ECEF tmp = ecef.ecef2latlon(self.phyp[i].x, self.phyp[i].y, self.phyp[i].z) epi_ecef = Vector.fromPoint(geo.point.Point(tmp[1], tmp[0], 0.0)) epi_col = np.array([[epi_ecef.x], [epi_ecef.y], [epi_ecef.z]]) # First compute along strike vector P0, P1, P2, P3 = self._rup.getQuadrilaterals()[i] p0 = Vector.fromPoint(P0) # convert to ECEF p1 = Vector.fromPoint(P1) e01 = p1 - p0 e01norm = e01.norm() hp0 = p0 - epi_ecef hp1 = p1 - epi_ecef strike_min = Vector.dot(hp0, e01norm) / 1000.0 # convert to km strike_max = Vector.dot(hp1, e01norm) / 1000.0 # convert to km strike_col = np.array( [[e01norm.x], [e01norm.y], [e01norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([np.reshape(site_ecef_x, (-1,)), np.reshape(site_ecef_y, (-1,)), np.reshape(site_ecef_z, (-1,))]) # Epicenter-to-site matrix e2s_mat = site_mat - epi_col # in ECEF mag = np.sqrt(np.sum(e2s_mat * e2s_mat, axis=0)) # Avoid division by zero mag[mag == 0] = 1e-12 e2s_norm = e2s_mat / mag # Dot epicenter-to-site with along-strike vector s_raw = np.sum(e2s_mat * strike_col, axis=0) / 1000.0 # conver to km # Put back into a 2d array s_raw = np.reshape(s_raw, self._lat.shape) self.s = np.abs(s_raw.clip(min=strike_min, max=strike_max)).clip(min=np.exp(1)) # Compute theta sdots = np.sum(e2s_norm * strike_col, axis=0) theta_raw = np.arccos(sdots) # But theta is defined to be the reference angle # (i.e., the equivalent angle between 0 and 90 deg) sintheta = np.abs(np.sin(theta_raw)) costheta = np.abs(np.cos(theta_raw)) theta = np.arctan2(sintheta, costheta) self.theta = np.reshape(theta, self._lat.shape)
def test_so6(): magnitude = 7.2 dip = np.array([70]) rake = 135 width = np.array([15]) L = 80 rupx = np.array([0, 0]) rupy = np.array([0, L]) zp = np.array([0]) # Convert to lat/lon proj = geo.utils.get_orthographic_projection(-122, -120, 39, 37) tlon, tlat = proj(rupx, rupy, reverse=True) # Dummy origin origin = Origin({'lat': 0, 'lon': 0, 'depth': 0, 'mag': 0, 'eventsourcecode': 'so6', 'rake': rake}) # Rupture rup = QuadRupture.fromTrace( np.array([tlon[0]]), np.array([tlat[0]]), np.array([tlon[1]]), np.array([tlat[1]]), zp, width, dip, origin, reference='rv4') # Sites x = np.linspace(-80, 80, 21) y = np.linspace(-50, 130, 21) site_x, site_y = np.meshgrid(x, y) slon, slat = proj(site_x, site_y, reverse=True) sdepth = np.zeros_like(slon) # Fix origin tmp = rup.getQuadrilaterals()[0] pp0 = Vector.fromPoint(point.Point( tmp[0].longitude, tmp[0].latitude, tmp[0].depth)) pp1 = Vector.fromPoint(point.Point( tmp[1].longitude, tmp[1].latitude, tmp[1].depth)) pp2 = Vector.fromPoint(point.Point( tmp[2].longitude, tmp[2].latitude, tmp[2].depth)) pp3 = Vector.fromPoint(point.Point( tmp[3].longitude, tmp[3].latitude, tmp[3].depth)) dxp = 10 / L dyp = (width - 5) / width mp0 = pp0 + (pp1 - pp0) * dxp mp1 = pp3 + (pp2 - pp3) * dxp rp = mp0 + (mp1 - mp0) * dyp epilat, epilon, epidepth = ecef2latlon(rp.x, rp.y, rp.z) epix, epiy = proj(epilon, epilat, reverse=False) origin = Origin({'lat': epilat, 'lon': epilon, 'depth': epidepth, 'mag': magnitude, 'eventsourcecode': 'so6', 'rake': rake}) ruplat = [a.latitude for a in rup.getQuadrilaterals()[0]] ruplon = [a.longitude for a in rup.getQuadrilaterals()[0]] ruplat = np.append(ruplat, ruplat[0]) ruplon = np.append(ruplon, ruplon[0]) rupx, rupy = proj(ruplon, ruplat, reverse=False) test1 = Bayless2013(origin, rup, slat, slon, sdepth, T=5) fd = test1.getFd() fd_test = np.array( [[0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -8.92879772e-03, -1.74526918e-02, -2.22981746e-02, -2.34350450e-02, -2.13620062e-02, -1.72712346e-02, -1.29509613e-02, -1.02545064e-02, -1.03010185e-02, -1.28847597e-02, -1.66274727e-02, -1.96984070e-02, -2.05377743e-02, -1.81831337e-02, -1.21881814e-02, -2.64862879e-03, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, -8.73221519e-03, -2.21421374e-02, -3.18438939e-02, -3.71488270e-02, -3.76239913e-02, -3.35015951e-02, -2.61748968e-02, -1.83864728e-02, -1.34793002e-02, -1.36687799e-02, -1.85727143e-02, -2.55527671e-02, -3.14227568e-02, -3.38933995e-02, -3.19289607e-02, -2.53396980e-02, -1.45943649e-02, -3.71405488e-04, 0.00000000e+00], [0.00000000e+00, -2.54621422e-03, -2.11428566e-02, -3.68609103e-02, -4.87464747e-02, -5.56539037e-02, -5.64419387e-02, -5.05331157e-02, -3.52919381e-02, -2.18782050e-02, -1.40858125e-02, -1.47354546e-02, -2.35727189e-02, -3.74838465e-02, -4.75915414e-02, -5.13000399e-02, -4.87882409e-02, -4.05716321e-02, -2.77368254e-02, -1.13542729e-02, 0.00000000e+00], [0.00000000e+00, -1.21642958e-02, -3.33747360e-02, -5.21661817e-02, -6.74724509e-02, -7.77628842e-02, -8.00243748e-02, -6.42496853e-02, -4.38124530e-02, -1.97027426e-02, -1.45897731e-02, -1.07427056e-02, -3.08235222e-02, -4.82656988e-02, -6.67692677e-02, -7.35152908e-02, -6.85574283e-02, -5.71811573e-02, -4.12138780e-02, -2.20396726e-02, -6.24121310e-04], [0.00000000e+00, -2.00643401e-02, -4.39827328e-02, -6.62722434e-02, -8.60268414e-02, -1.01730306e-01, -9.86277741e-02, -9.82914922e-02, -5.22335876e-02, -1.54622435e-02, -1.57487554e-02, -3.06190808e-03, -4.81481586e-02, -8.92480491e-02, -8.63776477e-02, -9.98130440e-02, -8.95491230e-02, -7.33553695e-02, -5.34401725e-02, -3.11601812e-02, -7.33715103e-03], [0.00000000e+00, -2.50053614e-02, -5.11695772e-02, -7.65997026e-02, -1.00809054e-01, -1.22877573e-01, -1.18738178e-01, -1.55236782e-01, -7.45388001e-02, 1.92779182e-03, -1.94380016e-02, 1.94922939e-02, -7.66669920e-02, -1.53909722e-01, -1.10846875e-01, -1.19746768e-01, -1.07680300e-01, -8.59905101e-02, -6.22042294e-02, -3.71802472e-02, -1.13867485e-02], [0.00000000e+00, -2.63645827e-02, -5.37984901e-02, -8.11337022e-02, -1.08298371e-01, -1.35146441e-01, -1.34825430e-01, -1.85836050e-01, -1.10730875e-01, -3.18861095e-02, 4.14395701e-02, -1.52711946e-02, -1.31840763e-01, -1.96794707e-01, -1.33453212e-01, -1.34989129e-01, -1.17922385e-01, -9.21637323e-02, -6.58369237e-02, -3.91646838e-02, -1.22685698e-02], [0.00000000e+00, -2.64622244e-02, -5.40483999e-02, -8.16190336e-02, -1.09162854e-01, -1.36656677e-01, -1.37081504e-01, -1.89522811e-01, -1.17723634e-01, -4.88765748e-02, -5.04529015e-03, -5.76414497e-02, -1.45712183e-01, -2.03062804e-01, -1.36859828e-01, -1.37107390e-01, -1.19124650e-01, -9.28263279e-02, -6.61800709e-02, -3.93088682e-02, -1.22842049e-02], [0.00000000e+00, -2.58466495e-02, -5.24858827e-02, -7.86086164e-02, -1.03856343e-01, -1.27529509e-01, -1.23794779e-01, -1.68810613e-01, -8.22602627e-02, 1.74236964e-02, 9.38708725e-02, 4.23208284e-02, -8.46343723e-02, -1.70476759e-01, -1.17547884e-01, -1.24569752e-01, -1.11518670e-01, -8.84736806e-02, -6.38037151e-02, -3.81874381e-02, -1.19867610e-02], [0.00000000e+00, -2.42186547e-02, -4.84175525e-02, -7.09428614e-02, -9.07754575e-02, -1.06117824e-01, -9.50228292e-02, -1.29781980e-01, -3.08573454e-02, 7.39058739e-02, 1.30478117e-01, 8.28181149e-02, -2.70389535e-02, -1.20837502e-01, -8.02081725e-02, -9.70274506e-02, -9.35853383e-02, -7.77422806e-02, -5.77817530e-02, -3.53067886e-02, -1.12414659e-02], [0.00000000e+00, -2.16818717e-02, -4.22363856e-02, -5.96909893e-02, -7.24805224e-02, -7.81867829e-02, -6.11838569e-02, -9.05679744e-02, 9.95934969e-03, 1.07503875e-01, 1.52073917e-01, 1.05894634e-01, 8.68652263e-03, -7.98571818e-02, -4.16548658e-02, -6.40511838e-02, -6.99337160e-02, -6.26305633e-02, -4.89098800e-02, -3.09284566e-02, -1.00919381e-02], [0.00000000e+00, -1.84940182e-02, -3.47054606e-02, -4.65278129e-02, -5.22037664e-02, -4.93977115e-02, -2.95395230e-02, -5.82421092e-02, 3.91025654e-02, 1.29337956e-01, 1.67436703e-01, 1.21969296e-01, 3.20823547e-02, -5.00287386e-02, -9.22993907e-03, -3.27186625e-02, -4.52706958e-02, -4.57409325e-02, -3.84701291e-02, -2.55751405e-02, -8.64950254e-03], [0.00000000e+00, -1.49431380e-02, -2.65887341e-02, -3.29162158e-02, -3.22994323e-02, -2.29081781e-02, -2.60259636e-03, -3.29856530e-02, 6.02631314e-02, 1.45003704e-01, 1.79361264e-01, 1.34292814e-01, 4.88007115e-02, -2.82328554e-02, 1.64212421e-02, -5.72391847e-03, -2.23438861e-02, -2.90246794e-02, -2.76054402e-02, -1.97779758e-02, -7.03945406e-03], [0.00000000e+00, -1.12771143e-02, -1.84737590e-02, -1.98228664e-02, -1.40092305e-02, 1.84580818e-04, 1.95817303e-02, -1.32608487e-02, 7.62783168e-02, 1.57076433e-01, 1.89083905e-01, 1.44259188e-01, 6.15722813e-02, -1.17505212e-02, 3.65938109e-02, 1.66937711e-02, -2.18970818e-03, -1.35507683e-02, -1.70890527e-02, -1.39519424e-02, -5.37036892e-03], [0.00000000e+00, -7.67615215e-03, -1.07348257e-02, -7.75276739e-03, 2.22351695e-03, 1.98662250e-02, 3.77611177e-02, 2.42018661e-03, 8.89036172e-02, 1.66855206e-01, 1.97260700e-01, 1.52590263e-01, 7.17981256e-02, 1.18005972e-03, 5.26852303e-02, 3.51638855e-02, 1.51012176e-02, 2.69654076e-04, -7.33815554e-03, -8.36639665e-03, -3.72176313e-03], [0.00000000e+00, -4.50552324e-03, -4.32262850e-03, 1.73559158e-03, 1.42670366e-02, 3.35040699e-02, 4.97279358e-02, 1.85410528e-02, 9.39950666e-02, 1.46646579e-01, 9.13474746e-02, 1.37004651e-01, 7.74648339e-02, 1.59777072e-02, 6.25334939e-02, 4.74577418e-02, 2.72155518e-02, 1.06174952e-02, 3.94103899e-04, -3.68465400e-03, -2.19830733e-03], [0.00000000e+00, -1.74629916e-03, 5.44471813e-04, 8.22933499e-03, 2.15699287e-02, 4.04232250e-02, 5.69678048e-02, 5.52408259e-02, 9.04381272e-02, 1.08204635e-01, 9.14439984e-02, 1.06884511e-01, 8.17241884e-02, 5.55282924e-02, 6.78528399e-02, 5.47188925e-02, 3.35251483e-02, 1.69615982e-02, 5.72048628e-03, -8.81437278e-05, -7.36518436e-04], [0.00000000e+00, 4.07838765e-05, 3.63933766e-03, 1.20080876e-02, 2.51274691e-02, 4.25687176e-02, 6.25685606e-02, 7.33480475e-02, 8.37515545e-02, 9.52500287e-02, 9.15135660e-02, 9.66442834e-02, 8.66659913e-02, 8.10325633e-02, 7.18836713e-02, 5.45548434e-02, 3.55884875e-02, 2.00142359e-02, 8.71200201e-03, 2.04407846e-03, -6.53680674e-06], [0.00000000e+00, 2.40054729e-04, 4.44975227e-03, 1.27572519e-02, 2.49362989e-02, 4.03831326e-02, 5.80039988e-02, 7.61280192e-02, 8.37404162e-02, 8.89634569e-02, 9.15651607e-02, 9.13586235e-02, 8.83589144e-02, 8.27804032e-02, 6.75666471e-02, 5.00483249e-02, 3.36733366e-02, 1.96758691e-02, 9.00603204e-03, 2.18370401e-03, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 2.78776980e-03, 1.05086036e-02, 2.13238822e-02, 3.45577738e-02, 4.91570145e-02, 6.36787133e-02, 7.63710088e-02, 8.54072310e-02, 8.92960200e-02, 8.75702197e-02, 8.07095447e-02, 6.97999389e-02, 5.63787286e-02, 4.20734776e-02, 2.83073312e-02, 1.61614525e-02, 6.56194125e-03, 1.00721924e-04, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 5.49667845e-03, 1.47563319e-02, 2.57955743e-02, 3.76689418e-02, 4.91861917e-02, 5.90108907e-02, 6.58478416e-02, 6.87018515e-02, 6.73174642e-02, 6.20270643e-02, 5.35456385e-02, 4.29400416e-02, 3.14129728e-02, 2.00795162e-02, 9.84001885e-03, 1.53992995e-03, 0.00000000e+00, 0.00000000e+00]] ) np.testing.assert_allclose(fd, fd_test, rtol=1e-4)
def test_rv4(): magnitude = 7.0 rake = 90.0 width = np.array([28]) rupx = np.array([0, 0]) rupy = np.array([0, 32]) zp = np.array([0]) dip = np.array([30]) # Convert to lat/lon proj = geo.utils.get_orthographic_projection(-122, -120, 39, 37) tlon, tlat = proj(rupx, rupy, reverse=True) # Dummy Origin origin = Origin({ 'lat': 0, 'lon': 0, 'depth': 0, 'mag': 0, 'eventsourcecode': 'rv4', 'rake': rake }) # Rupture rup = QuadRupture.fromTrace(np.array([tlon[0]]), np.array([tlat[0]]), np.array([tlon[1]]), np.array([tlat[1]]), zp, width, dip, origin, reference='') L = rup.getLength() # Figure out epicenter tmp = rup.getQuadrilaterals()[0] pp0 = Vector.fromPoint( point.Point(tmp[0].longitude, tmp[0].latitude, tmp[0].depth)) pp1 = Vector.fromPoint( point.Point(tmp[1].longitude, tmp[1].latitude, tmp[1].depth)) pp2 = Vector.fromPoint( point.Point(tmp[2].longitude, tmp[2].latitude, tmp[2].depth)) pp3 = Vector.fromPoint( point.Point(tmp[3].longitude, tmp[3].latitude, tmp[3].depth)) dxp = 6 / L dyp = (width - 8) / width mp0 = pp0 + (pp1 - pp0) * dxp mp1 = pp3 + (pp2 - pp3) * dxp rp = mp0 + (mp1 - mp0) * dyp epilat, epilon, epidepth = ecef2latlon(rp.x, rp.y, rp.z) # Fix Origin: origin = Origin({ 'lat': epilat, 'lon': epilon, 'depth': epidepth, 'mag': magnitude, 'eventsourcecode': 'rv4', 'rake': rake }) x = np.linspace(-50, 50, 11) y = np.linspace(-50, 50, 11) site_x, site_y = np.meshgrid(x, y) slon, slat = proj(site_x, site_y, reverse=True) deps = np.zeros_like(slon) test1 = Bayless2013(origin, rup, slat, slon, deps, T=2.0) # Test fd fd = test1.getFd() fd_test = np.array( [[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.72143257e-03, 1.34977260e-03, 4.33616224e-15, 1.24446253e-03, 1.16142357e-03, 2.25464716e-03, 7.05281751e-04, 0.00000000e+00 ], [ 0.00000000e+00, 0.00000000e+00, 7.62610242e-03, 1.25133844e-02, 5.61896104e-03, 7.63126014e-15, 4.52266194e-03, 4.67970900e-03, 1.02820316e-02, 5.13160096e-03, -6.13926251e-03 ], [ 0.00000000e+00, 4.00495234e-03, 2.37608386e-02, 2.37139333e-02, 9.55224050e-03, 5.66364910e-15, 7.70344813e-03, 7.36466362e-03, 1.48239704e-02, 8.40388145e-03, -1.58592485e-02 ], [ 8.08385547e-19, 9.38150101e-03, 3.38610620e-02, 3.85351492e-02, 1.91044918e-02, 3.98697802e-15, 1.54321666e-02, 1.21913760e-02, 2.04435166e-02, 1.04931859e-02, -1.85935894e-02 ], [ 2.12025421e-18, 1.37316085e-02, 4.40193799e-02, 6.16562477e-02, 4.77612496e-02, 2.60257085e-15, 3.86322888e-02, 1.97965887e-02, 2.64882038e-02, 1.23335908e-02, -2.07389932e-02 ], [ 2.64338576e-18, 1.45898292e-02, 4.89104213e-02, 7.70703166e-02, 9.55225258e-02, 1.01875104e-01, 7.73459329e-02, 2.50275508e-02, 2.93537540e-02, 1.30949577e-02, -2.15685454e-02 ], [ 2.64330042e-18, 1.45898262e-02, 4.89104186e-02, 7.70703146e-02, 9.55225248e-02, 1.01910945e-01, 7.74050835e-02, 2.52307946e-02, 2.92970736e-02, 1.30880504e-02, -2.15685424e-02 ], [ 2.64318867e-18, 1.45898259e-02, 4.89104184e-02, 7.70703144e-02, 9.55225247e-02, 1.01933432e-01, 7.74421258e-02, 2.53572923e-02, 2.92615130e-02, 1.30837284e-02, -2.15685422e-02 ], [ 2.64305117e-18, 1.45898284e-02, 4.89104206e-02, 7.70703161e-02, 9.55225256e-02, 1.01942593e-01, 7.74571359e-02, 2.54081640e-02, 2.92472117e-02, 1.30819985e-02, -2.15685446e-02 ], [ 2.30141673e-18, 1.40210825e-02, 4.56205547e-02, 6.63109661e-02, 5.79266964e-02, 2.33044622e-15, 4.69672564e-02, 2.18401553e-02, 2.72864925e-02, 1.25728575e-02, -2.10227772e-02 ], [ 1.10672535e-18, 1.04777076e-02, 3.59041065e-02, 4.24614318e-02, 2.24217216e-02, 3.66914762e-15, 1.81728517e-02, 1.39301504e-02, 2.14956836e-02, 1.08711460e-02, -1.90802849e-02 ]]) np.testing.assert_allclose(fd, fd_test, rtol=2e-4)
def __setPseudoHypocenters(self): """ Set a pseudo-hypocenter. Adapted from ShakeMap 3.5 src/contour/directivity.c From Bayless and Somerville: "Define the pseudo-hypocenter for rupture of successive segments as the point on the side edge of the rupture segment that is closest to the side edge of the previous segment, and that lies half way between the top and bottom of the rupture. We assume that the rupture is segmented along strike, not updip. All geometric parameters are computed relative to the pseudo-hypocenter." """ hyp_ecef = Vector.fromPoint( geo.point.Point(self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) # Loop over each quad self.phyp = [None] * self._nq for i in range(self._nq): P0, P1, P2, P3 = self._rup.getQuadrilaterals()[i] p0 = Vector.fromPoint(P0) # convert to ECEF p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Create 4 planes with normals pointing outside rectangle hpnp = Vector.cross(p1 - p0, p2 - p0).norm() hpp = -hpnp.x * p0.x - hpnp.y * p0.y - hpnp.z * p0.z n0 = Vector.cross(p1 - p0, hpnp) n1 = Vector.cross(p2 - p1, hpnp) n2 = Vector.cross(p3 - p2, hpnp) n3 = Vector.cross(p0 - p3, hpnp) # ----------------------------------------------------------------- # Is the hypocenter inside the projected rectangle? # Dot products show which side the origin is on. # If origin is on same side of all the planes, then it is 'inside' # ----------------------------------------------------------------- sgn0 = np.signbit(Vector.dot(n0, p0 - hyp_ecef)) sgn1 = np.signbit(Vector.dot(n1, p1 - hyp_ecef)) sgn2 = np.signbit(Vector.dot(n2, p2 - hyp_ecef)) sgn3 = np.signbit(Vector.dot(n3, p3 - hyp_ecef)) if (sgn0 == sgn1) and (sgn1 == sgn2) and (sgn2 == sgn3): # Origin is inside. Use distance-to-plane formula. d = Vector.dot(hpnp, hyp_ecef) + hpp d = d * d # Put the pseudo hypocenter on the plane D = Vector.dot(hpnp, hyp_ecef) + hpp self.phyp[i] = hyp_ecef - hpnp * D else: # Origin is outside. Find distance to edges p0p = np.reshape(p0.getArray() - hyp_ecef.getArray(), [1, 3]) p1p = np.reshape(p1.getArray() - hyp_ecef.getArray(), [1, 3]) p2p = np.reshape(p2.getArray() - hyp_ecef.getArray(), [1, 3]) p3p = np.reshape(p3.getArray() - hyp_ecef.getArray(), [1, 3]) s1 = _distance_sq_to_segment(p1p, p2p) s3 = _distance_sq_to_segment(p3p, p0p) # Assuming that the rupture is segmented along strike and not # updip (as described by Bayless and somerville), we only # need to consider s1 and s3: if s1 > s3: e30 = p0 - p3 e30norm = e30.norm() mag = e30.mag() self.phyp[i] = p3 + e30norm * (0.5 * mag) else: e21 = p1 - p2 e21norm = e21.norm() mag = e21.mag() self.phyp[i] = p2 + e21norm * (0.5 * mag)
def _quad_distance(q, points, horizontal=False): """ Calculate the shortest distance from a set of points to a rupture surface. Args: q (list): A quadrilateral; list of four points. points (array): Numpy array Nx3 of points (ECEF) to calculate distance from. horizontal: Boolean indicating whether to treat points inside quad as 0 distance. Returns: float: Array of size N of distances (in km) from input points to rupture surface. """ P0, P1, P2, P3 = q if horizontal: # project this quad to the surface P0 = Point(P0.x, P0.y, 0) P1 = Point(P1.x, P1.y, 0) P2 = Point(P2.x, P2.y, 0) P3 = Point(P3.x, P3.y, 0) # Convert to ecef p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) # Make a unit vector normal to the plane normalVector = (p1 - p0).cross(p2 - p0).norm() dist = np.ones_like(points[:, 0]) * np.nan p0d = p0.getArray() - points p1d = p1.getArray() - points p2d = p2.getArray() - points p3d = p3.getArray() - points # Create 4 planes with normals pointing outside rectangle n0 = (p1 - p0).cross(normalVector).getArray() n1 = (p2 - p1).cross(normalVector).getArray() n2 = (p3 - p2).cross(normalVector).getArray() n3 = (p0 - p3).cross(normalVector).getArray() sgn0 = np.signbit(np.sum(n0 * p0d, axis=1)) sgn1 = np.signbit(np.sum(n1 * p1d, axis=1)) sgn2 = np.signbit(np.sum(n2 * p2d, axis=1)) sgn3 = np.signbit(np.sum(n3 * p3d, axis=1)) inside_idx = (sgn0 == sgn1) & (sgn1 == sgn2) & (sgn2 == sgn3) if horizontal: dist[inside_idx] = 0.0 else: dist[inside_idx] = np.power(np.abs( np.sum(p0d[inside_idx, :] * normalVector.getArray(), axis=1)), 2) outside_idx = np.logical_not(inside_idx) s0 = _distance_sq_to_segment(p0d, p1d) s1 = _distance_sq_to_segment(p1d, p2d) s2 = _distance_sq_to_segment(p2d, p3d) s3 = _distance_sq_to_segment(p3d, p0d) smin = np.minimum(np.minimum(s0, s1), np.minimum(s2, s3)) dist[outside_idx] = smin[outside_idx] dist = np.sqrt(dist) / 1000.0 shp = dist.shape if len(shp) == 1: dist.shape = (shp[0], 1) if np.any(np.isnan(dist)): raise ShakeLibException("Could not calculate some distances!") dist = np.fliplr(dist) return dist
def _computeStrikeDip(self): """ Loop over all triangles and get the average normal, north, and up vectors in ECEF. Use these to compute a representative strike and dip. """ seg = self._group_index groups = np.unique(seg) ng = len(groups) norm_vec = Vector(0, 0, 0) north_vec = Vector(0, 0, 0) up_vec = Vector(0, 0, 0) for i in range(ng): group_segments = np.where(groups[i] == seg)[0] nseg = len(group_segments) - 1 for j in range(nseg): ind = group_segments[j] P0 = Point(self._toplons[ind], self._toplats[ind], self._topdeps[ind]) P1 = Point(self._toplons[ind + 1], self._toplats[ind + 1], self._topdeps[ind + 1]) P2 = Point(self._botlons[ind + 1], self._botlats[ind + 1], self._botdeps[ind + 1]) P3 = Point(self._botlons[ind], self._botlats[ind], self._botdeps[ind]) P1up = Point(self._toplons[ind + 1], self._toplats[ind + 1], self._topdeps[ind + 1] - 1.0) P1N = Point(self._toplons[ind + 1], self._toplats[ind + 1] + 0.001, self._topdeps[ind + 1]) P3up = Point(self._botlons[ind], self._botlats[ind], self._botdeps[ind] - 1.0) P3N = Point(self._botlons[ind], self._botlats[ind] + 0.001, self._botdeps[ind]) p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) p2 = Vector.fromPoint(P2) p3 = Vector.fromPoint(P3) p1up = Vector.fromPoint(P1up) p1N = Vector.fromPoint(P1N) p3up = Vector.fromPoint(P3up) p3N = Vector.fromPoint(P3N) # Sides s01 = p1 - p0 s02 = p2 - p0 s03 = p3 - p0 s21 = p1 - p2 s23 = p3 - p2 # First triangle t1norm = (s02.cross(s01)).norm() a = s01.mag() b = s02.mag() c = s21.mag() s = (a + b + c) / 2 A1 = np.sqrt(s * (s - a) * (s - b) * (s - c)) / 1000 # Second triangle t2norm = (s03.cross(s02)).norm() a = s03.mag() b = s23.mag() c = s02.mag() s = (a + b + c) / 2 A2 = np.sqrt(s * (s - a) * (s - b) * (s - c)) / 1000 # Up and North p1up = (p1up - p1).norm() p3up = (p3up - p3).norm() p1N = (p1N - p1).norm() p3N = (p3N - p3).norm() # Combine norm_vec = norm_vec + A1 * t1norm + A2 * t2norm north_vec = north_vec + A1 * p1N + A2 * p3N up_vec = up_vec + A1 * p1up + A2 * p3up norm_vec = norm_vec.norm() north_vec = north_vec.norm() up_vec = up_vec.norm() # Do I need to flip the vector because it is pointing down (i.e., # right-hand rule is violated)? flip = np.sign(up_vec.dot(norm_vec)) norm_vec = flip * norm_vec # Angle between up_vec and norm_vec is dip self._dip = np.arcsin(up_vec.cross(norm_vec).mag()) * 180 / np.pi # Normal vector projected to horizontal plane nvph = (norm_vec - up_vec.dot(norm_vec) * up_vec).norm() # Dip direction is angle between nvph and north; strike is orthogonal. cp = nvph.cross(north_vec) sign = np.sign(cp.dot(up_vec)) dp = nvph.dot(north_vec) strike = np.arctan2(sign * cp.mag(), dp) * 180 / np.pi - 90 if strike < -180: strike = strike + 360 self._strike = strike
def getDepthAtPoint(self, lat, lon): SMALL_DISTANCE = 2e-03 # 2 meters depth = np.nan tmp, _ = self.computeRjb(np.array([lon]), np.array([lat]), np.array([0])) if tmp > SMALL_DISTANCE: return depth i = 0 imin = -1 dmin = 9999999999999999 for quad in self.getQuadrilaterals(): pX = Vector.fromPoint(Point(lon, lat, 0)) points = np.reshape(np.array([pX.x, pX.y, pX.z]), (1, 3)) rjb = utils._quad_distance(quad, points, horizontal=True) if rjb[0][0] < dmin: dmin = rjb[0][0] imin = i i += 1 quad = self._quadrilaterals[imin] P0, P1, P2, P3 = quad # project the quad and the point in question to orthographic defined by # quad xmin = np.min([P0.x, P1.x, P2.x, P3.x]) xmax = np.max([P0.x, P1.x, P2.x, P3.x]) ymin = np.min([P0.y, P1.y, P2.y, P3.y]) ymax = np.max([P0.y, P1.y, P2.y, P3.y]) proj = OrthographicProjection(xmin, xmax, ymax, ymin) # project each vertex of quad (at 0 depth) s0x, s0y = proj(P0.x, P0.y) s1x, s1y = proj(P1.x, P1.y) s2x, s2y = proj(P2.x, P2.y) s3x, s3y = proj(P3.x, P3.y) sxx, sxy = proj(lon, lat) # turn these to vectors s0 = Vector(s0x, s0y, 0) s1 = Vector(s1x, s1y, 0) s3 = Vector(s3x, s3y, 0) sx = Vector(sxx, sxy, 0) # Compute vector from s0 to s1 s0s1 = s1 - s0 # Compute the vector from s0 to s3 s0s3 = s3 - s0 # Compute the vector from s0 to sx s0sx = sx - s0 # cross products s0normal = s0s3.cross(s0s1) dd = s0s1.cross(s0normal) # normalize dd (down dip direction) ddn = dd.norm() # dot product sxdd = ddn.dot(s0sx) # get width of quad (convert from km to m) w = utils.get_quad_width(quad) * 1000 # Get weights for top and bottom edge depths N = utils.get_quad_normal(quad) V = utils.get_vertical_vector(quad) dip = np.degrees(np.arccos(Vector.dot(N, V))) ws = (w * np.cos(np.radians(dip))) wtt = (ws - sxdd) / ws wtb = sxdd / ws # Compute the depth of of the plane at Px: depth = wtt * P0.z + wtb * P3.z * 1000 return depth