## LSDMap_GDALIO.py
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## These functions are tools to deal with rasters
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## SMM
## 26/07/2014
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#from __future__ import absolute_import, division, print_function, unicode_literals
from __future__ import absolute_import, division, print_function

import osgeo.gdal as gdal
import osgeo.gdal_array as gdal_array
import numpy as np
from osgeo import osr
from osgeo import ogr
import os
from os.path import exists
from osgeo.gdalconst import GA_ReadOnly
import rasterio as rio
from lsdviztools.lsdplottingtools import lsdmap_basicplotting as bm
import utm

#==============================================================================
def getNoDataValue(rasterfn):
    """This gets the nodata value from the raster

    Args:
        rasterfn (str): The filename (with path and extension) of the raster

    Returns:
        float: nodatavalue; the nodata value

    Author: SMM
    """
    raster = gdal.Open(rasterfn)
    band = raster.GetRasterBand(1)
    return band.GetNoDataValue()
#==============================================================================

#==============================================================================
def setNoDataValue(rasterfn):
    """This sets the nodata value from the raster

    Args:
        rasterfn (str): The filename (with path and extension) of the raster

    Returns:
        None

    Author: SMM
    """

    raster = gdal.Open(rasterfn)
    band = raster.GetRasterBand(1)
    return band.SetNoDataValue()
#==============================================================================

#==============================================================================
def GetUTMMaxMin(FileName):
    """This gets the minimum and maximum UTM values.

    *WARNING* it assumes raster is already projected into UTM, and is in ENVI format! It reads from an ENVI header file.

    Args:
        FileName (str): The filename (with path and extension) of the raster

    Returns:
        float: The cell size in metres
        float: The X minimum (easting) in metres
        float: The X maximum (easting) in metres
        float: The Y minimum (northing) in metres
        float: The Y maximum (northing) in metres

    Author: SMM
    """


    if exists(FileName) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')

    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)
    CellSize = GeoT[1]
    XMin = GeoT[0]
    XMax = XMin+CellSize*xsize

    YMax = GeoT[3]
    YMin = YMax-CellSize*ysize

    return CellSize,XMin,XMax,YMin,YMax
#==============================================================================

#==============================================================================
# Gets the pixel area, assumes units are projected
#==============================================================================
def GetPixelArea(FileName):
    """Gets the area in m^2 of the pixels

    Args:
        rasterfn (str): The filename (with path and extension) of the raster

    Returns:
        float: Pixel_area (float): The area of each pixel

    Author: SMM
    """

    if exists(FileName) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')

    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)
    CellSize = GeoT[1]

    return CellSize*CellSize
#==============================================================================


#==============================================================================
# this takes rows and columns of minium and maximum values and converts them
# to UTM
def GetUTMMaxMinFromRowsCol(FileName,x_max_col,x_min_col,y_max_row,y_min_row):
    """This gets the minimum and maximum UTM values but you give it the row and column numbers.

    Note:
        This assumes raster is already projected into UTM, and is in ENVI format! It reads from an ENVI header file.

    Args:
        FileName (str): The filename (with path and extension) of the raster
        x_max_col (int): The column to use as the maximum
        x_min_col (int): The column to use as the minimum
        y_max_row (int): The row to use as the maximum
        y_min_row (int): The row to use as the minimum

    Returns:

        float: The X maximum (easting) in metres
        float: The X minimum (easting) in metres
        float: The Y maximum (northing) in metres
        float: The Y minimum (northing) in metres

    Author: SMM
    """


    if exists(FileName) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')

    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)
    CellSize = GeoT[1]
    XMin = GeoT[0]

    YMax = GeoT[3]
    YMin = YMax-CellSize*ysize

    xmax_UTM = XMin+x_max_col*CellSize
    xmin_UTM = XMin+x_min_col*CellSize

    # need to be careful with the ymax_UTM since the rows go from the top
    # but the header index is to bottom corner

    print("yll: "+str(YMin)+" and nrows: " +str(ysize) + " dx: "+str(CellSize))

    ymax_from_bottom = ysize-y_min_row
    ymin_from_bottom = ysize-y_max_row
    ymax_UTM = YMin+ymax_from_bottom*CellSize
    ymin_UTM = YMin+ymin_from_bottom*CellSize

    return xmax_UTM,xmin_UTM,ymax_UTM,ymin_UTM
#==============================================================================

#==============================================================================
# This gets the x and y vectors of the data
#==============================================================================
def GetLocationVectors(FileName):
    """This gets a vector of the x and y locations of the coordinates

    Note:
        This assumes raster is already projected into UTM, and is in ENVI format! It reads from an ENVI header file.

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        float: A vector of the x locations (eastings)
        float: A vector of the y locations (northings)

    Author: SMM
    """



    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)

    CellSize,XMin,XMax,YMin,YMax = GetUTMMaxMin(FileName)



    x_vec = np.arange(XMin,XMax,CellSize)
    y_vec = np.arange(YMin,YMax,CellSize)

    return x_vec,y_vec
