Пример #1
0
def align(src_rst, template_rst, out_rst, verbose=False):
    """ Align a source raster on a template
    
    Args : 
        src_rst (str) : path to the source raster
        template_rst (str) : path to the template raster
        out_rst (str) : path to the output raster
        
    Return :
        out_rst
        
    """
    
    # apply the verbose option
    v_print = custom_print(verbose)
    
    # get template crs and transform 
    with rio.open(template_rst) as tplt, rio.open(src_rst) as src:
        
        transform, width, height = warp.calculate_default_transform(
            src.crs, 
            tplt.crs, 
            tplt.width, 
            tplt.height, 
            *tplt.bounds
        )
        
        kwargs = src.meta.copy()
        
        kwargs.update(
            driver    = 'GTiff',
            height    = height,
            width     = width,
            transform = transform,
            compress  = 'lzw'
        )
        
        #destination
        with rio.open(out_rst, 'w', **kwargs) as dst:
            for i in range(1, dst.count+1):
                warp.reproject(
                    source        = rio.band(src, i),
                    destination   = rio.band(dst, i),
                    src_transform = src.transform,
                    src_crs       = src.crs,
                    dst_transform = transform,
                    dst_crs       = tplt.crs,
                    resampling    = warp.Resampling.bilinear
                )
                
    v_print(f'The raster {src_rst} has been align on {template_rst} in {out_rst}')
                
    return
Пример #2
0
def cutline(src_rst, shape_vector, out_rst, verbose=False):
    """ Cut the raster on a .shp vector file
    
    Args : 
        src_rst (str) : path to the source raster
        shape_vector (str) : path to the cutting shapefile
        out_rst (str) : path to the output raster
        verbose (bool) : wether to display the print info
        
    Return :
        out_rst
        
    """

    # apply the verbose option
    v_print = custom_print(verbose)

    # cut the source raster
    with rio.open(src_rst) as src:

        # get the crs
        crs = src.crs

        # read the shapefile
        gdf = gpd.read_file(shape_vector).to_crs(crs)
        shapes = gdf.geometry

        out_image, out_transform = mask(src,
                                        shapes,
                                        all_touched=True,
                                        crop=True)

        out_meta = src.meta.copy()
        out_meta.update(driver='GTiff',
                        height=out_image.shape[1],
                        width=out_image.shape[2],
                        transform=out_transform,
                        compress='lzw')

    # write the croped image into the dst file
    with rio.open(out_rst, "w", **out_meta) as dst:
        dst.write(out_image)

    v_print(
        f'The raster has been cut to {shape_vector} extends and saved in {out_rst}'
    )

    return
Пример #3
0
def rasterize(src_vector, out_rst, res=30, column=None, verbose=False):
    """ Burns vector geometries into a raster.
    
    Args : 
        src_vector (str) : path to the source vector shapefile
        out_rst (str, optional) : path to the output raster
        res (int, optional) : the resolution of the output raster. If none, the default landsat 7 30m res will be used
        column (str, optional) : the name of the column to use as value in the output raster. default ot the first one        
    """

    # apply the verbose option
    v_print = custom_print(verbose)

    # read the vector data
    gdf = gpd.read_file(src_vector).to_crs("EPSG:4326")

    # identify the column to be burn in the raster
    if not column:
        column = gdf.columns[0]

    # optimize dtype
    dtype = rio.dtypes.get_minimum_dtype(gdf[column])

    # optimize the nodata value to meet the dtype
    fill = np.nan
    if np.issubdtype(dtype, np.integer):
        fill = 0

    # convert the metric resolution into deg (as we work in EPSG 4326)
    # consider the equator approximation : 1° = 111 Km
    res = (res / 111) * (10**(-3))

    out_grid = make_geocube(vector_data=gdf,
                            measurements=[column],
                            resolution=(-res, res),
                            fill=fill)

    # write the column to raster file
    out_grid[column].rio.to_raster(out_rst, dtype=dtype)

    v_print(
        f'The vector geometries have been burn into the raster : {out_rst}')

    return
