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)
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)
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
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
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
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
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')
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')
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
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))
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))
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
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
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 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)
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
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)
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))
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
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
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)
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))
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
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')
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])
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)
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
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])
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
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
def _computeGC2(rupture, lon, lat, depth): """ Method for computing GC2 from a ShakeMap Rupture instance. Args: rupture (Rupture): ShakeMap rupture object. lon (array): Numpy array of site longitudes. lat (array): Numpy array of site latitudes. depth (array): Numpy array of site depths. Returns: dict: Dictionary of GC2 distances. Keys include "T", "U", "rx" "ry", "ry0". """ quadlist = rupture.getQuadrilaterals() quadgc2 = copy.deepcopy(quadlist) oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) # ------------------------------------------------------------------------- # Define a projection that spans sites and rupture # ------------------------------------------------------------------------- all_lat = np.append(lat, rupture.lats) all_lon = np.append(lon, rupture.lons) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) # ------------------------------------------------------------------------- # First sort out strike discordance and nominal strike prior to # starting the loop if there is more than one group/trace. # ------------------------------------------------------------------------- group_ind = rupture._getGroupIndex() # Need group_ind as numpy array for sensible indexing... group_ind_np = np.array(group_ind) uind = np.unique(group_ind_np) n_groups = len(uind) if n_groups > 1: # --------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is "selected from the two endpoints most # distant from each other." # --------------------------------------------------------------------- # Need to get index of first and last quad # for each segment iq0 = np.zeros(n_groups, dtype='int16') iq1 = np.zeros(n_groups, dtype='int16') for k in uind: ii = [i for i, j in enumerate(group_ind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) # --------------------------------------------------------------------- # This is an iterator for each possible combination of traces # including trace orientations (i.e., flipped). # --------------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the trace pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 # --------------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". # --------------------------------------------------------------------- # This goofy while-loop is to adjust the side of the rupture where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(n_groups) b_prime = [None] * n_groups for j in range(n_groups): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(n_groups): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.deepcopy(A0) tmpA1 = copy.deepcopy(A1) A0 = tmpA1 A1 = tmpA0 # --------------------------------------------------------------------- # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # --------------------------------------------------------------------- # 1) flip quads for i in range(len(quadgc2)): if dc[group_ind[i]] < 0: quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist order qind = np.arange(len(quadgc2)) for i in range(n_groups): qsel = qind[group_ind_np == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[group_ind_np == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] # End of if-statement for adjusting group discordancy s_i = 0.0 l_i = np.zeros(len(quadgc2)) for i in range(len(quadgc2)): G0, G1, G2, G3 = quadgc2[i] # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length (top edge) l_i[i] = get_quad_length(quadgc2[i]) # --------------------------------------------------------------------- # Weight of segment, three cases # --------------------------------------------------------------------- # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan((l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if n_groups == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadgc2))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(group_ind_np == group_ind_np[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) # First endpoint in the current 'group' (or 'trace' in GC2 terms) p1 = Vector.fromPoint(quadgc2[iq0[group_ind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E) * ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] GC2T = GC2T / totweight GC2U = GC2U / totweight # Dictionary for holding the distances distdict = dict() distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if n_groups > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 return distdict
def 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)
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
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
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)
def _computeGC2(rupture, lon, lat, depth): """ Method for computing GC2 from a ShakeMap Rupture instance. Args: rupture (Rupture): ShakeMap rupture object. lon (array): Numpy array of site longitudes. lat (array): Numpy array of site latitudes. depth (array): Numpy array of site depths. Returns: dict: Dictionary of GC2 distances. Keys include "T", "U", "rx" "ry", "ry0". """ quadlist = rupture.getQuadrilaterals() quadgc2 = copy.deepcopy(quadlist) oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) # ------------------------------------------------------------------------- # Define a projection that spans sites and rupture # ------------------------------------------------------------------------- all_lat = np.append(lat, rupture.lats) all_lon = np.append(lon, rupture.lons) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) # ------------------------------------------------------------------------- # First sort out strike discordance and nominal strike prior to # starting the loop if there is more than one group/trace. # ------------------------------------------------------------------------- group_ind = rupture._getGroupIndex() # Need group_ind as numpy array for sensible indexing... group_ind_np = np.array(group_ind) uind = np.unique(group_ind_np) n_groups = len(uind) if n_groups > 1: # --------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is "selected from the two endpoints most # distant from each other." # --------------------------------------------------------------------- # Need to get index of first and last quad # for each segment iq0 = np.zeros(n_groups, dtype='int16') iq1 = np.zeros(n_groups, dtype='int16') for k in uind: ii = [i for i, j in enumerate(group_ind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) # --------------------------------------------------------------------- # This is an iterator for each possible combination of traces # including trace orientations (i.e., flipped). # --------------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the trace pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 # --------------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". # --------------------------------------------------------------------- # This goofy while-loop is to adjust the side of the rupture where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(n_groups) b_prime = [None] * n_groups for j in range(n_groups): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(n_groups): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.deepcopy(A0) tmpA1 = copy.deepcopy(A1) A0 = tmpA1 A1 = tmpA0 # --------------------------------------------------------------------- # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # --------------------------------------------------------------------- # 1) flip quads for i in range(len(quadgc2)): if dc[group_ind[i]] < 0: quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist order qind = np.arange(len(quadgc2)) for i in range(n_groups): qsel = qind[group_ind_np == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[group_ind_np == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] # End of if-statement for adjusting group discordancy s_i = 0.0 l_i = np.zeros(len(quadgc2)) for i in range(len(quadgc2)): G0, G1, G2, G3 = quadgc2[i] # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length (top edge) l_i[i] = get_quad_length(quadgc2[i]) # --------------------------------------------------------------------- # Weight of segment, three cases # --------------------------------------------------------------------- # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan( (l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if n_groups == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadgc2))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(group_ind_np == group_ind_np[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) # First endpoint in the current 'group' (or 'trace' in GC2 terms) p1 = Vector.fromPoint(quadgc2[iq0[group_ind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E) * ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] GC2T = GC2T / totweight GC2U = GC2U / totweight # Dictionary for holding the distances distdict = dict() distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if n_groups > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 return distdict
def 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')