#==============================================================================




#==============================================================================
# This gets the extent of the raster
def GetRasterExtent(FileName):
    """This gets a vector of the minimums and maximums of the coordinates

    Note:
        This assumes raster is already projected into UTM, and is in ENVI format! It reads from an ENVI header file.

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        float: A vector that contains

            * extent[0]: XMin
            * extent[1]: XMax
            * extent[2]: YMin
            * extent[3]: YMax

    Author: SMM
    """
    CellSize,XMin,XMax,YMin,YMax = GetUTMMaxMin(FileName)
    extent = [XMin,XMax,YMin,YMax]
    return extent

#==============================================================================
# Function to read the original file's projection:
def GetGeoInfo(FileName):
    """This gets information from the raster file using gdal

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        float: A vector that contains:
            * NDV: the nodata values
            * xsize: cellsize in x direction
            * ysize: cellsize in y direction
            * GeoT: the tranform (a string)
            * Projection: the Projection (a string)
            * DataType: The type of data (an int explaing the bits of each data element)

    Author: SMM
    """


    if exists(FileName) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')


    SourceDS = gdal.Open(FileName, gdal.GA_ReadOnly)
    if SourceDS == None:
        raise Exception("Unable to read the data file")

    NDV = SourceDS.GetRasterBand(1).GetNoDataValue()
    xsize = SourceDS.RasterXSize
    ysize = SourceDS.RasterYSize
    GeoT = SourceDS.GetGeoTransform()
    Projection = osr.SpatialReference()
    Projection.ImportFromWkt(SourceDS.GetProjectionRef())
    DataType = SourceDS.GetRasterBand(1).DataType
    DataType = gdal.GetDataTypeName(DataType)

    return NDV, xsize, ysize, GeoT, Projection, DataType
#==============================================================================

#==============================================================================
# This gets the UTM zone, if it exists
def GetUTMEPSG(FileName):
    """Uses GDAL to get the EPSG string from the raster.

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        str: The EPSG string

    Author: SMM
    """
    if exists(FileName) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')

    # see if the file exists and get the dataset
    SourceDS = gdal.Open(FileName, gdal.GA_ReadOnly)
    if SourceDS == None:
        raise Exception("Unable to read the data file")

    EPSG_string = 'NULL'

    # get the projection
    print("Let me get that projection for you")
    print("In this function I will extract the UTM zone")
    prj=SourceDS.GetProjection()
    srs=osr.SpatialReference(wkt=prj)

    if srs.IsProjected:
        print("The dataset is projected.")
        #print("Trying projcs")
        #print(str(srs.GetAttrValue(str('PROJCS'),0)))
        #print(srs.GetAttrValue(str('projcs')))
        proj_str = srs.GetAttrValue(str('projcs'))
        print("The projection string is: "+proj_str)

        print(proj_str)


        if proj_str != None:



            # extract the UTM information
            if "UTM Zone" in proj_str:
                print("Found the string UTM zone")

                first_split = proj_str.split(',')
                first_half = first_split[0]
                second_half = first_split[1]
                if "Northern" in second_half:
                    N_or_S = "N"
                else:
                    N_or_S = "S"
                second_split = first_half.split(' ')
                zone = second_split[2]


            elif "UTM zone" in proj_str:

                print("This seems to be from the new gdal version")
                first_split = proj_str.split(' ')
                zone_str = first_split[-1]
                print("Zone string is: "+zone_str)
                zone = zone_str[:-1]
                print("The zone is: "+zone)

                if zone_str[-1]=="S":
                    N_or_S = "S"
                else:
                    N_or_S = "N"

                print("And the hemisphere is: "+N_or_S)


            elif "_Hemisphere" in proj_str:
                if "Southern" in proj_str:
                    N_or_S = "S"
                else:
                    N_or_S = "N"
                proj_split = proj_str.split('_')
                zone = proj_split[2]


            else:

                proj_split = proj_str.split('_')
                zone = proj_split[-1]

                N_or_S = zone[-1]
                zone = zone[:-1]


            # adding some logic for zones < 10
            if len(zone) < 2:
                zone = '0'+zone

            EPSG_string = 'epsg:'
            if N_or_S == 'S':
                EPSG_string = EPSG_string+'327'+zone
            else:
                EPSG_string = EPSG_string+'326'+zone
            print("The EPSG string is: "+EPSG_string)
    else:
        raise Exception("This is not a projected coordinate system!")



    print(EPSG_string)
    return EPSG_string


#==============================================================================
# Function to read the original file's projection:
def GetNPixelsInRaster(FileName):
    """This gets the total number of pixels in the raster

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        int: The total number of pixels

    Author: SMM
    """

    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)

    return xsize*ysize

#==============================================================================

