示例#1
0
文件: utils.py 项目: ynthdhj/shakemap
def get_quad_slip(q, rake):
    """
    Compute the unit slip vector in ECEF space for a quad and rake angle.

    Args:
        q (list): A quadrilateral; list of four points.
        rake (float): Direction of motion of the hanging wall relative to
        the foot wall, as measured by the angle (deg) from the strike vector.

    Returns:
        Vector: Unit slip vector in ECEF space.

    """
    P0, P1, P2 = q[0:3]
    strike = P0.azimuth(P1)
    dip = get_quad_dip(q)
    s1_local = get_local_unit_slip_vector(strike, dip, rake)
    s0_local = Vector(0, 0, 0)
    qlats = [a.latitude for a in q]
    qlons = [a.longitude for a in q]
    proj = get_orthographic_projection(
        np.min(qlons), np.max(qlons), np.min(qlats), np.max(qlats))
    s1_ll = proj(np.array([s1_local.x]), np.array([s1_local.y]), reverse=True)
    s0_ll = proj(np.array([s0_local.x]), np.array([s0_local.y]), reverse=True)
    s1_ecef = Vector.fromTuple(latlon2ecef(s1_ll[1], s1_ll[0], s1_local.z))
    s0_ecef = Vector.fromTuple(latlon2ecef(s0_ll[1], s0_ll[0], s0_local.z))
    slp_ecef = (s1_ecef - s0_ecef).norm()
    return slp_ecef
示例#2
0
def get_quad_slip(q, rake):
    """
    Compute the unit slip vector in ECEF space for a quad and rake angle.

    Args:
        q (list): A quadrilateral; list of four points.
        rake (float): Direction of motion of the hanging wall relative to
        the foot wall, as measured by the angle (deg) from the strike vector.

    Returns:
        Vector: Unit slip vector in ECEF space.

    """
    P0, P1, P2 = q[0:3]
    strike = P0.azimuth(P1)
    dip = get_quad_dip(q)
    s1_local = get_local_unit_slip_vector(strike, dip, rake)
    s0_local = Vector(0, 0, 0)
    qlats = [a.latitude for a in q]
    qlons = [a.longitude for a in q]
    proj = OrthographicProjection(
        np.min(qlons), np.max(qlons), np.min(qlats), np.max(qlats))
    s1_ll = proj(np.array([s1_local.x]), np.array([s1_local.y]), reverse=True)
    s0_ll = proj(np.array([s0_local.x]), np.array([s0_local.y]), reverse=True)
    s1_ecef = Vector.fromTuple(latlon2ecef(s1_ll[1], s1_ll[0], s1_local.z))
    s0_ecef = Vector.fromTuple(latlon2ecef(s0_ll[1], s0_ll[0], s0_local.z))
    slp_ecef = (s1_ecef - s0_ecef).norm()
    return slp_ecef
示例#3
0
    def getArea(self):
        """
        Compute the rupture area. For EdgeRupture, we compute this by grouping
        the traces into "quadrilaterals" for which the verticies may not be
        co-planar. We then break up the quadrilaterals into triangles for which
        we can compute area.

        Returns:
            float: Rupture area in square km.
        """
        seg = self._group_index
        groups = np.unique(seg)
        ng = len(groups)
        area = 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 = latlon2ecef(self._toplats[ind],
                                 self._toplons[ind],
                                 self._topdeps[ind])
                p1 = latlon2ecef(self._toplats[ind + 1],
                                 self._toplons[ind + 1],
                                 self._topdeps[ind + 1])
                p2 = latlon2ecef(self._botlats[ind + 1],
                                 self._botlons[ind + 1],
                                 self._botdeps[ind + 1])
                p3 = latlon2ecef(self._botlats[ind],
                                 self._botlons[ind],
                                 self._botdeps[ind])
                a = np.sqrt((p1[0] - p0[0])**2 +
                            (p1[1] - p0[1])**2 +
                            (p1[2] - p0[2])**2)
                b = np.sqrt((p2[0] - p0[0])**2 +
                            (p2[1] - p0[1])**2 +
                            (p2[2] - p0[2])**2)
                c = np.sqrt((p2[0] - p1[0])**2 +
                            (p2[1] - p1[1])**2 +
                            (p2[2] - p1[2])**2)
                s = (a + b + c) / 2
                A1 = np.sqrt(s * (s - a) * (s - b) * (s - c))
                a = np.sqrt((p0[0] - p3[0])**2 +
                            (p0[1] - p3[1])**2 +
                            (p0[2] - p3[2])**2)
                b = np.sqrt((p2[0] - p3[0])**2 +
                            (p2[1] - p3[1])**2 +
                            (p2[2] - p3[2])**2)
                c = np.sqrt((p0[0] - p2[0])**2 +
                            (p0[1] - p2[1])**2 +
                            (p0[2] - p2[2])**2)
                s = (a + b + c) / 2
                A2 = np.sqrt(s * (s - a) * (s - b) * (s - c))
                area = area + (A1 + A2) / 1000 / 1000
        return area
