def _regrid(lons, lats, data, olons, olats, shuffle=False, tri=None): """ performs triangulation and linear interpolation. lons,lats: 1d arrays of mesh lat/lon values in radians. data: 1d array of mesh data values. olons, olats: 1d arrays describing 2d output mesh (degrees) shuffle : if True, randomly shuffle mesh points tri : if not None, use existing triangulation. returns 2d data array on regular lat/lon mesh""" if tri is None: if shuffle: # randomly shuffle points (may speed up triangulation) ix = np.arange(len(lons)) np.random.shuffle(ix) lons = lons[ix] lats = lats[ix] data = data[ix] t1 = time.clock() print 'triangulation of', len(lons), ' points' tri = trmesh(lons, lats) if shuffle: tri._shuffle = shuffle tri._ix = ix print 'triangulation took', time.clock() - t1, ' secs' olons = np.radians(olons) olats = np.radians(olats) olons, olats = np.meshgrid(olons, olats) t1 = time.clock() latlon_data = tri.interp_linear(olons, olats, data) print 'interpolation took', time.clock() - t1, ' secs' print 'min/max field:', latlon_data.min(), latlon_data.max() return latlon_data, tri
#mesh_filename = 'x1.2621442.grid.nc' # 15 km mesh mesh_nc = Dataset(mesh_filename) lats = mesh_nc.variables['latCell'][:] print('min/max lats:',lats.min(), lats.max()) lons = mesh_nc.variables['lonCell'][:] print('min/max lons:',lons.min(), lons.max()) # fake test data. def test_func(lon, lat): nexp = 8 return np.cos(nexp*lon)*np.sin(0.5*lon)**nexp*np.cos(lat)**nexp+np.sin(lat)**nexp icos_data = test_func(lons,lats) t1 = time.clock() print('triangulation of', len(lons),' points') tri = trmesh(lons, lats) print('triangulation took',time.clock()-t1,' secs') nlons = 360; nlats = nlons/2 # 1 degree output mesh delta = 360./nlons olons = delta*np.arange(nlons) olats = -90.0 + 0.5*delta + delta*np.arange(nlats) olons = np.radians(olons) olats = np.radians(olats) olons, olats = np.meshgrid(olons, olats) t1 = time.clock() order = 1 # can be 0 (nearest neighbor) or 1 (linear) latlon_data = tri.interp(olons,olats,icos_data,order=order) print('interpolation took',time.clock()-t1,' secs')
0.5 * lon)**nexp * np.cos(lat)**nexp + np.sin(lat)**nexp npts = 100000 lats, lons = fibonacci_pts(npts) shuffle = True # shuffling the points speeds up the triangulation if shuffle: ix = np.arange(npts) np.random.shuffle(ix) lats = lats[ix] lons = lons[ix] icos_data = test_func(lons, lats) t1 = time.clock() print('triangulation of', len(lons), ' points') tri = trmesh(lons, lats) print('triangulation took', time.clock() - t1, ' secs') nlons = 1440 nlats = nlons / 2 + 1 # 1 degree output mesh delta = 360. / nlons olons = delta * np.arange(nlons) olats = -90.0 + delta * np.arange(nlats) olons = np.radians(olons) olats = np.radians(olats) olons, olats = np.meshgrid(olons, olats) t1 = time.clock() order = 1 # can be 0 (nearest neighbor) or 1 (linear) latlon_data = tri.interp(olons, olats, icos_data, order=order) print('interpolation took', time.clock() - t1, ' secs')
def add_dem_3D(self, x, y, dem, z0=0., z1=np.infty, zref=None, khorizontal=3, s=None, mode='cartesian'): ''' Add topography by vertically stretching the domain in the region [z0, z1] - points below z0 and above z1 are kept fixed, points in between are linearly interpolated. Usage: first call add_dem_3D for each boundary that is to be perturbed and finally call apply_dem to add the perturbation to the mesh coordinates. The DEM can be either structured or unstructured, this is determined from the shapes of x and y. See scipy.interpolate.RectBivariateSpline for structured Spline interpolation and scipy.interpolate.LinearNDInterpolator (khorizontal=1) or scipy.interpolate.CloughTocher2DInterpolator (khorizontal=3) for more information on the unstructured interpolation. :param x: x coordinates of the DEM (colatitude in spherical modes in radians) :type x: numpy array, either 1D for structured data or 2D for unstructured data :param y: y coordinates of the DEM (longitude in spherical modes in radians) :type y: numpy array, either 1D for structured data or 2D for unstructured data :param dem: the DEM :type dem: numpy array :param z0: vertical coordinate, at which the stretching begins :type z0: float :param z1: vertical coordinate, at which the stretching ends, can be np.infty to just move all points above zref with the DEM :type z1: float :param zref: vertical coordinate, at which the stretching ends :type zref: float :param khorizontal: horizontal degree of the spline interpolation. For unstructured data either 1 or 3. :type khorizontal: integer :param s: smoothing factor :type s: float ''' if not self.ndim == 3: # pragma: no cover raise ValueError('apply_dem_3D works on 3D meshes only') if mode not in ['cartesian', 'spherical', 'spherical_full']: # pragma: no cover # NoQa raise ValueError("mode should be in ['cartesian', 'spherical', " "'spherical_full']") structured = ((x.size, y.size) == dem.shape) # setup callable interpolation function if mode in ['cartesian', 'spherical']: # RectBivariateSpline is much faster for regular grids, so we use # it here. if structured: sbs = RectBivariateSpline(x, y, dem, kx=khorizontal, ky=khorizontal, s=s).ev else: if khorizontal == 1: sbs = LinearNDInterpolator(np.c_[x.flatten(), y.flatten()], dem.flatten(), fill_value=0.) elif khorizontal == 3: sbs = CloughTocher2DInterpolator( np.c_[x.flatten(), y.flatten()], dem.flatten(), fill_value=0.) else: # pragma: no cover raise ValueError('For unstructured data, only linear and ' 'cubic interpolation is supported.') elif mode == 'spherical_full': # full sphere interpolation functions need to be used in this case if structured: theta = x.copy() eps = 1e-10 theta[theta < eps] = eps theta[theta > np.pi - eps] = np.pi - eps phi = y.copy() phi[phi < eps] = eps phi[phi > 2 * np.pi - eps] = 2 * np.pi - eps sbs = RectSphereBivariateSpline(theta, phi, dem, s=s).ev else: # a variant using SmoothSphereBivariateSpline # Disadvantage: smoothing seems to be really sensitive to the # absolute values and for s=0 some error appears, so trying # some default that seems to work: # if s is None: # s = dem.max() * 1e-4 # sbs = SmoothSphereBivariateSpline(x, y, dem, s=s).ev # a variant using LSQSphereBivariateSpline # Disadvantage: a regular grid has to be chosen manually, not # allways stable # eps = 1e-10 # sbs = LSQSphereBivariateSpline( # x, y, dem, np.linspace(eps, np.pi - eps, 30), # np.linspace(eps, 2 * np.pi - eps, 60)).ev # a variant using stripack # Disadvantage: external dependency. Fails for regular grid # data and order 3. Some problems for regular grid data and # order 1 close to the axis. from stripack import trmesh # shuffle data to ensure it also works with structured data and # speed up the triangulation ix = np.arange(len(x)) np.random.shuffle(ix) tri = trmesh(y[ix], np.pi / 2. - x[ix]) # adapt the interface to the other interpolation functions def sbs(theta, phi, eps=1e-10): lat = np.pi / 2. - theta # can't query with an empty array if len(theta) > 0: return tri.interp(phi, lat, dem[ix], order=khorizontal) # add to topography if mode == 'cartesian': if self.topography is None: self.topography = np.zeros_like(self.points[:, -1]) if zref is None: zref = self.points[:, 2].max() # manually vectorized linear interpolation sl1 = np.logical_and(self.points[:, 2] > zref, self.points[:, 2] <= z1) sl2 = np.logical_and(self.points[:, 2] <= zref, self.points[:, 2] > z0) dz1 = sbs(self.points[sl1, 0], self.points[sl1, 1]) if z1 < np.infty: z2 = self.points[sl1, 2] self.topography[sl1] += dz1 * ((z1 - z2) / (z1 - zref)) else: self.topography[sl1] += dz1 dz2 = sbs(self.points[sl2, 0], self.points[sl2, 1]) z2 = self.points[sl2, 2] self.topography[sl2] += dz2 * ((z2 - z0) / (zref - z0)) elif mode in ['spherical', 'spherical_full']: if self.topography is None: self.topography = np.zeros_like(self.points) dr = np.zeros_like(self.points[:, -1]) r = np.sqrt((self.points ** 2).sum(axis=1)) th = np.zeros_like(r) sl = r > 0 th[sl] = np.arccos(self.points[sl, 2] / r[sl]) ph = np.arctan2(self.points[:, 1], self.points[:, 0]) ph[ph < 0] += 2 * np.pi if zref is None: # pragma: no cover zref = r.max() # manually vectorized linear interpolation sl1 = np.logical_and(r > zref, r <= z1) sl2 = np.logical_and(r <= zref, r > z0) dz1 = sbs(th[sl1], ph[sl1]) if z1 < np.infty: z2 = r[sl1] dr[sl1] = dz1 * ((z1 - z2) / (z1 - zref)) else: dr[sl1] = dz1 dz2 = sbs(th[sl2], ph[sl2]) z2 = r[sl2] dr[sl2] = dz2 * ((z2 - z0) / (zref - z0)) sl = np.logical_or(sl1, sl2) self.topography[sl, 0] += dr[sl] * np.sin(th[sl]) * np.cos(ph[sl]) self.topography[sl, 1] += dr[sl] * np.sin(th[sl]) * np.sin(ph[sl]) self.topography[sl, 2] += dr[sl] * np.cos(th[sl])
def boundary(self, nodes, geodesic=True, gap=0.0): """ Return a list of ordered lists of nodes on the connected parts of the boundary of a subset of nodes and a list of ordered lists of (lat,lon) coordinates of the corresponding polygons * EXPERIMENTAL! * """ # Optional import for this experimental method try: import stripack # @UnresolvedImport # tries to import stripack.so which must have been compiled with # f2py -c -m stripack stripack.f90 except ImportError: raise RuntimeError("NOTE: stripack.so not available, boundary() \ won't work.") N = self.N nodes_set = set(nodes) if len(nodes_set) >= N: return [], [], [], [(0.0, 0.0)] # find grid neighbours: if geodesic: if self.cartesian is not None: pos = self.cartesian else: # find cartesian coordinates of nodes, # assuming a perfect unit radius sphere: lat = self.grid.lat_sequence() * np.pi / 180 lon = self.grid.lon_sequence() * np.pi / 180 pos = self.cartesian = np.zeros((N, 3)) coslat = np.cos(lat) self.cartesian[:, 0] = coslat * np.sin(lon) self.cartesian[:, 1] = coslat * np.cos(lon) self.cartesian[:, 2] = np.sin(lat) # find neighbours of each node in Delaunay triangulation, # sorted in counter-clockwise order, using stripack fortran # library: # will contain 1-based node indices list_ = np.zeros(6 * (N - 2)).astype("int32") # will contain 1-based list_ indices lptr = np.zeros(6 * (N - 2)).astype("int32") # will contain 1-based list_ indices lend = np.zeros(N).astype("int32") lnew = 0 near = np.zeros(N).astype("int32") foll = np.zeros(N).astype("int32") dist = np.zeros(N) ier = 0 stripack.trmesh( self.cartesian[:, 0], self.cartesian[:, 1], self.cartesian[:, 2], list_, lptr, lend, lnew, # output vars near, foll, dist, ier) # output var self.grid_neighbours = [None for i in range(N)] self.grid_neighbours_set = [None for i in range(N)] rN = range(N) for i in rN: nbsi = [] ptr0 = ptr = lend[i] - 1 for j in rN: nbsi.append(list_[ptr] - 1) ptr = lptr[ptr] - 1 if ptr == ptr0: break self.grid_neighbours[i] = nbsi self.grid_neighbours_set[i] = set(nbsi) else: raise NotImplementedError("Not yet implemented for \ lat-lon-regular grids!") remaining = nodes_set.copy() boundary = [] shape = [] fullshape = [] representative = [] # find a node on the boundary and an outer neighbour: lam = 0.5 + gap / 2 lam1 = 1 - lam while remaining: i = list(remaining)[0] this_remove = [i] cont = False while self.grid_neighbours_set[i] <= nodes_set: i = self.grid_neighbours[i][int( np.floor(len(self.grid_neighbours[i]) * random.uniform()))] if i not in remaining: # we had this earlier cont = True break this_remove.append(i) remaining -= set(this_remove) # if len(nodes_set)==151: print(i,this_remove,remaining,cont) if cont: continue o = list(self.grid_neighbours_set[i] - nodes_set)[0] # traverse boundary: partial_boundary = [i] partial_shape = [lam * pos[i] + lam1 * pos[o]] partial_fullshape = [0.49 * pos[i] + 0.51 * pos[o]] print(partial_shape) steps = [(i, o)] for it in range(N): # at most this many steps we need nbi = self.grid_neighbours[i] j = nbi[0] try: j = nbi[(nbi.index(o) - 1) % len(nbi)] except IndexError: print("O!", i, o, j, nbi, self.grid_neighbours[o], steps) raise if j in nodes_set: i = j partial_boundary.append(i) try: remaining.remove(i) except KeyError: pass else: partial_fullshape.append(0.32 * pos[i] + 0.34 * pos[o] + 0.34 * pos[j]) o = j partial_shape.append(lam * pos[i] + lam1 * pos[o]) partial_fullshape.append(0.49 * pos[i] + 0.51 * pos[o]) if (i, o) in steps: break steps.append((i, o)) mind2 = np.inf latlon_shape = [] latlon_fullshape = [] length = len(partial_shape) - 1 off = length / 2 for it in range(length): pos1 = partial_shape[it] pos2 = partial_shape[int((it + off) % length)] latlon_shape.append(self.cartesian2latlon(pos1)) d2 = ((pos2 - pos1)**2).sum() if d2 < mind2: rep = self.cartesian2latlon((pos1 + pos2) / 2) mind2 = d2 latlon_shape.append(self.cartesian2latlon(partial_shape[-1])) for it, _ in enumerate(partial_fullshape): pos1 = partial_fullshape[it] latlon_fullshape.append(self.cartesian2latlon(pos1)) boundary.append(partial_boundary) shape.append(latlon_shape) fullshape.append(latlon_fullshape) representative.append(rep) # TODO: sort sub-regions by descending size! return boundary, shape, fullshape, representative