Example #1
0
    def test_projection_across_international_date_line(self):
        # tests that given a polygon crossing the internation date line,
        # projecting its coordinates from spherical to cartesian and then back
        # to spherical gives the original values
        west = 179.7800
        east = -179.8650
        north = 51.4320
        south = 50.8410
        proj = utils.get_orthographic_projection(west, east, north, south)

        lons = numpy.array([
            179.8960, 179.9500, -179.9930, -179.9120, -179.8650, -179.9380,
            179.9130, 179.7800
        ])
        lats = numpy.array([
            50.8410, 50.8430, 50.8490, 50.8540, 51.4160, 51.4150, 51.4270,
            51.4320
        ])
        xx, yy = proj(lons, lats)
        comp_lons, comp_lats = proj(xx, yy, reverse=True)
        numpy.testing.assert_allclose(lons, comp_lons)
        numpy.testing.assert_allclose(lats, comp_lats)

        west = 179.
        east = -179.
        north = 1
        south = -1
        proj = utils.get_orthographic_projection(west, east, north, south)
        lons = numpy.array([179.0, -179.0])
        lats = numpy.array([-1, 1])
        xx, yy = proj(lons, lats)
        comp_lons, comp_lats = proj(xx, yy, reverse=True)
        numpy.testing.assert_allclose(lons, comp_lons)
        numpy.testing.assert_allclose(lats, comp_lats)
Example #2
0
    def test_projection_across_international_date_line(self):
        # tests that given a polygon crossing the internation date line,
        # projecting its coordinates from spherical to cartesian and then back
        # to spherical gives the original values
        west = 179.7800
        east = -179.8650
        north = 51.4320
        south = 50.8410
        proj = utils.get_orthographic_projection(west, east, north, south)

        lons = numpy.array([179.8960, 179.9500, -179.9930, -179.9120,
                            -179.8650, -179.9380, 179.9130, 179.7800])
        lats = numpy.array([50.8410, 50.8430, 50.8490, 50.8540, 51.4160,
                            51.4150, 51.4270, 51.4320])
        xx, yy = proj(lons, lats)
        comp_lons, comp_lats = proj(xx, yy, reverse=True)
        numpy.testing.assert_allclose(lons, comp_lons)
        numpy.testing.assert_allclose(lats, comp_lats)

        west = 179.
        east = -179.
        north = 1
        south = -1
        proj = utils.get_orthographic_projection(west, east, north, south)
        lons = numpy.array([179.0, -179.0])
        lats = numpy.array([-1, 1])
        xx, yy = proj(lons, lats)
        comp_lons, comp_lats = proj(xx, yy, reverse=True)
        numpy.testing.assert_allclose(lons, comp_lons)
        numpy.testing.assert_allclose(lats, comp_lats)
Example #3
0
    def _get_cartesian_edge_set(self):
        """
        For the GC2 calculations a set of cartesian representations of the
        fault edges are needed. In this present case we use a common cartesian
        framework for all edges, as opposed to defining a separate orthographic
        projection per edge
        """
        # Get projection space for cartesian projection
        edge_sets = numpy.vstack(self.edge_set)
        west, east, north, south = utils.get_spherical_bounding_box(
            edge_sets[:, 0],
            edge_sets[:, 1])
        self.proj = utils.get_orthographic_projection(west, east, north, south)

        for edges in self.edge_set:
            # Project edges into cartesian space
            px, py = self.proj(edges[:, 0], edges[:, 1])
            # Store the two end-points of the trace
            self.cartesian_endpoints.append(
                numpy.array([[px[0], py[0], edges[0, 2]],
                             [px[-1], py[-1], edges[-1, 2]]])
                )
            self.cartesian_edges.append(numpy.column_stack([px, py,
                                                            edges[:, 2]]))
            # Get surface length vector for the trace - easier in cartesian
            lengths = numpy.sqrt((px[:-1] - px[1:]) ** 2. +
                                 (py[:-1] - py[1:]) ** 2.)
            self.length_set.append(lengths)
            # Get cumulative surface length vector
            self.cum_length_set.append(numpy.hstack([0.,
                                                     numpy.cumsum(lengths)]))
        return edge_sets
Example #4
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 = 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
Example #5
0
    def _get_cartesian_edge_set(self):
        """
        For the GC2 calculations a set of cartesian representations of the
        fault edges are needed. In this present case we use a common cartesian
        framework for all edges, as opposed to defining a separate orthographic
        projection per edge
        """
        # Get projection space for cartesian projection
        edge_sets = numpy.vstack(self.edge_set)
        west, east, north, south = utils.get_spherical_bounding_box(
            edge_sets[:, 0],
            edge_sets[:, 1])
        self.proj = utils.get_orthographic_projection(west, east, north, south)

        for edges in self.edge_set:
            # Project edges into cartesian space
            px, py = self.proj(edges[:, 0], edges[:, 1])
            # Store the two end-points of the trace
            self.cartesian_endpoints.append(
                numpy.array([[px[0], py[0], edges[0, 2]],
                             [px[-1], py[-1], edges[-1, 2]]])
                )
            self.cartesian_edges.append(numpy.column_stack([px, py,
                                                            edges[:, 2]]))
            # Get surface length vector for the trace - easier in cartesian
            lengths = numpy.sqrt((px[:-1] - px[1:]) ** 2. +
                                 (py[:-1] - py[1:]) ** 2.)
            self.length_set.append(lengths)
            # Get cumulative surface length vector
            self.cum_length_set.append(numpy.hstack([0.,
                                                     numpy.cumsum(lengths)]))
        return edge_sets