#==============================================================================
# Function to read the original file's projection:
def CheckNoData(FileName):
    """This looks through the head file of an ENVI raster and if it doesn't find the nodata line it rewrites the file to include the nodata line.

    Args:
        FileName (str): The filename (with path and extension) of the raster.

    Return:
        int: The total number of pixels (although what it is really doing is updating the header file. The return is just to check if it is working and yes I know this is stupid. )

    Author: SMM
    """


    if exists(FileName) is False:
        raise Exception('[Errno 2] No such file or directory: \'' + FileName + '\'')

    # read the file, and check if there is a no data value
    SourceDS = gdal.Open(FileName, gdal.GA_ReadOnly)
    if SourceDS == None:
        raise Exception("Unable to read the data file")
    NoDataValue = SourceDS.GetRasterBand(1).GetNoDataValue()

    print("In the check nodata routine. Nodata is: ")
    print(NoDataValue)

    if NoDataValue == None:
        print("This raster does not have no data. Updating the header file")
        header_name = FileName[:-4]
        header_name = header_name+".hdr"

        # read the header
        if exists(header_name) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + header_name + '\'')
        else:
            this_file = open(header_name, 'r')
            lines = this_file.readlines()
            lines.append("data ignore value = -9999")
            this_file.close()

            this_file = open(header_name, 'w')
            for item in lines:
                this_file.write("%s" % item)        # no newline since a newline command character comes with the lines

            this_file.close()

    NDV, xsize, ysize, GeoT, Projection, DataType = GetGeoInfo(FileName)

    return xsize*ysize

#==============================================================================


#==============================================================================
def ReadRasterArrayBlocks(raster_file,raster_band=1):
    """This reads a raster file (from GDAL) into an array. The "blocks" bit makes it efficient.
    Args:
        FileName (str): The filename (with path and extension) of the raster.
        raster_band (int): the band of the raster (almost all uses with LSDTopoTools will have a 1 band raster)

    Return:
        np.array: A numpy array with the data from the raster.

    Author: SMM
    """


    if exists(raster_file) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + raster_file + '\'')

    dataset = gdal.Open(raster_file, GA_ReadOnly )
    if dataset == None:
        raise Exception("Unable to read the data file")

    band = dataset.GetRasterBand(raster_band)
    NoDataValue = dataset.GetRasterBand(1).GetNoDataValue()

    block_sizes = band.GetBlockSize()
    x_block_size = block_sizes[0]
    y_block_size = block_sizes[1]

    #If the block y size is 1, as in a GeoTIFF image, the gradient can't be calculated,
    #so more than one block is used. In this case, using8 lines gives a similar
    #result as taking the whole array.
    if y_block_size < 8:
        y_block_size = 8

    xsize = band.XSize
    ysize = band.YSize

    print("xsize: " +str(xsize)+" and y size: " + str(ysize))

    max_value = band.GetMaximum()
    min_value = band.GetMinimum()

    # now initiate the array
    data_array = np.zeros((ysize,xsize))

    #print "data shape is: "
    #print data_array.shape

    if max_value == None or min_value == None:
        stats = band.GetStatistics(0, 1)
        max_value = stats[1]
        min_value = stats[0]

    for i in range(0, ysize, y_block_size):
        if i + y_block_size < ysize:
            rows = y_block_size
        else:
            rows = ysize - i

        for j in range(0, xsize, x_block_size):
            if j + x_block_size < xsize:
                cols = x_block_size
            else:
                cols = xsize - j

            # get the values for this block
            values = band.ReadAsArray(j, i, cols, rows)

            # move these values to the data array
            data_array[i:i+rows,j:j+cols] = values

    print("NoData is:", NoDataValue)
    if NoDataValue is not None:
        nodata_mask = data_array == NoDataValue
        data_array[nodata_mask] = np.nan

    return data_array
#==============================================================================

