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 getDip(self): """ Return average dip of all quadrilaterals in the rupture. Returns: float: Average dip in degrees. """ dipsum = 0.0 for quad in self._quadrilaterals: N = utils.get_quad_normal(quad) V = utils.get_vertical_vector(quad) dipsum = dipsum + np.degrees(np.arccos(Vector.dot(N, V))) dip = dipsum / len(self._quadrilaterals) return dip
def get_quad_dip(q): """ Dip of a quadrilateral. Args: q (list): A quadrilateral; list of four points. Returns: float: Dip in degrees. """ N = get_quad_normal(q) V = get_vertical_vector(q) dip = np.degrees(np.arccos(Vector.dot(N, V))) return dip
def get_quad_dip(q): """ Dip of a quadrilateral. Args: q (list): A quadrilateral; list of four points. Returns: float: Dip in degrees. """ N = get_quad_normal(q) V = get_vertical_vector(q) dip = np.degrees(np.arccos(Vector.dot(N, V))) return dip
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 _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 _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 __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 __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 __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 __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 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