Example #6
0
def get_quad_slip(q, rake):
    """
    Compute the unit slip vector in ECEF space 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.
    :returns:
        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
Example #7
0
    def _get_proj_enclosing_polygon(self):
        """
        See :meth:`Mesh._get_proj_enclosing_polygon`.

        :class:`RectangularMesh` contains an information about relative
        positions of points, so it allows to define the minimum polygon,
        containing the projection of the mesh, which doesn't necessarily
        have to be convex (in contrast to :class:`Mesh` implementation).

        :returns:
            Same structure as :meth:`Mesh._get_proj_convex_hull`.
        """
        if self.lons.size < 4:
            # the mesh doesn't contain even a single cell, use :class:`Mesh`
            # method implementation (which would dilate the point or the line)
            return super(RectangularMesh, self)._get_proj_enclosing_polygon()

        proj = geo_utils.get_orthographic_projection(
            *geo_utils.get_spherical_bounding_box(self.lons.flatten(),
                                                  self.lats.flatten()))
        mesh2d = numpy.array(proj(self.lons.transpose(),
                                  self.lats.transpose())).transpose()
        lines = iter(mesh2d)
        # we iterate over horizontal stripes, keeping the "previous"
        # line of points. we keep it reversed, such that together
        # with the current line they define the sequence of points
        # around the stripe.
        prev_line = next(lines)[::-1]
        polygons = []
        for i, line in enumerate(lines):
            coords = numpy.concatenate((prev_line, line, prev_line[0:1]))
            # create the shapely polygon object from the stripe
            # coordinates and simplify it (remove redundant points,
            # if there are any lying on the straight line).
            stripe = shapely.geometry.LineString(coords) \
                                     .simplify(self.DIST_TOLERANCE) \
                                     .buffer(self.DIST_TOLERANCE, 2)
            polygons.append(shapely.geometry.Polygon(stripe.exterior))
            prev_line = line[::-1]
        try:
            # create a final polygon as the union of all the stripe ones
            polygon = shapely.ops.cascaded_union(polygons) \
                                 .simplify(self.DIST_TOLERANCE)
        except ValueError:
            # NOTE(larsbutler): In some rare cases, we've observed ValueErrors
            # ("No Shapely geometry can be created from null value") with very
            # specific sets of polygons such that there are two unique
            # and many duplicates of one.
            # This bug is very difficult to reproduce consistently (except on
            # specific platforms) so the work around here is to remove the
            # duplicate polygons. In fact, we only observed this error on our
            # CI/build machine. None of our dev environments or production
            # machines has encountered this error, at least consistently. >:(
            polygons = [
                shapely.wkt.loads(x)
                for x in list(set(p.wkt for p in polygons))
            ]
            polygon = shapely.ops.cascaded_union(polygons) \
                                 .simplify(self.DIST_TOLERANCE)
        return proj, polygon
Example #8
0
 def test_points_too_far(self):
     proj = utils.get_orthographic_projection(180, 180, 45, 45)
     with self.assertRaises(ValueError) as ar:
         proj(90, -45)
     self.assertEqual(ar.exception.message,
                      'some points are too far from the projection '
                      'center lon=180.0 lat=45.0')
Example #9
0
 def test_points_too_far(self):
     proj = utils.get_orthographic_projection(180, 180, 45, 45)
     with self.assertRaises(ValueError) as ar:
         proj(90, -45)
     self.assertEqual(
         str(ar.exception), 'some points are too far from the projection '
         'center lon=180.0 lat=45.0')
Example #10
0
    def _get_proj_enclosing_polygon(self):
        """
        See :meth:`Mesh._get_proj_enclosing_polygon`.

        :class:`RectangularMesh` contains an information about relative
        positions of points, so it allows to define the minimum polygon,
        containing the projection of the mesh, which doesn't necessarily
        have to be convex (in contrast to :class:`Mesh` implementation).

        :returns:
            Same structure as :meth:`Mesh._get_proj_convex_hull`.
        """
        if self.lons.size < 4:
            # the mesh doesn't contain even a single cell, use :class:`Mesh`
            # method implementation (which would dilate the point or the line)
            return super(RectangularMesh, self)._get_proj_enclosing_polygon()

        proj = geo_utils.get_orthographic_projection(
            *geo_utils.get_spherical_bounding_box(self.lons.flatten(),
                                                  self.lats.flatten())
        )
        mesh2d = numpy.array(proj(self.lons.transpose(),
                                  self.lats.transpose())).transpose()
        lines = iter(mesh2d)
        # we iterate over horizontal stripes, keeping the "previous"
        # line of points. we keep it reversed, such that together
        # with the current line they define the sequence of points
        # around the stripe.
        prev_line = next(lines)[::-1]
        polygons = []
        for i, line in enumerate(lines):
            coords = numpy.concatenate((prev_line, line, prev_line[0:1]))
            # create the shapely polygon object from the stripe
            # coordinates and simplify it (remove redundant points,
            # if there are any lying on the straight line).
            stripe = shapely.geometry.LineString(coords) \
                                     .simplify(self.DIST_TOLERANCE) \
                                     .buffer(self.DIST_TOLERANCE, 2)
            polygons.append(shapely.geometry.Polygon(stripe.exterior))
            prev_line = line[::-1]
        try:
            # create a final polygon as the union of all the stripe ones
            polygon = shapely.ops.cascaded_union(polygons) \
                                 .simplify(self.DIST_TOLERANCE)
        except ValueError:
            # NOTE(larsbutler): In some rare cases, we've observed ValueErrors
            # ("No Shapely geometry can be created from null value") with very
            # specific sets of polygons such that there are two unique
            # and many duplicates of one.
            # This bug is very difficult to reproduce consistently (except on
            # specific platforms) so the work around here is to remove the
            # duplicate polygons. In fact, we only observed this error on our
            # CI/build machine. None of our dev environments or production
            # machines has encountered this error, at least consistently. >:(
            polygons = [shapely.wkt.loads(x) for x in
                        list(set(p.wkt for p in polygons))]
            polygon = shapely.ops.cascaded_union(polygons) \
                                 .simplify(self.DIST_TOLERANCE)
        return proj, polygon
Example #11
0
 def test_projection(self):
     # values verified against pyproj's implementation
     proj = utils.get_orthographic_projection(10, 16, -2, 30)
     lons = numpy.array([10., 20., 30., 40.])
     lats = numpy.array([-1., -2., -3., -4.])
     xx, yy = proj(lons, lats)
     exx = [-309.89151465, 800.52541443, 1885.04014687, 2909.78079661]
     eyy = [-1650.93260348, -1747.79256663, -1797.62444771, -1802.28117183]
     self.assertTrue(numpy.allclose(xx, exx, atol=0.01, rtol=0.005))
     self.assertTrue(numpy.allclose(yy, eyy, atol=0.01, rtol=0.005))
Example #12
0
 def test_projection(self):
     # values verified against pyproj's implementation
     proj = utils.get_orthographic_projection(10, 16, -2, 30)
     lons = numpy.array([10., 20., 30., 40.])
     lats = numpy.array([-1., -2., -3., -4.])
     xx, yy = proj(lons, lats)
     exx = [-309.89151465, 800.52541443, 1885.04014687, 2909.78079661]
     eyy = [-1650.93260348, -1747.79256663, -1797.62444771, -1802.28117183]
     self.assertTrue(numpy.allclose(xx, exx, atol=0.01, rtol=0.005))
     self.assertTrue(numpy.allclose(yy, eyy, atol=0.01, rtol=0.005))
Example #13
0
 def test_projecting_back_and_forth(self):
     lon0, lat0 = -10.4, 20.3
     proj = utils.get_orthographic_projection(lon0, lat0, lon0, lat0)
     lons = lon0 + (numpy.random.random((20, 10)) * 50 - 25)
     lats = lat0 + (numpy.random.random((20, 10)) * 50 - 25)
     xx, yy = proj(lons, lats, reverse=False)
     self.assertEqual(xx.shape, (20, 10))
     self.assertEqual(yy.shape, (20, 10))
     blons, blats = proj(xx, yy, reverse=True)
     self.assertTrue(numpy.allclose(blons, lons))
     self.assertTrue(numpy.allclose(blats, lats))
Example #14
0
 def test_projecting_back_and_forth(self):
     lon0, lat0 = -10.4, 20.3
     proj = utils.get_orthographic_projection(lon0, lat0, lon0, lat0)
     lons = lon0 + (numpy.random.random((20, 10)) * 50 - 25)
     lats = lat0 + (numpy.random.random((20, 10)) * 50 - 25)
     xx, yy = proj(lons, lats, reverse=False)
     self.assertEqual(xx.shape, (20, 10))
     self.assertEqual(yy.shape, (20, 10))
     blons, blats = proj(xx, yy, reverse=True)
     self.assertTrue(numpy.allclose(blons, lons))
     self.assertTrue(numpy.allclose(blats, lats))
Example #15
0
def calc_u_i(P0, P1, lat, lon):
    """
    Calculate u_i distance. See Spudich and Chiou OFR 2015-1028. This is the distance
    along strike from the first vertex (P0) of the i-th segment. 
    
    :param P0:
        Point object, representing the first top-edge vertex of a fault quadrilateral.
    :param P1:
        Point object, representing the second top-edge vertex of a fault quadrilateral.
    :param lat:
        A numpy array of latitude.
    :param lon:
        A numpy array of longitude.
    :returns:
        Array of size lat.shape of distances (in km).
    """
    # Project to Cartesian space
    west = min(P0.x, P1.x)
    east = max(P0.x, P1.x)
    south = min(P0.y, P1.y)
    north = max(P0.y, P1.y)
    proj = get_orthographic_projection(west, east, north, south)

    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)

    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)

    # Unit vector pointing along strike
    u_i_hat = Vector(p1x - p0x, p1y - p0y, 0).norm()

    # Convert sites to Cartesian
    sx, sy = proj(lon, lat)
    sx1d = np.reshape(sx, (-1, ))
    sy1d = np.reshape(sy, (-1, ))

    # Vectors from P0 to sites
    r = np.zeros([len(sx1d), 2])
    r[:, 0] = sx1d - p0x
    r[:, 1] = sy1d - p0y

    # Dot product gives t_i
    u_i = np.sum(u_i_hat.getArray()[0:2] * r, axis=1)
    shp = u_i.shape
    if len(shp) == 1:
        u_i.shape = (shp[0], 1)
    u_i = np.fliplr(u_i)

    return u_i
Example #16
0
def calc_u_i(P0, P1, lat, lon):
    """
    Calculate u_i distance. See Spudich and Chiou OFR 2015-1028. This is the distance
    along strike from the first vertex (P0) of the i-th segment. 
    
    :param P0:
        Point object, representing the first top-edge vertex of a fault quadrilateral.
    :param P1:
        Point object, representing the second top-edge vertex of a fault quadrilateral.
    :param lat:
        A numpy array of latitude.
    :param lon:
        A numpy array of longitude.
    :returns:
        Array of size lat.shape of distances (in km).
    """
    # Project to Cartesian space
    west = min(P0.x, P1.x)
    east = max(P0.x, P1.x)
    south = min(P0.y, P1.y)
    north = max(P0.y, P1.y)
    proj = get_orthographic_projection(west, east, north, south)
    
    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)

    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)
    
    # Unit vector pointing along strike
    u_i_hat = Vector(p1x - p0x, p1y - p0y, 0).norm()
    
    # Convert sites to Cartesian
    sx, sy = proj(lon, lat)
    sx1d = np.reshape(sx, (-1,))
    sy1d = np.reshape(sy, (-1,))
    
    # Vectors from P0 to sites
    r = np.zeros([len(sx1d), 2])
    r[:,0] = sx1d - p0x
    r[:,1] = sy1d - p0y
    
    # Dot product gives t_i
    u_i = np.sum(u_i_hat.getArray()[0:2]*r, axis = 1)
    shp = u_i.shape
    if len(shp) == 1:
        u_i.shape = (shp[0], 1)
    u_i = np.fliplr(u_i)
    
    return u_i
Example #17
0
def __calc_t_i(P0, P1, lat, lon):
    """
    Calculate t_i distance. See Spudich and Chiou OFR 2015-1028. This is the distance
    measured normal to strike from the i-th segment. Values on the hanging-wall are
    positive and those on the foot-wall are negative.

    :param P0:
        Point object, representing the first top-edge vertex of a fault quadrilateral.
    :param P1:
        Point object, representing the second top-edge vertex of a fault quadrilateral.
    :param lat:
        A numpy array of latitudes.
    :param lon:
        A numpy array of longitudes.
    :returns:
        Array of size N of distances (in km) from input points to rupture surface.
    """
    # Project to Cartesian space
    west = min(P0.x, P1.x)
    east = max(P0.x, P1.x)
    south = min(P0.y, P1.y)
    north = max(P0.y, P1.y)
    proj = get_orthographic_projection(west, east, north, south)

    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)

    # Unit vector pointing normal to strike
    t_i_hat = Vector(p1y - p0y, -(p1x - p0x), 0).norm()

    # Convert sites to Cartesian
    sx, sy = proj(lon, lat)
    sx1d = np.reshape(sx, (-1,))
    sy1d = np.reshape(sy, (-1,))

    # Vectors from P0 to sites
    r = np.zeros([len(sx1d), 2])
    r[:, 0] = sx1d - p0x
    r[:, 1] = sy1d - p0y

    # Dot product gives t_i
    t_i = np.sum(t_i_hat.getArray()[0:2] * r, axis=1)
    shp = t_i.shape
    if len(shp) == 1:
        t_i.shape = (shp[0], 1)
    t_i = np.fliplr(t_i)
    return t_i
Example #18
0
def calc_t_i(P0, P1, lat, lon):
    """
    Calculate t_i distance. See Spudich and Chiou OFR 2015-1028. This is the distance
    measured normal to strike from the i-th segment. Values on the hanging-wall are
    positive and those on the foot-wall are negative.
    :param P0: 
        Point object, representing the first top-edge vertex of a fault quadrilateral.
    :param P1: 
        Point object, representing the second top-edge vertex of a fault quadrilateral.
    :param lat: 
        A numpy array of latitudes. 
    :param lon: 
        A numpy array of longitudes. 
    :returns:
        Array of size N of distances (in km) from input points to rupture surface.
    """
    # Project to Cartesian space
    west = min(P0.x, P1.x)
    east = max(P0.x, P1.x)
    south = min(P0.y, P1.y)
    north = max(P0.y, P1.y)
    proj = get_orthographic_projection(west, east, north, south)

    # projected coordinates are in km
    p0x, p0y = proj(P0.x, P0.y)
    p1x, p1y = proj(P1.x, P1.y)

    # Unit vector pointing normal to strike
    t_i_hat = Vector(p1y - p0y, -(p1x - p0x), 0).norm()

    # Convert sites to Cartesian
    sx, sy = proj(lon, lat)
    sx1d = np.reshape(sx, (-1, ))
    sy1d = np.reshape(sy, (-1, ))

    # Vectors from P0 to sites
    r = np.zeros([len(sx1d), 2])
    r[:, 0] = sx1d - p0x
    r[:, 1] = sy1d - p0y

    # Dot product gives t_i
    t_i = np.sum(t_i_hat.getArray()[0:2] * r, axis=1)
    shp = t_i.shape
    if len(shp) == 1:
        t_i.shape = (shp[0], 1)
    t_i = np.fliplr(t_i)
    return t_i
    def test(self):
        polygon2d = shapely.geometry.Polygon([(-12, 0), (0, 14.5), (17.1, 3), (18, 0), (16.5, -3), (0, -10)])
        proj = geo_utils.get_orthographic_projection(0, 0, 0, 0)
        poly = polygon.Polygon._from_2d(polygon2d, proj)
        elons = [-0.10791866, 0.0, 0.1537842, 0.1618781, 0.14838825, 0.0]
        elats = [0.0, 0.13040175, 0.02697965, 0.0, -0.02697965, -0.0899322]
        ebbox = [-0.10791866, 0.1618781, 0.13040175, -0.0899322]
        numpy.testing.assert_allclose(poly.lons, elons)
        numpy.testing.assert_allclose(poly.lats, elats)
        numpy.testing.assert_allclose(poly._bbox, ebbox)
        self.assertIs(poly._polygon2d, polygon2d)
        self.assertIs(poly._projection, proj)

        poly = polygon.Polygon._from_2d(poly._polygon2d, poly._projection)
        numpy.testing.assert_allclose(poly.lons, elons)
        numpy.testing.assert_allclose(poly.lats, elats)
        numpy.testing.assert_allclose(poly._bbox, ebbox)
        self.assertIs(poly._polygon2d, polygon2d)
        self.assertIs(poly._projection, proj)
Example #20
0
    def _get_proj_enclosing_polygon(self):
        """
        See :meth:`Mesh._get_proj_enclosing_polygon`.

        :class:`RectangularMesh` contains an information about relative
        positions of points, so it allows to define the minimum polygon,
        containing the projection of the mesh, which doesn't necessarily
        have to be convex (in contrast to :class:`Mesh` implementation).

        :returns:
            Same structure as :meth:`Mesh._get_proj_convex_hull`.
        """
        if self.lons.size < 4:
            # the mesh doesn't contain even a single cell, use :class:`Mesh`
            # method implementation (which would dilate the point or the line)
            return super(RectangularMesh, self)._get_proj_enclosing_polygon()

        proj = geo_utils.get_orthographic_projection(
            *geo_utils.get_spherical_bounding_box(self.lons, self.lats)
        )
        mesh2d = numpy.array(proj(self.lons.transpose(),
                                  self.lats.transpose())).transpose()
        lines = iter(mesh2d)
        # we iterate over horizontal stripes, keeping the "previous"
        # line of points. we keep it reversed, such that together
        # with the current line they define the sequence of points
        # around the stripe.
        prev_line = lines.next()[::-1]
        polygons = []
        for i, line in enumerate(lines):
            coords = numpy.concatenate((prev_line, line, prev_line[0:1]))
            # create the shapely polygon object from the stripe
            # coordinates and simplify it (remove redundant points,
            # if there are any lying on the straight line).
            stripe = shapely.geometry.LineString(coords) \
                                     .simplify(self.DIST_TOLERANCE) \
                                     .buffer(self.DIST_TOLERANCE, 2)
            polygons.append(shapely.geometry.Polygon(stripe.exterior))
            prev_line = line[::-1]
        # create a final polygon as the union of all the stripe ones
        polygon = shapely.ops.cascaded_union(polygons) \
                             .simplify(self.DIST_TOLERANCE)
        return proj, polygon
Example #21
0
    def test(self):
        polygon2d = shapely.geometry.Polygon([(-12, 0), (0, 14.5), (17.1, 3),
                                              (18, 0), (16.5, -3), (0, -10)])
        proj = geo_utils.get_orthographic_projection(0, 0, 0, 0)
        poly = polygon.Polygon._from_2d(polygon2d, proj)
        elons = [-0.10791866, 0., 0.1537842, 0.1618781, 0.14838825, 0.]
        elats = [0., 0.13040175, 0.02697965, 0., -0.02697965, -0.0899322]
        ebbox = [-0.10791866, 0.1618781, 0.13040175, -0.0899322]
        numpy.testing.assert_allclose(poly.lons, elons)
        numpy.testing.assert_allclose(poly.lats, elats)
        numpy.testing.assert_allclose(poly._bbox, ebbox)
        self.assertIs(poly._polygon2d, polygon2d)
        self.assertIs(poly._projection, proj)

        poly = polygon.Polygon._from_2d(poly._polygon2d, poly._projection)
        numpy.testing.assert_allclose(poly.lons, elons)
        numpy.testing.assert_allclose(poly.lats, elats)
        numpy.testing.assert_allclose(poly._bbox, ebbox)
        self.assertIs(poly._polygon2d, polygon2d)
        self.assertIs(poly._projection, proj)
Example #22
0
    def _init_polygon2d(self):
        """
        Spherical bounding box, projection, and Cartesian polygon are all
        cached to prevent redundant computations.

        If any of them are `None`, recalculate all of them.
        """
        if self._polygon2d is None or self._projection is None or self._bbox is None:
            # resample polygon line segments:
            lons, lats = get_resampled_coordinates(self.lons, self.lats)

            # find the bounding box of a polygon in spherical coordinates:
            self._bbox = utils.get_spherical_bounding_box(lons, lats)

            # create a projection that is centered in a polygon center:
            self._projection = utils.get_orthographic_projection(*self._bbox)

            # project polygon vertices to the Cartesian space and create
            # a shapely polygon object:
            xx, yy = self._projection(lons, lats)
            self._polygon2d = shapely.geometry.Polygon(zip(xx, yy))
Example #23
0
    def _get_proj_convex_hull(self):
        """
        Create a projection centered in the center of this mesh and define
        a convex polygon in that projection, enveloping all the points
        of the mesh.

        :returns:
            Tuple of two items: projection function and shapely 2d polygon.
            Note that the result geometry can be line or point depending
            on number of points in the mesh and their arrangement.
        """
        # create a projection centered in the center of points collection
        proj = geo_utils.get_orthographic_projection(*geo_utils.get_spherical_bounding_box(self.lons, self.lats))
        # project all the points and create a shapely multipoint object.
        # need to copy an array because otherwise shapely misinterprets it
        coords = numpy.transpose(proj(self.lons.flatten(), self.lats.flatten())).copy()
        multipoint = shapely.geometry.MultiPoint(coords)
        # create a 2d polygon from a convex hull around that multipoint.
        polygon2d = multipoint.convex_hull

        return proj, polygon2d
Example #24
0
    def _get_proj_convex_hull(self):
        """
        Create a projection centered in the center of this mesh and define
        a convex polygon in that projection, enveloping all the points
        of the mesh.

        :returns:
            Tuple of two items: projection function and shapely 2d polygon.
            Note that the result geometry can be line or point depending
            on number of points in the mesh and their arrangement.
        """
        # create a projection centered in the center of points collection
        proj = geo_utils.get_orthographic_projection(
            *geo_utils.get_spherical_bounding_box(self.lons, self.lats))
        # project all the points and create a shapely multipoint object.
        # need to copy an array because otherwise shapely misinterprets it
        coords = numpy.transpose(proj(self.lons.flatten(),
                                      self.lats.flatten())).copy()
        multipoint = shapely.geometry.MultiPoint(coords)
        # create a 2d polygon from a convex hull around that multipoint.
        polygon2d = multipoint.convex_hull
        return proj, polygon2d
Example #25
0
    def to_polygon(self, radius):
        """
        Create a circular polygon with specified radius centered in the point.

        :param radius:
            Required radius of a new polygon, in km.
        :returns:
            Instance of :class:`~openquake.hazardlib.geo.polygon.Polygon` that
            approximates a circle around the point with specified radius.
        """
        assert radius > 0
        # avoid circular imports
        from openquake.hazardlib.geo.polygon import Polygon
        # get a projection that is centered in the point
        proj = geo_utils.get_orthographic_projection(
            self.longitude, self.longitude, self.latitude, self.latitude
        )
        # create a shapely object from a projected point coordinates,
        # which are supposedly (0, 0)
        point = shapely.geometry.Point(*proj(self.longitude, self.latitude))
        # extend the point to a shapely polygon using buffer()
        # and create openquake.hazardlib.geo.polygon.Polygon object from it
        return Polygon._from_2d(point.buffer(radius), proj)
Example #26
0
    def _init_polygon2d(self):
        """
        Spherical bounding box, projection, and Cartesian polygon are all
        cached to prevent redundant computations.

        If any of them are `None`, recalculate all of them.
        """
        if (self._polygon2d is None or self._projection is None
                or self._bbox is None):
            # resample polygon line segments:
            lons, lats = get_resampled_coordinates(self.lons, self.lats)

            # find the bounding box of a polygon in spherical coordinates:
            self._bbox = utils.get_spherical_bounding_box(lons, lats)

            # create a projection that is centered in a polygon center:
            self._projection = \
                utils.get_orthographic_projection(*self._bbox)

            # project polygon vertices to the Cartesian space and create
            # a shapely polygon object:
            xx, yy = self._projection(lons, lats)
            self._polygon2d = shapely.geometry.Polygon(zip(xx, yy))
Example #27
0
    def to_polygon(self, radius):
        """
        Create a circular polygon with specified radius centered in the point.

        :param radius:
            Required radius of a new polygon, in km.
        :returns:
            Instance of :class:`~openquake.hazardlib.geo.polygon.Polygon` that
            approximates a circle around the point with specified radius.
        """
        assert radius > 0
        # avoid circular imports
        from openquake.hazardlib.geo.polygon import Polygon
        # get a projection that is centered in the point
        proj = geo_utils.get_orthographic_projection(
            self.longitude, self.longitude, self.latitude, self.latitude
        )
        # create a shapely object from a projected point coordinates,
        # which are supposedly (0, 0)
        point = shapely.geometry.Point(*proj(self.longitude, self.latitude))
        # extend the point to a shapely polygon using buffer()
        # and create openquake.hazardlib.geo.polygon.Polygon object from it
        return Polygon._from_2d(point.buffer(radius), proj)
Example #28
0
    def containsEpicenter(self, lat, lon):
        """Determine whether a given lat/lon is inside the region.

        :param lat:
          Epicentral latitude.
        :param lon:
          Epicentral longitude.
        :returns:
          True if epicenter is inside region, False if not.
        """
        #the polygon is a JSON string encoded to bytes - decode, convert to JSON,
        #project into an orthographic projection,  then turn into shapely object.
        polystr = self.poly.decode('utf-8')
        polydict = json.loads(polystr)
        pcoords = []
        for cblock in polydict['coordinates']:
            plon, plat = zip(*cblock)
            lonmin = min(plon)
            lonmax = max(plon)
            latmin = min(plat)
            latmax = max(plat)
            proj = get_orthographic_projection(lonmin, lonmax, latmax, latmin)
            try:
                x, y = proj(lon, lat)
            except ValueError as ve:
                continue
            try:
                px, py = proj(plon, plat)
            except ValueError as ve:
                x = 1
            pxy = zip(px, py)
            polygon = Polygon(pxy)
            if polygon.contains(Point(x, y)):
                return True

        return False
Example #29
0
def test_chichi():
    print('Testing Chi-Chi...')
    # read in fault file
    f = '../data/0137A.POL'
    i0 = np.arange(0, 9 * 11 * 3, 11)
    i1 = i0 + 10
    cs = zip(i0, i1)
    df = pd.read_fwf(f, cs, skiprows=2, nrows=5, header=None)
    mat = df.as_matrix()
    ix = np.arange(0, 9 * 3, 3)
    iy = ix + 1
    iz = ix + 2
    x0 = mat[0, ix]
    x1 = mat[1, ix]
    x2 = mat[2, ix]
    x3 = mat[3, ix]
    y0 = mat[0, iy]
    y1 = mat[1, iy]
    y2 = mat[2, iy]
    y3 = mat[3, iy]
    # Depth, positive down
    z0 = np.abs(mat[0, iz])
    z1 = np.abs(mat[1, iz])
    z2 = np.abs(mat[2, iz])
    z3 = np.abs(mat[3, iz])
    epilat = 23.85
    epilon = 120.82
    proj = get_orthographic_projection(epilon - 1, epilon + 1, epilat + 1,
                                       epilat - 1)
    lon0, lat0 = proj(x0, y0, reverse=True)
    lon1, lat1 = proj(x1, y1, reverse=True)
    lon2, lat2 = proj(x2, y2, reverse=True)
    lon3, lat3 = proj(x3, y3, reverse=True)
    flt = Fault.fromVertices(lon0, lat0, z0, lon1, lat1, z1, lon2, lat2, z2,
                             lon3, lat3, z3)
    ask14 = AbrahamsonEtAl2014()
    # event information doesn't matter...
    event = {
        'lat': 0,
        'lon': 0,
        'depth': 0,
        'mag': 7,
        'id': '',
        'locstring': '',
        'type': 'U',
        'time': ShakeDateTime.utcfromtimestamp(int(time.time())),
        'timezone': 'UTC'
    }
    source = Source(event, flt)

    # Get NGA distances
    distfile = '../data/NGAW2_distances.csv'
    df = pd.read_csv(distfile)
    df2 = df.loc[df['EQID'] == 137]
    slat = df2['Station Latitude'].as_matrix()
    slon = df2['Station Longitude'].as_matrix()
    sdep = np.zeros(slat.shape)
    nga_repi = df2['EpiD (km)'].as_matrix()
    nga_rhypo = df2['HypD (km)'].as_matrix()
    nga_rrup = df2['ClstD (km)'].as_matrix()
    nga_rjb = df2['Joyner-Boore Dist. (km)'].as_matrix()
    nga_rx = df2['T'].as_matrix()

    dist = Distance(ask14, source, slat, slon, sdep)
    dctx = dist.getDistanceContext()
    fig = plt.figure(figsize=(8, 8))
    plt.scatter(nga_rjb, dctx.rjb, alpha=0.5, facecolors='none')
    plt.plot([0, nga_rjb.max()], [0, dctx.rjb.max()], 'b')
    plt.savefig('Chi-Chi_Rjb.png')
    fig = plt.figure(figsize=(8, 8))
    plt.scatter(nga_rrup, dctx.rrup, alpha=0.5, facecolors='none')
    plt.plot([0, nga_rrup.max()], [0, dctx.rrup.max()], 'b')
    plt.savefig('Chi-Chi_Rrup.png')
    fig = plt.figure(figsize=(8, 8))
    plt.scatter(nga_rx, dctx.rx, alpha=0.5, facecolors='none')
    plt.plot([nga_rx.min(), nga_rx.max()],
             [dctx.rx.min(), dctx.rx.max()], 'b')
    plt.savefig('Chi-Chi_Rx.png')
Example #30
0
def get_extent(rupture):
    """
    Method to compute map extent from rupture.

    Args:
        rupture (Rupture): A ShakeMap Rupture instance.

    Returns:
        tuple: lonmin, lonmax, latmin, latmax rounded to the nearest
        arc-minute..

    """

    if not rupture or not isinstance(rupture, Rupture):
        raise TypeError('get_extent() takes exactly 1 argument (0 given)')

    origin = rupture.getOrigin()
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        lats = rupture.lats
        lons = rupture.lons

        # Remove nans
        lons = lons[~np.isnan(lons)]
        lats = lats[~np.isnan(lats)]

        clat = 0.5 * (np.nanmax(lats) + np.nanmin(lats))
        clon = 0.5 * (np.nanmax(lons) + np.nanmin(lons))
    else:
        clat = origin.lat
        clon = origin.lon

    mag = origin.mag

    # Is this a stable or active tectonic event?
    # (this could be made an attribute of the ShakeMap Origin class)
    hypo = origin.getHypo()
    stable = is_stable(hypo.longitude, hypo.latitude)

    if stable is False:
        if mag < 6.48:
            mindist_km = 100.
        else:
            mindist_km = 27.24 * mag**2 - 250.4 * mag + 579.1
    else:
        if mag < 6.10:
            mindist_km = 100.
        else:
            mindist_km = 63.4 * mag**2 - 465.4 * mag + 581.3

    # Apply an upper limit on extent. This should only matter for large
    # magnitudes (> ~8.25) in stable tectonic environments.
    if mindist_km > 1000.:
        mindist_km = 1000.

    # Projection
    proj = get_orthographic_projection(clon - 4, clon + 4, clat + 4, clat - 4)
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        ruptx, rupty = proj(lons, lats)
    else:
        ruptx, rupty = proj(clon, clat)

    xmin = np.nanmin(ruptx) - mindist_km
    ymin = np.nanmin(rupty) - mindist_km
    xmax = np.nanmax(ruptx) + mindist_km
    ymax = np.nanmax(rupty) + mindist_km

    # Put a limit on range of aspect ratio
    dx = xmax - xmin
    dy = ymax - ymin
    ar = dy / dx
    if ar > 1.25:
        # Inflate x
        dx_target = dy / 1.25
        ddx = dx_target - dx
        xmax = xmax + ddx / 2
        xmin = xmin - ddx / 2
    if ar < 0.6:
        # inflate y
        dy_target = dx * 0.6
        ddy = dy_target - dy
        ymax = ymax + ddy / 2
        ymin = ymin - ddy / 2

    lonmin, latmin = proj(np.array([xmin]), np.array([ymin]), reverse=True)
    lonmax, latmax = proj(np.array([xmax]), np.array([ymax]), reverse=True)

    #
    # Round coordinates to the nearest minute -- that should make the
    # output grid register with common grid resolutions (60c, 30c,
    # 15c, 7.5c)
    #
    return _round_coord(lonmin[0]), _round_coord(lonmax[0]), \
           _round_coord(latmin[0]), _round_coord(latmax[0])
Example #31
0
    def fromTrace(cls,
                  xp0,
                  yp0,
                  xp1,
                  yp1,
                  zp,
                  widths,
                  dips,
                  origin,
                  strike=None,
                  group_index=None,
                  reference=""):
        """
        Create a QuadRupture instance from a set of vertices that define the
        top of the rupture, and an array of widths/dips.

        Each rupture quadrilaterial is defined by specifying the latitude,
        longitude, and depth of the two vertices on the top edges, which must
        have the dame depths. The other verticies are then constructed from
        the top edges and the width and dip of the quadrilateral.

        Args:
            xp0 (array): Array or list of longitudes (floats) of p0.
            yp0 (array): Array or list of latitudes (floats) of p0.
            xp1 (array): Array or list of longitudes (floats) of p1.
            yp1 (array): Array or list of latitudes (floats) of p1.
            zp (array): Array or list of depths for each of the top of rupture
                rectangles (km).
            widths (array): Array of widths for each of rectangle (km).
            dips (array): Array of dips for each of rectangle (degrees).
            origin (Origin): Reference to a ShakeMap origin object.
            strike (array): If None then strike is computed from verticies of
                top edge of each quadrilateral. If a scalar, then all
                quadrilaterals are constructed assuming this strike direction.
                If an array with the same length as the trace coordinates then
                it specifies the strike for each quadrilateral.
            group_index (list): List of integers to indicate group index. If
                None then each quadrilateral is assumed to be in a different
                group since there is no guarantee that any of them are
                continuous.
            reference (str): String explaining where the rupture definition
                came from (publication style reference, etc.).

        Returns:
            QuadRupture instance.

        """
        if len(xp0) == len(yp0) == len(xp1) == len(yp1) == len(zp) == len(
                dips) == len(widths):
            pass
        else:
            raise ShakeLibException(
                'Number of xp0,yp0,xp1,yp1,zp,widths,dips points must be '
                'equal.')
        if strike is None:
            pass
        else:
            if (len(xp0) == len(strike)) | (len(strike) == 1):
                pass
            else:
                raise ShakeLibException(
                    'Strike must be None, scalar, or same length as '
                    'trace coordinates.')

        if group_index is None:
            group_index = np.array(range(len(xp0)))

        # Convert dips to radians
        dips = np.radians(dips)

        # Ensure that all input sequences are numpy arrays
        xp0 = np.array(xp0, dtype='d')
        xp1 = np.array(xp1, dtype='d')
        yp0 = np.array(yp0, dtype='d')
        yp1 = np.array(yp1, dtype='d')
        zp = np.array(zp, dtype='d')
        widths = np.array(widths, dtype='d')
        dips = np.array(dips, dtype='d')

        # Get a projection object
        west = np.min((xp0.min(), xp1.min()))
        east = np.max((xp0.max(), xp1.max()))
        south = np.min((yp0.min(), yp1.min()))
        north = np.max((yp0.max(), yp1.max()))

        # Projected coordinates are in km
        proj = get_orthographic_projection(west, east, north, south)
        xp2 = np.zeros_like(xp0)
        xp3 = np.zeros_like(xp0)
        yp2 = np.zeros_like(xp0)
        yp3 = np.zeros_like(xp0)
        zpdown = np.zeros_like(zp)
        for i in range(0, len(xp0)):
            # Project the top edge coordinates
            p0x, p0y = proj(xp0[i], yp0[i])
            p1x, p1y = proj(xp1[i], yp1[i])

            # Get the rotation angle defined by these two points
            if strike is None:
                dx = p1x - p0x
                dy = p1y - p0y
                theta = np.arctan2(dx, dy)  # theta is angle from north
            elif len(strike) == 1:
                theta = np.radians(strike[0])
            else:
                theta = np.radians(strike[i])

            R = np.array([[np.cos(theta), -np.sin(theta)],
                          [np.sin(theta), np.cos(theta)]])

            # Rotate the top edge points into a new coordinate system (vertical
            # line)
            p0 = np.array([p0x, p0y])
            p1 = np.array([p1x, p1y])
            p0p = np.dot(R, p0)
            p1p = np.dot(R, p1)

            # Get right side coordinates in project, rotated system
            dz = np.sin(dips[i]) * widths[i]
            dx = np.cos(dips[i]) * widths[i]
            p3xp = p0p[0] + dx
            p3yp = p0p[1]
            p2xp = p1p[0] + dx
            p2yp = p1p[1]

            # Get right side coordinates in un-rotated projected system
            p3p = np.array([p3xp, p3yp])
            p2p = np.array([p2xp, p2yp])
            Rback = np.array([[np.cos(-theta), -np.sin(-theta)],
                              [np.sin(-theta), np.cos(-theta)]])
            p3 = np.dot(Rback, p3p)
            p2 = np.dot(Rback, p2p)
            p3x = np.array([p3[0]])
            p3y = np.array([p3[1]])
            p2x = np.array([p2[0]])
            p2y = np.array([p2[1]])

            # project lower edge points back to lat/lon coordinates
            lon3, lat3 = proj(p3x, p3y, reverse=True)
            lon2, lat2 = proj(p2x, p2y, reverse=True)

            xp2[i] = lon2
            xp3[i] = lon3
            yp2[i] = lat2
            yp3[i] = lat3
            zpdown[i] = zp[i] + dz

        # ---------------------------------------------------------------------
        # Create GeoJSON object
        # ---------------------------------------------------------------------

        coords = []
        u_groups = np.unique(group_index)
        n_groups = len(u_groups)
        for i in range(n_groups):
            ind = np.where(u_groups[i] == group_index)[0]
            lons = np.concatenate([
                xp0[ind[0]].reshape((1, )), xp1[ind], xp2[ind][::-1],
                xp3[ind][::-1][-1].reshape((1, )), xp0[ind[0]].reshape((1, ))
            ])
            lats = np.concatenate([
                yp0[ind[0]].reshape((1, )), yp1[ind], yp2[ind][::-1],
                yp3[ind][::-1][-1].reshape((1, )), yp0[ind[0]].reshape((1, ))
            ])
            deps = np.concatenate([
                zp[ind[0]].reshape((1, )), zp[ind], zpdown[ind][::-1],
                zpdown[ind][::-1][-1].reshape((1, )), zp[ind[0]].reshape((1, ))
            ])

            poly = []
            for lon, lat, dep in zip(lons, lats, deps):
                poly.append([lon, lat, dep])
            coords.append(poly)

        d = {
            "type":
            "FeatureCollection",
            "metadata": {
                "reference": reference
            },
            "features": [{
                "type": "Feature",
                "properties": {
                    "rupture type": "rupture extent"
                },
                "geometry": {
                    "type": "MultiPolygon",
                    "coordinates": [coords]
                }
            }]
        }

        # Add origin information to metadata
        odict = origin.__dict__
        for k, v in odict.items():
            if isinstance(v, HistoricTime):
                d['metadata'][k] = v.strftime(constants.TIMEFMT)
            else:
                d['metadata'][k] = v

        return cls(d, origin)
Example #32
0
    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 = get_orthographic_projection(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
        w = utils.get_quad_width(quad)

        # 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
Example #33
0
def get_extent(rupture, config=None):
    """
    Method to compute map extent from rupture.

    Args:
        rupture (Rupture): A ShakeMap Rupture instance.

    Returns:
        tuple: lonmin, lonmax, latmin, latmax rounded to the nearest
        arc-minute..

    """

    # check to see what parameters are specified in the extent config
    coeffs = []
    spans = {}
    bounds = []
    if config is not None:
        if 'extent' in config:
            if 'coefficients' in config['extent']:
                if 'coeffs' in config['extent']['coefficients']:
                    if config['extent']['coefficients']['coeffs'][0] != 0.0:
                        coeffs = config['extent']['coefficients']['coeffs']
            if 'magnitude_spans' in config['extent']:
                if len(config['extent']['magnitude_spans']):
                    if isinstance(config['extent']['magnitude_spans'], dict):
                        spans = config['extent']['magnitude_spans']
            if 'bounds' in config['extent']:
                if 'extent' in config['extent']['bounds']:
                    if config['extent']['bounds']['extent'][0] != -999.0:
                        bounds = config['extent']['bounds']['extent']

    if len(bounds):
        xmin, ymin, xmax, ymax = bounds
        return (xmin, xmax, ymin, ymax)

    if not rupture or not isinstance(rupture, Rupture):
        raise TypeError('get_extent() takes exactly 1 argument (0 given)')

    origin = rupture.getOrigin()
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        lats = rupture.lats
        lons = rupture.lons

        # Remove nans
        lons = lons[~np.isnan(lons)]
        lats = lats[~np.isnan(lats)]

        clat = 0.5 * (np.nanmax(lats) + np.nanmin(lats))
        clon = 0.5 * (np.nanmax(lons) + np.nanmin(lons))
    else:
        clat = origin.lat
        clon = origin.lon

    mag = origin.mag

    if len(spans):
        xmin = None
        xmax = None
        ymin = None
        ymax = None
        for spankey, span in spans.items():
            if mag > span[0] and mag <= span[1]:
                ymin = clat - span[2] / 2
                ymax = clat + span[2] / 2
                xmin = clon - span[3] / 2
                xmax = clon + span[3] / 2
                break
        if xmin is not None:
            return (xmin, xmax, ymin, ymax)

    # Is this a stable or active tectonic event?
    # (this could be made an attribute of the ShakeMap Origin class)
    hypo = origin.getHypo()
    stable = is_stable(hypo.longitude, hypo.latitude)

    if stable is False:
        if mag < 6.48:
            mindist_km = 100.
        else:
            if len(coeffs):
                C1, C2, C3 = coeffs
            else:
                C1, C2, C3 = DEFAULT_ACTIVE_COEFFS
            mindist_km = C1 * mag**2 - C2 * mag + C3
    else:
        if mag < 6.10:
            mindist_km = 100.
        else:
            if len(coeffs):
                C1, C2, C3 = coeffs
            else:
                C1, C2, C3 = DEFAULT_STABLE_COEFFS
            mindist_km = C1 * mag**2 - C2 * mag + C3

    # Apply an upper limit on extent. This should only matter for large
    # magnitudes (> ~8.25) in stable tectonic environments.
    if mindist_km > 1000.:
        mindist_km = 1000.

    # Projection
    proj = get_orthographic_projection(clon - 4, clon + 4, clat + 4, clat - 4)
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        ruptx, rupty = proj(lons, lats)
    else:
        ruptx, rupty = proj(clon, clat)

    xmin = np.nanmin(ruptx) - mindist_km
    ymin = np.nanmin(rupty) - mindist_km
    xmax = np.nanmax(ruptx) + mindist_km
    ymax = np.nanmax(rupty) + mindist_km

    # Put a limit on range of aspect ratio
    dx = xmax - xmin
    dy = ymax - ymin
    ar = dy / dx
    if ar > 1.25:
        # Inflate x
        dx_target = dy / 1.25
        ddx = dx_target - dx
        xmax = xmax + ddx / 2
        xmin = xmin - ddx / 2
    if ar < 0.6:
        # inflate y
        dy_target = dx * 0.6
        ddy = dy_target - dy
        ymax = ymax + ddy / 2
        ymin = ymin - ddy / 2

    lonmin, latmin = proj(np.array([xmin]), np.array([ymin]), reverse=True)
    lonmax, latmax = proj(np.array([xmax]), np.array([ymax]), reverse=True)

    #
    # Round coordinates to the nearest minute -- that should make the
    # output grid register with common grid resolutions (60c, 30c,
    # 15c, 7.5c)
    #
    return _round_coord(lonmin[0]), _round_coord(lonmax[0]), \
        _round_coord(latmin[0]), _round_coord(latmax[0])
Example #34
0
def get_distance(methods, lat, lon, dep, source,
                 use_median_distance=True):
    """
    Calculate distance using any one of a number of distance measures.
    One of quadlist OR hypo must be specified. The following table gives
    the allowed distance strings and a description of each. 

    +--------+----------------------------------------------------------+
    | String | Description                                              |
    +========+==========================================================+
    | repi   | Distance to epicenter.                                   |
    +--------+----------------------------------------------------------+
    | rhypo  | Distance to hypocenter.                                  |
    +--------+----------------------------------------------------------+
    | rjb    | Joyner-Boore distance; this is closest distance to the   |
    |        | surface projection of the rupture plane.                 |
    +--------+----------------------------------------------------------+
    | rrup   | Rupture distance; closest distance to the rupture plane. |
    +--------+----------------------------------------------------------+
    | rx     | Strike-normal distance; same as GC2 coordiante T.        |
    +--------+----------------------------------------------------------+
    | ry     | Strike-parallel distance; same as GC2 coordiante U, but  |
    |        | with a shift in origin definition. See Spudich and Chiou |
    |        | (2015) http://dx.doi.org/10.3133/ofr20151028.            |
    +--------+----------------------------------------------------------+
    | ry0    | Horizontal distance off the end of the rupture measured  |
    |        | parallel to strike. Can only be zero or positive. We     |
    |        | compute this as a function of GC2 coordinate U.          |
    +--------+----------------------------------------------------------+
    | U      | GC2 coordinate U.                                        |
    +--------+----------------------------------------------------------+
    | T      | GC2 coordinate T.                                        |
    +--------+----------------------------------------------------------+

    :param methods:
        List of strings (or just a string) of distances to compute.
    :param lat:
       A numpy array of latitudes.
    :param lon:
       A numpy array of longidues.
    :param dep:
       A numpy array of depths (km).
    :param source:
       source instance.
    :param use_median_distance:
        Boolean; only used if GMPE requests fault distances and not fault is
        availalbe. Default is True, meaning that point-source distances are
        adjusted based on magnitude to get the median fault distance.
    :returns:
       dictionary of numpy arrays of distances, size of lon.shape
       IMPORTANT: If a finite fault is not supplied, and the distance
       measures requested include rx, ry, ry0, U, or T, then zeros will
       be returned; if rjb is requested, repi will be returned; if rrup
       is requested, rhypo will be returned.
    """
    fault = source.getFault()
    hypo = source.getHypo()
    if fault is not None:
        quadlist = fault.getQuadrilaterals()
        # Need a copy for GC2 since order of verticies/quads needs to be modivied.
        quadgc2 = copy.deepcopy(quadlist)
    else:
        quadlist = None

    # Dictionary for holding the distances
    distdict = dict()

    if not isinstance(methods, list):
        methods = [methods]

    methods_available = set(get_distance_measures())
    if not set(methods).issubset(methods_available):
        raise NotImplementedError(
            'One or more requested distance method is not '
            'valid or is not implemented yet')

    if (lat.shape == lon.shape) and (lat.shape == dep.shape):
        pass
    else:
        raise ShakeMapException('lat, lon, and dep must have the same shape.')

    oldshape = lon.shape

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

    if ('rrup' in methods) or ('rjb' in methods):
        x, y, z = latlon2ecef(lat, lon, dep)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

    # Define a projection that spands sites and fault
    if fault is None:
        all_lat = lat
        all_lon = lon
    else:
        all_lat = np.append(lat, fault.getLats())
        all_lon = np.append(lon, fault.getLons())

    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)

    # ---------------------------------------------
    # Distances that do not require loop over quads
    # ---------------------------------------------

    if ('repi' in methods) or \
       (('rjb' in methods) and (quadlist is None)) or \
       (('rrup' in methods) and (quadlist is None)) or \
       (('ry0' in methods) and (quadlist is None)) or \
       (('rx' in methods) and (quadlist is None)) or \
       (('T' in methods) and (quadlist is None)) or \
       (('U' in methods) and (quadlist is None)):
        # I don't think this error check makes sense any more because hypo
        # is assigned above with source.getHypo() that constructs it from
        # source._event_dict entries. 
        if hypo is None:
            raise ShakeMapException('Cannot calculate epicentral distance '
                                    'without a point object')
        repidist = geodetic.distance(hypo.longitude, hypo.latitude, 0.0,
                                     lon, lat, dep)
        repidist = repidist.reshape(oldshape)
        distdict['repi'] = repidist

    if ('rhypo' in methods) or \
       (('rrup' in methods) and (quadlist is None)):
        if hypo is None:
            raise ShakeMapException('Cannot calculate epicentral distance '
                                    'without a point object')
        rhypodist = geodetic.distance(
            hypo.longitude, hypo.latitude, hypo.depth, lon, lat, dep)
        rhypodist = rhypodist.reshape(oldshape)
        distdict['rhypo'] = rhypodist

    # --------------------------------------------------------
    # Loop over quadlist for those distances that require loop
    # --------------------------------------------------------
    if 'rrup' in methods:
        minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16
    if 'rjb' in methods:
        minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16
    if ('rx' in methods) or ('ry' in methods) or \
       ('ry0' in methods) or ('U' in methods) or ('T' in methods):
        totweight = np.zeros(newshape, dtype=lon.dtype)
        GC2T = np.zeros(newshape, dtype=lon.dtype)
        GC2U = np.zeros(newshape, dtype=lon.dtype)

        if quadlist is not None:
            #-----------------------------------------------------------------
            # For these distances, we need to sort out strike discordance and
            # nominal strike prior to starting the loop if there is more than
            # one segment.
            #-----------------------------------------------------------------

            segind = fault._getSegmentIndex()
            segindnp = np.array(segind)
            uind = np.unique(segind)
            nseg = len(uind)

            #-------------------------------------------------------------------
            # 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." 
            #-------------------------------------------------------------------

            if nseg > 1:
                # Need to get index of first and last quad
                # for each segment
                iq0 = np.zeros(nseg, dtype='int16')
                iq1 = np.zeros(nseg, dtype='int16')
                for k in uind:
                    ii = [i for i, j in enumerate(segind) 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 segments
                # including segment orientations (i.e., flipped). 
                #---------------------------------------------------------------

                it_seg = it.product(it.combinations(uind, 2),
                                    it.product([0, 1], [0, 1]))

                # Placeholder for the segment 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".
                #---------------------------------------------------------------

                # Goofy while-loop is to adjust the side of the fault 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(nseg)
                    b_prime = [None] * nseg
                    for j in range(nseg):
                        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(nseg):
                        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.copy(A0)
                        tmpA1 = copy.copy(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[segind[i]] < 0: #***************U*UUUUUUUSDFUSDfkjjhakjsdhfljkhn
                        quadgc2[i] = reverse_quad(quadgc2[i])

                # 2) rearrange quadlist to remove discordancy
                qind = np.arange(len(quadgc2))
                segnp = np.array(segind)
                for i in range(nseg):
                    qsel = qind[segnp == uind[i]]
                    if dc[i] < 0:
                        qrev = qsel[::-1]
                        qind[segnp == uind[i]] = qrev

                quadgc2old = copy.deepcopy(quadgc2)
                for i in range(len(qind)):
                    quadgc2[i] = quadgc2old[qind[i]]


    if quadlist is not None:
        # Length of prior segments
        s_i = 0.0
        l_i = np.zeros(len(quadlist))
        for i in range(len(quadlist)):
            P0, P1, P2, P3 = quadlist[i]
            G0, G1, G2, G3 = quadgc2[i]

            if 'rrup' in methods:
                rrupdist = _calc_rupture_distance(P0, P1, P2, P3, sites_ecef)
                minrrup = np.minimum(minrrup, rrupdist)

            if 'rjb' in methods:
                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
                rjbdist = _calc_rupture_distance(S0, S1, S2, S3, sites_ecef)
                minrjb = np.minimum(minrjb, rjbdist)

            if ('rx' in methods) or ('ry' in methods) or \
               ('ry0' in methods) or ('U' in methods) or ('T' in methods):
                # Rx, Ry, and Ry0 are all computed if one is requested since
                # they all require similar information for the weights. This
                # isn't necessary for a single segment fault though.
                # Note, we are basing these calculations on GC2 coordinates U
                # and T as described in:
                # Spudich and Chiou (2015)
                # http://dx.doi.org/10.3133/ofr20151028.

                # 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
                l_i[i] = get_quad_length(quadlist[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 nseg == 1:
                    GC2U = GC2U + w_i * (u_i + s_i)
                else:
                    if i == 0:
                        qind = np.array(range(len(quadlist)))
                        l_kj = 0
                        s_ij_1 = 0
                    else:
                        l_kj = l_i[(segindnp == segindnp[i]) & (qind < i)]
                        s_ij_1 = np.sum(l_kj)

                    p1 = Vector.fromPoint(quadgc2[iq0[segind[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]

        # Collect distances from loop into the distance dict
        if 'rjb' in methods:
            minrjb = minrjb.reshape(oldshape)
            distdict['rjb'] = minrjb

        if ('rx' in methods) or ('ry' in methods) or \
           ('ry0' in methods) or ('U' in methods) or ('T' in methods):
            # Normalize by sum of quad weights
            GC2T = GC2T / totweight
            GC2U = GC2U / totweight
            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 nseg > 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

        if 'rrup' in methods:
            minrrup = minrrup.reshape(oldshape)
            distdict['rrup'] = minrrup

    else:
        if 'rjb' in methods:
            if use_median_distance:
                warnings.warn(
                    'No fault; Replacing rjb with median rjb given M and repi.')
                cdir, tmp = os.path.split(__file__)

                # -------------------
                # Sort out file names
                # -------------------
                mech = source.getEventParam('mech')
                if not hasattr(source, '_tectonic_region'):
                    rf = os.path.join(
                        cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv")
                elif source._tectonic_region == 'Active Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Var.csv")
                elif source._tectonic_region == 'Stable Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Var.csv")
                else:
                    warnings.warn(
                        'Unsupported tectonic region; using coefficients for unknown'
                        'tectonic region.')
                    rf = os.path.join(
                        cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv")

                # -----------------
                # Start with ratios
                # -----------------
                repi2rjb_ratios_tbl = pd.read_csv(rf, comment='#')
                r2rrt_cols = repi2rjb_ratios_tbl.columns[1:]
                mag_list = []
                for column in (r2rrt_cols):
                    if re.search('R\d+\.*\d*', column):
                        magnitude = float(re.findall(
                            'R(\d+\.*\d*)', column)[0])
                        mag_list.append(magnitude)
                mag_list = np.array(mag_list)
                dist_list = np.log(np.array(repi2rjb_ratios_tbl['Repi_km']))
                repi2rjb_grid = repi2rjb_ratios_tbl.values[:, 1:]
                repi2rjb_obj = spint.RectBivariateSpline(
                    dist_list, mag_list, repi2rjb_grid, kx=1, ky=1)

                def repi2rjb_tbl(repi, M):
                    ratio = repi2rjb_obj.ev(np.log(repi), M)
                    rjb = repi * ratio
                    return rjb
                repis = distdict['repi']
                mags = np.ones_like(repis) * source.getEventParam('mag')
                rjb_hat = repi2rjb_tbl(repis, mags)
                distdict['rjb'] = rjb_hat
                # -------------------
                # Additional Variance
                # -------------------
                repi2rjbvar_ratios_tbl = pd.read_csv(vf, comment='#')
                repi2rjbvar_grid = repi2rjbvar_ratios_tbl.values[:, 1:]
                repi2rjbvar_obj = spint.RectBivariateSpline(
                    dist_list, mag_list, repi2rjbvar_grid, kx=1, ky=1)
                rjbvar = repi2rjbvar_obj.ev(np.log(repis), mags)
                distdict['rjbvar'] = rjbvar
            else:
                warnings.warn('No fault; Replacing rjb with repi')
                distdict['rjb'] = distdict['repi'].copy()
        if 'rrup' in methods:
            if use_median_distance:
                warnings.warn(
                    'No fault; Replacing rrup with median rrup given M and repi.')
                cdir, tmp = os.path.split(__file__)

                # -------------------
                # Sort out file names
                # -------------------
                rake = source._event_dict.get('rake')
                mech = rake_to_mech(rake)
                if not hasattr(source, '_tectonic_region'):
                    rf = os.path.join(
                        cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv")
                elif source._tectonic_region == 'Active Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Var.csv")
                elif source._tectonic_region == 'Stable Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Var.csv")
                else:
                    warnings.warn(
                        'Unsupported tectonic region; using coefficients for unknown'
                        'tectonic region.')
                    rf = os.path.join(
                        cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv")

                # -----------------
                # Start with ratios
                # -----------------
                repi2rrup_ratios_tbl = pd.read_csv(rf, comment='#')
                r2rrt_cols = repi2rrup_ratios_tbl.columns[1:]
                mag_list = []
                for column in (r2rrt_cols):
                    if re.search('R\d+\.*\d*', column):
                        magnitude = float(re.findall(
                            'R(\d+\.*\d*)', column)[0])
                        mag_list.append(magnitude)
                mag_list = np.array(mag_list)
                dist_list = np.log(np.array(repi2rrup_ratios_tbl['Repi_km']))
                repi2rrup_grid = repi2rrup_ratios_tbl.values[:, 1:]
                repi2rrup_obj = spint.RectBivariateSpline(
                    dist_list, mag_list, repi2rrup_grid, kx=1, ky=1)

                def repi2rrup_tbl(repi, M):
                    ratio = repi2rrup_obj.ev(np.log(repi), M)
                    rrup = repi * ratio
                    return rrup
                repis = distdict['repi']
                mags = np.ones_like(repis) * source.getEventParam('mag')
                rrup_hat = repi2rrup_tbl(repis, mags)
                distdict['rrup'] = rrup_hat

                # -------------------
                # Additional Variance
                # -------------------
                repi2rrupvar_ratios_tbl = pd.read_csv(vf, comment='#')
                repi2rrupvar_grid = repi2rrupvar_ratios_tbl.values[:, 1:]
                repi2rrupvar_obj = spint.RectBivariateSpline(
                    dist_list, mag_list, repi2rrupvar_grid, kx=1, ky=1)
                rrupvar = repi2rrupvar_obj.ev(np.log(repis), mags)
                distdict['rrupvar'] = rrupvar
            else:
                warnings.warn('No fault; Replacing rrup with rhypo')
                distdict['rrup'] = distdict['rhypo'].copy()
        if 'rx' in methods:
            warnings.warn('No fault; Setting Rx to zero.')
            distdict['rx'] = np.zeros_like(distdict['repi'])
        if 'ry0' in methods:
            warnings.warn('No fault; Setting ry0 to zero')
            distdict['ry0'] = np.zeros_like(distdict['repi'])
        if 'ry' in methods:
            warnings.warn('No fault; Setting ry to zero')
            distdict['ry'] = np.zeros_like(distdict['repi'])
        if 'U' in methods:
            warnings.warn('No fault; Setting U to zero')
            distdict['U'] = np.zeros_like(distdict['repi'])
        if 'T' in methods:
            warnings.warn('No fault; Setting T to zero')
            distdict['T'] = np.zeros_like(distdict['repi'])

    return distdict
Example #35
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 fault) array giving center points of each sub fault
        in ECEF coords.
    :param p:
        A 3x(n sub fault) array giving the unit vectors of the propagation
        vector on each sub fault 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, P2 = q[0:3]
    strike = P0.azimuth(P1)
    dip = fault.get_quad_dip(q)

    # Slip unit vectors in 'local' (i.e., z-up, x-east) coordinates
    d1_local = fault.get_local_unit_slip_vector_DS(strike, dip, rake)
    s1_local = fault.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
Example #36
0
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
Example #37
0
    def fromTrace(cls,
                  xp0,
                  yp0,
                  xp1,
                  yp1,
                  zp,
                  widths,
                  dips,
                  strike=None,
                  reference=None):
        """
        Create a fault object from a set of vertices that define the top of the
        fault, and an array of widths/dips.

        These top of rupture points are defined by specifying the x and y
        coordinates of each of the two vertices, and then specifying an array of
        depths,widths, and dips for each rectangle.

        :param xp0:
            Array of longitude coordinates for the first (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param yp0:
            Array of latitude coordinates for the first (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param xp1:
            Array of longitude coordinates for the second (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param yp1:
            Array of latitude coordinates for the second (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param zp:
            Array of depths for each of the top of rupture rectangles (km).
        :param widths:
            Array of widths for each of rectangle (km).
        :param dips:
            Array of dips for each of rectangle (degrees).
        :param strike:
            If None then strike is computed from verticies of top edge of each
            quadrilateral. If a scalar, then all quadrilaterals are constructed
            assuming this strike direction. If a vector with the same length as
            the trace coordinates then it specifies the strike for each 
            quadrilateral.
        :param reference:
            String explaining where the fault definition came from (publication
            style reference, etc.)
        :returns:
            Fault object, where the fault is modeled as a series of rectangles.
        """
        if len(xp0) == len(yp0) == len(xp1) == len(yp1) == len(zp) == len(
                dips) == len(widths):
            pass
        else:
            raise ShakeMapException(
                'Number of xp0,yp0,xp1,yp1,zp,widths,dips points must be equal.'
            )
        if strike is None:
            pass
        else:
            if (len(xp0) == len(strike)) | (len(strike) == 1):
                pass
            else:
                raise ShakeMapException(
                    'Strike must be None, scalar, or same length as trace coordinates.'
                )

        # convert dips to radians
        dips = np.radians(dips)

        # ensure that all input sequences are numpy arrays
        xp0 = np.array(xp0, dtype='d')
        xp1 = np.array(xp1, dtype='d')
        yp0 = np.array(yp0, dtype='d')
        yp1 = np.array(yp1, dtype='d')
        zp = np.array(zp, dtype='d')
        widths = np.array(widths, dtype='d')
        dips = np.array(dips, dtype='d')

        # get a projection object
        west = np.min((xp0.min(), xp1.min()))
        east = np.max((xp0.max(), xp1.max()))
        south = np.min((yp0.min(), yp1.min()))
        north = np.max((yp0.max(), yp1.max()))

        # projected coordinates are in km
        proj = get_orthographic_projection(west, east, north, south)
        surfaces = []
        xp2 = np.zeros_like(xp0)
        xp3 = np.zeros_like(xp0)
        yp2 = np.zeros_like(xp0)
        yp3 = np.zeros_like(xp0)
        zpdown = np.zeros_like(zp)
        for i in range(0, len(xp0)):
            # Project the top edge coordinates
            p0x, p0y = proj(xp0[i], yp0[i])
            p1x, p1y = proj(xp1[i], yp1[i])

            # Get the rotation angle defined by these two points
            if strike is None:
                dx = p1x - p0x
                dy = p1y - p0y
                theta = np.arctan2(dx, dy)  # theta is angle from north
            elif len(strike) == 1:
                theta = np.radians(strike)
            else:
                theta = np.radians(strike[i])

            R = np.array([[np.cos(theta), -np.sin(theta)],
                          [np.sin(theta), np.cos(theta)]])

            # Rotate the top edge points into a new coordinate system (vertical
            # line)
            p0 = np.array([p0x, p0y])
            p1 = np.array([p1x, p1y])
            p0p = np.dot(R, p0)
            p1p = np.dot(R, p1)

            # Get right side coordinates in project,rotated system
            dz = np.sin(dips[i]) * widths[i]
            dx = np.cos(dips[i]) * widths[i]
            p3xp = p0p[0] + dx
            p3yp = p0p[1]
            p2xp = p1p[0] + dx
            p2yp = p1p[1]

            # Get right side coordinates in un-rotated projected system
            p3p = np.array([p3xp, p3yp])
            p2p = np.array([p2xp, p2yp])
            Rback = np.array([[np.cos(-theta), -np.sin(-theta)],
                              [np.sin(-theta), np.cos(-theta)]])
            p3 = np.dot(Rback, p3p)
            p2 = np.dot(Rback, p2p)
            p3x = np.array([p3[0]])
            p3y = np.array([p3[1]])
            p2x = np.array([p2[0]])
            p2y = np.array([p2[1]])

            # project lower edge points back to lat/lon coordinates
            lon3, lat3 = proj(p3x, p3y, reverse=True)
            lon2, lat2 = proj(p2x, p2y, reverse=True)

            xp2[i] = lon2
            xp3[i] = lon3
            yp2[i] = lat2
            yp3[i] = lat3
            zpdown[i] = zp[i] + dz

        # assemble the vertices as the Fault constructor needs them...
        # which is: for each rectangle, there should be the four corners, the
        # first corner repeated, and then a nan.
        nrects = len(zp)
        anan = np.ones_like(xp0) * np.nan
        lon = np.array(list(zip(xp0, xp1, xp2, xp3, xp0, anan))).reshape(
            (nrects, 6)).flatten(order='C')
        lat = np.array(list(zip(yp0, yp1, yp2, yp3, yp0, anan))).reshape(
            (nrects, 6)).flatten(order='C')

        # we need an array of depths, but we need to double each zp and zpdown
        # element we have
        dep = []
        for i in range(0, nrects):
            dep += [zp[i], zp[i], zpdown[i], zpdown[i], zp[i], np.nan]
        dep = np.array(dep)

        # take the nans off the end of each array
        lon = lon[0:-1]
        lat = lat[0:-1]
        dep = dep[0:-1]

        return cls(lon, lat, dep, reference)
Example #38
0
def get_distance(methods, lat, lon, dep, source, use_median_distance=True):
    """
    Calculate distance using any one of a number of distance measures.
    One of quadlist OR hypo must be specified. The following table gives
    the allowed distance strings and a description of each. 

    +--------+----------------------------------------------------------+
    | String | Description                                              |
    +========+==========================================================+
    | repi   | Distance to epicenter.                                   |
    +--------+----------------------------------------------------------+
    | rhypo  | Distance to hypocenter.                                  |
    +--------+----------------------------------------------------------+
    | rjb    | Joyner-Boore distance; this is closest distance to the   |
    |        | surface projection of the rupture plane.                 |
    +--------+----------------------------------------------------------+
    | rrup   | Rupture distance; closest distance to the rupture plane. |
    +--------+----------------------------------------------------------+
    | rx     | Strike-normal distance; same as GC2 coordiante T.        |
    +--------+----------------------------------------------------------+
    | ry     | Strike-parallel distance; same as GC2 coordiante U, but  |
    |        | with a shift in origin definition. See Spudich and Chiou |
    |        | (2015) http://dx.doi.org/10.3133/ofr20151028.            |
    +--------+----------------------------------------------------------+
    | ry0    | Horizontal distance off the end of the rupture measured  |
    |        | parallel to strike. Can only be zero or positive. We     |
    |        | compute this as a function of GC2 coordinate U.          |
    +--------+----------------------------------------------------------+
    | U      | GC2 coordinate U.                                        |
    +--------+----------------------------------------------------------+
    | T      | GC2 coordinate T.                                        |
    +--------+----------------------------------------------------------+

    :param methods:
        List of strings (or just a string) of distances to compute.
    :param lat:
       A numpy array of latitudes.
    :param lon:
       A numpy array of longidues.
    :param dep:
       A numpy array of depths (km).
    :param source:
       source instance.
    :param use_median_distance:
        Boolean; only used if GMPE requests fault distances and not fault is
        availalbe. Default is True, meaning that point-source distances are
        adjusted based on magnitude to get the median fault distance.
    :returns:
       dictionary of numpy array of distances, size of lon.shape
    """
    fault = source.getFault()
    hypo = source.getHypo()
    if fault is not None:
        quadlist = fault.getQuadrilaterals()
    else:
        quadlist = None

    # Dictionary for holding the distances
    distdict = dict()

    if not isinstance(methods, list):
        methods = [methods]

    methods_available = set(
        ['repi', 'rhypo', 'rjb', 'rrup', 'rx', 'ry', 'ry0', 'U', 'T'])
    if not set(methods).issubset(methods_available):
        raise NotImplementedError(
            'One or more requested distance method is not '
            'valid or is not implemented yet')

    if (lat.shape == lon.shape) and (lat.shape == dep.shape):
        pass
    else:
        raise ShakeMapException('lat, lon, and dep must have the same shape.')

    oldshape = lon.shape

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

    if ('rrup' in methods) or ('rjb' in methods):
        x, y, z = latlon2ecef(lat, lon, dep)
        x.shape = newshape
        y.shape = newshape
        z.shape = newshape
        sites_ecef = np.hstack((x, y, z))

    # Define a projection that spands sites and fault
    if fault is None:
        all_lat = lat
        all_lon = lon
    else:
        all_lat = np.append(lat, fault.getLats())
        all_lon = np.append(lon, fault.getLons())

    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)

    # ---------------------------------------------
    # Distances that do not require loop over quads
    # ---------------------------------------------

    if ('repi' in methods) or \
       (('rjb' in methods) and (quadlist is None)) or \
       (('rrup' in methods) and (quadlist is None)) or \
       (('ry0' in methods) and (quadlist is None)) or \
       (('rx' in methods) and (quadlist is None)) or \
       (('T' in methods) and (quadlist is None)) or \
       (('U' in methods) and (quadlist is None)):
        if hypo is None:
            raise ShakeMapException('Cannot calculate epicentral distance '
                                    'without a point object')
        repidist = geodetic.distance(hypo.longitude, hypo.latitude, 0.0, lon,
                                     lat, dep)
        repidist = repidist.reshape(oldshape)
        distdict['repi'] = repidist

    if ('rhypo' in methods) or \
       (('rrup' in methods) and (quadlist is None)):
        if hypo is None:
            raise ShakeMapException('Cannot calculate epicentral distance '
                                    'without a point object')
        rhypodist = geodetic.distance(hypo.longitude, hypo.latitude,
                                      hypo.depth, lon, lat, dep)
        rhypodist = rhypodist.reshape(oldshape)
        distdict['rhypo'] = rhypodist

    # --------------------------------------------------------
    # Loop over quadlist for those distances that require loop
    # --------------------------------------------------------
    if 'rrup' in methods:
        minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16
    if 'rjb' in methods:
        minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16
    if ('rx' in methods) or ('ry' in methods) or \
       ('ry0' in methods) or ('U' in methods) or ('T' in methods):
        totweight = np.zeros(newshape, dtype=lon.dtype)
        GC2T = np.zeros(newshape, dtype=lon.dtype)
        GC2U = np.zeros(newshape, dtype=lon.dtype)

        if quadlist is not None:
            #-----------------------------------------------------------------
            # For these distances, we need to sort out strike discordance and
            # nominal strike prior to starting the loop if there is more than
            # one segment.
            #-----------------------------------------------------------------

            segind = fault._getSegmentIndex()
            segindnp = np.array(segind)
            uind = np.unique(segind)
            nseg = len(uind)

            #-------------------------------------------------------------------
            # 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."
            #-------------------------------------------------------------------

            if nseg > 1:
                # Need to get index of first and last quad
                # for each segment
                iq0 = np.zeros(nseg, dtype='int16')
                iq1 = np.zeros(nseg, dtype='int16')
                for k in uind:
                    ii = [i for i, j in enumerate(segind) 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 segments
                # including segment orientations (i.e., flipped).
                #---------------------------------------------------------------

                it_seg = it.product(it.combinations(uind, 2),
                                    it.product([0, 1], [0, 1]))

                # Placeholder for the segment 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".
                #---------------------------------------------------------------

                # Primate fixes the trend of the trial a vector.
                primate = -1
                while primate < 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(nseg)
                    b_prime = [None] * nseg
                    for j in range(nseg):
                        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(nseg):
                        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()
                    primate = bhat.dot(ahat)
                    if primate < 0:
                        tmpA0 = copy.copy(A0)
                        tmpA1 = copy.copy(A1)
                        A0 = tmpA1
                        A1 = tmpA0

    if quadlist is not None:
        # Length of prior segments
        s_i = 0.0
        l_i = np.zeros(len(quadlist))
        for i in range(len(quadlist)):
            P0, P1, P2, P3 = quadlist[i]

            if 'rrup' in methods:
                rrupdist = _calc_rupture_distance(P0, P1, P2, P3, sites_ecef)
                minrrup = np.minimum(minrrup, rrupdist)

            if 'rjb' in methods:
                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
                rjbdist = _calc_rupture_distance(S0, S1, S2, S3, sites_ecef)
                minrjb = np.minimum(minrjb, rjbdist)

            if ('rx' in methods) or ('ry' in methods) or \
               ('ry0' in methods) or ('U' in methods) or ('T' in methods):
                # Rx, Ry, and Ry0 are all computed if one is requested since
                # they all require similar information for the weights. This
                # isn't necessary for a single segment fault though.
                # Note, we are basing these calculations on GC2 coordinates U
                # and T as described in:
                # Spudich and Chiou (2015)
                # http://dx.doi.org/10.3133/ofr20151028.

                # Compute u_i and t_i for this segment
                t_i = __calc_t_i(P0, P1, lat, lon, proj)
                u_i = __calc_u_i(P0, P1, lat, lon, proj)

                # Quad length
                l_i[i] = get_quad_length(quadlist[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 nseg == 1:
                    GC2U = GC2U + w_i * (u_i + s_i)
                else:
                    if i == 0:
                        qind = np.array(range(len(quadlist)))
                        l_kj = 0
                        s_ij_1 = 0
                    else:
                        l_kj = l_i[(segindnp == segindnp[i]) & (qind < i)]
                        s_ij_1 = np.sum(l_kj)

                    p1 = Vector.fromPoint(quadlist[iq0[segind[i]]][0])
                    s_ij_2 = (
                        (p1 - p_origin) * dc[segind[i]]).dot(ahat) / 1000.0
                    # This is implemented with 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]

        # Collect distances from loop into the distance dict
        if 'rjb' in methods:
            minrjb = minrjb.reshape(oldshape)
            distdict['rjb'] = minrjb

        if ('rx' in methods) or ('ry' in methods) or \
           ('ry0' in methods) or ('U' in methods) or ('T' in methods):
            # Normalize by sum of quad weights
            GC2T = GC2T / totweight
            GC2U = GC2U / totweight
            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 nseg > 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

        if 'rrup' in methods:
            minrrup = minrrup.reshape(oldshape)
            distdict['rrup'] = minrrup

    else:
        if 'rjb' in methods:
            if use_median_distance:
                warnings.warn(
                    'No fault; Replacing rjb with median rjb given M and repi.'
                )
                cdir, tmp = os.path.split(__file__)

                # -------------------
                # Sort out file names
                # -------------------
                mech = source.getEventParam('mech')
                if not hasattr(source, '_tectonic_region'):
                    rf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv")
                    vf = os.path.join(cdir, "data", "ps2ff",
                                      "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv")
                elif source._tectonic_region == 'Active Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechA_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechA_ar1p7_seis0_20_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechR_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechR_ar1p7_seis0_20_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechN_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechN_ar1p7_seis0_20_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechSS_ar1p7_seis0_20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_WC94_mechSS_ar1p7_seis0_20_Var.csv")
                elif source._tectonic_region == 'Stable Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechA_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechA_ar1p0_seis0_15_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechR_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechR_ar1p0_seis0_15_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechN_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechN_ar1p0_seis0_15_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechSS_ar1p0_seis0_15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rjb_S14_mechSS_ar1p0_seis0_15_Var.csv")
                else:
                    warnings.warn(
                        'Unsupported tectonic region; using coefficients for unknown'
                        'tectonic region.')
                    rf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv")
                    vf = os.path.join(cdir, "data", "ps2ff",
                                      "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv")

                # -----------------
                # Start with ratios
                # -----------------
                repi2rjb_ratios_tbl = pd.read_csv(rf, comment='#')
                r2rrt_cols = repi2rjb_ratios_tbl.columns[1:]
                mag_list = []
                for column in (r2rrt_cols):
                    if re.search('R\d+\.*\d*', column):
                        magnitude = float(
                            re.findall('R(\d+\.*\d*)', column)[0])
                        mag_list.append(magnitude)
                mag_list = np.array(mag_list)
                dist_list = np.log(np.array(repi2rjb_ratios_tbl['Repi_km']))
                repi2rjb_grid = repi2rjb_ratios_tbl.values[:, 1:]
                repi2rjb_obj = spint.RectBivariateSpline(dist_list,
                                                         mag_list,
                                                         repi2rjb_grid,
                                                         kx=1,
                                                         ky=1)

                def repi2rjb_tbl(repi, M):
                    ratio = repi2rjb_obj.ev(np.log(repi), M)
                    rjb = repi * ratio
                    return rjb

                repis = distdict['repi']
                mags = np.ones_like(repis) * source.getEventParam('mag')
                rjb_hat = repi2rjb_tbl(repis, mags)
                distdict['rjb'] = rjb_hat
                # -------------------
                # Additional Variance
                # -------------------
                repi2rjbvar_ratios_tbl = pd.read_csv(vf, comment='#')
                repi2rjbvar_grid = repi2rjbvar_ratios_tbl.values[:, 1:]
                repi2rjbvar_obj = spint.RectBivariateSpline(dist_list,
                                                            mag_list,
                                                            repi2rjbvar_grid,
                                                            kx=1,
                                                            ky=1)
                rjbvar = repi2rjbvar_obj.ev(np.log(repis), mags)
                distdict['rjbvar'] = rjbvar
            else:
                warnings.warn('No fault; Replacing rjb with repi')
                distdict['rjb'] = distdict['repi']
        if 'rrup' in methods:
            if use_median_distance:
                warnings.warn(
                    'No fault; Replacing rrup with median rrup given M and repi.'
                )
                cdir, tmp = os.path.split(__file__)

                # -------------------
                # Sort out file names
                # -------------------
                rake = source._event_dict.get('rake')
                mech = rake_to_mech(rake)
                if not hasattr(source, '_tectonic_region'):
                    rf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv")
                elif source._tectonic_region == 'Active Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechA_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechA_ar1p7_seis0-20_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechR_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechR_ar1p7_seis0-20_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechN_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechN_ar1p7_seis0-20_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechSS_ar1p7_seis0-20_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_WC94_mechSS_ar1p7_seis0-20_Var.csv")
                elif source._tectonic_region == 'Stable Shallow Crust':
                    if mech == 'ALL':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechA_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechA_ar1p0_seis0-15_Var.csv")
                    elif mech == 'RS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechR_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechR_ar1p0_seis0-15_Var.csv")
                    elif mech == 'NM':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechN_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechN_ar1p0_seis0-15_Var.csv")
                    elif mech == 'SS':
                        rf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechSS_ar1p0_seis0-15_Ratios.csv")
                        vf = os.path.join(
                            cdir, "data", "ps2ff",
                            "Rrup_S14_mechSS_ar1p0_seis0-15_Var.csv")
                else:
                    warnings.warn(
                        'Unsupported tectonic region; using coefficients for unknown'
                        'tectonic region.')
                    rf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv")
                    vf = os.path.join(
                        cdir, "data", "ps2ff",
                        "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv")

                # -----------------
                # Start with ratios
                # -----------------
                repi2rrup_ratios_tbl = pd.read_csv(rf, comment='#')
                r2rrt_cols = repi2rrup_ratios_tbl.columns[1:]
                mag_list = []
                for column in (r2rrt_cols):
                    if re.search('R\d+\.*\d*', column):
                        magnitude = float(
                            re.findall('R(\d+\.*\d*)', column)[0])
                        mag_list.append(magnitude)
                mag_list = np.array(mag_list)
                dist_list = np.log(np.array(repi2rrup_ratios_tbl['Repi_km']))
                repi2rrup_grid = repi2rrup_ratios_tbl.values[:, 1:]
                repi2rrup_obj = spint.RectBivariateSpline(dist_list,
                                                          mag_list,
                                                          repi2rrup_grid,
                                                          kx=1,
                                                          ky=1)

                def repi2rrup_tbl(repi, M):
                    ratio = repi2rrup_obj.ev(np.log(repi), M)
                    rrup = repi * ratio
                    return rrup

                repis = distdict['repi']
                mags = np.ones_like(repis) * source.getEventParam('mag')
                rrup_hat = repi2rrup_tbl(repis, mags)
                distdict['rrup'] = rrup_hat

                # -------------------
                # Additional Variance
                # -------------------
                repi2rrupvar_ratios_tbl = pd.read_csv(vf, comment='#')
                repi2rrupvar_grid = repi2rrupvar_ratios_tbl.values[:, 1:]
                repi2rrupvar_obj = spint.RectBivariateSpline(dist_list,
                                                             mag_list,
                                                             repi2rrupvar_grid,
                                                             kx=1,
                                                             ky=1)
                rrupvar = repi2rrupvar_obj.ev(np.log(repis), mags)
                distdict['rrupvar'] = rrupvar
            else:
                warnings.warn('No fault; Replacing rrup with rhypo')
                distdict['rrup'] = distdict['rhypo']
        if 'rx' in methods:
            warnings.warn('No fault; Setting Rx to zero.')
            distdict['rx'] = np.zeros_like(distdict['repi'])
        if 'ry0' in methods:
            warnings.warn('No fault; Replacing ry0 with repi')
            distdict['ry0'] = distdict['repi']
        if 'ry' in methods:
            warnings.warn('No fault; Replacing ry with repi')
            distdict['ry'] = distdict['repi']

    return distdict
Example #39
0
def test_chichi_with_get_distance():
    # read in rupture file
    f = os.path.join(shakedir, 'tests/data/0137A.POL')
    i0 = np.arange(0, 9 * 11 * 3, 11)
    i1 = i0 + 10
    cs = list(zip(i0, i1))
    df = pd.read_fwf(f, cs, skiprows=2, nrows=5, header=None)
    mat = df.as_matrix()
    ix = np.arange(0, 9 * 3, 3)
    iy = ix + 1
    iz = ix + 2
    x0 = mat[0, ix]
    x1 = mat[1, ix]
    x2 = mat[2, ix]
    x3 = mat[3, ix]
    y0 = mat[0, iy]
    y1 = mat[1, iy]
    y2 = mat[2, iy]
    y3 = mat[3, iy]
    # Depth, positive down
    z0 = np.abs(mat[0, iz])
    z1 = np.abs(mat[1, iz])
    z2 = np.abs(mat[2, iz])
    z3 = np.abs(mat[3, iz])
    epilat = 23.85
    epilon = 120.82
    proj = get_orthographic_projection(
        epilon - 1, epilon + 1, epilat + 1, epilat - 1)
    lon0, lat0 = proj(x0, y0, reverse=True)
    lon1, lat1 = proj(x1, y1, reverse=True)
    lon2, lat2 = proj(x2, y2, reverse=True)
    lon3, lat3 = proj(x3, y3, reverse=True)
    # event information doesn't matter except hypocenter
    event = {'lat': 23.85, 'lon': 120.82, 'depth': 8, 'mag': 7.62,
             'id': '', 'locstring': '', 'type': 'ALL',
             'timezone': 'UTC'}
    origin = Origin(event)
    rup = QuadRupture.fromVertices(
        lon0, lat0, z0, lon1, lat1, z1, lon2, lat2, z2, lon3, lat3, z3,
        origin)

    # Get NGA distances
    distfile = os.path.join(shakedir, 'tests/data/NGAW2_distances.csv')
    df = pd.read_csv(distfile)
    df2 = df.loc[df['EQID'] == 137]
    slat = df2['Station Latitude'].as_matrix()
    slon = df2['Station Longitude'].as_matrix()
    sdep = np.zeros(slat.shape)
    nga_repi = df2['EpiD (km)'].as_matrix()
    nga_rhypo = df2['HypD (km)'].as_matrix()
    nga_rrup = df2['ClstD (km)'].as_matrix()
    nga_rjb = df2['Joyner-Boore Dist. (km)'].as_matrix()
    nga_rx = df2['T'].as_matrix()
    nga_T = df2['T'].as_matrix()
    nga_U = df2['U'].as_matrix()
    test_ry = np.array([
        -49.25445446, -76.26871272, -37.1288192, -53.47792996,
        -50.30711637, -63.96322125, -61.01988704, -81.2001781,
        -76.00646939, -74.39038054, -92.23617124, -90.66976945,
        -89.68551411, -102.98798328, -114.70036085, -29.83636082,
        -28.50133134, -27.86922916, -36.00619214, -44.68826209,
        -47.64580208, -53.92619079, -59.11962858, -55.90584822,
        -55.00772025, -48.81756715, -59.27542007, -62.13633659,
        -70.0673351, -75.96977638, -61.6959293, -60.34564074,
        -81.49792285, -78.75933138, -80.80533738, -85.24473008,
        -94.07519297, -93.75010471, -96.87089883, -100.06112271,
        -98.86980873, -95.92330113, -107.44086722, -119.1065369,
        -120.60405905, -113.42995442, -115.94930662, -115.2398216,
        -107.37840927, -49.25445446, -48.78386688, -108.49133002,
        -88.03303353, -44.66653428, -81.04476548, -38.26801619,
        -70.51178983, -69.15679931, -74.74562139, -86.51133446,
        -27.62153029, -48.33279375, -30.0808298, -113.98345018,
        -97.96609537, -87.9863122, -39.45970018, -80.1387617,
        -42.27121388, -82.05027834, -81.55987067, -81.55987067,
        -107.25255717, 67.62695516, -3.27797047, -197.98554369,
        82.30996151, 18.42180605, -22.88851072, -35.75245916,
        -19.54788146, -18.19780517, 19.85077702, 20.33310282,
        19.95448398, 20.55508903, 18.17428572, 17.87997374,
        16.97323804, 16.0025885, 13.88001846, 18.42180605,
        -3.27797047, 51.43098894, 28.97695533, -53.20579538,
        38.7537468, 33.48878882, 26.25189111, 22.54251612,
        13.37141837, -5.80928302, -6.68056794, -14.50860117,
        -15.23992093, -27.63281952, -11.66075049, -36.94595337,
        -40.97168031, -41.2814342, -48.64456898, -61.55777751,
        -11.15038984, -17.16482959, 55.84202839, 36.78540588,
        21.18550074, 19.14658833, 19.22680282, 5.76327358,
        -47.45309937, -44.33194991, -55.15852372, 37.33066096,
        37.64135657, 14.31598698, 4.60495737, 6.87107021,
        18.42180605, 113.59285783, 109.06420877, 104.23416509,
        99.21599973, 95.25204545, 90.29487934, 86.26977557,
        95.28705209, 87.12907925, 101.40561896, 96.68858152,
        92.90287952, 100.36659012, 97.19448577, 92.8627461,
        85.01448355, 93.36767736, 96.90824009, 86.48002825,
        88.71037964, 106.17282325, 102.56142319, 97.60004093,
        99.61798574, 97.36337239, 94.22000798, 86.99488734,
        90.05981676, 90.51189502, 100.7166391, 100.31931988,
        67.62695516, 94.15062409, 87.77053675, 124.21806013,
        99.23108884, 101.48199452, 92.63771423, 78.88723272,
        72.7261356, 80.58682246, 73.30258213, 70.20783518,
        60.57963211, -87.72265602, -148.10933308, -150.41334959,
        -144.12558375, -145.5625388, -132.09479688, -135.12980144,
        -121.10883695, -143.75755221, -117.73616176, -115.28563276,
        -138.79652905, -143.10405603, -151.78419035, -159.75299736,
        -149.69457229, -175.20332448, -181.00970647, -188.86536942,
        -176.88178468, -194.20978527, -204.54944453, -161.04413103,
        -197.98554369, -96.74089367, -133.49237232, -84.71198922,
        -164.97719097, -202.48241157, -74.54550169, -147.37402934,
        -144.64074441, -147.94282804, -122.80976842, -133.1671346,
        -136.3051809, -113.93174768, -151.02125407, -146.5198829,
        -156.19720713, -126.06138725, -131.44422964, -197.62591198,
        -204.42320856, -149.84576063, -121.56474664, -130.99947339,
        -148.41767074, -145.28448367, 104.58903799, 82.1649906,
        67.69977397, 39.46989193, -69.00949731, -133.49237232,
        -128.264754, -84.71198922, -108.49133002, 119.86128724,
        122.73556155, 126.28254009, 125.12436373, 123.32498578,
        123.8337768, 121.39931427, 121.48412837, 122.03669249,
        122.59675818, 119.54338365, 120.33961222, 120.69581745,
        116.96928355, 117.6687724, 116.62277942, 115.39650689,
        112.60751523, 109.82643069, 108.2500678, 130.9143614,
        126.50049543, 112.76229057, 132.76840098, 107.27099883,
        128.16063464, 123.83157143, 120.46711628, 112.55756637,
        135.59953867, 136.66138116, 136.98573162, 134.04528777,
        116.27744752, 129.2794577, 119.13550981, 124.67196321,
        130.9728774, 130.9039439, 128.70028371, 130.04592892,
        140.21819548, 140.60370422, 113.37585901, 123.21523323,
        123.88149248, 128.56894995, 128.45186255, 118.74080853,
        126.71189149, 119.79833338, 130.00866791, -160.01242472,
        13.55424709, 110.26938756, 97.71987778, 110.93671325,
        108.71965725, 105.03432063, 106.36049687, 99.27569343,
        115.06168146, 77.00378531, 81.50139192, 92.15605815,
        79.94311644, 83.16892433, 52.23389149, 50.97110177,
        67.95167063, 63.43930833, 40.20494692, 43.22936492,
        47.21513635, 38.94380012, 53.85489136, 56.69935207,
        48.07036522, 64.46887891, 14.98020647, 17.35046801,
        16.15236633, 14.41062231, 19.99605739, 18.31076661,
        15.07058247, 12.34339267, 13.57621451, 14.72685201,
        22.04539325, 20.47982142, 9.66768974, 8.05139052,
        29.22924869, 3.75876894, 7.8610467, 29.20272495,
        15.19325822, -2.38981899, 5.58349359, -0.62239018,
        -4.38178769, -11.43651893, -20.07048519, -16.0588668,
        82.30996151, 13.55424709, 104.49355303, -11.29628168,
        82.1649906, 34.22207039, 38.08490923, -10.15855131,
        111.0308369, 81.78397481, 73.56334665, 81.27164139,
        74.55979012, 16.08437955, 23.8203941, 24.68836209,
        28.73767914, 21.06714416, 19.44159522, 4.62135887,
        3.41771413, 5.051121, -6.81834189, 6.40341853,
        -0.35693923, -17.74409367, -8.91759817, -18.05278804,
        7.70695248, -5.52733835, -16.02924961, -4.54310111,
        -22.84234773, -1.71908199, 39.46989193, -14.74007542,
        23.59992543, -10.49966883, -11.47733869, -22.8200901,
        -9.72486483, 95.96997763, -115.36487081, -52.88924268,
        -90.2275069, -132.22657274, -100.52455976, -115.24052939,
        -113.84482359, -114.41088165, -114.63386688, -115.92829006,
        -117.52597227, -114.49770514, -114.46881502, -76.26871272,
        -115.36487081, -160.01242472, -110.6429636, -77.47722955,
        -80.24672646, -85.90422427, -94.92075147, -102.44309541,
        -106.23741455, -111.56110193, -115.13402727, -48.64043046,
        -60.86151946, -66.52137871, -110.04628212, -75.27694696,
        -78.87041369, -88.08700161, -90.18844188, -93.65776393,
        -92.58976279, -107.31364843, -115.04064471, -125.98500718,
        -75.9341032, -39.45970018, -14.74007542, -23.16835763])
    test_ry0 = np.array([
        5.38783354, 32.4020918, 0., 9.61130904,
        6.44049545, 20.09660033, 17.15326613, 37.33355718,
        32.13984847, 30.52375962, 48.36955032, 46.80314854,
        45.81889319, 59.12136236, 70.83373993, 0.,
        0., 0., 0., 0.82164117,
        3.77918116, 10.05956987, 15.25300766, 12.0392273,
        11.14109933, 4.95094623, 15.40879915, 18.26971567,
        26.20071419, 32.10315546, 17.82930838, 16.47901983,
        37.63130193, 34.89271046, 36.93871646, 41.37810916,
        50.20857205, 49.88348379, 53.00427791, 56.19450179,
        55.00318781, 52.05668021, 63.5742463, 75.23991598,
        76.73743813, 69.5633335, 72.0826857, 71.37320068,
        63.51178836, 5.38783354, 4.91724596, 64.6247091,
        44.16641261, 0.79991336, 37.17814456, 0.,
        26.64516892, 25.2901784, 30.87900047, 42.64471355,
        0., 4.46617283, 0., 70.11682926,
        54.09947445, 44.11969128, 0., 36.27214079,
        0., 38.18365743, 37.69324975, 37.69324975,
        63.38593626, 31.95985109, 0., 154.11892278,
        46.64285745, 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 15.76388487, 0., 9.33917446,
        3.08664273, 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 4.77794806, 17.69115659,
        0., 0., 20.17492433, 1.11830182,
        0., 0., 0., 0.,
        3.58647845, 0.46532899, 11.2919028, 1.6635569,
        1.97425251, 0., 0., 0.,
        0., 77.92575377, 73.39710471, 68.56706103,
        63.54889567, 59.58494138, 54.62777528, 50.6026715,
        59.61994802, 51.46197518, 65.7385149, 61.02147746,
        57.23577546, 64.69948606, 61.52738171, 57.19564204,
        49.34737949, 57.7005733, 61.24113602, 50.81292419,
        53.04327558, 70.50571919, 66.89431913, 61.93293686,
        63.95088168, 61.69626833, 58.55290391, 51.32778327,
        54.3927127, 54.84479095, 65.04953504, 64.65221582,
        31.95985109, 58.48352003, 52.10343269, 88.55095607,
        63.56398477, 65.81489046, 56.97061016, 43.22012866,
        37.05903154, 44.9197184, 37.63547806, 34.54073112,
        24.91252804, 43.85603511, 104.24271216, 106.54672867,
        100.25896283, 101.69591788, 88.22817597, 91.26318052,
        77.24221603, 99.89093129, 73.86954084, 71.41901185,
        94.92990813, 99.23743511, 107.91756944, 115.88637645,
        105.82795138, 131.33670356, 137.14308555, 144.9987485,
        133.01516376, 150.34316435, 160.68282361, 117.17751011,
        154.11892278, 52.87427275, 89.6257514, 40.8453683,
        121.11057005, 158.61579065, 30.67888078, 103.50740842,
        100.77412349, 104.07620713, 78.9431475, 89.30051368,
        92.43855998, 70.06512676, 107.15463315, 102.65326198,
        112.33058622, 82.19476634, 87.57760872, 153.75929106,
        160.55658764, 105.97913971, 77.69812572, 87.13285248,
        104.55104982, 101.41786275, 68.92193392, 46.49788654,
        32.0326699, 3.80278787, 25.14287639, 89.6257514,
        84.39813309, 40.8453683, 64.6247091, 84.19418317,
        87.06845748, 90.61543602, 89.45725966, 87.65788171,
        88.16667274, 85.73221021, 85.81702431, 86.36958842,
        86.92965411, 83.87627959, 84.67250815, 85.02871339,
        81.30217949, 82.00166833, 80.95567535, 79.72940282,
        76.94041117, 74.15932662, 72.58296373, 95.24725733,
        90.83339137, 77.0951865, 97.10129692, 71.60389476,
        92.49353057, 88.16446736, 84.80001222, 76.89046231,
        99.93243461, 100.9942771, 101.31862755, 98.37818371,
        80.61034346, 93.61235363, 83.46840575, 89.00485915,
        95.30577334, 95.23683984, 93.03317965, 94.37882485,
        104.55109142, 104.93660016, 77.70875494, 87.54812917,
        88.21438842, 92.90184589, 92.78475848, 83.07370447,
        91.04478743, 84.13122931, 94.34156384, 116.14580381,
        0., 74.60228349, 62.05277372, 75.26960919,
        73.05255319, 69.36721657, 70.69339281, 63.60858937,
        79.3945774, 41.33668124, 45.83428785, 56.48895409,
        44.27601238, 47.50182027, 16.56678743, 15.30399771,
        32.28456656, 27.77220427, 4.53784286, 7.56226086,
        11.54803229, 3.27669605, 18.1877873, 21.032248,
        12.40326116, 28.80177485, 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        46.64285745, 0., 68.82644897, 0.,
        46.49788654, 0., 2.41780516, 0.,
        75.36373283, 46.11687074, 37.89624258, 45.60453732,
        38.89268605, 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 0., 0.,
        0., 0., 3.80278787, 0.,
        0., 0., 0., 0.,
        0., 60.30287357, 71.49824989, 9.02262176,
        46.36088598, 88.35995182, 56.65793884, 71.37390848,
        69.97820268, 70.54426073, 70.76724596, 72.06166914,
        73.65935135, 70.63108422, 70.6021941, 32.4020918,
        71.49824989, 116.14580381, 66.77634268, 33.61060864,
        36.38010555, 42.03760335, 51.05413055, 58.57647449,
        62.37079364, 67.69448101, 71.26740635, 4.77380954,
        16.99489854, 22.65475779, 66.1796612, 31.41032604,
        35.00379277, 44.22038069, 46.32182096, 49.79114301,
        48.72314188, 63.44702751, 71.1740238, 82.11838626,
        32.06748228, 0., 0., 0.])

    dist_types = ['repi', 'rhypo', 'rjb', 'rrup', 'rx', 'ry', 'ry0', 'U', 'T']
    dists = get_distance(dist_types, slat, slon, sdep, rup)

    np.testing.assert_allclose(
        nga_repi, dists['repi'], rtol=0, atol=2)

    np.testing.assert_allclose(
        nga_rhypo, dists['rhypo'], rtol=0, atol=2)

    np.testing.assert_allclose(
        nga_rjb, dists['rjb'], rtol=0, atol=2)

    np.testing.assert_allclose(
        nga_rrup, dists['rrup'], rtol=0, atol=2)

    np.testing.assert_allclose(
        nga_rx, dists['rx'], rtol=0, atol=2)

    np.testing.assert_allclose(
        test_ry, dists['ry'], rtol=0, atol=2)

    np.testing.assert_allclose(
        test_ry0, dists['ry0'], rtol=0, atol=2)

    np.testing.assert_allclose(
        nga_U, dists['U'], rtol=0, atol=6)

    np.testing.assert_allclose(
        nga_T, dists['T'], rtol=0, atol=2)
def get_extent(origin, rupture=None):
    """
    Method to compute map extent from rupture.

    Args:
        origin (Origin): A ShakeMap Origin instance.
        rupture (Rupture): A ShakeMap Rupture instance (optional).

    Returns:
        tuple: lonmin, lonmax, latmin, latmax.

    """

    # Is there a rupture?
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        lats = rupture.lats
        lons = rupture.lons

        # Remove nans
        lons = lons[~np.isnan(lons)]
        lats = lats[~np.isnan(lats)]

        clat = 0.5 * (np.nanmax(lats) + np.nanmin(lats))
        clon = 0.5 * (np.nanmax(lons) + np.nanmin(lons))
    else:
        clat = origin.lat
        clon = origin.lon

    mag = origin.mag

    # Is this a stable or active tectonic event?
    # (this could be made an attribute of the ShakeMap Origin class)
    hypo = origin.getHypo()
    stable = is_stable(hypo.longitude, hypo.latitude)

    if stable is False:
        if mag < 6.48:
            mindist_km = 100.
        else:
            mindist_km = 27.24 * mag**2 - 250.4 * mag + 579.1
    else:
        if mag < 6.10:
            mindist_km = 100.
        else:
            mindist_km = 63.4 * mag**2 - 465.4 * mag + 581.3

    # Apply an upper limit on extent. This should only matter for large
    # magnitudes (> ~8.25) in stable tectonic environments.
    if mindist_km > 1000.:
        mindist_km = 1000.

    # Projection
    proj = get_orthographic_projection(clon - 4, clon + 4, clat + 4, clat - 4)
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        ruptx, rupty = proj(lons, lats)
    else:
        ruptx, rupty = proj(clon, clat)

    xmin = np.nanmin(ruptx) - mindist_km
    ymin = np.nanmin(rupty) - mindist_km
    xmax = np.nanmax(ruptx) + mindist_km
    ymax = np.nanmax(rupty) + mindist_km

    # Put a limit on range of aspect ratio
    dx = xmax - xmin
    dy = ymax - ymin
    ar = dy / dx
    if ar > 1.25:
        # Inflate x
        dx_target = dy / 1.25
        ddx = dx_target - dx
        xmax = xmax + ddx / 2
        xmin = xmin - ddx / 2
    if ar < 0.6:
        # inflate y
        dy_target = dx * 0.6
        ddy = dy_target - dy
        ymax = ymax + ddy / 2
        ymin = ymin - ddy / 2

    lonmin, latmin = proj(np.array([xmin]), np.array([ymin]), reverse=True)
    lonmax, latmax = proj(np.array([xmax]), np.array([ymax]), reverse=True)

    return lonmin, lonmax, latmin, latmax
Example #41
0
    def fromTrace(cls, xp0, yp0, xp1, yp1, zp, widths, dips, strike=None,
                  reference=None):
        """
        Create a fault object from a set of vertices that define the top of the
        fault, and an array of widths/dips.

        These top of rupture points are defined by specifying the x and y
        coordinates of each of the two vertices, and then specifying an array of
        depths,widths, and dips for each rectangle.

        :param xp0:
            Array of longitude coordinates for the first (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param yp0:
            Array of latitude coordinates for the first (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param xp1:
            Array of longitude coordinates for the second (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param yp1:
            Array of latitude coordinates for the second (top of rupture) vertex
            of each rectangle (decimal degrees).
        :param zp:
            Array of depths for each of the top of rupture rectangles (km).
        :param widths:
            Array of widths for each of rectangle (km).
        :param dips:
            Array of dips for each of rectangle (degrees).
        :param strike:
            If None then strike is computed from verticies of top edge of each
            quadrilateral. If a scalar, then all quadrilaterals are constructed
            assuming this strike direction. If a vector with the same length as
            the trace coordinates then it specifies the strike for each 
            quadrilateral.
        :param reference:
            String explaining where the fault definition came from (publication
            style reference, etc.)
        :returns:
            Fault object, where the fault is modeled as a series of rectangles.
        """
        if len(xp0) == len(yp0) == len(xp1) == len(
                yp1) == len(zp) == len(dips) == len(widths):
            pass
        else:
            raise ShakeMapException(
                'Number of xp0,yp0,xp1,yp1,zp,widths,dips points must be equal.')
        if strike is None:
            pass
        else:
            if (len(xp0) == len(strike)) | (len(strike) == 1):
                pass
            else:
                raise ShakeMapException(
                    'Strike must be None, scalar, or same length as trace coordinates.')

        # convert dips to radians
        dips = np.radians(dips)

        # ensure that all input sequences are numpy arrays
        xp0 = np.array(xp0, dtype='d')
        xp1 = np.array(xp1, dtype='d')
        yp0 = np.array(yp0, dtype='d')
        yp1 = np.array(yp1, dtype='d')
        zp = np.array(zp, dtype='d')
        widths = np.array(widths, dtype='d')
        dips = np.array(dips, dtype='d')

        # get a projection object
        west = np.min((xp0.min(), xp1.min()))
        east = np.max((xp0.max(), xp1.max()))
        south = np.min((yp0.min(), yp1.min()))
        north = np.max((yp0.max(), yp1.max()))

        # projected coordinates are in km
        proj = get_orthographic_projection(west, east, north, south)
        surfaces = []
        xp2 = np.zeros_like(xp0)
        xp3 = np.zeros_like(xp0)
        yp2 = np.zeros_like(xp0)
        yp3 = np.zeros_like(xp0)
        zpdown = np.zeros_like(zp)
        for i in range(0, len(xp0)):
            # Project the top edge coordinates
            p0x, p0y = proj(xp0[i], yp0[i])
            p1x, p1y = proj(xp1[i], yp1[i])

            # Get the rotation angle defined by these two points
            if strike is None:
                dx = p1x - p0x
                dy = p1y - p0y
                theta = np.arctan2(dx, dy)  # theta is angle from north
            elif len(strike) == 1:
                theta = np.radians(strike)
            else:
                theta = np.radians(strike[i])

            R = np.array([[np.cos(theta), -np.sin(theta)],
                          [np.sin(theta), np.cos(theta)]])

            # Rotate the top edge points into a new coordinate system (vertical
            # line)
            p0 = np.array([p0x, p0y])
            p1 = np.array([p1x, p1y])
            p0p = np.dot(R, p0)
            p1p = np.dot(R, p1)

            # Get right side coordinates in project,rotated system
            dz = np.sin(dips[i]) * widths[i]
            dx = np.cos(dips[i]) * widths[i]
            p3xp = p0p[0] + dx
            p3yp = p0p[1]
            p2xp = p1p[0] + dx
            p2yp = p1p[1]

            # Get right side coordinates in un-rotated projected system
            p3p = np.array([p3xp, p3yp])
            p2p = np.array([p2xp, p2yp])
            Rback = np.array([[np.cos(-theta), -np.sin(-theta)],
                              [np.sin(-theta), np.cos(-theta)]])
            p3 = np.dot(Rback, p3p)
            p2 = np.dot(Rback, p2p)
            p3x = np.array([p3[0]])
            p3y = np.array([p3[1]])
            p2x = np.array([p2[0]])
            p2y = np.array([p2[1]])

            # project lower edge points back to lat/lon coordinates
            lon3, lat3 = proj(p3x, p3y, reverse=True)
            lon2, lat2 = proj(p2x, p2y, reverse=True)

            xp2[i] = lon2
            xp3[i] = lon3
            yp2[i] = lat2
            yp3[i] = lat3
            zpdown[i] = zp[i] + dz

        # assemble the vertices as the Fault constructor needs them...
        # which is: for each rectangle, there should be the four corners, the
        # first corner repeated, and then a nan.
        nrects = len(zp)
        anan = np.ones_like(xp0) * np.nan
        lon = np.array(list(zip(xp0, xp1, xp2, xp3, xp0, anan))
                       ).reshape((nrects, 6)).flatten(order='C')
        lat = np.array(list(zip(yp0, yp1, yp2, yp3, yp0, anan))
                       ).reshape((nrects, 6)).flatten(order='C')

        # we need an array of depths, but we need to double each zp and zpdown
        # element we have
        dep = []
        for i in range(0, nrects):
            dep += [zp[i], zp[i], zpdown[i], zpdown[i], zp[i], np.nan]
        dep = np.array(dep)

        # take the nans off the end of each array
        lon = lon[0:-1]
        lat = lat[0:-1]
        dep = dep[0:-1]

        return cls(lon, lat, dep, reference)
Example #42
0
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
Example #43
0
def test_chichi():
    print('Testing Chi-Chi...')
    # read in fault file
    f = '../data/0137A.POL'
    i0 = np.arange(0, 9*11*3, 11)
    i1 = i0 + 10
    cs = zip(i0, i1)
    df = pd.read_fwf(f, cs, skiprows = 2, nrows = 5, header = None)
    mat = df.as_matrix()
    ix = np.arange(0, 9*3, 3)
    iy = ix + 1
    iz = ix + 2
    x0 = mat[0, ix]
    x1 = mat[1, ix]
    x2 = mat[2, ix]
    x3 = mat[3, ix]
    y0 = mat[0, iy]
    y1 = mat[1, iy]
    y2 = mat[2, iy]
    y3 = mat[3, iy]
    # Depth, positive down
    z0 = np.abs(mat[0, iz])
    z1 = np.abs(mat[1, iz])
    z2 = np.abs(mat[2, iz])
    z3 = np.abs(mat[3, iz])
    epilat = 23.85
    epilon = 120.82
    proj = get_orthographic_projection(
        epilon-1, epilon+1, epilat+1, epilat-1)
    lon0,lat0 = proj(x0, y0, reverse = True)
    lon1,lat1 = proj(x1, y1, reverse = True)
    lon2,lat2 = proj(x2, y2, reverse = True)
    lon3,lat3 = proj(x3, y3, reverse = True)
    flt = Fault.fromVertices(
        lon0, lat0, z0, lon1, lat1, z1, lon2, lat2, z2, lon3, lat3, z3)
    ask14 = AbrahamsonEtAl2014()
    # event information doesn't matter...
    event = {'lat': 0,  'lon': 0, 'depth':0, 'mag': 7, 
             'id':'', 'locstring':'', 'type':'U', 
             'time':ShakeDateTime.utcfromtimestamp(int(time.time())), 
             'timezone':'UTC'}
    source = Source(event, flt)
    
    # Get NGA distances
    distfile = '../data/NGAW2_distances.csv'
    df = pd.read_csv(distfile)
    df2 = df.loc[df['EQID'] == 137]
    slat = df2['Station Latitude'].as_matrix()
    slon = df2['Station Longitude'].as_matrix()
    sdep = np.zeros(slat.shape)
    nga_repi = df2['EpiD (km)'].as_matrix()
    nga_rhypo = df2['HypD (km)'].as_matrix()
    nga_rrup = df2['ClstD (km)'].as_matrix()
    nga_rjb = df2['Joyner-Boore Dist. (km)'].as_matrix()
    nga_rx = df2['T'].as_matrix()
    
    dist = Distance(ask14, source, slat, slon, sdep)
    dctx = dist.getDistanceContext()
    fig = plt.figure(figsize=(8,8))
    plt.scatter(nga_rjb, dctx.rjb, alpha = 0.5, facecolors='none')
    plt.plot([0, nga_rjb.max()], [0, dctx.rjb.max()], 'b');
    plt.savefig('Chi-Chi_Rjb.png')
    fig = plt.figure(figsize=(8,8))
    plt.scatter(nga_rrup, dctx.rrup, alpha = 0.5, facecolors='none')
    plt.plot([0, nga_rrup.max()], [0, dctx.rrup.max()], 'b');
    plt.savefig('Chi-Chi_Rrup.png')
    fig = plt.figure(figsize=(8,8))
    plt.scatter(nga_rx, dctx.rx, alpha = 0.5, facecolors='none')
    plt.plot([nga_rx.min(), nga_rx.max()],
             [dctx.rx.min(), dctx.rx.max()], 'b');
    plt.savefig('Chi-Chi_Rx.png')
Example #44
0
def get_extent(rupture):
    """
    Method to compute map extent from rupture.

    Args:
        rupture (Rupture): A ShakeMap Rupture instance.

    Returns:
        tuple: lonmin, lonmax, latmin, latmax rounded to the nearest
        arc-minute..

    """

    if not rupture or not isinstance(rupture, Rupture):
        raise TypeError('get_extent() takes exactly 1 argument (0 given)')

    origin = rupture.getOrigin()
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        lats = rupture.lats
        lons = rupture.lons

        # Remove nans
        lons = lons[~np.isnan(lons)]
        lats = lats[~np.isnan(lats)]

        clat = 0.5 * (np.nanmax(lats) + np.nanmin(lats))
        clon = 0.5 * (np.nanmax(lons) + np.nanmin(lons))
    else:
        clat = origin.lat
        clon = origin.lon

    mag = origin.mag

    # Is this a stable or active tectonic event?
    # (this could be made an attribute of the ShakeMap Origin class)
    hypo = origin.getHypo()
    stable = is_stable(hypo.longitude, hypo.latitude)

    if stable is False:
        if mag < 6.48:
            mindist_km = 100.
        else:
            mindist_km = 27.24 * mag**2 - 250.4 * mag + 579.1
    else:
        if mag < 6.10:
            mindist_km = 100.
        else:
            mindist_km = 63.4 * mag**2 - 465.4 * mag + 581.3

    # Apply an upper limit on extent. This should only matter for large
    # magnitudes (> ~8.25) in stable tectonic environments.
    if mindist_km > 1000.:
        mindist_km = 1000.

    # Projection
    proj = get_orthographic_projection(clon - 4, clon + 4, clat + 4, clat - 4)
    if isinstance(rupture, (QuadRupture, EdgeRupture)):
        ruptx, rupty = proj(lons, lats)
    else:
        ruptx, rupty = proj(clon, clat)

    xmin = np.nanmin(ruptx) - mindist_km
    ymin = np.nanmin(rupty) - mindist_km
    xmax = np.nanmax(ruptx) + mindist_km
    ymax = np.nanmax(rupty) + mindist_km

    # Put a limit on range of aspect ratio
    dx = xmax - xmin
    dy = ymax - ymin
    ar = dy / dx
    if ar > 1.25:
        # Inflate x
        dx_target = dy / 1.25
        ddx = dx_target - dx
        xmax = xmax + ddx / 2
        xmin = xmin - ddx / 2
    if ar < 0.6:
        # inflate y
        dy_target = dx * 0.6
        ddy = dy_target - dy
        ymax = ymax + ddy / 2
        ymin = ymin - ddy / 2

    lonmin, latmin = proj(np.array([xmin]), np.array([ymin]), reverse=True)
    lonmax, latmax = proj(np.array([xmax]), np.array([ymax]), reverse=True)

    #
    # Round coordinates to the nearest minute -- that should make the
    # output grid register with common grid resolutions (60c, 30c,
    # 15c, 7.5c)
    #
    return _round_coord(lonmin[0]), _round_coord(lonmax[0]), \
           _round_coord(latmin[0]), _round_coord(latmax[0])