Пример #1
0
def arc_ascii_3070(test_output_path, rotation=0):
    filename = os.path.join(test_output_path, 'test_arcascii_3070.asc')

    array = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
    dx = 5.
    xll, yll = 0., 0.
    write_raster(filename,
                 array,
                 xll=xll,
                 yll=yll,
                 dx=dx,
                 dy=None,
                 rotation=rotation,
                 proj_str='epsg:3070',
                 nodata=-9999)
    return filename
Пример #2
0
def geotiff_3070(test_output_path, rotation=0):
    filename = os.path.join(test_output_path, 'test_raster_3070.tif')

    array = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
    dx = 5.
    xll, yll = 0., 0.
    write_raster(filename,
                 array,
                 xll=xll,
                 yll=yll,
                 dx=dx,
                 dy=None,
                 rotation=rotation,
                 crs='epsg:3070',
                 nodata=-9999)
    return filename
Пример #3
0
def test_write_raster_crs(crs, test_output_path):
    filename = os.path.join(test_output_path, 'test_raster.tif')

    array = np.array([[0, 1], [2, 3]])
    height, width = array.shape
    dx = 5.
    xll, yll = 0., 0.
    write_raster(filename,
                 array,
                 xll=xll,
                 yll=yll,
                 dx=dx,
                 dy=None,
                 rotation=0,
                 crs=crs,
                 nodata=-9999)
    if crs is not None:
        with rasterio.open(filename) as src:
            written_crs = get_authority_crs(src.crs)
            assert written_crs == get_authority_crs(crs)
Пример #4
0
def arc_ascii(test_output_path):
    filename = os.path.join(test_output_path, 'test_raster.asc')

    array = np.array([[0, 1], [2, 3]])
    height, width = array.shape
    dx = 5.
    xll, yll = 0., 0.
    rotation = 0.
    write_raster(filename,
                 array,
                 xll=xll,
                 yll=yll,
                 dx=dx,
                 dy=None,
                 rotation=rotation,
                 nodata=-9999)
    xul = _xll_to_xul(xll, height * dx, rotation)
    yul = _yll_to_yul(yll, height * dx, rotation)
    transform = get_transform(xul=xul,
                              yul=yul,
                              dx=dx,
                              dy=-dx,
                              rotation=rotation)
    return filename, transform
Пример #5
0
def geotiff(test_output_path, rotation=45.):
    filename = os.path.join(test_output_path, 'test_raster.tif')

    array = np.array([[0, 1], [2, 3]])
    height, width = array.shape
    dx = 5.
    xll, yll = 0., 0.
    write_raster(filename,
                 array,
                 xll=xll,
                 yll=yll,
                 dx=dx,
                 dy=None,
                 rotation=rotation,
                 proj_str='epsg:3070',
                 nodata=-9999)
    xul = _xll_to_xul(xll, height * dx, rotation)
    yul = _yll_to_yul(yll, height * dx, rotation)
    transform = get_transform(xul=xul,
                              yul=yul,
                              dx=dx,
                              dy=-dx,
                              rotation=rotation)
    return filename, transform