#==============================================================================
def ReadRasterArrayBlocks_numpy(raster_file,raster_band=1):
    """
    This reads a raster file into an array using numpy. The "blocks" bit makes it efficient.
    Args:
        FileName (str): The filename (with path and extension) of the raster.
        raster_band (int): the band of the raster (almost all uses with LSDTopoTools will have a 1 band raster) Doesn't work yet for more than one band.

    Return:
        np.array: A numpy array with the data from the raster.

    Author: SMM
    """

    print("I will now use numpy.fromfile to load your raster, I still need to be tested, If something goes wrong switch back to the classic method by switching NFF_opti = False. Classic method = unefficient.")
    if exists(raster_file) is False:
            raise Exception('[Errno 2] No such file or directory: \'' + raster_file + '\'')

    with open(raster_file[:-3]+"hdr","r") as hdr_file:
        print("I am opening your raster info")
        no_data_hdr = -9999 # setting the default noData in case there is no value setted in the hdr file
        for line in hdr_file:
            #testing the data type
            if(line[0:9] == "data type"):
                info_dtype = line[-2:]
                info_dtype = int(info_dtype)
            else:
                if(line[0:8] == "map info"):
                    info = line[12:-2]
                    info = info.split(",")
                    x_min = float(info[3])
                    y_max = float(info[4])
                    x_res = float(info[5])
                    y_res = float(info[6])
                    utm_zone = int(info[7])
                    utm_hemisphere = info[8]
                else:
                    if(line[0:7] == "samples"):
                        num_col = line.replace(" ","").split("=")[1]
                        print("there are " + str(num_col) + " columns")
                        num_col = int(num_col)
                    else:
                        if(line[0:5] == "lines"):
                            num_lines = line.replace(" ","").split("=")[1]
                            print("there are " + str(num_lines) + " lines")
                            num_lines = int(num_lines)
                        else:
                            if(line[0:17] == "data ignore value"):
                                no_data_hdr = line.replace(" ","").split("=")[1]
                                print("No data value is: " + str(no_data_hdr) )
                                no_data_hdr = float(no_data_hdr)


        #The type of data representation:
        #1 = Byte: 8-bit unsigned integer
        #2 = Integer: 16-bit signed integer
        #3 = Long: 32-bit signed integer
        #4 = Floating-point: 32-bit single-precision
        #5 = Double-precision: 64-bit double-precision floating-point
        #6 = Complex: Real-imaginary pair of single-precision floating-point
        #9 = Double-precision complex: Real-imaginary pair of double precision floating-point
        #12 = Unsigned integer: 16-bit
        #13 = Unsigned long integer: 32-bit
        #14 = 64-bit long integer (signed)
        #15 = 64-bit unsigned long integer (unsigned)

    if(info_dtype == 1):
        data_type = np.dtype('uint8')
    else:
        if(info_dtype == 2):
            data_type = np.dtype('int16')
        else:
            if(info_dtype == 3):
                data_type = np.dtype('int32')
            else:
                if(info_dtype == 4):
                    data_type = np.dtype('Float32')
                else:
                    if(info_dtype == 5):
                        data_type = np.dtype('Float64')
                    else:
                        if(info_dtype == 12):
                            data_type = np.dtype('uint16')
    print("your data type is "  + str(data_type))
    # Alright now loading and converting the data
    print("I am now ingesting your raster")
    data_array = np.fromfile(raster_file,data_type).reshape(num_lines,num_col)
    if(info_dtype in [1,2,3,12]):
        data_array = data_array.astype(float)
    print("I nailed it")
    x_max = x_min + x_res*num_col
    y_min = y_max - y_res*num_lines
    print("I am returning the raster array and info")
    NoDataValue = no_data_hdr
    if NoDataValue is not None:
        data_array[data_array == NoDataValue] = np.nan


    return data_array
#==============================================================================

#==============================================================================
def array2raster(rasterfn,newRasterfn,array,driver_name = "ENVI", noDataValue = -9999):
    """Takes an array and writes to a GDAL compatible raster. It needs another raster to map the dimensions.

    Args:
        FileName (str): The filename (with path and extension) of a raster that has the same dimensions as the raster to be written.
        newRasterfn (str): The filename (with path and extension) of the new raster.
        array (np.array): The array to be written
        driver_name (str): The type of raster to write. Default is ENVI since that is the LSDTOpoTools format
        noDataValue (float): The no data value

    Return:
        np.array: A numpy array with the data from the raster.

    Author: SMM
    """

    raster = gdal.Open(rasterfn)
    geotransform = raster.GetGeoTransform()
    originX = geotransform[0]
    originY = geotransform[3]
    pixelWidth = geotransform[1]
    pixelHeight = geotransform[5]
    cols = raster.RasterXSize
    rows = raster.RasterYSize


    driver = gdal.GetDriverByName(driver_name)
    outRaster = driver.Create(newRasterfn, cols, rows, 1, gdal.GDT_Float32)
    outRaster.SetGeoTransform((originX, pixelWidth, 0, originY, 0, pixelHeight))
    outRaster.GetRasterBand(1).SetNoDataValue( noDataValue )
    outband = outRaster.GetRasterBand(1)
    outband.WriteArray(array)
    outRasterSRS = osr.SpatialReference()
    outRasterSRS.ImportFromWkt(raster.GetProjectionRef())
    outRaster.SetProjection(outRasterSRS.ExportToWkt())
    outband.FlushCache()
    raster=None
    outRaster=None

    # Get the raster prefix
    SplitRasterfile = newRasterfn.split(".")
    RasterPrefix = ".".join(SplitRasterfile[:-1])
    hdrname = RasterPrefix+".hdr"
    #print("The raster prefix is: "+RasterPrefix)

    if driver_name == "ENVI":

        with open(hdrname,"a") as f:
            #print("Appending data div to "+hdrname)
            f.write('data ignore value = '+str(noDataValue)+"\n")


#==============================================================================


