# -*- coding: utf-8 -*-
"""Module with a range of general purpose GIS tools, mainly (but not only) consisting of
GDAL routines. 

Some functions in this module will be deprecated in future versions.
"""
import os
from osgeo import osr
from osgeo import gdal
import gzip
import zipfile
import numpy as np
from scipy.interpolate import NearestNDInterpolator, LinearNDInterpolator
from pywapor.general.logger import log
import xarray as xr
import pandas as pd

def ds_remove_except(ds, keep_vars):
    """Remove all variables from a dataset except the variables specified with
    `keep_vars`. Variables that are coordinates are never removed.

    Parameters
    ----------
    ds : xr.Dataset
        Dataset from which to remove variables.
    keep_vars : list
        List of variables to keep.

    Returns
    -------
    xr.Dataset
        Dataset from which variables have been removed.
    """
    keep_vars_coords = list()
    for var in keep_vars:
        if var in list(ds.variables):
            keep_vars_coords += [k for k, v in ds[var].coords.items() if v.size > 0]
    keep_vars_coords = keep_vars + np.unique(keep_vars_coords).tolist()
    drop_vars = [x for x in list(ds.variables) if x not in keep_vars_coords]
    ds = ds.drop_vars(drop_vars)
    return ds

def export_ds_to_tif(ds, vars, base_folder):
    """Export selected `vars` from a xr.Dataset (`ds`) into `base_folder` as geotiffs.

    Parameters
    ----------
    ds : xr.Dataset
        Dataset to export.
    vars : list
        Variables in `ds` to export.
    base_folder : str
        Path to folder in which to store geotiffs.

    Returns
    -------
    dict
        Keys are variable names, values are lists with paths to the generated
        geotiffs.
    """

    all_files = dict()

    if isinstance(base_folder, type(None)):
        base_folder = os.path.split(ds.encoding["source"])[0]
    
    for var in vars:

        if var not in list(ds.variables):
            continue

        if "lat" not in list(ds[var].coords) or "lon" not in list(ds[var].coords):
            continue

        all_files[var] = list()

        folder = os.path.join(base_folder, f"{var}_output")
        
        if not os.path.exists(folder):
            os.makedirs(folder)

        if "source" in ds[var].attrs.keys():
            source = ds[var].source
        else:
            source = "-"

        if "unit" in ds[var].attrs.keys():
            unit = ds[var].unit
        else:
            unit = "-"

        if "time" in list(ds[var].coords):
            iterator = ds.time
            instantaneous = True
        elif "epoch" in list(ds[var].coords):
            iterator = ds.epoch
            instantaneous = False
        else:
            log.warning(f"! --> not exporting {var}")

        for t in iterator:

            if instantaneous:
                tlength = "inst"
                date_str = pd.Timestamp(t.values).strftime("%Y.%m.%d.%H.%M")
                array = ds[var].sel(time = t).values
            else:
                tdelta = ds.epoch_ends.sel(epoch = t) - ds.epoch_starts.sel(epoch = t)
                tlength = tdelta.values.astype('timedelta64[D]').astype(int)
                date_str = pd.Timestamp(ds.epoch_starts.sel(epoch = t).values).strftime("%Y.%m.%d")
                array = ds[var].sel(epoch = t).values

            fn = f"{var.replace('_','-')}_{source}_{unit}_{tlength}_{date_str}.tif"
            fh = os.path.join(folder, fn)

            Save_as_tiff(fh, array, ds.geotransform, ds.projection)

            all_files[var].append(fh)

    return all_files

def select_template(fhs):
    """Given a list of lists with paths to geotiffs, determines which one of 
    all the geotiffs has the highest resolution. Note that this is not based on 
    pixel size, so passing geotiffs with very different domains can give unexpected
    results.

    Parameters
    ----------
    fhs : list
        List of lists with paths to geotiffs.

    Returns
    -------
    str
        First file with the highest resolution.
    xr.Dataset
        Dataset with the required `lat` and `lon` values.
    tuple
        The geotransform of the selected geotiff.
    resolution
        Pixel size in units of the geotiffs projection.
    """
    # Flatten a list-of-lists into a  single list.
    fhs = [val for sublist in fhs for val in sublist]

    # Determine amount of pixels in each file.
    sizes = [gdal.Open(fh).RasterXSize * gdal.Open(fh).RasterYSize for fh in fhs]

    # Find index of file with most pixels.
    idx = np.argmax(sizes)

    # Create resample info.
    example_fh = fhs[idx]
    example_ds = xr.open_dataset(example_fh).isel(band = 0).drop_vars(["band", "spatial_ref"]).rename({"x": "lon", "y": "lat"})
    example_geoinfo = get_geoinfo(example_fh)

    # Calculate pixel size in meters.
    resolution = get_resolution(example_fh)
    log.info(f"--> Resampling resolution is ~{resolution:.0f} meter.")

    return example_fh, example_ds, example_geoinfo, resolution