Пример #6
0
def setup_model_layers(dem_means_raster,
                       facies_classes_netcdf,
                       framework_rasters,
                       modelgrid,
                       facies_class_variable='fac_a',
                       dem_elevation_units='meters',
                       framework_raster_elevation_units='meters',
                       model_length_units='meters',
                       output_folder='.',
                       framework_unit_names=None):
    """Generate model layering and property zones from voxel-based zone numbers at uniform depths
    and raster surfaces that represent hydrogeologic contacts. The voxel-based zone numbers may
    represent hydrogeologically different sediments, as determined from an airborne electromagnetic survey, 
    and are assumed to have priority over the raster surfaces. The model top is set at the land surface (DEM). 
    Subsequent cell bottoms beneath the land surface are set based on the voxel depths subtracted from the DEM elevations, 
    as sampled at the grid cell centers. Voxel cells without valid data are filled with zone numbers based
    on their position within the raster surfaces of hydrogeologic contacts. For example, a voxel
    cell that lies above all of the raster surfaces would be assigned a zone number equal to the
    highest voxel-based zone number + 3 (an arbitrary gap in the zone numbering between the voxel data
    and the raster surfaces). Similarly, beneath the lowest voxel cell at each location, grid cells are 
    assigned zone values based on their vertical position relative to the raster surfaces. Below the voxel data,
    botm_array and their bottom elevations correspond to the framework_raster surfaces. The lowermost
    (last) raster surface forms the model bottom.
    

    Parameters
    ----------
    dem_means_raster : str (filepath)
        Raster representing the land surface, at the highest resolution being contemplated for the model.
        Usually this is derived by sampling a higher resolution DEM using zonal statistics, taking
        the mean DEM value for each model cell.
    facies_classes_netcdf : str (filepath)
        NetCDF file with the voxel zone data, with a facies_class_variable that contains the zone numbers. 
    framework_rasters : list of strings (filepaths)
        Raster surfaces describing hydrogelogic contacts surrounding the voxel data.
    modelgrid : mfsetup.MFsetupGrid instance
        Modflow-setup grid instance describing the model grid
    facies_class_variable : str, optional
        Variable in facies_classes_netcdf containing the zone information, by default 'fac_a'
    dem_elevation_units : str, optional
        Elevation units of dem_means_raster, by default 'meters'
    framework_raster_elevation_units : str, optional
        Elevation units of the framework_rasters, by default 'meters'
    model_length_units : str, optional
        Length units used in the model, by default 'meters'
    output_folder : str, optional
        Location where results will be saved, by default '.'
    framework_unit_names : list, optional
        Unit names for the framework_rasters, by default None

    Returns
    -------
    botm_array : 3D numpy array
        Array of layer elevations, starting with the model top. 
        (Length equal to the number of botm_array + 1)
    zone_array : 3D numpy array 
        Array of zone numbers, including the values from facies_classes_netcdf,
        followed by a gap of 3 and then the numbers for the framework_rasters.
        For example, if there are 5 zones in facies_classes_netcdf, and 3 raster surfaces
        that intersect cells outside of the voxel data, zones 1-5 would correspond
        to the voxel zones, and zones 8-10 would correspond to hydrogeologic units
        bounded by the framework_rasters.
        
    Notes
    -----
    In addition to the variables returned, the results are written
    to text-array and GeoTiff files within the output_folder.
    
    Two PDFs of figures are also generated- one showing the layering
    in cross sections at regular intervals throughout the model grid, and one
    showing the zones within each layer in plan view.
    """

    # output folders
    figures_folder = os.path.join(output_folder, 'figures')
    layers_folder = os.path.join(output_folder, 'botm_array')
    zones_folder = os.path.join(output_folder, 'zones')
    # zone rasters are saved to zones_folder/rasters
    output_folders = [
        figures_folder,
        layers_folder,
        zones_folder,
        os.path.join(zones_folder, 'rasters'),
    ]
    for folder in output_folders:
        if not os.path.isdir(folder):
            os.makedirs(folder)

    # output PDF with cross sections
    out_xsection_pdf = os.path.join(figures_folder, 'zone_xsections.pdf')
    out_maps_pdf = os.path.join(figures_folder, 'zone_maps.pdf')

    # flopy model grid
    grid = modelgrid

    # read in the facies classes and resisitivity values
    # from framework team
    # and verify that the grid is aligned with the nhg
    ds = xr.load_dataset(facies_classes_netcdf)
    assert point_is_on_nhg(ds.x, ds.y, offset='center')

    # read in the dem and framework rasters and fill locations of no data
    # with the next valid elevation above
    filled_framework_layers = rasters_to_grid(
        modelgrid,
        dem_means_raster,
        framework_rasters,
        dem_elevation_units=dem_elevation_units,
        raster_elevation_units=framework_raster_elevation_units,
        dest_elevation_units=model_length_units)
    dem_means = filled_framework_layers[0]
    framework_layer_botms_filled = filled_framework_layers[1:]

    # make AEM framework z edge elevations by subtracting off depths from model top
    voxel_z_edges = []
    for depth in ds.zb.values:
        voxel_z_edges.append(dem_means - depth)
    # get the elevations of the voxel centers as well
    voxel_z_centers = []
    for depth in ds.z.values:
        voxel_z_centers.append(dem_means - depth)
    voxel_z_centers = np.array(voxel_z_centers)

    # get the resistivity facies for each model cell (nearest neighbor)
    voxel_array = ds[facies_class_variable].sel(x=grid.xcellcenters[0],
                                                y=grid.ycellcenters[:, 0],
                                                method='nearest')
    voxel_zones = np.unique(voxel_array.values[~np.isnan(voxel_array.values)])

    # fill areas of voxel_array that don't have AEM data with zone values
    # representing units in the original MERAS framework
    framework_layer = layers_to_zones(framework_layer_botms_filled,
                                      voxel_z_centers)

    # start framework zone numbers after resistivity facies, +3
    framework_zones = framework_layer + np.nanmax(voxel_array.values) + 3
    voxel_array = voxel_array.values
    voxel_array[np.isnan(voxel_array)] = framework_zones[np.isnan(voxel_array)]

    # put the botm_array together
    layers = voxels_to_layers(voxel_array,
                              z_edges=voxel_z_edges,
                              model_top=dem_means,
                              model_botm=framework_layer_botms_filled,
                              no_data_value=0)

    # add framework zones below the voxel botm_array
    nlay, nrow, ncol = layers.shape
    nlay -= 1  # botm_array includes the model top
    framework_layers = list(range(voxel_array.shape[0], nlay))
    first_framework_zone = framework_zones.min()
    n_framwork_zones = framework_layer_botms_filled.shape[0]
    framework_zone_numbers = np.arange(first_framework_zone,
                                       first_framework_zone + n_framwork_zones)

    # assume that the last len(framework_layers) are sequential
    framework_zones_below = []
    for zone_number in framework_zone_numbers[-len(framework_layers):]:
        framework_zones_below.append(np.ones((nrow, ncol)) * zone_number)
    framework_zones_below = np.array(framework_zones_below)

    # make an array of all zones, including the resisitivity facies
    # and the zones representing the units in the original meras framework
    zone_array = np.vstack([voxel_array, framework_zones_below])

    # plot cross sections
    framework_unit_labels = dict(
        zip(framework_zone_numbers.astype(int), framework_unit_names))
    plot_cross_sections(layers,
                        out_xsection_pdf,
                        property_data=zone_array,
                        voxel_start_layer=0,
                        voxel_zones=voxel_zones,
                        cmap='copper',
                        voxel_cmap='viridis',
                        unit_labels=framework_unit_labels)

    # plot maps showing distribution of zones in each layer
    plot_zone_maps(layers,
                   out_maps_pdf,
                   zones=zone_array,
                   voxel_zones=voxel_zones,
                   unit_labels=framework_unit_labels)

    # write out the layer elevation rasters
    write_raster(os.path.join(layers_folder, 'model_top.tif'),
                 layers[0],
                 xul=grid.xul,
                 yul=grid.yul,
                 dx=grid.delc[0],
                 dy=-grid.delr[0],
                 rotation=0.,
                 proj_str='epsg:5070',
                 nodata=-9999,
                 fieldname='elevation')
    for i, layer in enumerate(layers[1:]):
        write_raster(os.path.join(layers_folder, 'botm{}.tif'.format(i)),
                     layer,
                     xul=grid.xul,
                     yul=grid.yul,
                     dx=grid.delc[0],
                     dy=-grid.delr[0],
                     rotation=0.,
                     crs=grid.crs,
                     nodata=-9999,
                     fieldname='elevation')

    # write out zone arrays (in GeoTiff and text format)
    voxel_start_layer = 0
    voxel_end_layer = voxel_start_layer + voxel_array.shape[0]
    for i, zones2d in enumerate(zone_array):
        np.savetxt(os.path.join(zones_folder, 'res_fac{}.dat'.format(i)),
                   zones2d,
                   fmt='%.0f')
        write_raster(os.path.join(zones_folder,
                                  'rasters/res_fac{}.tif'.format(i)),
                     zones2d,
                     xul=grid.xul,
                     yul=grid.yul,
                     dx=grid.delc[0],
                     dy=-grid.delr[0],
                     rotation=0.,
                     crs=grid.crs,
                     nodata=-9999,
                     fieldname='elevation')
    return layers, zone_array