Пример #4
0
def add_pct(src_rst, colors, out_rst=None, verbose=False):
    """ Add a palette color table to the first band of a raster image
    
    Args : 
        src_rst (str) : path to the source raster (need to be in bytes (unint8))
        colors (str) : path to the color table
        out_rst (str, optional) : path to the output raster, will overwrite the input file if None
        verbose (bool, optional) : "wether the function must output information or not"
        
    Return :
        out_rst
        
    """
    # apply the verbose option
    v_print = custom_print(verbose)
    
    # read the colors palette 
    header = ['value', 'red', 'green', 'blue', 'alpha']
    df = pd.read_csv(colors, names= header).fillna(255,  downcast='infer')
    
    # transform the df into a dictionnary compatible with rasterio
    colormap = {}
    for index, row in df.iterrows():
        colormap[row.value] = (row.red, row.green, row.blue, row.alpha)
    
    # select output file 
    if not out_rst: 
        out_rst = src_rst
        
    # add the pct to the output file 
    with rio.open(src_rst) as src:
        out_meta = src.meta.copy()
        data = src.read()
        
    with rio.open(out_rst, "w", **out_meta) as dst:
        dst.write(data)
        dst.write_colormap(1, colormap)
        
    v_print(f'The palette color table has been added to {out_rst}')
    
    return
Пример #5
0
def grid(src_vector, out_vector, size, verbose=False):
    """ create a grid out of a shapefile and write it into another shapefile
    
    Args : 
        src_vector (str) : path to the source vector shapefile
        out_vector (str) : path to the output vector file
        size (float) : the grid size in meters        
    """

    # apply the verbose option
    v_print = custom_print(verbose)

    # read the vector data in mercator proj
    gdf = gpd.read_file(src_vector).to_crs('EPSG:3857')

    # extract bounds from gdf
    min_lon, min_lat, max_lon, max_lat = gdf.total_bounds

    # compute the longitudes and latitudes top left corner coordinates
    longitudes = np.arange(min_lon, max_lon, size)
    latitudes = np.arange(min_lat, max_lat, size)

    # create the grid geometry
    points = []
    for coords in product(longitudes, latitudes):
        points.append(Point(coords[0], coords[1]))

    # create a buffer grid in lat-long
    grid = gpd.GeoDataFrame({
        'geometry': points
    }, crs='EPSG:3857').buffer(size, cap_style=3)
    grid = gpd.clip(grid, gdf)
    grid = grid.to_crs('EPSG:4326')

    # export as shapefile
    grid.to_file(out_vector)

    v_print(
        f'The vector geometries have been transfor into grid : {out_vector}')

    return out_vector
Пример #6
0
def clump(src_rst, out_rst, band=1, mask_rst=None, verbose=False):
    """ Add spatial coherency to existing classes by combining
adjacent similar classified areas.
    
    Args : 
        src_rst (str) : path to the source raster
        out_rst (str) : path to the output raster
        band (int, optionnal) : use determined band of the image (if not defaulted to the first one)
        mask_rst (str, optional) : use maskfile and process only areas having mask
value >0
        verbose (bool) : wether to display the print info
    """
    # apply the verbose option
    v_print = custom_print(verbose)

    # default to 8 neigbours
    struct = ndi.generate_binary_structure(2, 2)

    with rio.open(src_rst) as src:

        raster = src.read(band)
        meta = src.meta.copy()

    # mask raster if needed
    if mask_rst:
        with rio.open(mask_rst) as src:
            mask = src.read(1)

            raster = (mask != 0) * raster

    # adapt the raster size to optimize memory usage
    dtype = rio.dtypes.get_minimum_dtype(raster)
    raster = raster.astype(dtype)

    # the raster can be non-binary each value need to be clump separately

    # identify the features
    count = np.bincount(raster.flatten())
    features = np.where(count != 0)[0]

    del count

    # init
    offset = 0
    raster_labeled = np.zeros(raster.shape, dtype=raster.dtype)

    # loop in values
    for feature in features[1:]:

        # label the filtered dataset
        label = ndi.label(raster == feature, structure=struct)[0]
        label[label != 0] = offset + label[label != 0]

        # add it to the dataset
        raster_labeled = raster_labeled.astype(label.dtype)
        raster_labeled += label

        # increase coef
        offset = np.amax(raster_labeled)

    # free memory
    del raster

    # adapt the raster size to optimize memory usage
    dtype = rio.dtypes.get_minimum_dtype(raster_labeled)
    raster_labeled = raster_labeled.astype(dtype)
    meta.update(dtype=dtype)

    with rio.open(out_rst, 'w', **meta) as dst:
        dst.write(raster_labeled, 1)

    del raster_labeled

    v_print(f'The raster has been clumped in {out_rst}')

    return