def get_resolution(example_filepath):
    """Get the average resolution of the given geotiff.

    Parameters
    ----------
    example_filepath : str
        Path to geotiff.

    Returns
    -------
    float
        Pixel size in units of the geotiffs projection.
    """
    ds = gdal.Open(example_filepath)
    geo_ex = ds.GetGeoTransform()
    xsize = ds.RasterXSize
    ysize = ds.RasterYSize
    dlat, dlon = calc_dlat_dlon(geo_ex, xsize, ysize)
    dem_resolution = (np.nanmean(dlon) + np.nanmean(dlat))/2
    return dem_resolution

def reproject_clip(source_fp, dest_fp = None, bb = None,
                    compress = True, dstSRS = "epsg:4326"):
    """Function that calls gdal.Warp(), can be used to clip and reproject 
    a geotiff in one go.

    Parameters
    ----------
    source_fp : str
        Path to file to be warped.
    dest_fp : [type], optional
        Warped output file, overwrites input when `None` given, by default None.
    bb : tuple, optional
        Clip the geotiff the a bounding-box.
        First item is latlim, second item is lonlim, by default None.
    compress : bool, optional
        Compress the data using DEFLATE, by default True.
    dstSRS : str, optional
        Reproject the output to `dstSRS`, by default "epsg:4326".
    """
    options_dict = {"dstSRS": dstSRS}
    
    if not isinstance(bb, type(None)):
        options_dict["outputBounds"] = (bb[1][0], bb[0][0], 
                                        bb[1][1], bb[0][1])

    if compress:
        options_dict["creationOptions"] = ["COMPRESS=DEFLATE", "ZLEVEL=8"]

    if isinstance(dest_fp, type(None)):
        log.debug(f"Overwriting input file ({source_fp}).")
        dest_fp = source_fp

    options = gdal.WarpOptions(**options_dict)

    out = gdal.Warp(dest_fp, source_fp, options = options)
    out.FlushCache()
    out = None

    return dest_fp

def apply_mask(a, indices, axis):
    """Select indices in `a` along an axis.

    Parameters
    ----------
    a : np.ndarray
        Array with one dimension more than `indices`.
    indices : np.ndarray
        Array used to index `a`, should have one dimension fewer than `a`.
    axis : int
        Axis along which to select values from `a`.

    Returns
    -------
    np.ndarray
        Array with same shape as `indices`.
    """
    # https://stackoverflow.com/questions/15469302/numpy-3d-to-2d-transformation-based-on-2d-mask-array
    # TODO replace with np.take(....)?

    magic_index = [np.arange(i) for i in indices.shape]
    magic_index = np.ix_(*magic_index)
    magic_index = magic_index[:axis] + (indices,) + magic_index[axis:]
    return a[magic_index]

def reproj_file(file, template, method):
    """Reprojects and resamples a file and return the output as an np.ndarray.

    Parameters
    ----------
    file : {str | gdal.Dataset}
        File to reproject.
    template : {str | gdal.Dataset}
        File to use a reprojection template.
    method : int
        Interpolation method, see 'pywapor.processing_functions.reproject_dataset_example'
        for more info.

    Returns
    -------
    np.ndarray
        The resamples data.
    """
    ds = reproject_dataset_example(file, template, method = method)
    array = open_as_array(ds)
    return array

def combine_dicts(dicts):
    """Combines dictionaries by appending values for identical keys in a list.

    Parameters
    ----------
    dicts : list
        A list of dictionaries.

    Returns
    -------
    dict
        Dictionary with all the values and keys from the dictionaries passed inside 
        the list `dicts`.
    """
    new_dict = dict()
    for d in dicts:
        for key, value in d.items():
            if key in new_dict.keys():
                new_dict[key].append(value)
            else:
                new_dict[key] = [value]
    return new_dict