示例#4
0
    def getArea(self):
        """
        Compute the rupture area. For EdgeRupture, we compute this by grouping
        the traces into "quadrilaterals" for which the verticies may not be
        co-planar. We then break up the quadrilaterals into triangles for which
        we can compute area.

        Returns:
            float: Rupture area in square km.
        """
        seg = self._group_index
        groups = np.unique(seg)
        ng = len(groups)
        area = 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 = latlon2ecef(self._toplats[ind], self._toplons[ind],
                                 self._topdeps[ind])
                p1 = latlon2ecef(self._toplats[ind + 1],
                                 self._toplons[ind + 1],
                                 self._topdeps[ind + 1])
                p2 = latlon2ecef(self._botlats[ind + 1],
                                 self._botlons[ind + 1],
                                 self._botdeps[ind + 1])
                p3 = latlon2ecef(self._botlats[ind], self._botlons[ind],
                                 self._botdeps[ind])
                a = np.sqrt((p1[0] - p0[0])**2 + (p1[1] - p0[1])**2 +
                            (p1[2] - p0[2])**2)
                b = np.sqrt((p2[0] - p0[0])**2 + (p2[1] - p0[1])**2 +
                            (p2[2] - p0[2])**2)
                c = np.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2 +
                            (p2[2] - p1[2])**2)
                s = (a + b + c) / 2
                A1 = np.sqrt(s * (s - a) * (s - b) * (s - c))
                a = np.sqrt((p0[0] - p3[0])**2 + (p0[1] - p3[1])**2 +
                            (p0[2] - p3[2])**2)
                b = np.sqrt((p2[0] - p3[0])**2 + (p2[1] - p3[1])**2 +
                            (p2[2] - p3[2])**2)
                c = np.sqrt((p0[0] - p2[0])**2 + (p0[1] - p2[1])**2 +
                            (p0[2] - p2[2])**2)
                s = (a + b + c) / 2
                A2 = np.sqrt(s * (s - a) * (s - b) * (s - c))
                area = area + (A1 + A2) / 1000 / 1000
        return area
示例#5
0
    def computeRjb(self, lon, lat, depth, var=False):
        """
        Method for computing Joyner-Boore distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).
            var (bool): Also return variance of prediction. Unused, and
                will raise an exception if not False.

        Returns:
           array: Joyner-Boore distance (km).

        """

        if var is True:
            raise ValueError('var must be False for EdgeRupture')

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16
        quads = self.getQuadrilaterals()

        for i in range(len(quads)):
            P0, P1, P2, P3 = quads[i]
            S0 = copy.deepcopy(P0)
            S1 = copy.deepcopy(P1)
            S2 = copy.deepcopy(P2)
            S3 = copy.deepcopy(P3)
            S0.depth = 0.0
            S1.depth = 0.0
            S2.depth = 0.0
            S3.depth = 0.0
            squad = [S0, S1, S2, S3]
            rjbdist = utils._quad_distance(squad, sites_ecef, horizontal=True)
            minrjb = np.minimum(minrjb, rjbdist)

        minrjb = minrjb.reshape(oldshape)
        return minrjb
示例#6
0
    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)
示例#7
0
    def computeRjb(self, lon, lat, depth):
        """
        Method for computing Joyner-Boore distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).

        Returns:
           tuple: A tuple of an array of Joyner-Boore distance (km), and None.

        """

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16
        quads = self.getQuadrilaterals()

        for i in range(len(quads)):
            P0, P1, P2, P3 = quads[i]
            S0 = copy.deepcopy(P0)
            S1 = copy.deepcopy(P1)
            S2 = copy.deepcopy(P2)
            S3 = copy.deepcopy(P3)
            S0.depth = 0.0
            S1.depth = 0.0
            S2.depth = 0.0
            S3.depth = 0.0
            squad = [S0, S1, S2, S3]
            rjbdist = utils._quad_distance(squad, sites_ecef, horizontal=True)
            minrjb = np.minimum(minrjb, rjbdist)

        minrjb = minrjb.reshape(oldshape)
        return minrjb, None
