def test_add_halo_where_halo_value_is_zero(): values = np.ones((3, 4), dtype=int) with_halo = add_halo(values, halo_value=0) assert np.all(with_halo == [ [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], ])
def test_add_halo_where_halo_value_is_nan(): values = np.ones((2, 3), dtype=float) with_halo = add_halo(values, halo=1, halo_value=np.nan) assert np.all(with_halo[1:-1, 1:-1] == approx(values)) assert np.all( np.isnan(with_halo) == [ [True, True, True, True, True], [True, False, False, False, True], [True, False, False, False, True], [True, True, True, True, True], ])
def test_add_halo_where_halo_value_is_nan(): values = np.ones((2, 3), dtype=float) with_halo = add_halo(values, halo=1, halo_value=np.nan) assert np.all(with_halo[1:-1, 1:-1] == approx(values)) assert np.all( np.isnan(with_halo) == [ [True, True, True, True, True], [True, False, False, False, True], [True, False, False, False, True], [True, True, True, True, True], ] )
def test_add_halo_where_halo_value_is_zero(): values = np.ones((3, 4), dtype=int) with_halo = add_halo(values, halo_value=0) assert np.all( with_halo == [ [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], ] )
def test_add_halo_with_bigger_halo(): values = np.ones((2, 4), dtype=int) with_halo = add_halo(values, halo=3, halo_value=0) assert np.all(with_halo == [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ])
def test_add_halo_with_bigger_halo(): values = np.ones((2, 4), dtype=int) with_halo = add_halo(values, halo=3, halo_value=0) assert np.all( with_halo == [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ] )
def read_netcdf( nc_file, grid=None, name=None, just_grid=False, halo=0, nodata_value=-9999.0 ): """Create a :class:`~.RasterModelGrid` from a netcdf file. Create a new :class:`~.RasterModelGrid` from the netcdf file, *nc_file*. If the netcdf file also contains data, add that data to the grid's fields. To create a new grid without any associated data from the netcdf file, set the *just_grid* keyword to ``True``. A halo can be added with the keyword *halo*. If you want the fields to be added to an existing grid, it can be passed to the keyword argument *grid*. Parameters ---------- nc_file : str Name of a netcdf file. grid : *grid* , optional Adds data to an existing *grid* instead of creating a new one. name : str, optional Add only fields with NetCDF variable name to the grid. Default is to add all NetCDF varibles to the grid. just_grid : boolean, optional Create a new grid but don't add value data. halo : integer, optional Adds outer border of depth halo to the *grid*. nodata_value : float, optional Value that indicates an invalid value. Default is -9999. Returns ------- :class:`~.RasterModelGrid` A newly-created :class:`~.RasterModelGrid`. Examples -------- Import :func:`read_netcdf` and the path to an example netcdf file included with landlab. >>> from landlab.io.netcdf import read_netcdf >>> from landlab.io.netcdf import NETCDF4_EXAMPLE_FILE Create a new grid from the netcdf file. The example grid is a uniform rectilinear grid with 4 rows and 3 columns of nodes with unit spacing. The data file also contains data defined at the nodes for the grid for a variable called, *surface__elevation*. >>> grid = read_netcdf(NETCDF4_EXAMPLE_FILE) >>> grid.shape == (4, 3) True >>> grid.dy, grid.dx (1.0, 1.0) >>> list(grid.at_node.keys()) ['surface__elevation'] >>> grid.at_node['surface__elevation'] array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.]) :func:`read_netcdf` will try to determine the format of the netcdf file. For example, the same call will also work for *netcdf3*-formatted files. >>> from landlab.io.netcdf import NETCDF3_64BIT_EXAMPLE_FILE >>> grid = read_netcdf(NETCDF3_64BIT_EXAMPLE_FILE) >>> grid.shape == (4, 3) True >>> grid.dy, grid.dx (1.0, 1.0) A more complicated example might add data with a halo to an existing grid. Note that the lower left corner must be specified correctly for the data and the grid to align correctly. >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((6, 5), xy_of_lower_left=(-1., -1.)) >>> grid = read_netcdf( ... NETCDF4_EXAMPLE_FILE, ... grid=grid, ... halo=1, ... nodata_value=-1) >>> grid.at_node['surface__elevation'].reshape(grid.shape) array([[ -1., -1., -1., -1., -1.], [ -1., 0., 1., 2., -1.], [ -1., 3., 4., 5., -1.], [ -1., 6., 7., 8., -1.], [ -1., 9., 10., 11., -1.], [ -1., -1., -1., -1., -1.]]) """ from landlab import RasterModelGrid try: root = nc.netcdf_file(nc_file, "r", version=2) except TypeError: root = nc4.Dataset(nc_file, "r", format="NETCDF4") try: node_coords = _read_netcdf_structured_grid(root) except ValueError: if (len(root.variables["x"].dimensions) == 1) and ( len(root.variables["y"].dimensions) == 1 ): node_coords = _read_netcdf_raster_structured_grid(root) else: assert ValueError( "x and y dimensions must both either be 2D " "(nj, ni) or 1D (ni,) and (nj)." ) assert len(node_coords) == 2 dx = _get_raster_spacing(node_coords) xy_spacing = (dx, dx) shape = node_coords[0].shape xy_of_lower_left = ( node_coords[0].min() - halo * dx, node_coords[1].min() - halo * dx, ) if grid is not None: if (grid.number_of_node_rows != shape[0] + 2 * halo) or ( grid.number_of_node_columns != shape[1] + 2 * halo ): raise MismatchGridDataSizeError( shape[0] + 2 * halo * shape[1] + 2 * halo, grid.number_of_node_rows * grid.number_of_node_columns, ) if (grid.dx, grid.dy) != xy_spacing: raise MismatchGridXYSpacing((grid.dx, grid.dy), xy_spacing) if grid.xy_of_lower_left != xy_of_lower_left: raise MismatchGridXYLowerLeft(grid.xy_of_lower_left, xy_of_lower_left) if grid is None: grid = RasterModelGrid( shape, xy_spacing=xy_spacing, xy_of_lower_left=xy_of_lower_left ) if not just_grid: fields, grid_mapping_dict = _read_netcdf_structured_data(root) for (field_name, values) in fields.items(): # add halo if necessary if halo > 0: values = add_halo( values.reshape(shape), halo=halo, halo_value=nodata_value ).reshape((-1,)) # add only the requested fields. if (name is None) or (field_name == name): add_field = True else: add_field = False if add_field: grid.add_field(field_name, values, at="node", noclobber=False) if (name is not None) and (name not in grid.at_node): raise ValueError( "Specified field {name} was not in provided NetCDF.".format(name=name) ) # save grid mapping if grid_mapping_dict is not None: grid.grid_mapping = grid_mapping_dict root.close() return grid
def test_add_halo_with_defaults(): values = np.arange(6, dtype=int).reshape((2, 3)) with_halo = add_halo(values) assert np.all(with_halo[1:-1, 1:-1] == values) assert values.dtype == with_halo.dtype
def test_add_halo_to_bool_array(): values = np.full((2, 3), True, dtype=bool) with_halo = add_halo(values) assert np.all(with_halo[1:-1, 1:-1] == values) assert values.dtype == with_halo.dtype
def read_esri_ascii(asc_file, grid=None, reshape=False, name=None, halo=0): """Read :py:class:`~landlab.RasterModelGrid` from an ESRI ASCII file. Read data from *asc_file*, an ESRI_ ASCII file, into a :py:class:`~landlab.RasterModelGrid`. *asc_file* is either the name of the data file or is a file-like object. The grid and data read from the file are returned as a tuple (*grid*, *data*) where *grid* is an instance of :py:class:`~landlab.RasterModelGrid` and *data* is a numpy array of doubles with that has been reshaped to have the number of rows and columns given in the header. .. _ESRI: http://resources.esri.com/help/9.3/arcgisengine/java/GP_ToolRef/spatial_analyst_tools/esri_ascii_raster_format.htm Parameters ---------- asc_file : str of file-like Data file to read. reshape : boolean, optional Reshape the returned array, otherwise return a flattened array. name : str, optional Add data to the grid as a named field. grid : *grid* , optional Adds data to an existing *grid* instead of creating a new one. halo : integer, optional Adds outer border of depth halo to the *grid*. Returns ------- (grid, data) : tuple A newly-created RasterModel grid and the associated node data. Raises ------ DataSizeError Data are not the same size as indicated by the header file. MismatchGridDataSizeError If a grid is passed, and the size of the grid does not agree with the size of the data. MismatchGridXYSpacing If a grid is passed, and the cellsize listed in the heading does not match the grid dx and dy. MismatchGridXYLowerLeft If a grid is passed and the xllcorner and yllcorner do not match that of the grid. Examples -------- Assume that fop is the name of a file that contains text below (make sure you have your path correct): ncols 3 nrows 4 xllcorner 1. yllcorner 2. cellsize 10. NODATA_value -9999 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. -------- >>> from landlab.io import read_esri_ascii >>> (grid, data) = read_esri_ascii('fop') # doctest: +SKIP >>> #grid is an object of type RasterModelGrid with 4 rows and 3 cols >>> #data contains an array of length 4*3 that is equal to >>> # [9., 10., 11., 6., 7., 8., 3., 4., 5., 0., 1., 2.] >>> (grid, data) = read_esri_ascii('fop', halo=1) # doctest: +SKIP >>> #now the data has a nodata_value ring of -9999 around it. So array is >>> # [-9999, -9999, -9999, -9999, -9999, -9999, >>> # -9999, 9., 10., 11., -9999, >>> # -9999, 6., 7., 8., -9999, >>> # -9999, 3., 4., 5., -9999, >>> # -9999, 0., 1., 2. -9999, >>> # -9999, -9999, -9999, -9999, -9999, -9999] """ from ..grid import RasterModelGrid # if the asc_file is provided as a string, open it and pass the pointer to # _read_asc_header, and _read_asc_data if isinstance(asc_file, six.string_types): with open(asc_file, "r") as f: header = read_asc_header(f) data = _read_asc_data(f) # otherwise, pass asc_file directly. else: header = read_asc_header(asc_file) data = _read_asc_data(asc_file) # There is no reason for halo to be negative. # Assume that if a negative value is given it should be 0. if halo <= 0: shape = (header["nrows"], header["ncols"]) if data.size != shape[0] * shape[1]: raise DataSizeError(shape[0] * shape[1], data.size) else: shape = (header["nrows"] + 2 * halo, header["ncols"] + 2 * halo) # check to see if a nodata_value was given. If not, assign -9999. if "nodata_value" in header.keys(): nodata_value = header["nodata_value"] else: header["nodata_value"] = -9999.0 nodata_value = header["nodata_value"] if data.size != (shape[0] - 2 * halo) * (shape[1] - 2 * halo): raise DataSizeError(shape[0] * shape[1], data.size) xy_spacing = (header["cellsize"], header["cellsize"]) xy_of_lower_left = ( header["xllcorner"] - halo * header["cellsize"], header["yllcorner"] - halo * header["cellsize"], ) data = np.flipud(data) if halo > 0: data = add_halo( data.reshape(header["nrows"], header["ncols"]), halo=halo, halo_value=nodata_value, ).reshape((-1,)) if not reshape: data = data.flatten() if grid is not None: if (grid.number_of_node_rows != shape[0]) or ( grid.number_of_node_columns != shape[1] ): raise MismatchGridDataSizeError( shape[0] * shape[1], grid.number_of_node_rows * grid.number_of_node_columns, ) if (grid.dx, grid.dy) != xy_spacing: raise MismatchGridXYSpacing((grid.dx, grid.dy), xy_spacing) if grid.xy_of_lower_left != xy_of_lower_left: raise MismatchGridXYLowerLeft(grid.xy_of_lower_left, xy_of_lower_left) if grid is None: grid = RasterModelGrid( shape, xy_spacing=xy_spacing, xy_of_lower_left=xy_of_lower_left ) if name: grid.add_field("node", name, data) return (grid, data)
def test_add_halo_to_float_array(): values = np.arange(6, dtype=float).reshape((2, 3)) with_halo = add_halo(values) assert np.all(with_halo[1:-1, 1:-1] == approx(values)) assert values.dtype == with_halo.dtype
def test_add_halo_with_zero_halo(): values = np.ones((2, 3), dtype=int) with_halo = add_halo(values, halo=0) assert np.all(with_halo == values)
def read_esri_ascii(asc_file, grid=None, reshape=False, name=None, halo=0): """Read :py:class:`~landlab.RasterModelGrid` from an ESRI ASCII file. Read data from *asc_file*, an ESRI_ ASCII file, into a :py:class:`~landlab.RasterModelGrid`. *asc_file* is either the name of the data file or is a file-like object. The grid and data read from the file are returned as a tuple (*grid*, *data*) where *grid* is an instance of :py:class:`~landlab.RasterModelGrid` and *data* is a numpy array of doubles with that has been reshaped to have the number of rows and columns given in the header. .. _ESRI: http://resources.esri.com/help/9.3/arcgisengine/java/GP_ToolRef/spatial_analyst_tools/esri_ascii_raster_format.htm Parameters ---------- asc_file : str of file-like Data file to read. reshape : boolean, optional Reshape the returned array, otherwise return a flattened array. name : str, optional Add data to the grid as a named field. grid : *grid* , optional Adds data to an existing *grid* instead of creating a new one. halo : integer, optional Adds outer border of depth halo to the *grid*. Returns ------- (grid, data) : tuple A newly-created RasterModel grid and the associated node data. Raises ------ DataSizeError Data are not the same size as indicated by the header file. MismatchGridDataSizeError If a grid is passed, and the size of the grid does not agree with the size of the data. MismatchGridXYSpacing If a grid is passed, and the cellsize listed in the heading does not match the grid dx and dy. MismatchGridXYLowerLeft If a grid is passed and the xllcorner and yllcorner do not match that of the grid. Examples -------- Assume that fop is the name of a file that contains text below (make sure you have your path correct): ncols 3 nrows 4 xllcorner 1. yllcorner 2. cellsize 10. NODATA_value -9999 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. -------- >>> from landlab.io import read_esri_ascii >>> (grid, data) = read_esri_ascii('fop') # doctest: +SKIP >>> #grid is an object of type RasterModelGrid with 4 rows and 3 cols >>> #data contains an array of length 4*3 that is equal to >>> # [9., 10., 11., 6., 7., 8., 3., 4., 5., 0., 1., 2.] >>> (grid, data) = read_esri_ascii('fop', halo=1) # doctest: +SKIP >>> #now the data has a nodata_value ring of -9999 around it. So array is >>> # [-9999, -9999, -9999, -9999, -9999, -9999, >>> # -9999, 9., 10., 11., -9999, >>> # -9999, 6., 7., 8., -9999, >>> # -9999, 3., 4., 5., -9999, >>> # -9999, 0., 1., 2. -9999, >>> # -9999, -9999, -9999, -9999, -9999, -9999] """ from ..grid import RasterModelGrid # if the asc_file is provided as a string, open it and pass the pointer to # _read_asc_header, and _read_asc_data if isinstance(asc_file, (str, pathlib.Path)): with open(asc_file, "r") as f: header = read_asc_header(f) data = _read_asc_data(f) # otherwise, pass asc_file directly. else: header = read_asc_header(asc_file) data = _read_asc_data(asc_file) # There is no reason for halo to be negative. # Assume that if a negative value is given it should be 0. if halo <= 0: shape = (header["nrows"], header["ncols"]) if data.size != shape[0] * shape[1]: raise DataSizeError(shape[0] * shape[1], data.size) else: shape = (header["nrows"] + 2 * halo, header["ncols"] + 2 * halo) # check to see if a nodata_value was given. If not, assign -9999. if "nodata_value" in header.keys(): nodata_value = header["nodata_value"] else: header["nodata_value"] = -9999.0 nodata_value = header["nodata_value"] if data.size != (shape[0] - 2 * halo) * (shape[1] - 2 * halo): raise DataSizeError(shape[0] * shape[1], data.size) xy_spacing = (header["cellsize"], header["cellsize"]) xy_of_lower_left = ( header["xllcorner"] - halo * header["cellsize"], header["yllcorner"] - halo * header["cellsize"], ) data = np.flipud(data) if halo > 0: data = add_halo( data.reshape(header["nrows"], header["ncols"]), halo=halo, halo_value=nodata_value, ).reshape((-1, )) if not reshape: data = data.flatten() if grid is not None: if (grid.number_of_node_rows != shape[0]) or (grid.number_of_node_columns != shape[1]): raise MismatchGridDataSizeError( shape[0] * shape[1], grid.number_of_node_rows * grid.number_of_node_columns, ) if (grid.dx, grid.dy) != xy_spacing: raise MismatchGridXYSpacing((grid.dx, grid.dy), xy_spacing) if grid.xy_of_lower_left != xy_of_lower_left: raise MismatchGridXYLowerLeft(grid.xy_of_lower_left, xy_of_lower_left) if grid is None: grid = RasterModelGrid(shape, xy_spacing=xy_spacing, xy_of_lower_left=xy_of_lower_left) if name: grid.add_field(name, data, at="node") return (grid, data)
def read_netcdf( nc_file, grid=None, name=None, just_grid=False, halo=0, nodata_value=-9999.0, ): """Create a :class:`~.RasterModelGrid` from a netcdf file. Create a new :class:`~.RasterModelGrid` from the netcdf file, *nc_file*. If the netcdf file also contains data, add that data to the grid's fields. To create a new grid without any associated data from the netcdf file, set the *just_grid* keyword to ``True``. A halo can be added with the keyword *halo*. If you want the fields to be added to an existing grid, it can be passed to the keyword argument *grid*. Parameters ---------- nc_file : str Name of a netcdf file. grid : *grid* , optional Adds data to an existing *grid* instead of creating a new one. name : str, optional Add only fields with NetCDF variable name to the grid. Default is to add all NetCDF varibles to the grid. just_grid : boolean, optional Create a new grid but don't add value data. halo : integer, optional Adds outer border of depth halo to the *grid*. nodata_value : float, optional Value that indicates an invalid value. Default is -9999. Returns ------- :class:`~.RasterModelGrid` A newly-created :class:`~.RasterModelGrid`. Examples -------- Import :func:`read_netcdf` and the path to an example netcdf file included with landlab. >>> from landlab.io.netcdf import read_netcdf Create a new grid from the netcdf file. The example grid is a uniform rectilinear grid with 4 rows and 3 columns of nodes with unit spacing. The data file also contains data defined at the nodes for the grid for a variable called, *surface__elevation*. >>> grid = read_netcdf("test-netcdf4.nc") # doctest: +SKIP >>> grid.shape == (4, 3) # doctest: +SKIP True >>> grid.dy, grid.dx # doctest: +SKIP (1.0, 1.0) >>> list(grid.at_node.keys()) # doctest: +SKIP ['surface__elevation'] >>> grid.at_node['surface__elevation'] # doctest: +SKIP array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.]) :func:`read_netcdf` will try to determine the format of the netcdf file. For example, the same call will also work for *netcdf3*-formatted files. >>> grid = read_netcdf("test-netcdf3-64bit.nc") # doctest: +SKIP >>> grid.shape == (4, 3) # doctest: +SKIP True >>> grid.dy, grid.dx # doctest: +SKIP (1.0, 1.0) A more complicated example might add data with a halo to an existing grid. Note that the lower left corner must be specified correctly for the data and the grid to align correctly. >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((6, 5), xy_of_lower_left=(-1., -1.)) # doctest: +SKIP >>> grid = read_netcdf( ... "test-netcdf4.nc", ... grid=grid, ... halo=1, ... nodata_value=-1, ... ) # doctest: +SKIP >>> grid.at_node['surface__elevation'].reshape(grid.shape) # doctest: +SKIP array([[ -1., -1., -1., -1., -1.], [ -1., 0., 1., 2., -1.], [ -1., 3., 4., 5., -1.], [ -1., 6., 7., 8., -1.], [ -1., 9., 10., 11., -1.], [ -1., -1., -1., -1., -1.]]) """ from landlab import RasterModelGrid dataset = xr.open_dataset(nc_file) if isinstance(name, str): names = {name} elif name is None: names = set(dataset.variables) else: names = set(name) # test if the input is a raster (x and y) are only 1-D instead of 2D. if len(dataset["x"].shape) == 1: y, x = np.meshgrid(dataset["y"], dataset["x"], indexing="ij") else: x = dataset["x"] y = dataset["y"] dx = np.diff(x, axis=1) dy = np.diff(y, axis=0) if np.all(dx == dx[0, 0]) and np.all(dy == dy[0, 0]): xy_spacing = (dx[0, 0], dy[0, 0]) else: raise NotRasterGridError() shape = x.shape xy_of_lower_left = ( x[0, 0] - halo * xy_spacing[0], y[0, 0] - halo * xy_spacing[1], ) if grid is None: grid = RasterModelGrid(shape, xy_spacing=xy_spacing, xy_of_lower_left=xy_of_lower_left) else: if grid.shape != (shape[0] + 2 * halo, shape[1] + 2 * halo): raise MismatchGridDataSizeError( shape[0] + 2 * halo * shape[1] + 2 * halo, grid.number_of_node_rows * grid.number_of_node_columns, ) if (grid.dx, grid.dy) != xy_spacing: raise MismatchGridXYSpacing((grid.dx, grid.dy), xy_spacing) if grid.xy_of_lower_left != xy_of_lower_left: raise MismatchGridXYLowerLeft(grid.xy_of_lower_left, xy_of_lower_left) if not just_grid: fields, grid_mapping_dict = _read_netcdf_structured_data(dataset) for (field_name, values) in fields.items(): # add halo if necessary if halo > 0: values = add_halo(values.reshape(shape), halo=halo, halo_value=nodata_value).reshape((-1, )) # add only the requested fields. if (name is None) or (field_name == name): add_field = True else: add_field = False if add_field: grid.add_field(field_name, values, at="node", clobber=True) if (name is not None) and (name not in grid.at_node): raise ValueError( "Specified field {name} was not in provided NetCDF.".format( name=name)) ignore = {"x", "y"} for name in names - ignore: values = dataset.variables[name].values if halo > 0: values = add_halo(values.reshape(shape), halo=halo, halo_value=nodata_value).reshape((-1, )) grid.add_field(name, values, at="node", clobber=True) return grid
def read_netcdf(nc_file, grid=None, name=None, just_grid=False, halo=0, nodata_value=-9999.0): """Create a :class:`~.RasterModelGrid` from a netcdf file. Create a new :class:`~.RasterModelGrid` from the netcdf file, *nc_file*. If the netcdf file also contains data, add that data to the grid's fields. To create a new grid without any associated data from the netcdf file, set the *just_grid* keyword to ``True``. A halo can be added with the keyword *halo*. If you want the fields to be added to an existing grid, it can be passed to the keyword argument *grid*. Parameters ---------- nc_file : str Name of a netcdf file. grid : *grid* , optional Adds data to an existing *grid* instead of creating a new one. name : str, optional Add only fields with NetCDF variable name to the grid. Default is to add all NetCDF varibles to the grid. just_grid : boolean, optional Create a new grid but don't add value data. halo : integer, optional Adds outer border of depth halo to the *grid*. nodata_value : float, optional Value that indicates an invalid value. Default is -9999. Returns ------- :class:`~.RasterModelGrid` A newly-created :class:`~.RasterModelGrid`. Examples -------- Import :func:`read_netcdf` and the path to an example netcdf file included with landlab. >>> from landlab.io.netcdf import read_netcdf Create a new grid from the netcdf file. The example grid is a uniform rectilinear grid with 4 rows and 3 columns of nodes with unit spacing. The data file also contains data defined at the nodes for the grid for a variable called, *surface__elevation*. >>> grid = read_netcdf("test-netcdf4.nc") # doctest: +SKIP >>> grid.shape == (4, 3) # doctest: +SKIP True >>> grid.dy, grid.dx # doctest: +SKIP (1.0, 1.0) >>> list(grid.at_node.keys()) # doctest: +SKIP ['surface__elevation'] >>> grid.at_node['surface__elevation'] # doctest: +SKIP array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.]) :func:`read_netcdf` will try to determine the format of the netcdf file. For example, the same call will also work for *netcdf3*-formatted files. >>> grid = read_netcdf("test-netcdf3-64bit.nc") # doctest: +SKIP >>> grid.shape == (4, 3) # doctest: +SKIP True >>> grid.dy, grid.dx # doctest: +SKIP (1.0, 1.0) A more complicated example might add data with a halo to an existing grid. Note that the lower left corner must be specified correctly for the data and the grid to align correctly. >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((6, 5), xy_of_lower_left=(-1., -1.)) # doctest: +SKIP >>> grid = read_netcdf( ... "test-netcdf4.nc", ... grid=grid, ... halo=1, ... nodata_value=-1, ... ) # doctest: +SKIP >>> grid.at_node['surface__elevation'].reshape(grid.shape) # doctest: +SKIP array([[ -1., -1., -1., -1., -1.], [ -1., 0., 1., 2., -1.], [ -1., 3., 4., 5., -1.], [ -1., 6., 7., 8., -1.], [ -1., 9., 10., 11., -1.], [ -1., -1., -1., -1., -1.]]) """ from landlab import RasterModelGrid try: root = nc.netcdf_file(nc_file, "r", version=2) except TypeError: root = nc4.Dataset(nc_file, "r", format="NETCDF4") try: node_coords = _read_netcdf_structured_grid(root) except ValueError: if (len(root.variables["x"].dimensions) == 1) and (len( root.variables["y"].dimensions) == 1): node_coords = _read_netcdf_raster_structured_grid(root) else: assert ValueError("x and y dimensions must both either be 2D " "(nj, ni) or 1D (ni,) and (nj).") assert len(node_coords) == 2 dx = _get_raster_spacing(node_coords) xy_spacing = (dx, dx) shape = node_coords[0].shape xy_of_lower_left = ( node_coords[0].min() - halo * dx, node_coords[1].min() - halo * dx, ) if grid is not None: if (grid.number_of_node_rows != shape[0] + 2 * halo) or ( grid.number_of_node_columns != shape[1] + 2 * halo): raise MismatchGridDataSizeError( shape[0] + 2 * halo * shape[1] + 2 * halo, grid.number_of_node_rows * grid.number_of_node_columns, ) if (grid.dx, grid.dy) != xy_spacing: raise MismatchGridXYSpacing((grid.dx, grid.dy), xy_spacing) if grid.xy_of_lower_left != xy_of_lower_left: raise MismatchGridXYLowerLeft(grid.xy_of_lower_left, xy_of_lower_left) if grid is None: grid = RasterModelGrid(shape, xy_spacing=xy_spacing, xy_of_lower_left=xy_of_lower_left) if not just_grid: fields, grid_mapping_dict = _read_netcdf_structured_data(root) for (field_name, values) in fields.items(): # add halo if necessary if halo > 0: values = add_halo(values.reshape(shape), halo=halo, halo_value=nodata_value).reshape((-1, )) # add only the requested fields. if (name is None) or (field_name == name): add_field = True else: add_field = False if add_field: grid.add_field(field_name, values, at="node", clobber=True) if (name is not None) and (name not in grid.at_node): raise ValueError( "Specified field {name} was not in provided NetCDF.".format( name=name)) # save grid mapping if grid_mapping_dict is not None: grid.grid_mapping = grid_mapping_dict root.close() return grid