def get_geoinfo(template_file):
    """Extract relevant geo information from a geotiff file.

    Parameters
    ----------
    template_file : str
        Path to geotiff file.

    Returns
    -------
    tuple
        Tuple with the geotransform, projection, amount of pixels in x-direction and
        amount of pixels in y-direction.
    """
    if isinstance(template_file, str):
        ds = gdal.Open(template_file)
    elif isinstance(template_file, gdal.Dataset):
        ds = template_file
    geo_ex = ds.GetGeoTransform()
    proj_ex = ds.GetProjection()
    size_x_ex = ds.RasterXSize
    size_y_ex = ds.RasterYSize
    return (geo_ex, proj_ex, size_x_ex, size_y_ex)

def Extract_Data_gz(zip_filename, outfilename):
    """Extract a zip-file and removes the zip-file afterwards.

    Parameters
    ----------
    zip_filename : str
        Path to zip-file.
    outfilename : str
        Path to output.
    """
    # TODO maybe move to CHIRPS?
    with gzip.GzipFile(zip_filename, 'rb') as zf:
        file_content = zf.read()
        save_file_content = open(outfilename, 'wb')
        save_file_content.write(file_content)
    save_file_content.close()
    zf.close()
    os.remove(zip_filename)
    
def Save_as_MEM(data='', geo='', projection=''):
    """Create a gdal.Dataset in memory from a np.ndarray.

    Parameters
    ----------
    data : np.ndarray
        Data to put inside the gdal.Dataset.
    geo : list
        Geotransform
    projection : osr.SpatialReference
        Projection to be used in the gdal.Dataset, by default "WGS84".
    """
    # TODO fix default values '' etc.
    driver = gdal.GetDriverByName("MEM")
    dst_ds = driver.Create('', int(data.shape[1]), int(data.shape[0]), 1,
                           gdal.GDT_Float32)
    srse = osr.SpatialReference()
    if projection == '':
        srse.SetWellKnownGeogCS("WGS84")

    else:
        # TODO Remove try/excepts
        try:
            if not srse.SetWellKnownGeogCS(projection) == 6:
                srse.SetWellKnownGeogCS(projection)
            else:
                try:
                    srse.ImportFromEPSG(int(projection))
                except:
                    srse.ImportFromWkt(projection)
        except:
            try:
                srse.ImportFromEPSG(int(projection))
            except:
                srse.ImportFromWkt(projection)
    dst_ds.SetProjection(srse.ExportToWkt())
    dst_ds.GetRasterBand(1).SetNoDataValue(-9999)
    dst_ds.SetGeoTransform(geo)
    dst_ds.GetRasterBand(1).WriteArray(data)
    return(dst_ds)   
    
def Save_as_tiff(name='', data='', geo='', projection=''):
    """Create a geotiff from a np.ndarray.

    Parameters
    ----------
    nme : str
        Path to output file.
    data : np.ndarray
        Data to put inside the geotiff.
    geo : list
        Geotransform
    projection : osr.SpatialReference
        Projection to be used in the geotiff, by default "WGS84".
    """
    # TODO fix default values '' etc.
    # Change no data values
    data[np.isnan(data)] = -9999

    if not os.path.exists(os.path.split(name)[0]):
        os.makedirs(os.path.split(name)[0])
    
    # save as a geotiff
    driver = gdal.GetDriverByName("GTiff")
    dst_ds = driver.Create(name, int(data.shape[1]), int(data.shape[0]), 1,
                           gdal.GDT_Float32, ['COMPRESS=LZW', 'PREDICTOR=3'])
    srse = osr.SpatialReference()
    if projection == '':
        srse.SetWellKnownGeogCS("WGS84")

    # Set the projection, which can be an EPSG code or a well known GeogCS
    else:
        # TODO get rid of all these try/excepts
        try:
            if not srse.SetWellKnownGeogCS(projection) == 6:
                srse.SetWellKnownGeogCS(projection)
            else:
                try:
                    srse.ImportFromEPSG(int(projection))
                except:
                    srse.ImportFromWkt(projection)
        except:
            try:
                srse.ImportFromEPSG(int(projection))
            except:
                srse.ImportFromWkt(projection)
                
    # Save the tiff file
    dst_ds.SetProjection(srse.ExportToWkt())
    dst_ds.GetRasterBand(1).SetNoDataValue(-9999)
    dst_ds.SetGeoTransform(geo)
    dst_ds.GetRasterBand(1).WriteArray(data)
    dst_ds = None
    
    return()
    