def RasterDifference(RasterFile1, RasterFile2, raster_band=1, OutFileName="Test.outfile", OutFileType="ENVI"):
    """
    Takes two rasters of same size and subtracts second from first,
    e.g. Raster1 - Raster2 = raster_of_difference
    then writes it out to file
    """

    Raster1 = gdal.Open(RasterFile1)
    Raster2 = gdal.Open(RasterFile2)

    print("RASTER 1: ")
    print(Raster1.GetGeoTransform())
    print(Raster1.RasterCount)
    print(Raster1.GetRasterBand(1).XSize)
    print(Raster1.GetRasterBand(1).YSize)
    print(Raster1.GetRasterBand(1).DataType)

    print("RASTER 2: ")
    print(Raster2.GetGeoTransform())
    print(Raster2.RasterCount)
    print(Raster2.GetRasterBand(1).XSize)
    print(Raster2.GetRasterBand(1).YSize)
    print(Raster2.GetRasterBand(1).DataType)

    raster_array1 = np.array(Raster1.GetRasterBand(raster_band).ReadAsArray())
    raster_array2 = np.array(Raster2.GetRasterBand(raster_band).ReadAsArray())

    assert(raster_array1.shape == raster_array2.shape )
    print("Shapes: ", raster_array1.shape, raster_array2.shape)

    difference_raster_array = raster_array1 - raster_array2

#    import matplotlib.pyplot as plt
#
#    plt.imshow(difference_raster_array)
#

    driver = gdal.GetDriverByName(OutFileType)

    dsOut = driver.Create(OutFileName,
                          Raster1.GetRasterBand(1).XSize,
                          Raster1.GetRasterBand(1).YSize,
                          1,
                          gdal.GDT_Float32)
                          #Raster1.GetRasterBand(raster_band).DataType)


    gdal_array.CopyDatasetInfo(Raster1,dsOut)
    bandOut = dsOut.GetRasterBand(1)
    gdal_array.BandWriteArray(bandOut, difference_raster_array)

#==============================================================================
def PolygoniseRaster(DataDirectory, RasterFile, OutputShapefile='polygons'):
    """
    This function takes in a raster and converts to a polygon shapefile using rasterio
    from https://gis.stackexchange.com/questions/187877/how-to-polygonize-raster-to-shapely-polygons/187883#187883?newreg=8b1f507529724a8488ce4789ba787363

    Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster
        OutputShapefile (str): the name of the output shapefile WITHOUT EXTENSION. Default = 'polygons'

    Returns:
        Dictionary where key is the raster value and the value is a shapely polygon

    Author: FJC
    """
    # import modules
    import rasterio
    from rasterio.features import shapes
    from shapely.geometry import shape, Polygon, mapping
    import fiona

    # define the mask
    #mask = None
    raster_band = 1

    # get raster no data value
    NDV = getNoDataValue(DataDirectory+RasterFile)

    # load in the raster using rasterio
    with rasterio.open(DataDirectory+RasterFile) as src:
        image = src.read(raster_band, masked=False)

        msk = src.read_masks(1)

        results = (
        {'properties': {'raster_val': v}, 'geometry': s}
        for i, (s, v)
        in enumerate(
            shapes(image, mask=msk, transform=src.transform)))

    # define shapefile attributes
    # crs = src.crs.wkt
    # print (crs)
    print("Let me grab the coordinate reference system.")
    crs = GetUTMEPSG(DataDirectory+RasterFile)
    schema = {'geometry': 'Polygon',
              'properties': { 'ID': 'float'}}



    # This is necessary to filter the basin results
    geoms = list(results)
    #print("Geom size is: "+str(len(geoms)))

    filtered_geoms = {}
    area_dict = {}
    for f in geoms:
        this_shape = Polygon(shape(f['geometry']))
        this_val = float(f['properties']['raster_val'])
        #print("ID is: "+str(this_val))
        this_area = this_shape.area
        if this_val in filtered_geoms.keys():
            print("Whoops. Found a repeated ID. Getting rid of the smaller one.")
            if area_dict[this_val] < this_area:
                filtered_geoms[this_val] = f
                area_dict[this_val] = this_area
                print("Found a repeated ID. Keeping the one with area of "+str(this_area))
            else:
                print("Keeping the initial ID.")
        else:
            filtered_geoms[this_val] = f
            area_dict[this_val] = this_area

    new_geoms = []
    for key,item in filtered_geoms.items():
        this_shape = Polygon(shape(item['geometry']))
        this_val = float(item['properties']['raster_val'])
        #print("ID is: "+str(this_val))
        this_area = this_shape.area
        #print("Area is: "+str(this_area))
        new_geoms.append(item)
    #print("Geom size is: "+str(len(new_geoms)))

    # transform results into shapely geometries and write to shapefile using fiona
    PolygonDict = {}
    with fiona.open(DataDirectory+OutputShapefile, 'w', crs=crs, driver='ESRI Shapefile', schema=schema) as output:
        for f in new_geoms:
            this_shape = Polygon(shape(f['geometry']))
            this_val = float(f['properties']['raster_val'])
            print("ID is: "+str(this_val))
            if this_val != NDV: # remove no data values
                output.write({'geometry': mapping(this_shape), 'properties':{'ID': this_val}})
            PolygonDict[this_val] = this_shape

    return PolygonDict