Пример #7
0
def setup_model_layers(dem_means_raster,
                       facies_classes_netcdf,
                       framework_rasters,
                       modelgrid,
                       facies_class_variable='facies-class',
                       facies_zedge_variable='z-edges',
                       dem_elevation_units='meters',
                       framework_raster_elevation_units='meters',
                       model_length_units='meters',
                       output_folder='.',
                       frac_valid_cells_thresh=0.50,
                       framework_unit_names=None):
    """Generate model layering and property zones from voxel-based zone numbers at uniform depths
    and raster surfaces that represent hydrogeologic contacts. The voxel-based zone numbers may
    represent hydrogeologically different sediments, as determined from an airborne electromagnetic survey, 
    and are assumed to have priority over the raster surfaces. The model top is set at the land surface (DEM). 
    Subsequent cell bottoms beneath the land surface are set based on the voxel depths subtracted from the DEM elevations, 
    as sampled at the grid cell centers. Voxel cells without valid data are filled with zone numbers based
    on their position within the raster surfaces of hydrogeologic contacts. For example, a voxel
    cell that lies above all of the raster surfaces would be assigned a zone number equal to the
    highest voxel-based zone number + 3 (an arbitrary gap in the zone numbering between the voxel data
    and the raster surfaces). Similarly, beneath the lowest voxel cell at each location, grid cells are 
    assigned zone values based on their vertical position relative to the raster surfaces. Below the voxel data,
    botm_array and their bottom elevations correspond to the framework_raster surfaces. The lowermost
    (last) raster surface forms the model bottom.
    

    Parameters
    ----------
    dem_means_raster : str (filepath)
        Raster representing the land surface, at the highest resolution being contemplated for the model.
        Usually this is derived by sampling a higher resolution DEM using zonal statistics, taking
        the mean DEM value for each model cell.
    facies_classes_netcdf : str (filepath)
        NetCDF file with the voxel zone data, with a facies_class_variable that contains the zone numbers. 
    framework_rasters : list of strings (filepaths)
        Raster surfaces describing hydrogelogic contacts surrounding the voxel data.
    modelgrid : mfsetup.MFsetupGrid instance
        Modflow-setup grid instance describing the model grid
    facies_class_variable : str, optional
        Variable in facies_classes_netcdf containing the zone information, 
        by default 'facies-class'
    facies_zedge_variable : str, optional
        Variable in facies_classes_netcdf containing the vertical depth edge information.
        by default 'z-edges'
    dem_elevation_units : str, optional
        Elevation units of dem_means_raster, by default 'meters'
    framework_raster_elevation_units : str, optional
        Elevation units of the framework_rasters, by default 'meters'
    model_length_units : str, optional
        Length units used in the model, by default 'meters'
    output_folder : str, optional
        Location where results will be saved, by default '.'
    frac_valid_cells_thresh : float between 0 and 1, optional
        Layers within facies_classes_netcdf must have at least this fraction
        of valid (not Nan) cells to be included.
    framework_unit_names : list, optional
        Unit names for the framework_rasters, by default None

    Returns
    -------
    botm_array : 3D numpy array
        Array of layer elevations, starting with the model top. 
        (Length equal to the number of botm_array + 1)
    zone_array : 3D numpy array 
        Array of zone numbers, including the values from facies_classes_netcdf,
        followed by a gap of 3 and then the numbers for the framework_rasters.
        For example, if there are 5 zones in facies_classes_netcdf, and 3 raster surfaces
        that intersect cells outside of the voxel data, zones 1-5 would correspond
        to the voxel zones, and zones 8-10 would correspond to hydrogeologic units
        bounded by the framework_rasters.
        
    Notes
    -----
    In addition to the variables returned, the results are written
    to text-array and GeoTiff files within the output_folder.
    
    Two PDFs of figures are also generated- one showing the layering
    in cross sections at regular intervals throughout the model grid, and one
    showing the zones within each layer in plan view.
    """

    # output folders
    figures_folder = os.path.join(output_folder, 'figures')
    layers_folder = os.path.join(output_folder, 'botm_array')
    zones_folder = os.path.join(output_folder, 'zones')
    # zone rasters are saved to zones_folder/rasters
    output_folders = [
        figures_folder,
        layers_folder,
        zones_folder,
        os.path.join(zones_folder, 'rasters'),
    ]
    for folder in output_folders:
        if not os.path.isdir(folder):
            os.makedirs(folder)

    # output PDF with cross sections
    out_xsection_pdf = os.path.join(figures_folder, 'zone_xsections.pdf')
    out_maps_pdf = os.path.join(figures_folder, 'zone_maps.pdf')

    # flopy model grid
    grid = modelgrid
    _, nrow, ncol = modelgrid.shape

    # read in the facies classes and resisitivity values
    # from framework team
    # and verify that the grid is aligned with the nhg
    ds = xr.load_dataset(facies_classes_netcdf)
    # check that cell corners are aligned with NHG
    x_edges = ds.x[:-1] - 0.5 * np.diff(ds.x)
    y_edges = ds.y[:-1] - 0.5 * np.diff(ds.y)
    assert point_is_on_nhg(x_edges, y_edges)

    # read in the dem and framework rasters and fill locations of no data
    # with the next valid elevation above
    filled_framework_layers = rasters_to_grid(
        modelgrid,
        dem_means_raster,
        framework_rasters,
        dem_elevation_units=dem_elevation_units,
        raster_elevation_units=framework_raster_elevation_units,
        dest_elevation_units=model_length_units)
    dem_means = filled_framework_layers[0]
    framework_layer_botms_filled = filled_framework_layers[1:]

    # make AEM framework z edge elevations by subtracting off depths from model top
    voxel_z_edges = []
    # 1D vector of uniform depths
    if len(ds[facies_zedge_variable].values.shape) == 1:
        for depth in ds[facies_zedge_variable].values:
            voxel_z_edges.append(dem_means - depth)

        # elevations of the voxel cell centers
        voxel_z_centers = voxel_z_edges[:-1] + \
            0.5 * np.diff(voxel_z_edges, axis=0)
        nlay = len(voxel_z_centers)
        #depth_centers_3d = np.ones((nlay, nrow, ncol)) * \
        #    np.reshape(depth_centers, (nlay, 1, 1))

    # 3D array of depths (edges) by cell
    else:
        depth_edges = ds[facies_zedge_variable].interp(x=grid.xcellcenters[0],
                                                       y=grid.ycellcenters[:,
                                                                           0],
                                                       method='linear')
        voxel_z_edges = dem_means - depth_edges

        # elevations of the voxel cell centers
        voxel_z_centers = voxel_z_edges.values[:-1] + \
            0.5 * np.diff(voxel_z_edges.values, axis=0)

        # get the fraction of nan values in each layer
        frac_nan = voxel_z_edges.isnull().sum(
            axis=(1, 2)) / (voxel_z_edges[0].size)
        nan_thresh = 1 - frac_valid_cells_thresh
        voxel_z_edges = voxel_z_edges.loc[frac_nan < nan_thresh, :, :]

        # fill NaN values to the east and west with last value in those directions
        voxel_z_edges_filled = voxel_z_edges.ffill(dim='x')
        voxel_z_edges_filled = voxel_z_edges_filled.bfill(dim='x')
        # convert to numpy array so we can use boolean indexing
        voxel_z_edges_filled = voxel_z_edges_filled.values
        min_thickness = 2
        # create a 3D boolean array indicated whether each cell
        # is at least the minimum thickness
        valid = np.vstack(
            (np.ones((1, *voxel_z_edges_filled.shape[1:])).astype(bool),
             np.diff(voxel_z_edges_filled, axis=0) > min_thickness))
        # set bottom edges of cells below the minimum thickness to Nans
        voxel_z_edges_filled[~valid] = np.nan

    #pdf_xs = PdfPages('STM_res_depth_grad_mrvacut_v1_xsections.pdf')
    #for row in np.arange(voxel_z_edges_filled.shape[1])[::50]:
    #    z = voxel_z_edges_filled[:, row, :]
    #    x = grid.xcellcenters[row, :]
    #    x = np.tile(x, (z.shape[0], 1))
    #    fig, ax = plt.subplots()
    #    ax.plot(x.T, z.T)
    #    ax.invert_yaxis()
    #    ax.set_title(f'Row {row}')
    #    pdf_xs.savefig()
    #    plt.close()
    #pdf_xs.close()

    # get the resistivity facies for each model cell (nearest neighbor)
    voxel_array = ds[facies_class_variable].sel(x=grid.xcellcenters[0],
                                                y=grid.ycellcenters[:, 0],
                                                method='nearest')
    voxel_zones = np.unique(voxel_array.values[~np.isnan(voxel_array.values)])

    # fill areas of voxel_array that don't have AEM data with zone values
    # representing units in the original MERAS framework
    framework_layer = layers_to_zones(framework_layer_botms_filled,
                                      voxel_z_centers)

    # start framework zone numbers after resistivity facies, +3
    framework_zones = framework_layer + np.nanmax(voxel_array.values) + 3
    voxel_array = voxel_array.values
    voxel_array[np.isnan(voxel_array)] = framework_zones[np.isnan(voxel_array)]

    # put the botm_array together
    layers = voxels_to_layers(voxel_array,
                              z_edges=voxel_z_edges,
                              model_top=dem_means,
                              model_botm=framework_layer_botms_filled,
                              no_data_value=0)

    # add framework zones below the voxel botm_array
    nlay, nrow, ncol = layers.shape
    nlay -= 1  # botm_array includes the model top
    framework_layers = list(range(voxel_array.shape[0], nlay))
    first_framework_zone = framework_zones.min()
    n_framwork_zones = framework_layer_botms_filled.shape[0]
    framework_zone_numbers = np.arange(first_framework_zone,
                                       first_framework_zone + n_framwork_zones)

    # assume that the last len(framework_layers) are sequential
    framework_zones_below = []
    for zone_number in framework_zone_numbers[-len(framework_layers):]:
        framework_zones_below.append(np.ones((nrow, ncol)) * zone_number)
    framework_zones_below = np.array(framework_zones_below)

    # make an array of all zones, including the resisitivity facies
    # and the zones representing the units in the original meras framework
    zone_array = np.vstack([voxel_array, framework_zones_below])

    # plot cross sections
    framework_unit_labels = dict(
        zip(framework_zone_numbers.astype(int), framework_unit_names))
    plot_cross_sections(layers,
                        out_xsection_pdf,
                        property_data=zone_array,
                        voxel_start_layer=0,
                        voxel_zones=voxel_zones,
                        cmap='copper',
                        voxel_cmap='viridis',
                        unit_labels=framework_unit_labels)

    # plot maps showing distribution of zones in each layer
    plot_zone_maps(layers,
                   out_maps_pdf,
                   zones=zone_array,
                   voxel_zones=voxel_zones,
                   unit_labels=framework_unit_labels)

    # write out the layer elevation rasters
    write_raster(os.path.join(layers_folder, 'model_top.tif'),
                 layers[0],
                 xul=grid.xul,
                 yul=grid.yul,
                 dx=grid.delc[0],
                 dy=-grid.delr[0],
                 rotation=0.,
                 proj_str='epsg:5070',
                 nodata=-9999,
                 fieldname='elevation')
    for i, layer in enumerate(layers[1:]):
        write_raster(os.path.join(layers_folder, 'botm{}.tif'.format(i)),
                     layer,
                     xul=grid.xul,
                     yul=grid.yul,
                     dx=grid.delc[0],
                     dy=-grid.delr[0],
                     rotation=0.,
                     crs=grid.crs,
                     nodata=-9999,
                     fieldname='elevation')

    # write out zone arrays (in GeoTiff and text format)
    voxel_start_layer = 0
    voxel_end_layer = voxel_start_layer + voxel_array.shape[0]
    for i, zones2d in enumerate(zone_array):
        np.savetxt(os.path.join(zones_folder, 'res_fac{}.dat'.format(i)),
                   zones2d,
                   fmt='%.0f')
        write_raster(os.path.join(zones_folder,
                                  'rasters/res_fac{}.tif'.format(i)),
                     zones2d,
                     xul=grid.xul,
                     yul=grid.yul,
                     dx=grid.delc[0],
                     dy=-grid.delr[0],
                     rotation=0.,
                     crs=grid.crs,
                     nodata=-9999,
                     fieldname='elevation')
    return layers, zone_array