def reproject_MODIS(input_name, epsg_to):
    # TODO move this to pywapor.collect.MODIS
    
    # Define the output name
    name_out = ''.join(input_name.split(".")[:-1]) + '_reprojected.tif'
   
    src_ds = gdal.Open(input_name)
    
    # Define target SRS
    dst_srs = osr.SpatialReference()
    dst_srs.ImportFromEPSG(int(epsg_to))
    dst_wkt = dst_srs.ExportToWkt()
    
    error_threshold = 0.125  # error threshold --> use same value as in gdalwarp
    resampling = gdal.GRA_NearestNeighbour
    
    # Call AutoCreateWarpedVRT() to fetch default values for target raster dimensions and geotransform
    tmp_ds = gdal.AutoCreateWarpedVRT( src_ds,
                                   None, # src_wkt : left to default value --> will use the one from source
                                   dst_wkt,
                                   resampling,
                                   error_threshold )
    dst_ds = gdal.GetDriverByName('GTiff').CreateCopy(name_out, tmp_ds)
    dst_ds = None 

    return(name_out)
    
def clip_data(input_file, latlim, lonlim):
    """Clip the extents of a geotiff.

    Parameters
    ----------
    input_file : {str | gdal.Dataset}
        Dataset to be clipped
    latlim : list
        List with lower and upper latitude boundaries.
    lonlim : list
        List with lower and upper longitude boundaries.

    Returns
    -------
    np.ndarray
        The clipped data.
    tuple
        The new geotransform.
    """
    # TODO merge with reproject_clip
    # TODO remove try/except.
    try:
        if input_file.split('.')[-1] == 'tif':
            dest_in = gdal.Open(input_file)
        else:
            dest_in = input_file
    except:
        dest_in = input_file

    # Open Array
    data_in = dest_in.GetRasterBand(1).ReadAsArray()

    # Define the array that must remain
    Geo_in = dest_in.GetGeoTransform()
    Geo_in = list(Geo_in)
    Start_x = np.max([int(np.floor(((lonlim[0]) - Geo_in[0])/ Geo_in[1])),0])
    End_x = np.min([int(np.ceil(((lonlim[1]) - Geo_in[0])/ Geo_in[1])),int(dest_in.RasterXSize)])

    Start_y = np.max([int(np.floor((Geo_in[3] - latlim[1])/ -Geo_in[5])),0])
    End_y = np.min([int(np.ceil(((latlim[0]) - Geo_in[3])/Geo_in[5])), int(dest_in.RasterYSize)])

    #Create new GeoTransform
    Geo_in[0] = Geo_in[0] + Start_x * Geo_in[1]
    Geo_in[3] = Geo_in[3] + Start_y * Geo_in[5]
    Geo_out = tuple(Geo_in)

    data = np.zeros([End_y - Start_y, End_x - Start_x])

    data = data_in[Start_y:End_y,Start_x:End_x]
    dest_in = None

    return(data, Geo_out)    
    
    
def Extract_Data(input_file, output_folder):
    """Extract a zipfile.

    Parameters
    ----------
    input_file : str
        Path to zip-file.
    output_folder : str
        Path to output folder.
    """
    # TODO merge with pywapor.general.processing_functions.Extract_Data_gz
    # extract the data, used in SRTM and Globcover
    z = zipfile.ZipFile(input_file, 'r')
    z.extractall(output_folder)
    z.close()    

def reproj_ds(source_file, example_file, ndv = -9999, override_dtype = True):
    """Reproject and resample (nearest neighbour) a geotiff to match with
    `example_file`.

    Parameters
    ----------
    source_file : str
        Path to source file.
    example_file : str
        Path to example file.
    ndv : int, optional
        No-data-value to be used in the reprojected dataset, by default -9999
    override_dtype : bool, optional
        Use the same dtype as `example_file` (True) or as `source_file` (False), 
        by default True.

    Returns
    -------
    gdal.Dataset
        The reprojected dataset.
    """
    # TODO merge with reproject_dataset_example
    source_ds = gdal.Open(source_file)

    example_ds = gdal.Open(example_file)
    example_xsize = example_ds.RasterXSize
    example_ysize = example_ds.RasterYSize

    if override_dtype:
        example_dtype = example_ds.GetRasterBand(1).DataType
    else:
        example_dtype = source_ds.GetRasterBand(1).DataType

    mem_drv = gdal.GetDriverByName('MEM')
    dest_ds = mem_drv.Create('', example_xsize, example_ysize, 1, example_dtype)
    dest_ds.SetGeoTransform(example_ds.GetGeoTransform())
    dest_ds.SetProjection(example_ds.GetProjection())
    dest_ds.GetRasterBand(1).Fill(ndv)
    dest_ds.GetRasterBand(1).SetNoDataValue(ndv)

    gdal.ReprojectImage(source_ds,
                        dest_ds,
                        source_ds.GetProjection(),
                        dest_ds.GetProjection(),
                        gdal.GRA_NearestNeighbour)

    return dest_ds