#==============================================================================
def PolygoniseRasterMerge(DataDirectory, RasterFile, OutputShapefile='polygons'):
    """
    This function takes in a raster and converts to a polygon shapefile using rasterio
    from https://gis.stackexchange.com/questions/187877/how-to-polygonize-raster-to-shapely-polygons/187883#187883?newreg=8b1f507529724a8488ce4789ba787363

    This version recognises where there are multiple polygons with the same key and merges
    them to a MultiPolygon using cascaded_union

    Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster
        OutputShapefile (str): the name of the output shapefile WITHOUT EXTENSION. Default = 'polygons'

    Returns:
        Dictionary where key is the raster value and the value is a shapely polygon

    Author: FJC
    """
    # import modules
    import rasterio
    from rasterio.features import shapes
    from shapely.geometry import shape, Polygon, mapping
    from shapely.ops import cascaded_union
    import fiona

    # define the mask
    #mask = None
    raster_band = 1

    # get raster no data value
    NDV = getNoDataValue(DataDirectory+RasterFile)

    # load in the raster using rasterio
    with rasterio.open(DataDirectory+RasterFile) as src:
        image = src.read(raster_band, masked=False)

        msk = src.read_masks(1)

        results = (
        {'properties': {'raster_val': v}, 'geometry': s}
        for i, (s, v)
        in enumerate(
            shapes(image, mask=msk, transform=src.transform)))

    # define shapefile attributes
    # crs = src.crs.wkt
    # print (crs)
    crs = GetUTMEPSG(DataDirectory+RasterFile)
    schema = {'geometry': 'Polygon',
              'properties': { 'ID': 'float'}}



    # transform results into shapely geometries and write to shapefile using fiona
    geoms = list(results)
    PolygonDict = {}
    with fiona.open(DataDirectory+OutputShapefile, 'w', crs=crs, driver='ESRI Shapefile', schema=schema) as output:
        for f in geoms:
            this_shape = Polygon(shape(f['geometry']))
            this_val = float(f['properties']['raster_val'])
            if this_val in PolygonDict:
                Polygons = [this_shape, PolygonDict[this_val]]
                this_shape = cascaded_union(Polygons)
            if this_val != NDV: # remove no data values
                output.write({'geometry': mapping(this_shape), 'properties':{'ID': this_val}})

            PolygonDict[this_val] = this_shape

    return PolygonDict


def CreateShapefileOfRasterFootprint(DataDirectory, RasterFile):
    """
    This function takes a raster and creates a shapefile that is the footprint
    of the raster. Used for plotting the raster footprint on regional maps using
    basemap.
    Variously put together from:
    http://osgeo-org.1560.x6.nabble.com/gdal-dev-Creating-a-simple-shapefile-with-ogr-td3749101.html


    Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster

    Returns:
        Shapefile of the raster footprint

    Author: SMM

    Date: 23/01/2018
    """

    print("Trying to create a shapefile.")
    print("The Data directory is: "+DataDirectory+ " and the raster is: "+ RasterFile)
    driver_name = "ESRI shapefile"
    driver = ogr.GetDriverByName(driver_name)

    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]

    # get the espg of the raster
    FullFilename = DataDirectory+RasterFile
    ESPG_this_raster = GetUTMEPSG(FullFilename)
    ESPG_this_raster = str(ESPG_this_raster)
    print("The raster has coordinate of: "+ESPG_this_raster)
    ESPG_this_raster_split = ESPG_this_raster.split(":")
    ESPG_this_raster = ESPG_this_raster_split[-1]
    print ("This ESPG is: "+str(ESPG_this_raster))

    # Get extent of raster
    [xmin,xmax,ymin,ymax] = GetRasterExtent(FullFilename)

    # Create ring
    ring = ogr.Geometry(ogr.wkbLinearRing)
    ring.AddPoint(xmin, ymin)
    ring.AddPoint(xmin, ymax)
    ring.AddPoint(xmax, ymax)
    ring.AddPoint(xmax, ymin)
    ring.AddPoint(xmin, ymin)

    # Create polygon
    poly = ogr.Geometry(ogr.wkbPolygon)
    poly.AddGeometry(ring)

    # Create a coordinate transformation
    source = osr.SpatialReference()
    source.ImportFromEPSG(int(ESPG_this_raster))

    target = osr.SpatialReference()
    target.ImportFromEPSG(4326)

    transform = osr.CoordinateTransformation(source, target)

    # now transformt the polygon
    poly.Transform(transform)

    # see what you got
    #print("The polygon is:")
    #print(poly.ExportToWkt())

    # create the data source
    OutFileName = DataDirectory+RasterPrefix+"_footprint.shp"
    print("The output shapefile is: "+OutFileName)
    datasource = driver.CreateDataSource(OutFileName)


    # create the layer
    layer = datasource.CreateLayer(OutFileName, target, ogr.wkbPolygon)
    feature = ogr.Feature(layer.GetLayerDefn())
    feature.SetGeometry(poly)
    layer.CreateFeature(feature)

    # Clean up
    feature.Destroy()
    datasource.Destroy()