示例#8
0
    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)
示例#9
0
    def computeRrup(self, lon, lat, depth, var=False):
        """
        Method for computing rupture distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).
            var (bool): Also return variance of prediction. Unused, and
                will raise an exception if not False.

        Returns:
           array: Rupture distance (km).

        """

        if var is True:
            raise ValueError('var must be False for EdgeRupture')

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16
        quads = self.getQuadrilaterals()

        for i in range(len(quads)):
            rrupdist = utils._quad_distance(quads[i], sites_ecef)
            minrrup = np.minimum(minrrup, rrupdist)

        minrrup = minrrup.reshape(oldshape)
        return minrrup
示例#10
0
    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
示例#11
0
    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
示例#12
0
def test_ecef():
    print('Testing ECEF conversion code...')
    lat = 32.1
    lon = 118.5
    dep = 10.0

    x, y, z = latlon2ecef(lat, lon, dep)

    TESTX = -2576515.419
    TESTY = 4745351.087
    TESTZ = 3364516.124

    np.testing.assert_almost_equal(x, TESTX, decimal=2)
    np.testing.assert_almost_equal(y, TESTY, decimal=2)
    np.testing.assert_almost_equal(z, TESTZ, decimal=2)

    lat2, lon2, dep2 = ecef2latlon(x, y, z)
    np.testing.assert_almost_equal(lat2, lat, decimal=2)
    np.testing.assert_almost_equal(lon2, lon, decimal=2)
    np.testing.assert_almost_equal(dep2, dep, decimal=2)
    print('Passed tests of ECEF conversion code.')
示例#13
0
    def computeRrup(self, lon, lat, depth):
        """
        Method for computing rupture distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).

        Returns:
           tuple: A tuple of an array of Rupture distance (km), and None.

        """

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16
        quads = self.getQuadrilaterals()

        for i in range(len(quads)):
            rrupdist = utils._quad_distance(quads[i], sites_ecef)
            minrrup = np.minimum(minrrup, rrupdist)

        minrrup = minrrup.reshape(oldshape)
        return minrrup, None
示例#14
0
    def computeRjb(self, lon, lat, depth, var=False):
        """
        Method for computing Joyner-Boore distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).
            var (bool): Also return variance of prediction. Unused, and
                will raise an exception if not False.

        Returns:
           array: Joyner-Boore distance (km).

        """

        if var is True:
            raise ValueError('var must be False for EdgeRupture')

        mesh_dx = self._mesh_dx

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        # ---------------------------------------------------------------------
        # Get mesh
        # ---------------------------------------------------------------------
        mx = []
        my = []
        mz = []
        u_groups = np.unique(self._group_index)
        n_groups = len(u_groups)
        for j in range(n_groups):
            g_ind = np.where(u_groups[j] == self._group_index)[0]
            nq = len(self._toplats[g_ind]) - 1
            for i in range(nq):
                q = [
                    Point(self._toplons[g_ind[i]], self._toplats[g_ind[i]], 0),
                    Point(self._toplons[g_ind[i + 1]],
                          self._toplats[g_ind[i + 1]], 0),
                    Point(self._botlons[g_ind[i + 1]],
                          self._botlats[g_ind[i + 1]], 0),
                    Point(self._botlons[g_ind[i]], self._botlats[g_ind[i]], 0)
                ]
                mesh = utils.get_quad_mesh(q, dx=mesh_dx)
                mx.extend(list(np.reshape(mesh['x'], (-1, ))))
                my.extend(list(np.reshape(mesh['y'], (-1, ))))
                mz.extend(list(np.reshape(mesh['z'], (-1, ))))
        mesh_mat = np.array([np.array(mx), np.array(my), np.array(mz)])

        # ---------------------------------------------------------------------
        # Compute distance
        # ---------------------------------------------------------------------
        dist = np.zeros_like(x)
        for i in range(len(x)):
            sitecol = sites_ecef[i, :].reshape([3, 1])
            dif = sitecol - mesh_mat
            distarray = np.sqrt(np.sum(dif * dif, axis=0))
            dist[i] = np.min(distarray) / 1000.0  # convert to km

        dist = np.reshape(dist, oldshape)

        return dist