def reproject_dataset_example(dataset, dataset_example, method=1):
    """Reproject and resample a dataset to match with `dataset_example`.

    Parameters
    ----------
    dataset : {str | gdal.Dataset}
        The dataset to be reprojected.
    dataset_example : {str | gdal.Dataset}
        The dataset to be used as a reprojection example.
    method : int, optional
        Select which method to use for resampling, with:
        1 = gdal.GRA_NearestNeighbour,
        2 = gdal.GRA_Bilinear,
        3 = gdal.GRA_Lanczos,
        4 = gdal.GRA_Average,
        5 = gdal.GRA_Cubic,
        6 = gdal.GRA_CubicSpline,
        7 = gdal.GRA_Mode,
        8 = gdal.GRA_Max,
        9 = gdal.GRA_Min,
        10 = gdal.GRA_Med,
        11 = gdal.GRA_Q1,
        12 = gdal.GRA_Q3,
        13 = gdal.GRA_Sum, by default 1.

    Returns
    -------
    gdal.Dataset
        The reprojected dataset.

    """
    # TODO merge with reproj_ds
    # open dataset that must be transformed
    if isinstance(dataset, str):
        g = gdal.Open(dataset)
    elif isinstance(dataset, gdal.Dataset):
        g = dataset
    else:
        raise ValueError
    epsg_from = Get_epsg(g)

    #exceptions
    if epsg_from == 9001:
        epsg_from = 5070

    # open dataset that is used for transforming the dataset
    if isinstance(dataset_example, str):
        gland = gdal.Open(dataset_example)
    elif isinstance(dataset_example, gdal.Dataset):
        gland = dataset_example
    else:
        raise ValueError
    epsg_to = Get_epsg(gland)

    # Set the EPSG codes
    osng = osr.SpatialReference()
    osng.ImportFromEPSG(epsg_to)
    wgs84 = osr.SpatialReference()
    wgs84.ImportFromEPSG(epsg_from)

    # Get shape and geo transform from example
    geo_land = gland.GetGeoTransform()
    col=gland.RasterXSize
    rows=gland.RasterYSize

    # Create new raster
    mem_drv = gdal.GetDriverByName('MEM')
    dest = mem_drv.Create('', col, rows, 1, gdal.GDT_Float32)
    dest.SetGeoTransform(geo_land)
    dest.SetProjection(osng.ExportToWkt())

    # https://gis.stackexchange.com/questions/158503/9999-no-data-value-becomes-0-when-writing-array-to-gdal-memory-file
    ndv = g.GetRasterBand(1).GetNoDataValue()
    band = dest.GetRasterBand(1)
    band.SetNoDataValue(ndv)
    band.Fill(ndv, 0.0)

    # Perform the projection/resampling
    if method == 1:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_NearestNeighbour)
    if method == 2:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Bilinear)
    if method == 3:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Lanczos)
    if method == 4:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Average)
    if method == 5:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Cubic)
    if method == 6:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_CubicSpline)
    if method == 7:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Mode)
    if method == 8:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Max)
    if method == 9:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Min)
    if method == 10:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Med)
    if method == 11:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Q1)
    if method == 12:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Q3)
    if method == 13:
        gdal.ReprojectImage(g, dest, wgs84.ExportToWkt(), osng.ExportToWkt(), gdal.GRA_Sum)
        
    return(dest)

def Get_epsg(g):
    # TODO deprecate
    try:
        # Get info of the dataset that is used for transforming
        g_proj = g.GetProjection()
        Projection=g_proj.split('EPSG","')
        epsg_to=int((str(Projection[-1]).split(']')[0])[0:-1])            
    except:
        epsg_to=4326

    return(epsg_to)

def open_as_array(input):
    """Open dataset as array, replacing no-data-values with np.nan.

    Parameters
    ----------
    input : {str | gdal.Dataset}
        The dataset to open.

    Returns
    -------
    np.ndarray
        The data contained inside `input`.
    """
    if isinstance(input, str):
        ds = gdal.Open(input)
    elif isinstance(input, gdal.Dataset):
        ds = input
    array = ds.GetRasterBand(1).ReadAsArray().astype(float)
    ndv = ds.GetRasterBand(1).GetNoDataValue()
    array[array == ndv] = np.nan
    return array  