def GetCentreAndExtentOfRaster(DataDirectory, RasterFile):
    """
    This function takes a raster and returns the centrepoint and the extent in both degrees and metres.

    Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster

    Returns:
        The lat-long of the centrepoint, the x-y- extent in both degrees and metres

    Author: SMM

    Date: 01/02/2018
    """

    print("Trying to create a shapefile.")
    print("The Data directory is: "+DataDirectory+ " and the raster is: "+ RasterFile)
    driver_name = "ESRI shapefile"
    driver = ogr.GetDriverByName(driver_name)

    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]

    # get the espg of the raster
    FullFilename = DataDirectory+RasterFile
    ESPG_this_raster = GetUTMEPSG(FullFilename)
    ESPG_this_raster = str(ESPG_this_raster)
    print("The raster has coordinate of: "+ESPG_this_raster)
    ESPG_this_raster_split = ESPG_this_raster.split(":")
    ESPG_this_raster = ESPG_this_raster_split[-1]
    print ("This ESPG is: "+str(ESPG_this_raster))

    # Get extent of raster
    [xmin,xmax,ymin,ymax] = GetRasterExtent(FullFilename)
    xproj_extent = xmax-xmin
    yproj_extent = ymax-ymin

    # Create ring
    ring = ogr.Geometry(ogr.wkbLinearRing)
    ring.AddPoint(xmin, ymin)
    ring.AddPoint(xmin, ymax)
    ring.AddPoint(xmax, ymax)
    ring.AddPoint(xmax, ymin)

    # Create a coordinate transformation
    source = osr.SpatialReference()
    source.ImportFromEPSG(int(ESPG_this_raster))

    target = osr.SpatialReference()
    target.ImportFromEPSG(4326)

    transform = osr.CoordinateTransformation(source, target)

    # now transform the ring so you can get coordinates in lat-long
    ring.Transform(transform)

    # now get the xmin,ymin, and xmax, ymax coords in lat-long
    pt1 = ring.GetPoint(0)
    min_long = pt1[0]
    min_lat = pt1[1]

    pt2 = ring.GetPoint(2)
    max_long = pt2[0]
    max_lat = pt2[1]

    extent_long = max_long-min_long
    extent_lat = max_lat-min_lat

    centre_long = min_long+extent_long*0.5
    centre_lat = min_lat+extent_lat*0.5

    return centre_lat, centre_long, extent_lat, extent_long, xproj_extent, yproj_extent



    # Leaving this here to show how to loop through points
    # Now try with the geometry tools
    #print("Trying ring")
    #for i in range(0, ring.GetPointCount()):
    #    pt = ring.GetPoint(i)
    #    print("The point is: ")
    #    print(str(pt[0])+" "+str(pt[1]))

def convert2bil(DataDirectory, RasterFile,minimum_elevation=0.01):
    """
    This converts a raster to bil format

     Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster
        minimum_elevation (float): the minimum elevation of the raster, below this you have nodata

    Returns:
        no return, but prints and ENVI bil to file

    Author: SMM

    Date: 07/07/2020

    """

    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]

    fname = DataDirectory+RasterFile

    src =  rio.open(fname)
    rast = src.read(1)
    nodatavalue = -9999

    rast = rast.astype(float)
    print("Removing pixels with an elevation less than "+str(minimum_elevation))
    rast[rast < minimum_elevation] = nodatavalue

    outname = DataDirectory+RasterPrefix+".bil"
    hname = DataDirectory+RasterPrefix+".hdr"

    array2raster(fname ,outname,rast,noDataValue=nodatavalue)