示例#15
0
def _get_quad_slip_ds_ss(q, rake, cp, p):
    """
    Compute the DIP SLIP and STRIKE SLIP components of the unit slip vector in
    ECEF coords for a quad and rake angle.
    :param q:
        A quadrilateral.
    :param rake:
        Direction of motion of the hanging wall relative to the
        foot wall, as measured by the angle (deg) from the strike vector.
    :param cp:
        A 3x(n sub rupture) array giving center points of each sub rupture
        in ECEF coords.
    :param p:
        A 3x(n sub rupture) array giving the unit vectors of the propagation
        vector on each sub rupture in ECEF coords.
    :returns:
        List of dip slip and strike slip components (each is a matrix)
        of the unit slip vector in ECEF space.
    """
    # Get quad vertices, strike, dip
    P0, P1 = q[0:2]
    strike = P0.azimuth(P1)
    dip = utils.get_quad_dip(q)

    # Slip unit vectors in 'local' (i.e., z-up, x-east) coordinates
    d1_local = utils.get_local_unit_slip_vector_DS(strike, dip, rake)
    s1_local = utils.get_local_unit_slip_vector_SS(strike, dip, rake)

    # Convert to a column array
    d1_col = np.array([[d1_local.x],
                       [d1_local.y],
                       [d1_local.z]])
    s1_col = np.array([[s1_local.x],
                       [s1_local.y],
                       [s1_local.z]])

    # Define 'local' coordinate system
    qlats = [a.latitude for a in q]
    qlons = [a.longitude for a in q]
    proj = get_orthographic_projection(
        np.min(qlons), np.max(qlons), np.min(qlats), np.max(qlats))

    # Convert p and cp to geographic coords
    p0lat, p0lon, p0z = ecef2latlon(cp[0, ], cp[1, ], cp[2, ])
    p1lat, p1lon, p1z = ecef2latlon(cp[0, ] + p[0, ],
                                    cp[1, ] + p[1, ],
                                    cp[2, ] + p[2, ])

    # Convert p to 'local' coords
    p0x, p0y = proj(p0lon, p0lat)
    p1x, p1y = proj(p1lon, p1lat)
    px = p1x - p0x
    py = p1y - p0y
    pz = p1z - p0z

    # Apply sign changes in 'local' coords
    s1mat = np.array([[np.abs(s1_col[0]) * np.sign(px)],
                      [np.abs(s1_col[1]) * np.sign(py)],
                      [np.abs(s1_col[2]) * np.sign(pz)]])
#                      [np.abs(s1_col[2])*np.ones_like(pz)]])

    dipsign = -np.sign(np.sin(np.radians(rake)))
    d1mat = np.array([[d1_col[0] * np.ones_like(px) * dipsign],
                      [d1_col[1] * np.ones_like(py) * dipsign],
                      [d1_col[2] * np.ones_like(pz) * dipsign]])

    # Need to track 'origin'
    s0 = np.array([[0], [0], [0]])

    # Convert from 'local' to geographic coords
    s1_ll = proj(s1mat[0, ], s1mat[1, ], reverse=True)
    d1_ll = proj(d1mat[0, ], d1mat[1, ], reverse=True)
    s0_ll = proj(s0[0], s0[1], reverse=True)

    # And then back to ECEF:
    s1_ecef = latlon2ecef(s1_ll[1], s1_ll[0], s1mat[2, ])
    d1_ecef = latlon2ecef(d1_ll[1], d1_ll[0], d1mat[2, ])
    s0_ecef = latlon2ecef(s0_ll[1], s0_ll[0], s0[2])
    s00 = s0_ecef[0].reshape(-1)
    s01 = s0_ecef[1].reshape(-1)
    s02 = s0_ecef[2].reshape(-1)
    d_mat = np.array([d1_ecef[0].reshape(-1) - s00,
                      d1_ecef[1].reshape(-1) - s01,
                      d1_ecef[2].reshape(-1) - s02])
    s_mat = np.array([s1_ecef[0].reshape(-1) - s00,
                      s1_ecef[1].reshape(-1) - s01,
                      s1_ecef[2].reshape(-1) - s02])
    return d_mat, s_mat