def Open_tiff_array(filename='', band=''):
    # TODO deprecate
    f = gdal.Open(filename)
    if f is None:
        print('%s does not exists' %filename)
    else:
        if band == '':
            band = 1
        Data = f.GetRasterBand(band).ReadAsArray()
    return(Data)

def Open_array_info(filename=''):
    # TODO deprecate
    try:
        if filename.split('.')[-1] == 'tif':
            f = gdal.Open(r"%s" %filename)
        else:
            f = filename
    except:
            f = filename       
    try:
        geo_out = f.GetGeoTransform()
        proj = f.GetProjection()
        size_X = f.RasterXSize
        size_Y = f.RasterYSize
        f = None
    except:
        print('%s does not exists' %filename)
        
    return(geo_out, proj, size_X, size_Y)

def gap_filling(dataset, NoDataValue, method = 1):
    # TODO deprecate
    try:
        if dataset.split('.')[-1] == 'tif':
            # Open the numpy array
            data = Open_tiff_array(dataset)
            Save_as_tiff_bool = 1
        else:
            data = dataset
            Save_as_tiff_bool = 0
    except:
        data = dataset
        Save_as_tiff_bool = 0

    # fill the no data values
    if NoDataValue == np.nan:
        mask = ~(np.isnan(data))
    else:
        mask = ~(data==NoDataValue)
    xx, yy = np.meshgrid(np.arange(data.shape[1]), np.arange(data.shape[0]))
    xym = np.vstack( (np.ravel(xx[mask]), np.ravel(yy[mask])) ).T
    data0 = np.ravel( data[:,:][mask] )

    if method == 1:
        interp0 = NearestNDInterpolator( xym, data0 )
        data_end = interp0(np.ravel(xx), np.ravel(yy)).reshape( xx.shape )

    if method == 2:
        interp0 = LinearNDInterpolator( xym, data0 )
        data_end = interp0(np.ravel(xx), np.ravel(yy)).reshape( xx.shape )

    if Save_as_tiff_bool == 1:
        EndProduct=dataset[:-4] + '_GF.tif'

        # collect the geoinformation
        geo_out, proj, size_X, size_Y = Open_array_info(dataset)

        # Save the filled array as geotiff
        Save_as_tiff(name=EndProduct, data=data_end, geo=geo_out, projection=proj)

    else:
        EndProduct = data_end

    return (EndProduct)  
    
def calc_dlat_dlon(geo_out, size_X, size_Y, lat_lon = None):
    """
    Calculated the dimensions of each pixel in meter.

    Parameters
    ----------
    geo_out: list
        Geotransform function of the array.
    size_X: int
        Number of pixels in x-direction.
    size_Y: int
        Number of pixels in y-direction.

    Returns
    -------
    np.ndarray
        Size of every pixel in the y-direction in meters.
    dlon: array
        Size of every pixel in the x-direction in meters.
    """
    if isinstance(lat_lon, type(None)):
        # Create the lat/lon rasters
        lon = np.arange(size_X + 1)*geo_out[1]+geo_out[0] - 0.5 * geo_out[1]
        lat = np.arange(size_Y + 1)*geo_out[5]+geo_out[3] - 0.5 * geo_out[5]
    else:
        lat, lon = lat_lon

    dlat_2d = np.array([lat,]*int(np.size(lon,0))).transpose()
    dlon_2d =  np.array([lon,]*int(np.size(lat,0)))

    # Radius of the earth in meters
    R_earth = 6371000

    # Calculate the lat and lon in radians
    lonRad = dlon_2d * np.pi/180
    latRad = dlat_2d * np.pi/180

    # Calculate the difference in lat and lon
    lonRad_dif = abs(lonRad[:,1:] - lonRad[:,:-1])
    latRad_dif = abs(latRad[:-1] - latRad[1:])

    # Calculate the distance between the upper and lower pixel edge
    a = np.sin(latRad_dif[:,:-1]/2) * np.sin(latRad_dif[:,:-1]/2)
    clat = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))
    dlat = R_earth * clat

    # Calculate the distance between the eastern and western pixel edge
    b = np.cos(latRad[1:,:-1]) * np.cos(latRad[:-1,:-1]) * np.sin(lonRad_dif[:-1,:]/2) * np.sin(lonRad_dif[:-1,:]/2)
    clon = 2 * np.arctan2(np.sqrt(b), np.sqrt(1-b))
    dlon = R_earth * clon

    return(dlat, dlon)