def convert2UTM(DataDirectory, RasterFile,minimum_elevation=0.01,resolution=30):
    """
    Converts the DEM to UTM coordinate system. Automatically checks the UTM zone using the python package utm.
    The output is a tif. Also includes resamplig and minimum elevation


     Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster
        minimum_elevation (float): the minimum elevation of the raster, below this you have nodata
        resolution (float): the resultution of the transformed DEM

    Returns:
        none, but prints a raster to file in UTM coordinate system

    Author: SMM

    Date: 27/07/2020
    """

    from pyproj import Proj
    from pyproj import transform as TFORM
    from rasterio.warp import calculate_default_transform, reproject, Resampling


    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]


    filename = DataDirectory+RasterFile

    # First get the source coordinate system
    # open dataset
    ds = gdal.Open(filename)
    prj=ds.GetProjection()
    print("The projections is:")
    print(prj)
    ds = []

    print("And some extra projection information strings:")
    srs=osr.SpatialReference(wkt=prj)
    if srs.IsProjected:
        print(srs.GetAttrValue('projcs'))
    print(srs.GetAttrValue('geogcs'))

    # now do it with rasterio
    dem_data = rio.open(filename)

    # Get the coordinate system
    this_crs = dem_data.crs
    this_epsg = this_crs.to_epsg()
    init_string = "epsg:"+str(this_epsg)



    # We need to get the centre of the DEM to get the UTM zone
    centre_point = dem_data.xy(dem_data.width // 2, dem_data.height // 2)
    print("The centre of the raster is at:")
    print(centre_point)
    print("dem_data.width is: "+ str(dem_data.width))

    # Get the latlong of this point
    if this_epsg != 4326:
        inProj = Proj(init_string)
        outProj = Proj(init='epsg:4326')
        x2,y2 = TFORM(inProj,outProj,centre_point[0],centre_point[1])
        print("The centre lat-long is")
        print(y2,x2)
    else:
        y2 = centre_point[1]
        x2 = centre_point[0]
        print("The latitude and longitude is")
        print(y2,x2)


    temp_info = utm.from_latlon(y2, x2)
    if(temp_info[3] in ['X','W','V','U','T','S','R','Q','P','N']):
        south = False
    else:
        south = True

    res = resolution

    UTMzone = temp_info[2]
    if south:
        EPSG = "327" + str(UTMzone)
    else:
        EPSG = "326" + str(UTMzone)

    res_tuple = (res,res)
    print("res tuple is:")
    print(res_tuple)

    dst_crs = 'EPSG:'+EPSG
    print("The destination CRS is: "+dst_crs)
    output_filename = DataDirectory+RasterPrefix+ "_UTM.tif"


    with rio.open(filename) as src:
        transform, width, height = calculate_default_transform(
            src.crs, dst_crs, src.width, src.height, resolution=res_tuple,*src.bounds)
        kwargs = src.meta.copy()
        kwargs.update({
            'crs': dst_crs,
            'transform': transform,
            'width': width,
            'height': height
        })

        with rio.open(output_filename, 'w', **kwargs) as dst:
            for i in range(1, src.count + 1):
                reproject(
                    source=rio.band(src, i),
                    destination=rio.band(dst, i),
                    src_transform=src.transform,
                    src_crs=src.crs,
                    dst_resolution=res_tuple,
                    dst_transform=transform,
                    dst_crs=dst_crs,
                    resampling=Resampling.cubic)
    dem_datam2 = rio.open(output_filename)

    print(dem_datam2.meta)


def convert4lsdtt(DataDirectory, RasterFile,minimum_elevation=0.01,resolution=30):
    """
    Converts the DEM to UTM coordinate system and then to bil format. Leaves a saved tif file in its wake


     Args:
        DataDirectory (str): the data directory with the basin raster
        RasterFile (str): the name of the raster
        minimum_elevation (float): the minimum elevation of the raster, below this you have nodata
        resolution (float): the resultution of the transformed DEM

    Returns:
        The name of the new raster file, and prints a raster to file in UTM coordinate system in ENVI bil format

    Author: SMM

    Date: 27/07/2020
    """

    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]
    RasterSuffix = SplitRasterfile[1]

    NewRasterName = RasterPrefix+"_UTM."+RasterSuffix

    convert2UTM(DataDirectory,RasterFile,minimum_elevation=minimum_elevation,resolution=resolution)
    convert2bil(DataDirectory,NewRasterName,minimum_elevation=minimum_elevation)

    return NewRasterName


#==============================================================================
# Make a simple hillshade plot
def write_hillshade_bil(DataDirectory, RasterFile, azimuth = 315, angle_altitude = 45, NoDataValue = -9999,z_factor = 1, resolution = 30.0):
    """Creates a hillshade raster

    Args:
        raster_file (str): The name of the raster file with path and extension.
        azimuth (float): Azimuth of sunlight
        angle_altitude (float): Angle altitude of sun
        NoDataValue (float): The nodata value of the raster

    Returns:
        HSArray (numpy.array): The hillshade array

    Author:
        SMM

    Date: 07/07/2020
    """


    # get the filename of the outfile.
    if not DataDirectory.endswith(os.sep):
        print("You forgot the separator at the end of the directory, appending...")
        DataDirectory = DataDirectory+os.sep

    # Get the raster prefix
    SplitRasterfile = RasterFile.split(".")
    RasterPrefix = SplitRasterfile[0]

    RasterName = DataDirectory+RasterFile


    hs = bm.Hillshade(RasterName, azimuth = azimuth, angle_altitude = angle_altitude, NoDataValue = NoDataValue,z_factor = z_factor, resolution = resolution)

    outname = DataDirectory+RasterPrefix+"_hs.bil"
    array2raster(RasterName,outname,hs)