示例#16
0
def _get_quad_slip_ds_ss(q, rake, cp, p):
    """
    Compute the DIP SLIP and STRIKE SLIP components of the unit slip vector in
    ECEF coords for a quad and rake angle.
    :param q:
        A quadrilateral.
    :param rake:
        Direction of motion of the hanging wall relative to the
        foot wall, as measured by the angle (deg) from the strike vector.
    :param cp:
        A 3x(n sub rupture) array giving center points of each sub rupture
        in ECEF coords.
    :param p:
        A 3x(n sub rupture) array giving the unit vectors of the propagation
        vector on each sub rupture in ECEF coords.
    :returns:
        List of dip slip and strike slip components (each is a matrix)
        of the unit slip vector in ECEF space.
    """
    # Get quad vertices, strike, dip
    P0, P1 = q[0:2]
    strike = P0.azimuth(P1)
    dip = utils.get_quad_dip(q)

    # Slip unit vectors in 'local' (i.e., z-up, x-east) coordinates
    d1_local = utils.get_local_unit_slip_vector_DS(strike, dip, rake)
    s1_local = utils.get_local_unit_slip_vector_SS(strike, dip, rake)

    # Convert to a column array
    d1_col = np.array([[d1_local.x], [d1_local.y], [d1_local.z]])
    s1_col = np.array([[s1_local.x], [s1_local.y], [s1_local.z]])

    # Define 'local' coordinate system
    qlats = [a.latitude for a in q]
    qlons = [a.longitude for a in q]
    proj = OrthographicProjection(np.min(qlons), np.max(qlons), np.min(qlats),
                                  np.max(qlats))

    # Convert p and cp to geographic coords
    p0lat, p0lon, p0z = ecef2latlon(cp[0, ], cp[1, ], cp[2, ])
    p1lat, p1lon, p1z = ecef2latlon(cp[0, ] + p[0, ], cp[1, ] + p[1, ],
                                    cp[2, ] + p[2, ])

    # Convert p to 'local' coords
    p0x, p0y = proj(p0lon, p0lat)
    p1x, p1y = proj(p1lon, p1lat)
    px = p1x - p0x
    py = p1y - p0y
    pz = p1z - p0z

    # Apply sign changes in 'local' coords
    s1mat = np.array([[np.abs(s1_col[0]) * np.sign(px)],
                      [np.abs(s1_col[1]) * np.sign(py)],
                      [np.abs(s1_col[2]) * np.sign(pz)]])
    #                      [np.abs(s1_col[2])*np.ones_like(pz)]])

    dipsign = -np.sign(np.sin(np.radians(rake)))
    d1mat = np.array([[d1_col[0] * np.ones_like(px) * dipsign],
                      [d1_col[1] * np.ones_like(py) * dipsign],
                      [d1_col[2] * np.ones_like(pz) * dipsign]])

    # Need to track 'origin'
    s0 = np.array([[0], [0], [0]])

    # Convert from 'local' to geographic coords
    s1_ll = proj(s1mat[0, ], s1mat[1, ], reverse=True)
    d1_ll = proj(d1mat[0, ], d1mat[1, ], reverse=True)
    s0_ll = proj(s0[0], s0[1], reverse=True)

    # And then back to ECEF:
    s1_ecef = latlon2ecef(s1_ll[1], s1_ll[0], s1mat[2, ])
    d1_ecef = latlon2ecef(d1_ll[1], d1_ll[0], d1mat[2, ])
    s0_ecef = latlon2ecef(s0_ll[1], s0_ll[0], s0[2])
    s00 = s0_ecef[0].reshape(-1)
    s01 = s0_ecef[1].reshape(-1)
    s02 = s0_ecef[2].reshape(-1)
    d_mat = np.array([
        d1_ecef[0].reshape(-1) - s00, d1_ecef[1].reshape(-1) - s01,
        d1_ecef[2].reshape(-1) - s02
    ])
    s_mat = np.array([
        s1_ecef[0].reshape(-1) - s00, s1_ecef[1].reshape(-1) - s01,
        s1_ecef[2].reshape(-1) - s02
    ])
    return d_mat, s_mat
示例#17
0
    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
