def test_plane_fit01(self): """ Tests the parameters of the plane obtained through the regression. """ pnt, par = utils.plane_fit(self.points) numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-5, atol=0) self.assertAlmostEqual(self.c[-1], -sum(par*pnt))
def test_plane_fit01(self): """ Tests the parameters of the plane obtained through the regression. """ pnt, par = utils.plane_fit(self.points) numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-5, atol=0) self.assertAlmostEqual(self.c[-1], -sum(par * pnt))
def get_points_on_plane(edges, poi): """ :param edges: :param points: """ # # creating a matrix of points pnts = [] for edge in edges: pnts += [[pnt.longitude, pnt.latitude, pnt.depth] for pnt in edge.points] pnts = np.array(pnts) # # projecting the points p = Proj(proj='lcc', lon_0=np.mean(pnts[:, 0]), lat_2=45) x, y = p(pnts[:, 0], pnts[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km # # fit the plane tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T pnt, ppar = plane_fit(tmp) # z = -(a * x + b * y - d) / c d = -np.sum(pnt * ppar) xp, yp = p(poi[:, 0], poi[:, 1]) poi[:, 2] = -(ppar[0] * xp / 1e3 + ppar[1] * yp / 1e3 + d) / ppar[2] # return poi
def test_plane_fit02(self): """ Tests the parameters of the plane obtained through the regression. In this second case we add noise to the z values """ self.points[:, 2] += numpy.random.random(self.npts) * 0.01 pnt, par = utils.plane_fit(self.points) numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-3, atol=0) self.assertAlmostEqual(self.c[-1], -sum(par * pnt), 2)
def test_plane_fit02(self): """ Tests the parameters of the plane obtained through the regression. In this second case we add noise to the z values """ self.points[:, 2] += numpy.random.random(self.npts) * 0.01 pnt, par = utils.plane_fit(self.points) numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-3, atol=0) self.assertAlmostEqual(self.c[-1], -sum(par*pnt), 2)
def create_lower_surface_mesh_old(mesh, slab_thickness): """ This method for the construction of the boottom surface of the slab finds the plane fitting the surface and the projects the top surface toward a direction perpendicular to the plane. NB don't forget the surface_to_mesh method in openquake.hazardlib.geo.mesh :parameter mesh: An instance of the :class:`openquake.hazardlib.geo.mesh.Mesh` describing the top of the slab within which we admit inslab seismicity :parameter float slab_thickness: Thickness of the slab [km] :returns: An instance of :class:`openquake.hazardlib.geo.mesh.Mesh` """ oshape = mesh.lons.shape # # create a 3xn array with the points composing the mesh lld = np.array([ mesh.lons.flatten('F'), mesh.lats.flatten('F'), mesh.depths.flatten('F') ]).T # # project the points using Lambert Conic Conformal - for the reference # meridian 'lon_0' we use the mean longitude of the grid p = Proj(proj='lcc', lon_0=np.mean(lld[:, 0]), lat_2=45) x, y = p(lld[:, 0], lld[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km # # compute the equation of the plane fitting the slab surface xx = np.vstack((x.T, y.T, lld[:, 2])).T pnt, ppar = plane_fit(xx) # # compute the points on the new surface. The new surface is at a distance # 'slab_tickness' below the original surface in a direction perpendicular # to the fitted plane corr = 1 if np.sign(ppar[2]) == -1: corr = -1 xls = x + corr * slab_thickness * ppar[0] yls = y + corr * slab_thickness * ppar[1] zls = lld[:, 2] + corr * slab_thickness * ppar[2] # # back-projection of the points composing the lower surface llo, lla = p(xls * 1e3, yls * 1e3, inverse=True) # # reshape the arrays containing the geographic coordinates of the lower # surface rllo = np.reshape(llo, oshape, order='F') rlla = np.reshape(lla, oshape, order='F') rzls = np.reshape(zls, oshape, order='F') # # return Mesh(rllo, rlla, rzls)
def _check_edges(edges): """ This checks that all the edges follow the right hand rule :param list edges: The list of edges to be analysed. :return: An instance of :class:`numpy.ndarray` of cardinality equal to the number of edges. Where integers are positive, the edges need to be flipped. """ # Check the input if len(edges) < 1: return None # Create a matrix of points pnts = [] for edge in edges: pnts += [[pnt.longitude, pnt.latitude, pnt.depth] for pnt in edge.points] pnts = np.array(pnts) # Project the points using Lambert Conic Conformal fmt = "+proj=lcc +lon_0={:f} +lat_1={:f} +lat_2={:f}" mla = np.mean(pnts[:, 1]) srs = CRS.from_proj4(fmt.format(np.mean(pnts[:, 0]), mla - 10, mla + 10)) p = Proj(srs) # From m to km x, y = p(pnts[:, 0], pnts[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km # Fit the plane tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T _, ppar = plane_fit(tmp) # Analyse the edges chks = [] for edge in edges: epnts = np.array([[pnt.longitude, pnt.latitude, pnt.depth] for pnt in edge.points[0:2]]) ex, ey = p(epnts[:, 0], epnts[:, 1]) ex = ex / 1e3 ey = ey / 1e3 # Check the edge direction Vs plane perpendicular edgv = np.array([np.diff(ex[0:2])[0], np.diff(ey[0:2])[0]]) chks.append(np.sign(np.cross(ppar[:2], edgv))) return np.array(chks)
def test_fit_plane_again(self): """ This is a trivial test to check (again) the code we use for fitting the plane """ a = 0.0 b = 0.0 c = 1 d = -1 expected_par = np.array([a, b, c]) x, y = np.meshgrid(np.linspace(0, 10, 10), np.linspace(0, 10, 10)) z = -(a * x + b * y + d) / c xx = np.vstack((x.flatten(), y.flatten(), z.flatten())).T pnt, par = plane_fit(xx) np.testing.assert_almost_equal(expected_par, par)
def _check_edges(edges): """ This checks that all the edges follow the right hand rule :param list edges: The list of edges to be analysed. :return: An instance of :class:`numpy.ndarray` of cardinality equal to the number of edges. Where integers are positive edges need to be flipped. """ # # creating a matrix of points pnts = [] for edge in edges: pnts += [[pnt.longitude, pnt.latitude, pnt.depth] for pnt in edge.points] pnts = np.array(pnts) # # projecting the points p = Proj(proj='lcc', lon_0=np.mean(pnts[:, 0]), lat_1=0., lat_2=60.) x, y = p(pnts[:, 0], pnts[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km # # fit the plane tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T _, ppar = plane_fit(tmp) # # analysing the edges chks = [] for edge in edges: epnts = np.array([[pnt.longitude, pnt.latitude, pnt.depth] for pnt in edge.points[0:2]]) ex, ey = p(epnts[:, 0], epnts[:, 1]) ex = ex / 1e3 ey = ey / 1e3 # # checking edge direction Vs plane perpendicular edgv = np.array([np.diff(ex[0:2])[0], np.diff(ey[0:2])[0]]) chks.append(np.sign(np.cross(ppar[:2], edgv))) # # return np.array(chks)
def create_faults(mesh, iedge, thickness, rot_angle, sampling): """ Creates a list of profiles at a given angle from a mesh limiting the fault at the top. The fault is confined within a seismogenic layer with a thickness provided by the user. :param numpy.ndarray mesh: The mesh defining the top of the slab :param int iedge: ID of the edge to be used for the construction of the plane :param float thickness: The thickness [km] of the layer containing the fault :param float rot_angle: Rotation angle of the new fault (reference is the dip direction of the plane interpolation the slab surface) :param float sampling: The sampling distance used to create the profiles of the virtual faults [km] :returns: A list of :class:`openquake.hazardlib.geo.line.Line` instances """ # # save mesh original shape shape = mesh[:, :, 0].shape # # get indexes of the edge idxs = np.nonzero(np.isfinite(mesh[iedge, :, 2])) # # create a 3xn array with the points composing the mesh lld = np.array([ mesh[:, :, 0].flatten('C'), mesh[:, :, 1].flatten('C'), mesh[:, :, 2].flatten('C') ]).T idx = np.isnan(lld[:, 0]) assert np.nanmax(mesh[:, :, 2]) < 750. # # project the points using Lambert Conic Conformal - for the reference # meridian 'lon_0' we use the mean longitude of the mesh melo = _get_mean_longitude(lld[:, 0]) p = Proj(proj='lcc', lon_0=melo, lat_1=0., lat_2=60.) x, y = p(lld[:, 0], lld[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km x[idx] = np.nan y[idx] = np.nan # # create a np.array with the same shape of the input 'mesh' but with # projected coordinates tmpx = np.reshape(x, shape, order='C') tmpy = np.reshape(y, shape, order='C') meshp = np.stack((tmpx, tmpy, mesh[:, :, 2]), axis=2) assert np.nanmax(meshp[:, :, 2]) < 750. # # check if the selected edge is continuous, otherwise split if np.all(np.diff(idxs) == 1): ilists = [list(idxs[0])] else: ilists = [] cnt = 0 tmp = [] for i, idx in enumerate(list(idxs[0])): if i == 0: tmp.append(idx) else: if idx - tmp[-1] == 1: tmp.append(idx) else: ilists.append(tmp) tmp = [idx] cnt += 1 if len(tmp) > 1: ilists.append(tmp) # # Remove single element lists for i, t in enumerate(ilists): if len(t) < 2: del ilists[i] # # plane fitting tmp = np.vstack( (meshp[:, :, 0].flatten(), meshp[:, :, 1].flatten(), meshp[:, :, 2].flatten())).T idx = np.isfinite(tmp[:, 2]) _, pppar = plane_fit(tmp[idx, :]) # # process the edge dlt = 1 rlow = iedge - dlt rupp = iedge + dlt + 1 plist = [] clist = [] # # loop over 'segments' composing the edge for ilist in ilists: temp_plist = [] check = False # # loop over the indexes of the nodes composing the edge 'segment' and # for each point we create a new profile using the dip angle for i, ic in enumerate(ilist): loccheck = False # # initialise the indexes clow = ic - dlt cupp = ic + dlt + 1 # # fixing indexes at the borders of the mesh if rlow < 0: rlow = 0 rupp = rlow + dlt * 2 + 1 if clow < 0: clow = 0 cupp = clow + dlt * 2 + 1 if rupp >= meshp.shape[0]: rupp = meshp.shape[0] - 1 rlow = max(0, rupp - (dlt * 2 + 1)) if cupp >= meshp.shape[1]: cupp = meshp.shape[1] - 1 clow = cupp - (dlt * 2 + 1) # # coordinates subset tmp = np.vstack((meshp[rlow:rupp, clow:cupp, 0].flatten(), meshp[rlow:rupp, clow:cupp, 1].flatten(), meshp[rlow:rupp, clow:cupp, 2].flatten())).T # # interpolate the plane ii = np.isfinite(tmp[:, 2]) if np.sum(ii) > 4: try: _, ppar = plane_fit(tmp[ii, :]) except ValueError: print('Plane interpolation failed') else: ppar = pppar # # vertical plane with the same strike vertical_plane = np.array([ppar[0], ppar[1], 0]) vertical_plane = vertical_plane / (sum(vertical_plane**2))**.5 # # strike direction stk = np.cross(ppar, vertical_plane) stk = stk / (sum(stk**2.))**0.5 # # compute the vector on the plane defining the steepest direction # https://goo.gl/UtKJxe dip = np.cross(ppar, np.cross([0, 0, -1], ppar)) dip = dip / (sum(dip**2.))**0.5 # # rotate the dip of the angle provided by the user. Note that the # rotation follows the right hand rule. The rotation axis is the # strike dirc = _rotate_vector(dip, stk, rot_angle) # # compute the points composing the new surface. The new surface # is at a distance 'slab_tickness' below the original surface in a # direction perpendicular to the fitted planes corr = -1 dstances = np.arange(0, thickness - 0.05 * sampling, sampling) xls = meshp[iedge, ic, 0] + corr * dstances * dirc[0] yls = meshp[iedge, ic, 1] + corr * dstances * dirc[1] zls = meshp[iedge, ic, 2] + corr * dstances * dirc[2] # CHECK THIS - This extends the last point of an addictional # fraction of distance xls[-1] += corr * sampling * 0.1 * dirc[0] yls[-1] += corr * sampling * 0.1 * dirc[1] zls[-1] += corr * sampling * 0.1 * dirc[2] # # back-conversion to geographic coordinates llo, lla = p(xls * 1e3, yls * 1e3, inverse=True) # # Check if this profile intersects with the previous one and fix # the problem if i > 0: # last profile stored pa = temp_plist[-1][0] pb = temp_plist[-1][-1] # new profile pc = Point(llo[0], lla[0], zls[0]) pd = Point(llo[-1], lla[-1], zls[-1]) out = intersect(pa, pb, pc, pd) # passes if profiles intersect if out: check = True loccheck = True """ TODO 2018.07.05 - This is experimental code trying to fix intersection of profiles. The current solution simply removes the profiles causing problems. # # compute the direction cosines of the segment connecting # the top vertexes of two consecutive profiles x1, y1 = p(pa.longitude, pa.latitude) x1 /= 1e3 y1 /= 1e3 z1 = pa.depth x2, y2 = p(mesh[iedge, ic+1, 0], mesh[iedge, ic+1, 1]) z2 = mesh[iedge, ic+1, 2] # x2, y2 = p(pc.longitude, pc.latitude) # z2 = pc.depth x2 /= 1e3 y2 /= 1e3 topdirc, _ = get_dcos(np.array([x1, y1, z1]), np.array([x2, y2, z2])) # get the direction cosines between the first point of the # new segment and the last point of the previous segment # slightly shifted x1, y1 = p(pc.longitude, pc.latitude) x1 /= 1e3 y1 /= 1e3 z1 = pc.depth # x2, y2 = p(pb.longitude, pb.latitude) x2 /= 1e3 y2 /= 1e3 fctor = 5.5 x2 += topdirc[0] * fctor * sampling y2 += topdirc[1] * fctor * sampling z2 = pb.depth + topdirc[2] * fctor * sampling tdirc, dst = get_dcos(np.array([x1, y1, z1]), np.array([x2, y2, z2])) # # new sampling distance # news = dst/len(dstances) # dstances = np.arange(0, dst+0.05*dst, news) dstances = np.arange(0, thickness+0.05*sampling, sampling) xls = meshp[iedge, ic, 0] + dstances * tdirc[0] yls = meshp[iedge, ic, 1] + dstances * tdirc[1] zls = meshp[iedge, ic, 2] + dstances * tdirc[2] # # back-conversion to geographic coordinates llo, lla = p(xls*1e3, yls*1e3, inverse=True) # raise ValueError('Intersecting profiles') """ # # Update the profile list assert not np.any(np.isnan(llo)) assert not np.any(np.isnan(lla)) assert not np.any(np.isnan(zls)) line = Line([Point(x, y, z) for x, y, z in zip(llo, lla, zls)]) if not loccheck: temp_plist.append(line) # # Check if some of the new profiles intersect # if check: # plot_profiles([temp_plist], mesh) check_intersection(temp_plist) # # updating the list of profiles if len(temp_plist) > 1: plist.append(temp_plist) if check: clist.append(temp_plist) # if len(clist): # plot_profiles(clist, mesh) # # Return the list of profiles groups. Each group is a set of lines return plist
def create_lower_surface_mesh(mesh, slab_thickness): """ This method used to build the bottom surface of the slab computes at each point the plane fitting a local portion of the top-surface and uses the perpendicular to find the corresponding node for the bottom surface. :parameter mesh: An instance of the :class:`openquake.hazardlib.geo.mesh.Mesh` that describes the top of the slab within which we place inslab seismicity :parameter slab_thickness: Thickness of the slab [km] :returns: An instance of :class:`openquake.hazardlib.geo.mesh.Mesh` """ # # save original shape of the 2.5D mesh oshape = mesh.lons.shape # # project the points using Lambert Conic Conformal - for the reference # meridian 'lon_0' we use the mean longitude of the grid p = Proj(proj='lcc', lon_0=np.mean(mesh.lons.flatten('F')), lat_2=45) x, y = p(mesh.lons.flatten('F'), mesh.lats.flatten('F')) x = x / 1e3 # m -> km y = y / 1e3 # m -> k # # reshaping x = np.reshape(x, oshape, order='F') y = np.reshape(y, oshape, order='F') # # initialize the lower mesh lowm = deepcopy(mesh) # # dlt = 1 for ir in range(0, x.shape[0]): for ic in range(0, x.shape[1]): # # initialise the indexes rlow = ir - dlt rupp = ir + dlt + 1 clow = ic - dlt cupp = ic + dlt + 1 # # fixing indexes at the borders of the mesh if rlow < 0: rlow = 0 rupp = rlow + dlt * 2 + 1 if clow < 0: clow = 0 cupp = clow + dlt * 2 + 1 if rupp >= x.shape[0]: rupp = x.shape[0] - 1 rlow = rupp - (dlt * 2 + 1) if cupp >= x.shape[1]: cupp = x.shape[1] - 1 clow = cupp - (dlt * 2 + 1) # # get the subset of nodes and compute equation of the interpolating # plane xx = np.vstack((x[rlow:rupp, clow:cupp].flatten(), y[rlow:rupp, clow:cupp].flatten(), mesh.depths[rlow:rupp, clow:cupp].flatten())).T ii = np.isfinite(xx[:, 2]) if np.sum(ii) > 4: try: pnt, ppar = plane_fit(xx[ii, :]) except: raise ValueError('Plane interpolation failed') # # compute the points composing the new surface. The new surface # is at a distance 'slab_tickness' below the original surface in a # direction perpendicular to the fitted planes corr = 1 if np.sign(ppar[2]) == -1: corr = -1 xls = x[ir, ic] + corr * slab_thickness * ppar[0] yls = y[ir, ic] + corr * slab_thickness * ppar[1] zls = mesh.depths[ir, ic] + corr * slab_thickness * ppar[2] # # back-conversion to geographic coordinates llo, lla = p(xls * 1e3, yls * 1e3, inverse=True) # # updating the mesh lowm.lons[ir, ic] = llo lowm.lats[ir, ic] = lla lowm.depths[ir, ic] = zls # # return lowm
def regularize(mesh, spacing): """ TODO 2018.05.18 - This is currenly not used. Consider removing. Fitting https://gist.github.com/amroamroamro/1db8d69b4b65e8bc66a6 """ # # dlt_x = 10 dlt_z = 10 # # create a 3xn array with the points composing the mesh lld = np.array([ mesh.lons.flatten('F'), mesh.lats.flatten('F'), mesh.depths.flatten('F') ]).T # # project the points using Lambert Conic Conformal - for the reference # meridian 'lon_0' we use the mean longitude of the mesh p = Proj(proj='lcc', lon_0=np.mean(lld[:, 0]), lat_2=45) x, y = p(lld[:, 0], lld[:, 1]) x = x / 1e3 # m -> km y = y / 1e3 # m -> km # # compute the distances between all the points composing the mesh and # a reference point indicated with the index 'idx' idx = 0 dx = np.sign(lld[idx, 0] - lld[:, 0]) * ((lld[idx, 0] - lld[:, 0])**2 + (lld[idx, 1] - lld[:, 1])**2)**.5 dz = (lld[idx, 2] - lld[:, 2]) # # find nodes within the delta X and delta Z distances idx = np.nonzero((dx <= dlt_x) & (dz <= dlt_z)) # # compute the equation of the plane fitting the portion of the slab surface xx = np.vstack((x[idx].T, y[idx].T, lld[idx, 2])).T pnt, ppar = plane_fit(xx) # # vertical plane vertical_plane = [ppar[0], ppar[1], 0] vertical_plane = vertical_plane / (sum(vertical_plane)**2)**.5 # # strike direction stk = np.cross(ppar, vertical_plane) stk = stk / sum(stk**2.)**0.5 # # project the top left point on the plane surface. First we compute # the distance from the point to the plane then we find the coordinates # of the point. TODO t = -np.sum(ppar * lld[0, :]) / np.sum(ppar**2) orig = np.array( [ppar[0] * t + x[0], ppar[1] * t + y[0], ppar[2] * t + lld[0, 2]]) # # compute the vector on the plane defining the steepest direction # https://www.physicsforums.com/threads/projecting-a-vector-onto-a-plane.496184/ dip = np.cross(ppar, np.cross([0, 0, -1], ppar)) dip = dip / sum(dip**2.)**0.5 # # create the rectangle in 3D rects = [] pnt0 = [x[0], y[0], lld[0, 2]] pnt1 = [ x[0] + stk[0] * dlt_x, y[0] + stk[1] * dlt_x, lld[0, 2] + stk[2] * dlt_x ] pnt2 = [ pnt1[0] + dip[0] * dlt_z, pnt1[1] + dip[1] * dlt_z, pnt1[2] + dip[2] * dlt_z ] pnt3 = [ x[0] + dip[0] * dlt_z, y[0] + dip[1] * dlt_z, lld[0, 2] + dip[2] * dlt_z ] rects.append([pnt0, pnt1, pnt2, pnt3]) rects = np.array(rects) # lo, la = p(, lld[:,1]) return rects