Пример #7
0
def zonal_stat(src_rst,
               mask_vector,
               out_vector,
               measurments=available_stats[:4],
               verbose=False):
    """Compute statistics over value of the source raster for each geometry defined in the mask. results are displayed in a vector file. The user can choose the statistics he want to inclued in the final file
    
    Args : 
        src_rst (str) : path to the source raster
        mask_vector (str) : path to the vector mask 
        out_vector (str) : path to the output vector 
        measurment ([str], optional) : list of the measurment you want to perform. available measurments are :
            - min
            - max
            - mean
            - count
            - sum
            - std
            - median
            - majority
            - minority
            - unique
            - range
            - nodata
            - percentile
        the first 4 are the defaulted values. 'all' key word can also be used to perform all measurments. ['hist'] will perform an histogram of the categorical value of the raster. it will be the only stat performed if it is found in the list
        verbose (bool) : wether or not to display text
    """

    # apply the verbose option
    v_print = custom_print(verbose)

    # get the file parameters
    with rio.open(src_rst) as f:
        crs = f.crs
        nodata = f.nodata

    # open the mask and project it into the raster crs
    gdf = gpd.read_file(mask_vector).to_crs(crs)

    categorical = False
    stats = None
    # filter the measurments

    if measurments:
        if 'hist' in measurments:
            categorical = True
        elif 'all' in measurments:
            stats = available_stats[:len(available_stats) - 2 - 1]
        else:
            tmp = [stat for stat in measurments if stat in available_stats]
            stats = None if len(tmp) == 0 else tmp

    # if categorical I need to fetch the potential values
    if categorical:
        with rio.open(src_rst) as f:

            count = np.bincount(f.read(1).flatten())
            features = np.where(count != 0)[0]

            # remove the nodata value from the feature
            features = features[~np.isin(features, nodata)]

            del count

    # loop over the different geometry
    # longer than using just rasterstats but then I cannot display any evolution informations
    tmp_res = {str(i): []
               for i in features} if categorical else {i: []
                                                       for i in stats}

    with tqdm(total=len(gdf), disable=not verbose) as pbar:
        for index, row in gdf.iterrows():

            #update tqdm
            pbar.update(1)

            # get the geometry coordinates
            left, bottom, right, top = row.geometry.bounds

            with rio.open(src_rst) as f:

                # window inside the extends of the raster
                left = max(left, f.bounds.left)
                top = min(top, f.bounds.top)
                right = min(right, f.bounds.right)
                bottom = max(bottom, f.bounds.bottom)

                # creat the window over the geometry
                tlx, tly = f.index(left, top)
                brx, bry = f.index(right, bottom)
                win = Window.from_slices((tlx, brx), (tly, bry))
                win_transform = f.window_transform(win)

                # read the data in the window
                data = f.read(1, window=win)

                # do a zonal stat on 1 geometry
                zs = rstats.zonal_stats(
                    [row.geometry],  #there is only one geometry 
                    data,
                    affine=win_transform,
                    nodata=nodata,
                    all_touched=True,
                    stats=stats,
                    categorical=categorical)[0]

                # free memory
                del data

            # for categorical measurments I need to transform the index in int
            if categorical:
                zs = {str(int(i)): zs[i] for i in zs}

            # fill the dictionary
            for idx in tmp_res:
                val = zs[idx] if idx in zs.keys() else 0
                tmp_res[idx].append(val)

    # create the result geodataframe
    stat_gdf = gpd.GeoDataFrame(
        tmp_res,
        geometry=gdf.geometry,
        columns=[str(i) for i in tmp_res] +
        ['geometry'],  # geopandas refuse int as column key
        crs=crs)

    # save it
    stat_gdf.to_file(out_vector)

    return