def get_cell_dimensions(self): """ Compute the area [km2] of the cells representing the surface. """ lo = self.mesh.lons la = self.mesh.lats de = self.mesh.depths # Calculating cells dimensions lo0 = lo[:-1, :] la0 = la[:-1, :] de0 = de[:-1, :] lo1 = lo[1:, :] la1 = la[1:, :] de1 = de[1:, :] idx = np.logical_and(np.isfinite(lo0), np.isfinite(lo1)) dy = np.full_like(lo0, np.nan) dy[idx] = distance(lo0[idx], la0[idx], de0[idx], lo1[idx], la1[idx], de1[idx]) lo0 = lo[:, 1:] la0 = la[:, 1:] de0 = de[:, 1:] lo1 = lo[:, :-1] la1 = la[:, :-1] de1 = de[:, :-1] idx = np.logical_and(np.isfinite(lo0), np.isfinite(lo1)) dx = np.full_like(lo0, np.nan) dx[idx] = distance(lo0[idx], la0[idx], de0[idx], lo1[idx], la1[idx], de1[idx]) lo0 = lo[1:, 1:] la0 = la[1:, 1:] de0 = de[1:, 1:] lo1 = lo[:-1, :-1] la1 = la[:-1, :-1] de1 = de[:-1, :-1] idx = np.logical_and(np.isfinite(lo0), np.isfinite(lo1)) dd = np.full_like(lo0, np.nan) dd[idx] = distance(lo0[idx], la0[idx], de0[idx], lo1[idx], la1[idx], de1[idx]) # Compute the area of the upper left triangles in each cell s = (dx[:-1, :] + dy[:, :-1] + dd) * 0.5 upp = (s * (s - dx[:-1, :]) * (s - dy[:, :-1]) * (s - dd))**0.5 # Compute the area of the lower right triangles in each cell s = (dx[1:, :] + dy[:, 1:] + dd) * 0.5 low = (s * (s - dx[1:, :]) * (s - dy[:, 1:]) * (s - dd))**0.5 # Compute the area of each cell area = np.full_like(dd, np.nan) idx = np.logical_and(np.isfinite(upp), np.isfinite(low)) area[idx] = upp[idx] + low[idx] # Retain the same output of the original function which provided for # each cell the centroid as 3d vector in a Cartesian space, the length # width (size along column of points) in km and the area in km2. return None, None, None, area
def get_width(self) -> float: # TODO this method is provisional. It works correctly for simple and # regular geometries defined using profiles parallel to the dip # direction """ Compute the width of the kite surface. Defining a width for a kite surface is quite difficult. At present we compute it as the mean width for all the columns of the mesh defining the surface. """ if self.width is None: widths = [] for col_idx in range(self.mesh.lons.shape[1]): tmpa = np.nonzero(np.isfinite(self.mesh.lons[:, col_idx]))[0] tmpb = (tmpa[1:] - tmpa[:-1] == 1).nonzero()[0] idxs_low = tmpa[tmpb.astype(int)] tmp = distance(self.mesh.lons[idxs_low, col_idx], self.mesh.lats[idxs_low, col_idx], self.mesh.depths[idxs_low, col_idx], self.mesh.lons[idxs_low + 1, col_idx], self.mesh.lats[idxs_low + 1, col_idx], self.mesh.depths[idxs_low + 1, col_idx]) if len(tmp) > 0: widths.append(np.sum(tmp)) self.width = np.mean(np.array(widths)) return self.width
def set_metadata(self): """ Set the metadata for the station """ stats = self.stream[0].stats self._starttime = stats.starttime self._station_code = stats.station if 'coordinates' not in stats: self._elevation = np.nan self._coordinates = (np.nan, np.nan) return lat = stats.coordinates.latitude lon = stats.coordinates.longitude if ('elevation' not in stats.coordinates or np.isnan(stats.coordinates.elevation)): elev = 0 else: elev = stats.coordinates.elevation self._elevation = elev self._coordinates = (lat, lon) if self.event is not None: event = self.event dist, _, _ = gps2dist_azimuth(lat, lon, event.latitude, event.longitude) self._epicentral_distance = dist / M_PER_KM if event.depth is not None: self._hypocentral_distance = distance(lat, lon, -elev / M_PER_KM, event.latitude, event.longitude, event.depth / M_PER_KM)
def distance_to_station(lat, long, depth): # station GPS coordinates of GS OK029 station - Liberty Lake, Oklahoma, USA lat0 = 35.796570 long0 = -97.454860 depth0 = -0.333 # elevation: 333m # return distance of the event to the station return distance(long, lat, depth, long0, lat0, depth0)
def distance_to_mesh(self, mesh, with_depths=True): """ Compute distance (in km) between this point and each point of ``mesh``. :param mesh: :class:`~openquake.hazardlib.geo.mesh.Mesh` of points to calculate distance to. :param with_depths: If ``True`` (by default), distance is calculated between actual point and the mesh, geodetic distance of projections is combined with vertical distance (difference of depths). If this is set to ``False``, only geodetic distance between projections is calculated. :returns: Numpy array of floats of the same shape as ``mesh`` with distance values in km in respective indices. """ if with_depths: if mesh.depths is None: mesh_depths = numpy.zeros_like(mesh.lons) else: mesh_depths = mesh.depths return geodetic.distance(self.longitude, self.latitude, self.depth, mesh.lons, mesh.lats, mesh_depths) else: return geodetic.geodetic_distance(self.longitude, self.latitude, mesh.lons, mesh.lats)
def set_metadata(self): """ Set the metadata for the station """ stats = self.stream[0].stats self._starttime = stats.starttime self._station_code = stats.station if 'coordinates' not in stats: self._elevation = np.nan self._coordinates = (np.nan, np.nan) return lat = stats.coordinates.latitude lon = stats.coordinates.longitude if 'elevation' not in stats.coordinates or np.isnan(stats.coordinates.elevation): elev = 0 else: elev = stats.coordinates.elevation self._elevation = elev self._coordinates = (lat, lon) if self.event is not None: event = self.event dist, _, _ = gps2dist_azimuth(lat, lon, event.latitude, event.longitude) self._epicentral_distance = dist / 1000 if event.depth is not None: self._hypocentral_distance = distance(lat, lon, elev / 1000, event.latitude, event.longitude, event.depth / 1000)
def get_dip(self) -> float: """ Return the fault dip as the average dip over the fault surface mesh. :returns: The average dip, in decimal degrees. """ if self.dip is None: dips = [] lens = [] for col_idx in range(self.mesh.lons.shape[1]): hdists = distance(self.mesh.lons[:-1, col_idx], self.mesh.lats[:-1, col_idx], np.zeros_like(self.mesh.depths[1:, col_idx]), self.mesh.lons[1:, col_idx], self.mesh.lats[1:, col_idx], np.zeros_like(self.mesh.depths[1:, col_idx])) vdists = (self.mesh.depths[1:, col_idx] - self.mesh.depths[:-1, col_idx]) ok = np.logical_and(np.isfinite(hdists), np.isfinite(vdists)) hdists = hdists[ok] vdists = vdists[ok] dips.append(np.mean(np.degrees(np.arctan(vdists / hdists)))) lens.append(np.sum((hdists**2 + vdists**2)**0.5)) lens = np.array(lens) self.dip = np.sum(np.array(dips) * lens / np.sum(lens)) return self.dip
def get_profiles_length(sps): """ :parameter dict sps: A dictionary containing the subduction profiles :returns: A dictionary where key is the ID of the profile and value is the length and, a string identifying the longest profile """ lengths = {} longest_key = None shortest_key = None longest_length = 0. shortest_length = 1e10 for key in sorted(sps.keys()): dat = sps[key] total_length = 0 for idx in range(0, len(dat) - 1): dst = distance(dat[idx, 0], dat[idx, 1], dat[idx, 2], dat[idx + 1, 0], dat[idx + 1, 1], dat[idx + 1, 2]) total_length += dst lengths[key] = total_length if longest_length < total_length: longest_length = total_length longest_key = key if shortest_length > total_length: shortest_length = total_length shortest_key = key return lengths, longest_key, shortest_key
def distance_to_station(lat, long, depth): # station GPS coordinates lat0 = 35.796570 long0 = -97.454860 depth0 = -0.333 # return distance of the event to the station return distance(long, lat, depth, long0, lat0, depth0)
def getValue(self,lat,lon,method='nearest'): if method == 'nearest': d = geodetic.distance(lon,lat,self._lons,self._lats) imin = d.argmin() return self._data[imin] else: raise NotImplementedError('Only nearest neighbor method implemented at this time.')
def _test(self, mlons, mlats, mdepths, slons, slats, sdepths, expected_mpoint_indices): mlons, mlats, mdepths = [numpy.array(arr, float) for arr in (mlons, mlats, mdepths)] slons, slats, sdepths = [numpy.array(arr, float) for arr in (slons, slats, sdepths)] actual_indices = geodetic.min_distance(mlons, mlats, mdepths, slons, slats, sdepths, indices=True) numpy.testing.assert_equal(actual_indices, expected_mpoint_indices) dists = geodetic.min_distance(mlons, mlats, mdepths, slons, slats, sdepths) expected_closest_mlons = mlons.flat[expected_mpoint_indices] expected_closest_mlats = mlats.flat[expected_mpoint_indices] expected_closest_mdepths = mdepths.flat[expected_mpoint_indices] expected_distances = geodetic.distance( expected_closest_mlons, expected_closest_mlats, expected_closest_mdepths, slons, slats, sdepths ) self.assertTrue((dists == expected_distances).all()) # testing min_geodetic_distance with the same lons and lats min_geod_distance = geodetic.min_geodetic_distance(mlons, mlats, slons, slats) min_geo_distance2 = geodetic.min_distance(mlons, mlats, mdepths * 0, slons, slats, sdepths * 0) numpy.testing.assert_almost_equal(min_geod_distance, min_geo_distance2)
def getValue(self, lat, lon, method='nearest'): if method == 'nearest': d = geodetic.distance(lon, lat, self._lons, self._lats) imin = d.argmin() return self._data[imin] else: raise NotImplementedError( 'Only nearest neighbor method implemented at this time.')
def _test(self, filename): profile = _read_profile(filename) sampling_distance = 10. rprofile = _resample_profile(profile, sampling_distance) pro = np.array([(pnt.longitude, pnt.latitude, pnt.depth) for pnt in rprofile.points]) dsts = distance(pro[:-1, 0], pro[:-1, 1], pro[:-1, 2], pro[1:, 0], pro[1:, 1], pro[1:, 2]) np.testing.assert_allclose(dsts, sampling_distance, rtol=1)
def test_mesh_creation(self): # Create the mesh: two parallel profiles - no top alignment hsmpl = 4 vsmpl = 4 idl = False alg = False srfc = KiteSurface.from_profiles(self.prf, hsmpl, vsmpl, idl, alg) smsh = srfc.mesh # # Check the horizontal mesh spacing computed = [] for i in range(0, smsh.lons.shape[0]): tmp = [] for j in range(0, smsh.lons.shape[1] - 1): k = j + 1 dst = distance(smsh.lons[i, j], smsh.lats[i, j], smsh.depths[i, j], smsh.lons[i, k], smsh.lats[i, k], smsh.depths[i, k]) tmp.append(dst) computed.append(dst) computed = np.array(computed) self.assertTrue(np.all(abs(computed - hsmpl) / vsmpl < 0.05)) # # Check the vertical mesh spacing computed = [] for i in range(0, smsh.lons.shape[0] - 1): tmp = [] k = i + 1 for j in range(0, smsh.lons.shape[1]): dst = distance(smsh.lons[i, j], smsh.lats[i, j], smsh.depths[i, j], smsh.lons[k, j], smsh.lats[k, j], smsh.depths[k, j]) tmp.append(dst) computed.append(dst) computed = np.array(computed) self.assertTrue(np.all(abs(computed - vsmpl) / vsmpl < 0.05)) if PLOTTING: title = 'Two parallel profiles' ppp(self.prf, srfc, title)
def travel_time(lat, long, depth, stationLAT, stationLONG, stationDEPTH, mean_velocity): #print("long ="+str(long)+", lat ="+str(lat)+", depth ="+str(depth)+", stationLONG ="+str(stationLONG)+", stationLAT ="+str(stationLAT)); distance_to_station = distance(long, lat, depth, stationLONG, stationLAT, stationDEPTH) #distance_to_station = distance(long, lat, 0, stationLONG, stationLAT, 0) #distance in km #print("distance_to_station = "+str(distance_to_station)+" km") travel_time = (distance_to_station / mean_velocity) * 3600 #print("travel_time = "+str(travel_time)+"s") return travel_time
def test_get_cell_dimensions(self): ksfc = self.ksfc _, _, _, areas = ksfc.get_cell_dimensions() idx = np.isfinite(areas) # Computing the lenght and the width of the rectangle representing the # rupture surface. This includes the cells which are empty iul = (1, 0) iur = (1, -1) slen = distance(self.lons[iul], self.lats[iul], self.deps[iul], self.lons[iur], self.lats[iur], self.deps[iur]) iul = (0, 2) ill = (-1, 2) swid = distance(self.lons[iul], self.lats[iul], self.deps[iul], self.lons[ill], self.lats[ill], self.deps[ill]) # Computing the surface area as the total area minus the area of 4 # cells. Note that here we assume that cells have approx the same area expected = slen * swid / areas.size * (np.sum(idx)) perc_diff = abs(expected - np.sum(areas[idx])) / expected * 100 self.assertTrue(perc_diff < 0.5)
def test_mesh_creation(self): """ Create the mesh: two parallel profiles - no top alignment """ h_sampl = 4 v_sampl = 4 idl = False alg = False smsh = create_from_profiles(self.profiles, h_sampl, v_sampl, idl, alg) # plotter(self.profiles, smsh) # # Check the horizontal mesh spacing computed = [] for i in range(0, smsh.shape[0]): tmp = [] for j in range(0, smsh.shape[1] - 1): k = j + 1 dst = distance(smsh[i, j, 0], smsh[i, j, 1], smsh[i, j, 2], smsh[i, k, 0], smsh[i, k, 1], smsh[i, k, 2]) tmp.append(dst) computed.append(dst) computed = numpy.array(computed) self.assertTrue(numpy.all(abs(computed - h_sampl) / h_sampl < 0.05)) # # Check the vertical mesh spacing computed = [] for i in range(0, smsh.shape[0] - 1): tmp = [] k = i + 1 for j in range(0, smsh.shape[1]): dst = distance(smsh[i, j, 0], smsh[i, j, 1], smsh[i, j, 2], smsh[k, j, 0], smsh[k, j, 1], smsh[k, j, 2]) tmp.append(dst) computed.append(dst) computed = numpy.array(computed) print(numpy.amax(abs(computed - v_sampl) / v_sampl)) self.assertTrue(numpy.all(abs(computed - v_sampl) / v_sampl < 0.05))
def test_v_spacing(self): """ Check v-spacing: two misaligned profiles - no top alignment """ smsh = self.smsh computed = [] for i in range(0, smsh.shape[0] - 1): tmp = [] k = i + 1 for j in range(0, smsh.shape[1]): dst = distance(smsh[i, j, 0], smsh[i, j, 1], smsh[i, j, 2], smsh[k, j, 0], smsh[k, j, 1], smsh[k, j, 2]) tmp.append(dst) computed.append(dst) computed = numpy.array(computed) tmp = abs(computed - self.h_sampl) / self.h_sampl self.assertTrue(numpy.all(tmp < 0.05))
def test__spacing(self): """ Check v-spacing: two misaligned profiles - no top alignment """ srfc = self.smsh smsh = srfc.mesh computed = [] for i in range(0, smsh.lons.shape[0]-1): tmp = [] k = i + 1 for j in range(0, smsh.lons.shape[1]): dst = distance(smsh.lons[i, j], smsh.lats[i, j], smsh.depths[i, j], smsh.lons[k, j], smsh.lats[k, j], smsh.depths[k, j]) tmp.append(dst) computed.append(dst) computed = np.array(computed) tmp = abs(computed-self.v_sampl)/self.v_sampl self.assertTrue(np.all(tmp < 0.01))
def calcStationMetrics(self, eventid, stations=None, labels=None): """Calculate distance measures for each station. Args: eventid (str): ID of event to search for in ASDF file. stations (list): List of stations to create metrics for. labels (list): List of processing labels to create metrics for. """ if not self.hasEvent(eventid): fmt = 'No event matching %s found in workspace.' raise KeyError(fmt % eventid) streams = self.getStreams(eventid, stations=stations, labels=labels) event = self.getEvent(eventid) for stream in streams: tag = stream.tag parts = tag.split('_') if len(parts) > 2: label = parts[-1] eventid = '_'.join(parts[0:-1]) else: eventid, label = tag.split('_') elat = event.latitude elon = event.longitude edepth = event.depth_km slat = stream[0].stats.coordinates.latitude slon = stream[0].stats.coordinates.longitude sdep = stream[0].stats.coordinates.elevation epidist_m, _, _ = gps2dist_azimuth(elat, elon, slat, slon) hypocentral_distance = distance(elon, elat, edepth, slon, slat, -sdep / M_PER_KM) xmlfmt = '''<station_metrics> <hypocentral_distance units="km">%.1f</hypocentral_distance> <epicentral_distance units="km">%.1f</epicentral_distance> </station_metrics> ''' xmlstr = xmlfmt % (hypocentral_distance, epidist_m / M_PER_KM) metricpath = '/'.join([ format_netsta(stream[0].stats), format_nslit(stream[0].stats, stream.get_inst(), eventid) ]) self.insert_aux(xmlstr, 'StationMetrics', metricpath)
def test_h_spacing(self): """ Check h-spacing: two misaligned profiles - no top alignment """ smsh = self.smsh # # Check the horizontal mesh spacing computed = [] for i in range(0, smsh.shape[0]): tmp = [] for j in range(0, smsh.shape[1] - 1): k = j + 1 dst = distance(smsh[i, j, 0], smsh[i, j, 1], smsh[i, j, 2], smsh[i, k, 0], smsh[i, k, 1], smsh[i, k, 2]) tmp.append(dst) computed.append(dst) computed = numpy.array(computed) tmp = abs(computed - self.h_sampl) / self.h_sampl self.assertTrue(numpy.all(tmp < 0.05))
def get_width(self) -> float: if self.width is None: widths = [] for col_idx in range(self.mesh.lons.shape[1]): tmpa = np.nonzero(np.isfinite(self.mesh.lons[:, col_idx]))[0] tmpb = (tmpa[1:] - tmpa[:-1] == 1).nonzero()[0] idxs_low = tmpa[tmpb.astype(int)] tmp = distance(self.mesh.lons[idxs_low, col_idx], self.mesh.lats[idxs_low, col_idx], self.mesh.depths[idxs_low, col_idx], self.mesh.lons[idxs_low + 1, col_idx], self.mesh.lats[idxs_low + 1, col_idx], self.mesh.depths[idxs_low + 1, col_idx]) if len(tmp) > 0: widths.append(np.sum(tmp)) self.width = np.mean(np.array(widths)) return self.width
def computeRepi(self, lon, lat, depth): """ Method for computing epicentral distance. Args: lon (array): Numpy array of longitudes. lat (array): Numpy array of latitudes. depth (array): Numpy array of depths (km; positive down). Returns: array: Epicentral distance (km). """ origin = self._origin oldshape = lon.shape repi = geodetic.distance(origin.lon, origin.lat, 0.0, lon, lat, depth) repi = repi.reshape(oldshape) return repi
def computeRhyp(self, lon, lat, depth): """ Method for computing hypocentral distance. Args: lon (array): Numpy array of longitudes. lat (array): Numpy array of latitudes. depth (array): Numpy array of depths (km; positive down). Returns: array: Hypocentral distance (km). """ origin = self._origin oldshape = lon.shape rhyp = geodetic.distance(origin.lon, origin.lat, origin.depth, lon, lat, depth) rhyp = rhyp.reshape(oldshape) return rhyp
def distance(self, point): """ Compute the distance (in km) between this point and the given point. Distance is calculated using pythagoras theorem, where the hypotenuse is the distance and the other two sides are the horizontal distance (great circle distance) and vertical distance (depth difference between the two locations). :param point: Destination point. :type point: Instance of :class:`Point` :returns: The distance. :rtype: float """ return geodetic.distance(self.longitude, self.latitude, self.depth, point.longitude, point.latitude, point.depth)
def test_resampling_01(self): """ Test trench axis resampling using a distance of 20 km """ # # resample the trench axis - output is a numpy array sampling = 20. rtrench = self.trench.resample(sampling) idx = len(rtrench.axis)-2 pts = rtrench.axis deps = np.zeros_like(pts[0:idx, 0]) # # compute distance between consecutive points dsts = distance(pts[0:idx, 0], pts[0:idx, 1], deps, pts[1:idx+1, 0], pts[1:idx+1, 1], deps) expected = np.ones_like(dsts)*sampling # # check that the spacing between points corresponds to the # sampling distance np.testing.assert_allclose(dsts, expected, rtol=1, atol=0.)
def closer_than(self, mesh, radius): """ Check for proximity of points in the ``mesh``. :param mesh: :class:`openquake.hazardlib.geo.mesh.Mesh` instance. :param radius: Proximity measure in km. :returns: Numpy array of boolean values in the same shape as the mesh coordinate arrays with ``True`` on indexes of points that are not further than ``radius`` km from this point. Function :func:`~openquake.hazardlib.geo.geodetic.distance` is used to calculate distances to points of the mesh. Points of the mesh that lie exactly ``radius`` km away from this point also have ``True`` in their indices. """ dists = geodetic.distance(self.longitude, self.latitude, self.depth, mesh.lons, mesh.lats, 0 if mesh.depths is None else mesh.depths) return dists <= radius
def get_dip(self) -> float: # TODO this method is provisional. It works correctly for simple and # regular geometries defined using profiles parallel to the dip # direction """ Computes the fault dip as the average dip over the surface. :returns: The average dip, in decimal degrees. """ if self.dip is None: dips = [] lens = [] for col_idx in range(self.mesh.lons.shape[1]): # For the calculation of the overall dip we use just the dip # values of contiguous points along a profile iii = np.isfinite(self.mesh.lons[1:, col_idx]) kkk = np.isfinite(self.mesh.lons[:-1, col_idx]) jjj = np.where(np.logical_and(kkk, iii))[0] zeros = np.zeros_like(self.mesh.depths[jjj, col_idx]) hdists = distance(self.mesh.lons[jjj + 1, col_idx], self.mesh.lats[jjj + 1, col_idx], zeros, self.mesh.lons[jjj, col_idx], self.mesh.lats[jjj, col_idx], zeros) vdists = (self.mesh.depths[jjj + 1, col_idx] - self.mesh.depths[jjj, col_idx]) ok = np.logical_and(np.isfinite(hdists), np.isfinite(vdists)) hdists = hdists[ok] vdists = vdists[ok] if len(vdists) > 0: dips.append(np.mean(np.degrees(np.arctan(vdists / hdists)))) lens.append(np.sum((hdists**2 + vdists**2)**0.5)) lens = np.array(lens) self.dip = np.sum(np.array(dips) * lens / np.sum(lens)) return self.dip
def test_h_spacing(self): """ Check v-spacing: two misaligned profiles - no top alignment """ srfc = self.smsh smsh = srfc.mesh # # Check the horizontal mesh spacing computed = [] for i in range(0, smsh.lons.shape[0]): tmp = [] for j in range(0, smsh.lons.shape[1] - 1): k = j + 1 dst = distance(smsh.lons[i, j], smsh.lats[i, j], smsh.depths[i, j], smsh.lons[i, k], smsh.lats[i, k], smsh.depths[i, k]) tmp.append(dst) computed.append(dst) computed = np.array(computed) tmp = abs(computed - self.h_sampl) / self.h_sampl self.assertTrue(np.all(tmp < 0.02)) if PLOTTING: ppp(self.profiles, srfc)
def test_interpolation_cam(self): """ Test profile interpolation: CAM | maximum sampling: 30 km """ # # read data and compute distances sps, dmin, dmax = read_profiles_csv(CAM_DATA_PATH) lengths, longest_key, shortest_key = get_profiles_length(sps) maximum_sampling_distance = 30. num_sampl = np.ceil(lengths[longest_key] / maximum_sampling_distance) # # get interpolated profiles ssps = get_interpolated_profiles(sps, lengths, num_sampl) lll = [] for key in sorted(ssps.keys()): odat = sps[key] dat = ssps[key] distances = distance(dat[0:-2, 0], dat[0:-2, 1], dat[0:-2, 2], dat[1:-1, 0], dat[1:-1, 1], dat[1:-1, 2]) expected = lengths[key] / num_sampl * np.ones_like(distances) np.testing.assert_allclose(distances, expected, rtol=3) # # update the list with the number of points in each profile lll.append(len(dat[:, 0])) # # check that the interpolated profile starts from the same point # of the original one self.assertListEqual([odat[0, 0], odat[0, 1]], [dat[0, 0], dat[0, 1]]) # # check that the depth of the profiles is always increasing computed = np.all(np.sign(dat[:-1, 2]-dat[1:, 2]) < 0) self.assertTrue(computed) # # check that all the profiles have all the same length dff = np.diff(np.array(lll)) zeros = np.zeros_like(dff) np.testing.assert_allclose(dff, zeros, rtol=2)
def test_edge_resampling02(self): """ Test edge resampling with a resampling distance of 10 km """ # # resampled profile sampling_distance = 10. out_line, _, _ = _resample_edge(self.edge, sampling_distance, 5) # # lists with coordinates for the resampled profile lo = [pnt.longitude for pnt in out_line.points] la = [pnt.latitude for pnt in out_line.points] de = [pnt.depth for pnt in out_line.points] # # lenghts of resampled segments dsts = [] for i in range(0, len(out_line)-1): dsts.append(distance(lo[i], la[i], de[i], lo[i+1], la[i+1], de[i+1])) # # testing expected = np.ones((len(out_line)-1))*sampling_distance np.testing.assert_allclose(dsts, expected, rtol=2, atol=0.)
def _computeGC2(rupture, lon, lat, depth): """ Method for computing GC2 from a ShakeMap Rupture instance. Args: rupture (Rupture): ShakeMap rupture object. lon (array): Numpy array of site longitudes. lat (array): Numpy array of site latitudes. depth (array): Numpy array of site depths. Returns: dict: Dictionary of GC2 distances. Keys include "T", "U", "rx" "ry", "ry0". """ quadlist = rupture.getQuadrilaterals() quadgc2 = copy.deepcopy(quadlist) oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) # ------------------------------------------------------------------------- # Define a projection that spans sites and rupture # ------------------------------------------------------------------------- all_lat = np.append(lat, rupture.lats) all_lon = np.append(lon, rupture.lons) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) # ------------------------------------------------------------------------- # First sort out strike discordance and nominal strike prior to # starting the loop if there is more than one group/trace. # ------------------------------------------------------------------------- group_ind = rupture._getGroupIndex() # Need group_ind as numpy array for sensible indexing... group_ind_np = np.array(group_ind) uind = np.unique(group_ind_np) n_groups = len(uind) if n_groups > 1: # --------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is "selected from the two endpoints most # distant from each other." # --------------------------------------------------------------------- # Need to get index of first and last quad # for each segment iq0 = np.zeros(n_groups, dtype='int16') iq1 = np.zeros(n_groups, dtype='int16') for k in uind: ii = [i for i, j in enumerate(group_ind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) # --------------------------------------------------------------------- # This is an iterator for each possible combination of traces # including trace orientations (i.e., flipped). # --------------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the trace pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 # --------------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". # --------------------------------------------------------------------- # This goofy while-loop is to adjust the side of the rupture where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(n_groups) b_prime = [None] * n_groups for j in range(n_groups): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(n_groups): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.deepcopy(A0) tmpA1 = copy.deepcopy(A1) A0 = tmpA1 A1 = tmpA0 # --------------------------------------------------------------------- # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # --------------------------------------------------------------------- # 1) flip quads for i in range(len(quadgc2)): if dc[group_ind[i]] < 0: quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist order qind = np.arange(len(quadgc2)) for i in range(n_groups): qsel = qind[group_ind_np == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[group_ind_np == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] # End of if-statement for adjusting group discordancy s_i = 0.0 l_i = np.zeros(len(quadgc2)) for i in range(len(quadgc2)): G0, G1, G2, G3 = quadgc2[i] # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length (top edge) l_i[i] = get_quad_length(quadgc2[i]) # --------------------------------------------------------------------- # Weight of segment, three cases # --------------------------------------------------------------------- # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan((l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if n_groups == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadgc2))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(group_ind_np == group_ind_np[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) # First endpoint in the current 'group' (or 'trace' in GC2 terms) p1 = Vector.fromPoint(quadgc2[iq0[group_ind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E) * ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] GC2T = GC2T / totweight GC2U = GC2U / totweight # Dictionary for holding the distances distdict = dict() distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if n_groups > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 return distdict
def test(self): p1 = (0, 0, 10) p2 = (0.5, -0.3, 5) distance = geodetic.distance(*(p1 + p2)) self.assertAlmostEqual(distance, 65.0295143)
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 test_topo(self): p1 = (0, 0, 1) p2 = (0, 0, -1) distance = geodetic.distance(*(p1 + p2)) self.assertAlmostEqual(distance, 2.0)
def test_topo2(self): p1 = (0, 0, 3) p2 = (0.5, -0.3, -2) distance = geodetic.distance(*(p1 + p2)) self.assertAlmostEqual(distance, 65.0295143)
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. :param methods: List of strings (or just a string) of distances to compute; can include: 'repi', 'rhypo', 'rjb', 'rrup', 'rx', 'ry', 'ry0' 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 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. https://github.com/gem/oq-hazardlib/blob/master/openquake/hazardlib/geo/point.py :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 :raises ShakeMapException: if a fault distance method is called without quadlist :raises NotImplementedError: for unknown distance measures or ones not yet implemented. """ 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)) # --------------------------------------------- # 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 are more than one segments segind = fault.getSegmentIndex() segindnp = np.array(segind) uind = np.unique(segind) nseg = len(uind) if nseg > 1: quadlist = fault.getQuadrilaterals() # 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)) it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) 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.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() 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) u_i = calc_u_i(P0, P1, lat, lon) # 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(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