示例#18
0
    def computeRjb(self, lon, lat, depth):
        """
        Method for computing Joyner-Boore distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).

        Returns:
           array: Joyner-Boore distance (km).

        """

        mesh_dx = self._mesh_dx

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        # ---------------------------------------------------------------------
        # Get mesh
        # ---------------------------------------------------------------------
        mx = []
        my = []
        mz = []
        u_groups = np.unique(self._group_index)
        n_groups = len(u_groups)
        for j in range(n_groups):
            g_ind = np.where(u_groups[j] == self._group_index)[0]
            nq = len(self._toplats[g_ind]) - 1
            for i in range(nq):
                q = [Point(self._toplons[g_ind[i]],
                           self._toplats[g_ind[i]],
                           0),
                     Point(self._toplons[g_ind[i + 1]],
                           self._toplats[g_ind[i + 1]],
                           0),
                     Point(self._botlons[g_ind[i + 1]],
                           self._botlats[g_ind[i + 1]],
                           0),
                     Point(self._botlons[g_ind[i]],
                           self._botlats[g_ind[i]],
                           0)
                     ]
                mesh = utils.get_quad_mesh(q, dx=mesh_dx)
                mx.extend(list(np.reshape(mesh['x'], (-1,))))
                my.extend(list(np.reshape(mesh['y'], (-1,))))
                mz.extend(list(np.reshape(mesh['z'], (-1,))))
        mesh_mat = np.array([np.array(mx), np.array(my), np.array(mz)])

        # ---------------------------------------------------------------------
        # Compute distance
        # ---------------------------------------------------------------------
        dist = np.zeros_like(x)
        for i in range(len(x)):
            sitecol = sites_ecef[i, :].reshape([3, 1])
            dif = sitecol - mesh_mat
            distarray = np.sqrt(np.sum(dif * dif, axis=0))
            dist[i] = np.min(distarray) / 1000.0  # convert to km

        dist = np.reshape(dist, oldshape)

        return dist
示例#19
0
    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)
示例#20
0
    def computeRrup(self, lon, lat, depth):
        """
        Method for computing rupture distance.

        Args:
            lon (array): Numpy array of longitudes.
            lat (array): Numpy array of latitudes.
            depth (array): Numpy array of depths (km; positive down).

        Returns:
           tuple: A tuple of an array of rupture distance (km), and None.

        """

        mesh_dx = self._mesh_dx

        # ---------------------------------------------------------------------
        # Sort out sites
        # ---------------------------------------------------------------------
        oldshape = lon.shape

        if len(oldshape) == 2:
            newshape = (oldshape[0] * oldshape[1], 1)
        else:
            newshape = (oldshape[0], 1)

        x, y, z = latlon2ecef(lat, lon, depth)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

        # ---------------------------------------------------------------------
        # Get mesh
        # ---------------------------------------------------------------------
        mx = []
        my = []
        mz = []
        u_groups = np.unique(self._group_index)
        n_groups = len(u_groups)
        for j in range(n_groups):
            g_ind = np.where(u_groups[j] == self._group_index)[0]
            nq = len(self._toplats[g_ind]) - 1
            for i in range(nq):
                q = [
                    Point(self._toplons[g_ind[i]], self._toplats[g_ind[i]],
                          self._topdeps[g_ind[i]]),
                    Point(self._toplons[g_ind[i + 1]],
                          self._toplats[g_ind[i + 1]],
                          self._topdeps[g_ind[i + 1]]),
                    Point(self._botlons[g_ind[i + 1]],
                          self._botlats[g_ind[i + 1]],
                          self._botdeps[g_ind[i + 1]]),
                    Point(self._botlons[g_ind[i]], self._botlats[g_ind[i]],
                          self._botdeps[g_ind[i]])
                ]
                mesh = utils.get_quad_mesh(q, dx=mesh_dx)
                mx.extend(list(np.reshape(mesh['x'], (-1, ))))
                my.extend(list(np.reshape(mesh['y'], (-1, ))))
                mz.extend(list(np.reshape(mesh['z'], (-1, ))))
        mesh_mat = np.array([np.array(mx), np.array(my), np.array(mz)])

        # ---------------------------------------------------------------------
        # Compute distance
        # ---------------------------------------------------------------------
        dist = np.zeros_like(x)
        for i in range(len(x)):
            sitecol = sites_ecef[i, :].reshape([3, 1])
            dif = sitecol - mesh_mat
            distarray = np.sqrt(np.sum(dif * dif, axis=0))
            dist[i] = np.min(distarray) / 1000.0  # convert to km

        dist = np.reshape(dist, oldshape)

        return dist, None
示例#21
0
    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)
示例#22
0
    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