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 _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 _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 __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 __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)