def determine_grid_type(dataset_url): try: UGrid.from_ncfile(dataset_url) grid_type = 'ugrid' except ValueError: try: from_ncfile(dataset_url) grid_type = 'sgrid' except SGridNonCompliantError: grid_type = None return grid_type
def plot(self, variable, show=False, index=None): if index is None: self.animation( variable, # show=True, save='/home/jreniel/pyschism/examples/example_1/test.gif', vmin=0, vmax=3, # start_frame=200, # end_frame=300, ) else: var = self.nc[variable] ugrid = UGrid.from_nc_dataset(self.nc) x = ugrid.nodes[:, 0] y = ugrid.nodes[:, 1] triangulation = Triangulation(x, y, ugrid.faces[:, :3]) triangulation.set_mask(self.nc['wetdry_elem'][index]) plt.tricontourf(triangulation, var[index, :], levels=256, cmap='jet') plt.gca().axis('scaled') if show: plt.show()
def wgs84_bounds(self, layer): with netCDF4.Dataset(self.topology_file) as nc: try: data_location = nc.variables['u'].location mesh_name = nc.variables['u'].mesh # Use local topology for pulling bounds data ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates minx = np.nanmin(coords[:, 0]) miny = np.nanmin(coords[:, 1]) maxx = np.nanmax(coords[:, 0]) maxy = np.nanmax(coords[:, 1]) return DotDict(minx=minx, miny=miny, maxx=maxx, maxy=maxy, bbox=(minx, miny, maxx, maxy)) except AttributeError: pass
def test_full_set(): grid = UGrid(nodes=nodes, faces=faces, edges=edges, boundaries=boundaries, ) # Check the dtype of key objects. # Implicitly makes sure they are numpy arrays (or array-like). assert grid.num_vertices == 3 assert grid.nodes.dtype == NODE_DT assert grid.faces.dtype == IND_DT assert grid.edges.dtype == IND_DT assert grid.boundaries.dtype == IND_DT # Check shape of grid arrays. assert len(grid.nodes.shape) == 2 assert len(grid.faces.shape) == 2 assert len(grid.edges.shape) == 2 assert len(grid.boundaries.shape) == 2 assert grid.nodes.shape[1] == 2 assert grid.faces.shape[1] == 3 assert grid.edges.shape[1] == 2 assert grid.boundaries.shape[1] == 2
def add_mesh(cube, url): """ Adds the unstructured mesh info the to cube. Soon in an iris near you! """ from pyugrid import UGrid ug = UGrid.from_ncfile(url) cube.mesh = ug cube.mesh_dimension = 1 return cube
def make_rtree(self): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) def rtree_faces_generator_function(): for face_idx, node_list in enumerate(ug.faces): nodes = ug.nodes[node_list] xmin, ymin = np.min(nodes, 0) xmax, ymax = np.max(nodes, 0) yield (face_idx, (xmin, ymin, xmax, ymax), face_idx) logger.info("Building Faces Rtree Topology Cache for {0}".format(self.name)) start = time.time() _, face_temp_file = tempfile.mkstemp(suffix='.face') pf = index.Property() pf.filename = str(face_temp_file) pf.overwrite = True pf.storage = index.RT_Disk pf.dimension = 2 idx = index.Index(pf.filename, rtree_faces_generator_function(), properties=pf, interleaved=True, overwrite=True) idx.close() logger.info("Built Faces Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(face_temp_file), self.face_tree_data_file) shutil.move('{}.idx'.format(face_temp_file), self.face_tree_index_file) def rtree_nodes_generator_function(): for node_index, (x, y) in enumerate(ug.nodes): yield (node_index, (x, y, x, y), node_index) logger.info("Building Nodes Rtree Topology Cache for {0}".format(self.name)) start = time.time() _, node_temp_file = tempfile.mkstemp(suffix='.node') pn = index.Property() pn.filename = str(node_temp_file) pn.overwrite = True pn.storage = index.RT_Disk pn.dimension = 2 idx = index.Index(pn.filename, rtree_nodes_generator_function(), properties=pn, interleaved=True, overwrite=True) idx.close() logger.info("Built Nodes Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(node_temp_file), self.node_tree_data_file) shutil.move('{}.idx'.format(node_temp_file), self.node_tree_index_file)
def make_rtree(self): p = rtree.index.Property() p.overwrite = True p.storage = rtree.index.RT_Disk p.Dimension = 2 with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) class FastRtree(rtree.Rtree): def dumps(self, obj): try: import cPickle return cPickle.dumps(obj, -1) except ImportError: super(FastRtree, self).dumps(obj) def rtree_faces_generator_function(): for face_idx, node_list in enumerate(ug.faces): nodes = ug.nodes[node_list] xmin, ymin = np.min(nodes, 0) xmax, ymax = np.max(nodes, 0) yield (face_idx, (xmin, ymin, xmax, ymax), face_idx) logger.info("Building Faces Rtree Topology Cache for {0}".format(self.name)) _, face_temp_file = tempfile.mkstemp(suffix='.face') start = time.time() FastRtree(face_temp_file, rtree_faces_generator_function(), properties=p, overwrite=True, interleaved=True) logger.info("Built Faces Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(face_temp_file), self.face_tree_data_file) shutil.move('{}.idx'.format(face_temp_file), self.face_tree_index_file) def rtree_nodes_generator_function(): for node_index, (x, y) in enumerate(ug.nodes): yield (node_index, (x, y, x, y), node_index) logger.info("Building Nodes Rtree Topology Cache for {0}".format(self.name)) _, node_temp_file = tempfile.mkstemp(suffix='.node') start = time.time() FastRtree(node_temp_file, rtree_nodes_generator_function(), properties=p, overwrite=True, interleaved=True) logger.info("Built Nodes Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(node_temp_file), self.node_tree_data_file) shutil.move('{}.idx'.format(node_temp_file), self.node_tree_index_file)
def make_rtree(self): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) def rtree_faces_generator_function(): for face_idx, node_list in enumerate(ug.faces): nodes = ug.nodes[node_list] xmin, ymin = np.min(nodes, 0) xmax, ymax = np.max(nodes, 0) yield (face_idx, (xmin, ymin, xmax, ymax), face_idx) logger.info("Building Faces Rtree Topology Cache for {0}".format(self.name)) start = time.time() _, face_temp_file = tempfile.mkstemp(suffix='.face') pf = index.Property() pf.filename = str(face_temp_file) pf.overwrite = True pf.storage = index.RT_Disk pf.dimension = 2 idx = index.Index(pf.filename.decode('utf-8'), rtree_faces_generator_function(), properties=pf, interleaved=True, overwrite=True) idx.close() logger.info("Built Faces Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(face_temp_file), self.face_tree_data_file) shutil.move('{}.idx'.format(face_temp_file), self.face_tree_index_file) def rtree_nodes_generator_function(): for node_index, (x, y) in enumerate(ug.nodes): yield (node_index, (x, y, x, y), node_index) logger.info("Building Nodes Rtree Topology Cache for {0}".format(self.name)) start = time.time() _, node_temp_file = tempfile.mkstemp(suffix='.node') pn = index.Property() pn.filename = str(node_temp_file) pn.overwrite = True pn.storage = index.RT_Disk pn.dimension = 2 idx = index.Index(pn.filename.decode('utf-8'), rtree_nodes_generator_function(), properties=pn, interleaved=True, overwrite=True) idx.close() logger.info("Built Nodes Rtree Topology Cache in {0} seconds.".format(time.time() - start)) shutil.move('{}.dat'.format(node_temp_file), self.node_tree_data_file) shutil.move('{}.idx'.format(node_temp_file), self.node_tree_index_file)
def update_cache(self, force=False): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) ug.save_as_netcdf(self.topology_file) if not os.path.exists(self.topology_file): logger.error( "Failed to create topology_file cache for Dataset '{}'". format(self.dataset)) return time_vars = nc.get_variables_by_attributes(standard_name='time') time_dims = list( itertools.chain.from_iterable( [time_var.dimensions for time_var in time_vars])) unique_time_dims = list(set(time_dims)) with EnhancedDataset(self.topology_file, mode='a') as cached_nc: # create pertinent time dimensions if they aren't already present for unique_time_dim in unique_time_dims: dim_size = len(nc.dimensions[unique_time_dim]) try: cached_nc.createDimension(unique_time_dim, size=dim_size) except RuntimeError: continue # support cases where there may be more than one variable with standard_name='time' in a dataset for time_var in time_vars: try: time_var_obj = cached_nc.createVariable( time_var._name, time_var.dtype, time_var.dimensions) except RuntimeError: time_var_obj = cached_nc.variables[time_var.name] time_var_obj[:] = time_var[:] time_var_obj.units = time_var.units time_var_obj.standard_name = 'time' # Now do the RTree index self.make_rtree() self.cache_last_updated = datetime.utcnow().replace(tzinfo=pytz.utc) self.save()
def update_cache(self, force=False): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) ug.save_as_netcdf(self.topology_file) if not os.path.exists(self.topology_file): logger.error("Failed to create topology_file cache for Dataset '{}'".format(self.dataset)) return time_vars = nc.get_variables_by_attributes(standard_name='time') time_dims = list(itertools.chain.from_iterable([time_var.dimensions for time_var in time_vars])) unique_time_dims = list(set(time_dims)) with EnhancedDataset(self.topology_file, mode='a') as cached_nc: # create pertinent time dimensions if they aren't already present for unique_time_dim in unique_time_dims: dim_size = len(nc.dimensions[unique_time_dim]) try: cached_nc.createDimension(unique_time_dim, size=dim_size) except RuntimeError: continue # support cases where there may be more than one variable with standard_name='time' in a dataset for time_var in time_vars: try: time_var_obj = cached_nc.createVariable(time_var._name, time_var.dtype, time_var.dimensions) except RuntimeError: time_var_obj = cached_nc.variables[time_var.name] time_var_obj[:] = time_var[:] time_var_obj.units = time_var.units time_var_obj.standard_name = 'time' # Now do the RTree index self.make_rtree() self.cache_last_updated = datetime.utcnow().replace(tzinfo=pytz.utc) self.save()
def wgs84_bounds(self, layer): with self.dataset() as nc: try: data_location = nc.variables[layer.access_name].location mesh_name = nc.variables[layer.access_name].mesh # Use local topology for pulling bounds data ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates minx = np.nanmin(coords[:, 1]) miny = np.nanmin(coords[:, 0]) maxx = np.nanmax(coords[:, 1]) maxy = np.nanmax(coords[:, 0]) return DotDict(minx=minx, miny=miny, maxx=maxx, maxy=maxy) except AttributeError: pass
def update_grid_cache(self, force=False): with self.dataset() as nc: if nc is None: logger.error("Failed update_grid_cache, could not load dataset " "as a netCDF4 object") return ug = UGrid.from_nc_dataset(nc=nc) # Atomic write tmphandle, tmpsave = tempfile.mkstemp() try: ug.save_as_netcdf(tmpsave) finally: os.close(tmphandle) if os.path.isfile(tmpsave): shutil.move(tmpsave, self.topology_file) else: logger.error("Failed to create topology_file cache for Dataset '{}'".format(self.dataset.name)) return # Now do the RTree index self.make_rtree()
def getmap(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] # Calculate any vector padding if we need to padding = None vector_step = request.GET['vectorstep'] if request.GET['image_type'] == 'vectors': padding_factor = calc_safety_factor(request.GET['vectorscale']) padding = calc_lon_lat_padding(lon, lat, padding_factor) * vector_step # Calculate the boolean spatial mask to slice with bool_spatial_idx = data_handler.ugrid_lat_lon_subset_idx(lon, lat, bbox=wgs84_bbox.bbox, padding=padding) # Randomize vectors to subset if we need to if request.GET['image_type'] == 'vectors' and vector_step > 1: num_vec = int(bool_spatial_idx.size / vector_step) step = int(bool_spatial_idx.size / num_vec) bool_spatial_idx[np.where(bool_spatial_idx==True)][0::step] = False # noqa: E225 # If no triangles intersect the field of view, return a transparent tile if not np.any(bool_spatial_idx): logger.info("No triangles in field of view, returning empty tile.") return self.empty_response(layer, request) if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, :] elif (len(data_obj.shape) == 2): data = data_obj[time_index, :] elif len(data_obj.shape) == 1: data = data_obj[:] else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] in ['pcolor', 'contours', 'filledcontours']: # Avoid triangles with nan values bool_spatial_idx[np.isnan(data)] = False # Get the faces to plot faces = ug.faces[:] face_idx = data_handler.face_idx_from_node_idx(faces, bool_spatial_idx) faces_subset = faces[face_idx] tri_subset = Tri.Triangulation(lon, lat, triangles=faces_subset) if request.GET['image_type'] == 'pcolor': return mpl_handler.tripcolor_response(tri_subset, data, request, data_location=data_location) else: return mpl_handler.tricontouring_response(tri_subset, data, request) elif request.GET['image_type'] in ['filledhatches', 'hatches']: raise NotImplementedError('matplotlib does not support hatching on triangular grids... sorry!') else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type'])) elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, bool_spatial_idx]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, bool_spatial_idx]) elif len(data_obj.shape) == 1: data.append(data_obj[bool_spatial_idx]) else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'vectors': return mpl_handler.quiver_response(lon[bool_spatial_idx], lat[bool_spatial_idx], data[0], data[1], request) else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type']))
def getmap(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] if request.GET[ 'vectorscale'] is not None: # is not None if vectors are being plotted vectorscale = request.GET['vectorscale'] padding_factor = calc_safety_factor(vectorscale) vectorstep = request.GET[ 'vectorstep'] # returns 1 by default if vectors are being plotted spatial_idx_padding = calc_lon_lat_padding( lon, lat, padding_factor) * vectorstep spatial_idx = data_handler.lat_lon_subset_idx( lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy, padding=spatial_idx_padding) if vectorstep > 1: np.random.shuffle(spatial_idx) nvec = int(len(spatial_idx) / vectorstep) spatial_idx = spatial_idx[:nvec] else: spatial_idx = data_handler.lat_lon_subset_idx( lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy) face_indicies = ug.faces[:] face_indicies_spatial_idx = data_handler.faces_subset_idx( face_indicies, spatial_idx) # If no triangles intersect the field of view, return a transparent tile if (len(spatial_idx) == 0) or (len(face_indicies_spatial_idx) == 0): logger.debug( "No triangles in field of view, returning empty tile.") return self.empty_response(layer, request) if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, :] elif (len(data_obj.shape) == 2): data = data_obj[time_index, :] elif len(data_obj.shape) == 1: data = data_obj[:] else: logger.debug( "Dimension Mismatch: data_obj.shape == {0} and time = {1}" .format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'filledcontours': mask = np.isnan(data) # array with NaNs appearing as True if mask.any(): data_mask = ~mask # negate the NaN boolean array; mask for non-NaN data elements # slice the data, lon, and lat to get elements that correspond to non-NaN values data = data_mask[data_mask] lon = lon[data_mask] lat = lat[data_mask] # recalculate the spatial index using the subsetted lat/lon spatial_idx = data_handler.lat_lon_subset_idx( lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy) face_indicies_spatial_idx = data_handler.faces_subset_idx( face_indicies, spatial_idx) tri_subset = Tri.Triangulation( lon, lat, triangles=face_indicies[face_indicies_spatial_idx]) return mpl_handler.tricontourf_response( tri_subset, data, request) else: raise NotImplementedError( 'Image type "{}" is not supported.'.format( request.GET['image_type'])) elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z( layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, :]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, :]) elif len(data_obj.shape) == 1: data.append(data_obj[:]) else: logger.debug( "Dimension Mismatch: data_obj.shape == {0} and time = {1}" .format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'vectors': return mpl_handler.quiver_response(lon[spatial_idx], lat[spatial_idx], data[0][spatial_idx], data[1][spatial_idx], request, vectorscale) else: raise NotImplementedError( 'Image type "{}" is not supported.'.format( request.GET['image_type']))
def make_rtree(self): p = rtree.index.Property() p.overwrite = True p.storage = rtree.index.RT_Disk p.Dimension = 2 with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc=nc) class FastRtree(rtree.Rtree): def dumps(self, obj): try: import cPickle return cPickle.dumps(obj, -1) except ImportError: super(FastRtree, self).dumps(obj) def rtree_faces_generator_function(): for face_idx, node_list in enumerate(ug.faces): nodes = ug.nodes[node_list] xmin, ymin = np.min(nodes, 0) xmax, ymax = np.max(nodes, 0) yield (face_idx, (xmin, ymin, xmax, ymax), face_idx) logger.info("Building Faces Rtree Topology Cache for {0}".format( self.name)) _, face_temp_file = tempfile.mkstemp(suffix='.face') start = time.time() FastRtree(face_temp_file, rtree_faces_generator_function(), properties=p, overwrite=True, interleaved=True) logger.info( "Built Faces Rtree Topology Cache in {0} seconds.".format( time.time() - start)) shutil.move('{}.dat'.format(face_temp_file), self.face_tree_data_file) shutil.move('{}.idx'.format(face_temp_file), self.face_tree_index_file) def rtree_nodes_generator_function(): for node_index, (x, y) in enumerate(ug.nodes): yield (node_index, (x, y, x, y), node_index) logger.info("Building Nodes Rtree Topology Cache for {0}".format( self.name)) _, node_temp_file = tempfile.mkstemp(suffix='.node') start = time.time() FastRtree(node_temp_file, rtree_nodes_generator_function(), properties=p, overwrite=True, interleaved=True) logger.info( "Built Nodes Rtree Topology Cache in {0} seconds.".format( time.time() - start)) shutil.move('{}.dat'.format(node_temp_file), self.node_tree_data_file) shutil.move('{}.idx'.format(node_temp_file), self.node_tree_index_file)
def getmap(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] # Calculate any vector padding if we need to padding = None vector_step = request.GET['vectorstep'] if request.GET['image_type'] == 'vectors': padding_factor = calc_safety_factor(request.GET['vectorscale']) padding = calc_lon_lat_padding(lon, lat, padding_factor) * vector_step # Calculate the boolean spatial mask to slice with bool_spatial_idx = data_handler.ugrid_lat_lon_subset_idx(lon, lat, bbox=wgs84_bbox.bbox, padding=padding) # Randomize vectors to subset if we need to if request.GET['image_type'] == 'vectors' and vector_step > 1: num_vec = int(bool_spatial_idx.size / vector_step) step = int(bool_spatial_idx.size / num_vec) bool_spatial_idx[np.where(bool_spatial_idx==True)][0::step] = False # If no triangles intersect the field of view, return a transparent tile if not np.any(bool_spatial_idx): logger.warning("No triangles in field of view, returning empty tile.") return self.empty_response(layer, request) if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, :] elif (len(data_obj.shape) == 2): data = data_obj[time_index, :] elif len(data_obj.shape) == 1: data = data_obj[:] else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] in ['pcolor', 'contours', 'filledcontours']: # Avoid triangles with nan values bool_spatial_idx[np.isnan(data)] = False # Get the faces to plot faces = ug.faces[:] face_idx = data_handler.face_idx_from_node_idx(faces, bool_spatial_idx) faces_subset = faces[face_idx] tri_subset = Tri.Triangulation(lon, lat, triangles=faces_subset) if request.GET['image_type'] == 'pcolor': return mpl_handler.tripcolor_response(tri_subset, data, request, data_location=data_location) else: return mpl_handler.tricontouring_response(tri_subset, data, request) elif request.GET['image_type'] in ['filledhatches', 'hatches']: raise NotImplementedError('matplotlib does not support hatching on triangular grids... sorry!') else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type'])) elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, bool_spatial_idx]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, bool_spatial_idx]) elif len(data_obj.shape) == 1: data.append(data_obj[bool_spatial_idx]) else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'vectors': return mpl_handler.quiver_response(lon[bool_spatial_idx], lat[bool_spatial_idx], data[0], data[1], request) else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type']))
def minmax(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] spatial_idx = data_handler.lat_lon_subset_idx( lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy) vmin = None vmax = None data = None if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, spatial_idx] elif (len(data_obj.shape) == 2): data = data_obj[time_index, spatial_idx] elif len(data_obj.shape) == 1: data = data_obj[spatial_idx] else: logger.debug( "Dimension Mismatch: data_obj.shape == {0} and time = {1}" .format(data_obj.shape, time_value)) if data is not None: vmin = np.nanmin(data).item() vmax = np.nanmax(data).item() elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z( layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, spatial_idx]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, spatial_idx]) elif len(data_obj.shape) == 1: data.append(data_obj[spatial_idx]) else: logger.debug( "Dimension Mismatch: data_obj.shape == {0} and time = {1}" .format(data_obj.shape, time_value)) if ',' in layer.var_name and data: # Vectors, so return magnitude data = [ sqrt((u * u) + (v * v)) for ( u, v, ) in data.T if u != np.nan and v != np.nan ] vmin = min(data) vmax = max(data) return gmd_handler.from_dict(dict(min=vmin, max=vmax))
def minmax(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] spatial_idx = data_handler.lat_lon_subset_idx(lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy) vmin = None vmax = None data = None if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, spatial_idx] elif (len(data_obj.shape) == 2): data = data_obj[time_index, spatial_idx] elif len(data_obj.shape) == 1: data = data_obj[spatial_idx] else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) if data is not None: vmin = np.nanmin(data).item() vmax = np.nanmax(data).item() elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, spatial_idx]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, spatial_idx]) elif len(data_obj.shape) == 1: data.append(data_obj[spatial_idx]) else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) if ',' in layer.var_name and data: # Vectors, so return magnitude data = [ sqrt((u*u) + (v*v)) for (u, v,) in data.T if u != np.nan and v != np.nan] vmin = min(data) vmax = max(data) return gmd_handler.from_dict(dict(min=vmin, max=vmax))
def update_cache(self, force=False): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc) ug.save_as_netcdf(self.topology_file) if not os.path.exists(self.topology_file): logger.error( "Failed to create topology_file cache for Dataset '{}'". format(self.dataset)) return uamp = nc.get_variables_by_attributes( standard_name='eastward_sea_water_velocity_amplitude')[0] vamp = nc.get_variables_by_attributes( standard_name='northward_sea_water_velocity_amplitude')[0] uphase = nc.get_variables_by_attributes( standard_name='eastward_sea_water_velocity_phase')[0] vphase = nc.get_variables_by_attributes( standard_name='northward_sea_water_velocity_phase')[0] tnames = nc.get_variables_by_attributes( standard_name='tide_constituent')[0] tfreqs = nc.get_variables_by_attributes( standard_name='tide_frequency')[0] with netCDF4.Dataset(self.topology_file, mode='a') as cnc: ntides = uamp.shape[uamp.dimensions.index('ntides')] nlocs = uamp.shape[uamp.dimensions.index(uamp.location)] cnc.createDimension('ntides', ntides) cnc.createDimension('maxStrlen64', 64) vdims = ('ntides', '{}_num_{}'.format(uamp.mesh, uamp.location)) # Swap ntides to always be the first dimension.. it can be the second in the source files! transpose = False if uamp.shape[0] > uamp.shape[1]: logger.info( "Found flipped dimensions in source file... fixing in local cache." ) transpose = True # We are changing the variable names to 'u' and 'v' from 'u_amp' and 'v_amp' so # the layer.access_method can find the variable from the virtual layer 'u,v' ua = cnc.createVariable('u', uamp.dtype, vdims, zlib=True, fill_value=uamp._FillValue, chunksizes=[1, nlocs / 4]) for x in uamp.ncattrs(): if x != '_FillValue': ua.setncattr(x, uamp.getncattr(x)) va = cnc.createVariable('v', vamp.dtype, vdims, zlib=True, fill_value=vamp._FillValue, chunksizes=[1, nlocs / 4]) for x in vamp.ncattrs(): if x != '_FillValue': va.setncattr(x, vamp.getncattr(x)) up = cnc.createVariable('u_phase', uphase.dtype, vdims, zlib=True, fill_value=uphase._FillValue, chunksizes=[1, nlocs / 4]) for x in uphase.ncattrs(): if x != '_FillValue': up.setncattr(x, uphase.getncattr(x)) vp = cnc.createVariable('v_phase', vphase.dtype, vdims, zlib=True, fill_value=vphase._FillValue, chunksizes=[1, nlocs / 4]) for x in vphase.ncattrs(): if x != '_FillValue': vp.setncattr(x, vphase.getncattr(x)) tc = cnc.createVariable('tidenames', tnames.dtype, tnames.dimensions) tc[:] = tnames[:] for x in tnames.ncattrs(): if x != '_FillValue': tc.setncattr(x, tnames.getncattr(x)) tf = cnc.createVariable('tidefreqs', tfreqs.dtype, ('ntides', )) tf[:] = tfreqs[:] for x in tfreqs.ncattrs(): if x != '_FillValue': tf.setncattr(x, tfreqs.getncattr(x)) for r in range(ntides): logger.info("Saving ntide {} into cache".format(r)) if transpose is True: ua[r, :] = uamp[:, r].T va[r, :] = vamp[:, r].T up[r, :] = uphase[:, r].T vp[r, :] = vphase[:, r].T else: ua[r, :] = uamp[r, :] va[r, :] = vamp[r, :] up[r, :] = uphase[r, :] vp[r, :] = vphase[r, :] # Now do the RTree index self.make_rtree() self.cache_last_updated = datetime.utcnow().replace(tzinfo=pytz.utc) self.save()
def getmap(self, layer, request): time_index, time_value = self.nearest_time(layer, request.GET['time']) wgs84_bbox = request.GET['wgs84_bbox'] with self.dataset() as nc: data_obj = nc.variables[layer.access_name] data_location = data_obj.location mesh_name = data_obj.mesh ug = UGrid.from_ncfile(self.topology_file, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] if request.GET['vectorscale'] is not None: # is not None if vectors are being plotted vectorscale = request.GET['vectorscale'] padding_factor = calc_safety_factor(vectorscale) vectorstep = request.GET['vectorstep'] # returns 1 by default if vectors are being plotted spatial_idx_padding = calc_lon_lat_padding(lon, lat, padding_factor) * vectorstep spatial_idx = data_handler.lat_lon_subset_idx(lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy, padding=spatial_idx_padding ) if vectorstep > 1: np.random.shuffle(spatial_idx) nvec = int(len(spatial_idx) / vectorstep) spatial_idx = spatial_idx[:nvec] else: spatial_idx = data_handler.lat_lon_subset_idx(lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy ) face_indicies = ug.faces[:] face_indicies_spatial_idx = data_handler.faces_subset_idx(face_indicies, spatial_idx) # If no triangles intersect the field of view, return a transparent tile if (len(spatial_idx) == 0) or (len(face_indicies_spatial_idx) == 0): logger.debug("No triangles in field of view, returning empty tile.") return self.empty_response(layer, request) if isinstance(layer, Layer): if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data = data_obj[time_index, z_index, :] elif (len(data_obj.shape) == 2): data = data_obj[time_index, :] elif len(data_obj.shape) == 1: data = data_obj[:] else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'filledcontours': mask = np.isnan(data) # array with NaNs appearing as True if mask.any(): data_mask = ~mask # negate the NaN boolean array; mask for non-NaN data elements # slice the data, lon, and lat to get elements that correspond to non-NaN values data = data_mask[data_mask] lon = lon[data_mask] lat = lat[data_mask] # recalculate the spatial index using the subsetted lat/lon spatial_idx = data_handler.lat_lon_subset_idx(lon, lat, wgs84_bbox.minx, wgs84_bbox.miny, wgs84_bbox.maxx, wgs84_bbox.maxy ) face_indicies_spatial_idx = data_handler.faces_subset_idx(face_indicies, spatial_idx) tri_subset = Tri.Triangulation(lon, lat, triangles=face_indicies[face_indicies_spatial_idx] ) return mpl_handler.tricontourf_response(tri_subset, data, request ) else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type'])) elif isinstance(layer, VirtualLayer): # Data needs to be [var1,var2] where var are 1D (nodes only, elevation and time already handled) data = [] for l in layer.layers: data_obj = nc.variables[l.var_name] if (len(data_obj.shape) == 3): z_index, z_value = self.nearest_z(layer, request.GET['elevation']) data.append(data_obj[time_index, z_index, :]) elif (len(data_obj.shape) == 2): data.append(data_obj[time_index, :]) elif len(data_obj.shape) == 1: data.append(data_obj[:]) else: logger.debug("Dimension Mismatch: data_obj.shape == {0} and time = {1}".format(data_obj.shape, time_value)) return self.empty_response(layer, request) if request.GET['image_type'] == 'vectors': return mpl_handler.quiver_response(lon[spatial_idx], lat[spatial_idx], data[0][spatial_idx], data[1][spatial_idx], request, vectorscale ) else: raise NotImplementedError('Image type "{}" is not supported.'.format(request.GET['image_type']))
def get_tidal_vectors(self, layer, time, bbox, vector_scale=None, vector_step=None): vector_scale = vector_scale or 1 vector_step = vector_step or 1 with netCDF4.Dataset(self.topology_file) as nc: data_obj = nc.variables[layer.access_name] data_location = getattr(data_obj, 'location', 'node') mesh_name = data_obj.mesh ug = UGrid.from_nc_dataset(nc, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] padding_factor = calc_safety_factor(vector_scale) padding = calc_lon_lat_padding(lon, lat, padding_factor) * vector_step spatial_idx = data_handler.ugrid_lat_lon_subset_idx( lon, lat, bbox=bbox, padding=padding) tnames = nc.get_variables_by_attributes( standard_name='tide_constituent')[0] tfreqs = nc.get_variables_by_attributes( standard_name='tide_frequency')[0] from utide import _ut_constants_fname from utide.utilities import loadmatbunch con_info = loadmatbunch(_ut_constants_fname)['const'] # Get names from the utide constant file utide_const_names = [e.strip() for e in con_info['name'].tolist()] # netCDF4-python is returning ugly arrays of bytes... names = [] for n in tnames[:]: z = ''.join([x.decode('utf-8') for x in n.tolist() if x]).strip() names.append(z) if 'STEADY' in names: names[names.index('STEADY')] = 'Z0' extract_names = list( set(utide_const_names).intersection(set(names))) ntides = data_obj.shape[data_obj.dimensions.index('ntides')] extract_mask = np.zeros(shape=(ntides, ), dtype=bool) for n in extract_names: extract_mask[names.index(n)] = True if not spatial_idx.any() or not extract_mask.any(): e = np.ma.empty(0) return e, e, e, e ua = nc.variables['u'][extract_mask, spatial_idx] va = nc.variables['v'][extract_mask, spatial_idx] up = nc.variables['u_phase'][extract_mask, spatial_idx] vp = nc.variables['v_phase'][extract_mask, spatial_idx] freqs = tfreqs[extract_mask] omega = freqs * 3600 # Convert from radians/s to radians/hour. from utide.harmonics import FUV from matplotlib.dates import date2num v, u, f = FUV( t=np.array([date2num(time) + 366.1667]), tref=np.array([0]), lind=np.array( [utide_const_names.index(x) for x in extract_names]), lat= 55, # Reference latitude for 3rd order satellites (degrees) (55 is fine always) ngflgs=[0, 0, 0, 0]) # [NodsatLint NodsatNone GwchLint GwchNone] s = calendar.timegm(time.timetuple()) / 60 / 60. v, u, f = map(np.squeeze, (v, u, f)) v = v * 2 * np.pi # Convert phase in radians. u = u * 2 * np.pi # Convert phase in radians. U = (f * ua.T * np.cos(v + s * omega + u - up.T * np.pi / 180)).sum(axis=1) V = (f * va.T * np.cos(v + s * omega + u - vp.T * np.pi / 180)).sum(axis=1) return U, V, lon[spatial_idx], lat[spatial_idx]
def update_cache(self, force=False): with self.dataset() as nc: ug = UGrid.from_nc_dataset(nc) ug.save_as_netcdf(self.topology_file) if not os.path.exists(self.topology_file): logger.error("Failed to create topology_file cache for Dataset '{}'".format(self.dataset)) return uamp = nc.get_variables_by_attributes(standard_name='eastward_sea_water_velocity_amplitude')[0] vamp = nc.get_variables_by_attributes(standard_name='northward_sea_water_velocity_amplitude')[0] uphase = nc.get_variables_by_attributes(standard_name='eastward_sea_water_velocity_phase')[0] vphase = nc.get_variables_by_attributes(standard_name='northward_sea_water_velocity_phase')[0] tnames = nc.get_variables_by_attributes(standard_name='tide_constituent')[0] tfreqs = nc.get_variables_by_attributes(standard_name='tide_frequency')[0] with netCDF4.Dataset(self.topology_file, mode='a') as cnc: ntides = uamp.shape[uamp.dimensions.index('ntides')] nlocs = uamp.shape[uamp.dimensions.index(uamp.location)] cnc.createDimension('ntides', ntides) cnc.createDimension('maxStrlen64', 64) vdims = ('ntides', '{}_num_{}'.format(uamp.mesh, uamp.location)) # Swap ntides to always be the first dimension.. it can be the second in the source files! transpose = False if uamp.shape[0] > uamp.shape[1]: logger.info("Found flipped dimensions in source file... fixing in local cache.") transpose = True # We are changing the variable names to 'u' and 'v' from 'u_amp' and 'v_amp' so # the layer.access_method can find the variable from the virtual layer 'u,v' ua = cnc.createVariable('u', uamp.dtype, vdims, zlib=True, fill_value=uamp._FillValue, chunksizes=[1, nlocs/4]) for x in uamp.ncattrs(): if x != '_FillValue': ua.setncattr(x, uamp.getncattr(x)) va = cnc.createVariable('v', vamp.dtype, vdims, zlib=True, fill_value=vamp._FillValue, chunksizes=[1, nlocs/4]) for x in vamp.ncattrs(): if x != '_FillValue': va.setncattr(x, vamp.getncattr(x)) up = cnc.createVariable('u_phase', uphase.dtype, vdims, zlib=True, fill_value=uphase._FillValue, chunksizes=[1, nlocs/4]) for x in uphase.ncattrs(): if x != '_FillValue': up.setncattr(x, uphase.getncattr(x)) vp = cnc.createVariable('v_phase', vphase.dtype, vdims, zlib=True, fill_value=vphase._FillValue, chunksizes=[1, nlocs/4]) for x in vphase.ncattrs(): if x != '_FillValue': vp.setncattr(x, vphase.getncattr(x)) tc = cnc.createVariable('tidenames', tnames.dtype, tnames.dimensions) tc[:] = tnames[:] for x in tnames.ncattrs(): if x != '_FillValue': tc.setncattr(x, tnames.getncattr(x)) tf = cnc.createVariable('tidefreqs', tfreqs.dtype, ('ntides',)) tf[:] = tfreqs[:] for x in tfreqs.ncattrs(): if x != '_FillValue': tf.setncattr(x, tfreqs.getncattr(x)) for r in range(ntides): logger.info("Saving ntide {} into cache".format(r)) if transpose is True: ua[r, :] = uamp[:, r].T va[r, :] = vamp[:, r].T up[r, :] = uphase[:, r].T vp[r, :] = vphase[:, r].T else: ua[r, :] = uamp[r, :] va[r, :] = vamp[r, :] up[r, :] = uphase[r, :] vp[r, :] = vphase[r, :] # Now do the RTree index self.make_rtree() self.cache_last_updated = datetime.utcnow().replace(tzinfo=pytz.utc) self.save()
def animation( self, variable, save=False, fps=3, start_frame=0, end_frame=-1, figsize=None, wireframe=False, cmap='jet', levels=256, show=False, xmin=None, xmax=None, ymin=None, ymax=None, vmin=None, vmax=None, ): fig = plt.figure(figsize) ax = fig.add_subplot(111) plt.tight_layout(pad=2) ugrid = UGrid.from_nc_dataset(self.nc) x = ugrid.nodes[:, 0] y = ugrid.nodes[:, 1] triangulation = Triangulation(x, y, ugrid.faces[:, :3]) xmin = np.min(x) if xmin is None else xmin xmax = np.max(x) if xmax is None else xmax ymin = np.min(y) if ymin is None else ymin ymax = np.max(y) if ymax is None else ymax vmin = np.min(self.nc[variable]) if vmin is None else vmin vmax = np.max(self.nc[variable]) if vmax is None else vmax unit = OutputVariableUnit[OutputVariableShortName(variable).name].value def animate(index): _ax = fig.get_axes() ax.clear() if len(_ax) > 1: cax = _ax[1] cax.cla() else: cax = None triangulation.set_mask(self.nc['wetdry_elem'][index]) if wireframe: ax.triplot(triangulation, color='k', linewidth=0.7) ax.tricontourf(triangulation, self.nc[variable][index, :], cmap=cmap, levels=levels, vmin=vmin, vmax=vmax) ax.set_ylim(ymin, ymax, auto=True) ax.set_xlim(xmin, xmax, auto=True) ax.set_xlabel('Longitude (°E)') ax.set_ylabel('Latitude (°N)') # ax.set_title(dates[i].strftime('%b %d, %Y %H:%M')) m = plt.cm.ScalarMappable(cmap=cmap) m.set_array(self.nc[variable][index, :]) m.set_clim(vmin, vmax) cbar = fig.colorbar(m, cax=cax, format='%.1f', boundaries=np.linspace(vmin, vmax, levels)) # cbar = fig.colorbar(_ax) cbar.ax.set_ylabel(f'{variable} [{unit}]', rotation=90) end_frame = end_frame % self.nc[variable].shape[0] \ if end_frame < 0 else end_frame start_frame = start_frame % self.nc[variable].shape[0] \ if start_frame < 0 else start_frame frames = range(start_frame, end_frame) anim = FuncAnimation(fig, animate, frames, blit=False) if save: anim.save(pathlib.Path(save), writer='imagemagick', fps=fps) if show: plt.show() return anim
def get_tidal_vectors(self, layer, time, bbox, vector_scale=None, vector_step=None): vector_scale = vector_scale or 1 vector_step = vector_step or 1 with netCDF4.Dataset(self.topology_file) as nc: data_obj = nc.variables[layer.access_name] data_location = getattr(data_obj, 'location', 'node') mesh_name = data_obj.mesh ug = UGrid.from_nc_dataset(nc, mesh_name=mesh_name) coords = np.empty(0) if data_location == 'node': coords = ug.nodes elif data_location == 'face': coords = ug.face_coordinates elif data_location == 'edge': coords = ug.edge_coordinates lon = coords[:, 0] lat = coords[:, 1] padding_factor = calc_safety_factor(vector_scale) padding = calc_lon_lat_padding(lon, lat, padding_factor) * vector_step spatial_idx = data_handler.ugrid_lat_lon_subset_idx(lon, lat, bbox=bbox, padding=padding) tnames = nc.get_variables_by_attributes(standard_name='tide_constituent')[0] tfreqs = nc.get_variables_by_attributes(standard_name='tide_frequency')[0] from utide import _ut_constants_fname from utide.utilities import loadmatbunch con_info = loadmatbunch(_ut_constants_fname)['const'] # Get names from the utide constant file utide_const_names = [ e.strip() for e in con_info['name'].tolist() ] # netCDF4-python is returning ugly arrays of bytes... names = [] for n in tnames[:]: z = ''.join([ x.decode('utf-8') for x in n.tolist() if x ]).strip() names.append(z) if 'STEADY' in names: names[names.index('STEADY')] = 'Z0' extract_names = list(set(utide_const_names).intersection(set(names))) ntides = data_obj.shape[data_obj.dimensions.index('ntides')] extract_mask = np.zeros(shape=(ntides,), dtype=bool) for n in extract_names: extract_mask[names.index(n)] = True if not spatial_idx.any() or not extract_mask.any(): e = np.ma.empty(0) return e, e, e, e ua = nc.variables['u'][extract_mask, spatial_idx] va = nc.variables['v'][extract_mask, spatial_idx] up = nc.variables['u_phase'][extract_mask, spatial_idx] vp = nc.variables['v_phase'][extract_mask, spatial_idx] freqs = tfreqs[extract_mask] omega = freqs * 3600 # Convert from radians/s to radians/hour. from utide.harmonics import FUV from matplotlib.dates import date2num v, u, f = FUV(t=np.array([date2num(time) + 366.1667]), tref=np.array([0]), lind=np.array([ utide_const_names.index(x) for x in extract_names ]), lat=55, # Reference latitude for 3rd order satellites (degrees) (55 is fine always) ngflgs=[0, 0, 0, 0]) # [NodsatLint NodsatNone GwchLint GwchNone] s = calendar.timegm(time.timetuple()) / 60 / 60. v, u, f = map(np.squeeze, (v, u, f)) v = v * 2 * np.pi # Convert phase in radians. u = u * 2 * np.pi # Convert phase in radians. U = (f * ua.T * np.cos(v + s * omega + u - up.T * np.pi / 180)).sum(axis=1) V = (f * va.T * np.cos(v + s * omega + u - vp.T * np.pi / 180)).sum(axis=1) return U, V, lon[spatial_idx], lat[spatial_idx]