def test_interpolation_grids_are_what_they_should_be(): # load data and initialize interpolator path = path_to_assets + '/bornholm.mat' bathy, lat, lon = load_data_from_file(path) ip = Interpolator2D(bathy, lat, lon) lat_c = 0.5 * (lat[0] + lat[-1]) lon_c = 0.5 * (lon[0] + lon[-1]) assert lat_c, lon_c == center_point(lat, lon)
def test_interpolation_tables_agree_anywhere(): # load data and initialize interpolator path = path_to_assets + '/bornholm.mat' bathy, lat, lon = load_data_from_file(path) ip = Interpolator2D(bathy, lat, lon) # --- at origo --- lat_c, lon_c = center_point(lat, lon) #lat_c = ip.origin.latitude #lon_c = ip.origin.longitude z_ll = ip.interp(lat=lat_c, lon=lon_c) # interpolate using lat-lon z_ll = float(z_ll) z_xy = ip.interp_xy(x=0, y=0) # interpolate using x-y z_xy = float(z_xy) assert z_ll == pytest.approx(z_xy, rel=1e-3) or z_xy == pytest.approx( z_ll, abs=0.1) # --- 0.1 degrees north of origo --- lat = lat_c + 0.1 lon = lon_c x, y = LLtoXY(lat=lat, lon=lon, lat_ref=lat_c, lon_ref=lon_c) z_ll = ip.interp(lat=lat, lon=lon) z_ll = float(z_ll) z_xy = ip.interp_xy(x=x, y=y) z_xy = float(z_xy) assert z_ll == pytest.approx(z_xy, rel=1e-3) or z_xy == pytest.approx( z_ll, abs=0.1) # --- 0.08 degrees south of origo --- lat = lat_c - 0.08 lon = lon_c x, y = LLtoXY(lat=lat, lon=lon, lat_ref=lat_c, lon_ref=lon_c) z_ll = ip.interp(lat=lat, lon=lon) z_ll = float(z_ll) z_xy = ip.interp_xy(x=x, y=y) z_xy = float(z_xy) assert z_ll == pytest.approx(z_xy, rel=1e-3) or z_xy == pytest.approx( z_ll, abs=0.1) # --- at shifted origo --- bathy, lat, lon = load_data_from_file(path) ip = Interpolator2D(bathy, lat, lon, origin=(55.30, 15.10)) lat_c = ip.origin[0] lon_c = ip.origin[1] z_ll = ip.interp(lat=lat_c, lon=lon_c) # interpolate using lat-lon z_ll = float(z_ll) z_xy = ip.interp_xy(x=0, y=0) # interpolate using x-y z_xy = float(z_xy) assert z_ll == pytest.approx(z_xy, rel=1e-3) or z_xy == pytest.approx( z_ll, abs=0.1)
def __init__(self, load_bathymetry=0, load_temp=0, load_salinity=0, load_wavedir=0, load_waveheight=0, load_waveperiod=0, load_wind_uv=0, load_wind_u=0, load_wind_v=0, load_water_uv=0, load_water_u=0, load_water_v=0, fetch=4, **kwargs): for kw in [ k for k in ('south', 'west', 'north', 'east', 'top', 'bottom', 'start', 'end') if k not in kwargs.keys() ]: kwargs[kw] = default_val[kw] data = {} callbacks = [] vartypes = [ 'bathy', 'temp', 'salinity', 'wavedir', 'waveheight', 'waveperiod', 'wind_uv', 'wind_u', 'wind_v', 'water_uv', 'water_u', 'water_v', ] load_args = [ load_bathymetry, load_temp, load_salinity, load_wavedir, load_waveheight, load_waveperiod, load_wind_uv, load_wind_u, load_wind_v, load_water_uv, load_water_u, load_water_v, ] # if load_args are not callable, convert it to a callable function for v, load_arg, ix in zip(vartypes, load_args, range(len(vartypes))): if callable(load_arg): callbacks.append(load_arg) elif isinstance(load_arg, str): key = f'{v}_{load_arg.lower()}' assert key in load_map.keys( ), f'no map for {key} in\n{load_map=}' callbacks.append(load_map[key]) if fetch is not False: fetch_handler(v, load_arg.lower(), parallel=fetch, **kwargs) elif isinstance(load_arg, (int, float)): data[f'{v}_val'] = load_arg data[f'{v}_lat'] = kwargs['south'] data[f'{v}_lon'] = kwargs['west'] data[f'{v}_time'] = dt_2_epoch(kwargs['start']) if v in var3d: data[f'{v}_depth'] = kwargs['top'] callbacks.append(load_callback) elif isinstance(load_arg, (list, tuple, np.ndarray)): if len(load_arg) not in (3, 4): raise ValueError( f'invalid array shape for load_{v}. ' 'arrays must be ordered by [val, lat, lon] for 2D data, or ' '[val, lat, lon, depth] for 3D data') data[f'{v}_val'] = load_arg[0] data[f'{v}_lat'] = load_arg[1] data[f'{v}_lon'] = load_arg[2] if len(load_arg) == 4: data[f'{v}_depth'] = load_arg[3] callbacks.append(load_callback) else: raise TypeError( f'invalid type for load_{v}. ' 'valid types include string, float, array, and callable') q = Queue() # prepare data pipeline pipe = zip(callbacks, vartypes) is_3D = [v in var3d for v in vartypes] is_arr = [not isinstance(arg, (int, float)) for arg in load_args] columns = [fcn(v=v, data=data, **kwargs) for fcn, v in pipe] intrpmap = [(Uniform2D, Uniform3D), (Interpolator2D, Interpolator3D)] reshapers = [reshape_3D if v else reshape_2D for v in is_3D] # map interpolations to dictionary self.interps = {} interpolators = map(lambda x, y: intrpmap[x][y], is_arr, is_3D) interpolations = map(lambda i, r, c, v, q=q: Process( target=worker, args=(i, r, c, v, q)), interpolators, reshapers, columns, vartypes) # assert that no empty arrays were returned by load function for col, var in zip(columns, vartypes): if isinstance(col, dict) or isinstance(col[0], (int, float)): continue assert len(col[0]) > 0, ( f'no data found for {var} in region {fmt_coords(kwargs)}. ' f'consider expanding the region') # compute interpolations in parallel and store in dict attribute if not os.environ.get('LOGLEVEL') == 'DEBUG': for i in interpolations: i.start() while len(self.interps.keys()) < len(vartypes): obj = q.get() self.interps[obj[0]] = obj[1] for i in interpolations: i.join() # debug mode: disable parallelization for nicer stack traces elif os.environ.get('LOGLEVEL') == 'DEBUG': logging.debug('OCEAN DEBUG MSG: parallelization disabled') for i, r, c, v in zip(interpolators, reshapers, columns, vartypes): logging.debug(f'interpolating {v}') logging.debug(f'{i = }\n{r = }\n{c = }\n{v = }') obj = i(**r(c)) q.put((v, obj)) while len(self.interps.keys()) < len(vartypes): obj = q.get() self.interps[obj[0]] = obj[1] logging.debug( f'done {obj[0]}... {len(self.interps.keys())}/{len(vartypes)}' ) q.close() # set ocean boundaries and interpolator origins self.boundaries = kwargs.copy() self.origin = center_point(lat=[kwargs['south'], kwargs['north']], lon=[kwargs['west'], kwargs['east']]) for v in vartypes: self.interps[v].origin = self.origin return
def __init__(self, values, lats, lons, depths, origin=None, method='linear', method_irreg='regularize', bins_irreg_max=200): # compute coordinates of origin, if not provided if origin is None: origin = center_point(lats, lons) self.origin = origin # check if bathymetry data are on a regular or irregular grid reggrid = (np.ndim(values) == 3) # convert to radians lats_rad, lons_rad = torad(lats, lons) # necessary to resolve a mismatch between scipy and underlying Fortran code # https://github.com/scipy/scipy/issues/6556 if np.min(lons_rad) < 0: self._lon_corr = np.pi else: self._lon_corr = 0 lons_rad += self._lon_corr # initialize lat-lon interpolator if reggrid: self.interp_ll = RegularGridInterpolator( (lats_rad, lons_rad, depths), values, method=method, bounds_error=False, fill_value=None) else: if method_irreg == 'regularize': # interpolators on irregular grid gd = GridData3D(u=lats_rad, v=lons_rad, w=depths, r=values, method='linear') gd_near = GridData3D(u=lats_rad, v=lons_rad, w=depths, r=values, method='nearest') # determine bin size for regular grid lat_diffs = np.diff(np.sort(np.unique(lats))) lat_diffs = lat_diffs[lat_diffs > 1e-4] lon_diffs = np.diff(np.sort(np.unique(lons))) lon_diffs = lon_diffs[lon_diffs > 1e-4] depth_diffs = np.diff(np.sort(np.unique(depths))) depth_diffs = depth_diffs[depth_diffs > 0.1] bin_size = (np.min(lat_diffs), np.min(lon_diffs), np.min(depth_diffs)) # regular grid that data will be mapped to lats_reg, lons_reg, depths_reg = self._create_grid( lats=lats, lons=lons, depths=depths, bin_size=bin_size, max_bins=bins_irreg_max) # map to regular grid lats_reg_rad, lons_reg_rad = torad(lats_reg, lons_reg) lons_reg_rad += self._lon_corr vi = gd(theta=lats_reg_rad, phi=lons_reg_rad, z=depths_reg, grid=True) vi_near = gd_near(theta=lats_reg_rad, phi=lons_reg_rad, z=depths_reg, grid=True) indices_nan = np.where(np.isnan(vi)) vi[indices_nan] = vi_near[indices_nan] # interpolator on regular grid self.interp_ll = RegularGridInterpolator( (lats_reg_rad, lons_reg_rad, depths_reg), vi, method=method, bounds_error=False, fill_value=None) else: self.interp_ll = GridData3D(u=lats_rad, v=lons_rad, w=depths, r=values, method=method_irreg) # store grids self.lat_nodes = lats self.lon_nodes = lons self.depth_nodes = depths self.values = values
def __init__(self, values, lats, lons, origin=None, method_irreg='regularize', bins_irreg_max=2000): # compute coordinates of origin, if not provided if origin is None: origin = center_point(lats, lons) self.origin = origin # check if bathymetry data are on a regular or irregular grid reggrid = (np.ndim(values) == 2) # convert to radians lats_rad, lons_rad = torad(lats, lons) # necessary to resolve a mismatch between scipy and underlying Fortran code # https://github.com/scipy/scipy/issues/6556 if np.min(lons_rad) < 0: self._lon_corr = np.pi else: self._lon_corr = 0 lons_rad += self._lon_corr # initialize lat-lon interpolator if reggrid: # regular grid if len(lats) > 2 and len(lons) > 2: self.interp_ll = RectSphereBivariateSpline(u=lats_rad, v=lons_rad, r=values) elif len(lats) > 1 and len(lons) > 1: z = np.swapaxes(values, 0, 1) self.interp_ll = interp2d(x=lats_rad, y=lons_rad, z=z, kind='linear') elif len(lats) == 1: self.interp_ll = interp1d(x=lons_rad, y=np.squeeze(values), kind='linear') elif len(lons) == 1: self.interp_ll = interp1d(x=lats_rad, y=np.squeeze(values), kind='linear') else: # irregular grid if len(np.unique(lats)) <= 1 or len(np.unique(lons)) <= 1: self.interp_ll = GridData2D(u=lats_rad, v=lons_rad, r=values, method='nearest') elif method_irreg == 'regularize': # initialize interpolators on irregular grid if len(np.unique(lats)) >= 2 and len(np.unique(lons)) >= 2: method = 'linear' else: method = 'nearest' gd = GridData2D(u=lats_rad, v=lons_rad, r=values, method=method) gd_near = GridData2D(u=lats_rad, v=lons_rad, r=values, method='nearest') # determine bin size for regular grid lat_diffs = np.diff(np.sort(np.unique(lats))) lat_diffs = lat_diffs[lat_diffs > 1e-4] lon_diffs = np.diff(np.sort(np.unique(lons))) lon_diffs = lon_diffs[lon_diffs > 1e-4] bin_size = (np.min(lat_diffs), np.min(lon_diffs)) # regular grid that data will be mapped to lats_reg, lons_reg = self._create_grid(lats=lats, lons=lons, bin_size=bin_size, max_bins=bins_irreg_max) # map to regular grid lats_reg_rad, lons_reg_rad = torad(lats_reg, lons_reg) lons_reg_rad += self._lon_corr vi = gd(theta=lats_reg_rad, phi=lons_reg_rad, grid=True) vi_near = gd_near(theta=lats_reg_rad, phi=lons_reg_rad, grid=True) indices_nan = np.where(np.isnan(vi)) vi[indices_nan] = vi_near[indices_nan] # initialize interpolator on regular grid self.interp_ll = RectSphereBivariateSpline(u=lats_reg_rad, v=lons_reg_rad, r=vi) else: self.interp_ll = GridData2D(u=lats_rad, v=lons_rad, r=values, method=method_irreg) # store data used for interpolation self.lat_nodes = lats self.lon_nodes = lons self.values = values