import datetime
import os
import ee
import math
import sys
import json
from ee.ee_exception import EEException
from gee.inputs import getLandsat, getS1


########## Helper functions ##########


def initialize(ee_account='', ee_key_path=''):
    try:
        if ee_account and ee_key_path and os.path.exists(ee_key_path):
            credentials = ee.ServiceAccountCredentials(ee_account, ee_key_path)
            ee.Initialize(credentials)
        else:
            ee.Initialize()
    except Exception as e:
        print(e)


def getReducer(reducer):
    reducerName = reducer.lower()
    if(reducerName == 'min'):
        return ee.Reducer.min()
    elif (reducerName == 'max'):
        return ee.Reducer.max()
    elif (reducerName == 'mean'):
        return ee.Reducer.mean()
    elif (reducerName == 'mode'):
        return ee.Reducer.mode()
    elif (reducerName == 'first'):
        return ee.Reducer.first()
    elif (reducerName == 'last'):
        return ee.Reducer.last()
    elif (reducerName == 'sum'):
        return ee.Reducer.sum()
    else:
        return ee.Reducer.median()


def reduceIC(imageCollection, reducer):
    reducerName = reducer.lower()
    if(reducerName == 'min'):
        return imageCollection.min()
    elif (reducerName == 'max'):
        return imageCollection.max()
    elif (reducerName == 'mean'):
        return imageCollection.mean()
    elif (reducerName == 'mode'):
        return imageCollection.mode()
    elif (reducerName == 'mosaic'):
        return imageCollection.mosaic()
    elif (reducerName == 'first'):
        return imageCollection.first()
    elif (reducerName == 'sum'):
        return imageCollection.sum()
    else:
        return imageCollection.median()


def safeParseJSON(val):
    if isinstance(val,  dict):
        return val
    else:
        try:
            return json.loads(val)
        except Exception as e:
            try:
                return json.loads(val.replace("'", "\""))
            except Exception as e:
                return {}


########## Helper routes ##########


def listAvailableBands(name, assetType):
    eeImage = None
    if assetType == "imageCollection":
        eeImage = ee.ImageCollection(name).first()
    else:
        eeImage = ee.Image(name)
    return {
        'bands': eeImage.bandNames().getInfo(),
        'imageName': name
    }

########## ee.Image ##########


def imageToMapId(image, visParams):
    eeImage = ee.Image(image)
    mapId = eeImage.getMapId(visParams)
    # TODO, just return URL so the routes are easier to deduce whats being returned.
    return {
        'url': mapId['tile_fetcher'].url_format
    }

########## ee.ImageCollection ##########


def imageCollectionToMapId(assetId, visParams, reducer, startDate, endDate):
    eeCollection = ee.ImageCollection(assetId)
    if (startDate and endDate):
        eeFilterDate = ee.Filter.date(startDate, endDate)
        eeCollection = eeCollection.filter(eeFilterDate)

    reducedImage = ee.Image(reduceIC(eeCollection, reducer))
    return imageToMapId(reducedImage, visParams)

# TODO, should we allow user to select first cloud free image again?


def firstCloudFreeImageInMosaicToMapId(assetId, visParams, startDate, endDate):
    skipCloudMask = False
    eeCollection = ee.ImageCollection(assetId)
    lowerAsset = assetId.lower()
    if("b2" not in visParams["bands"].lower()):
        skipCloudMask = True
    elif ("lc8" in lowerAsset):
        skipCloudMask = False
    elif ("le7" in lowerAsset):
        skipCloudMask = False
    elif ("lt5" in lowerAsset):
        skipCloudMask = False
    else:
        skipCloudMask = True
    if (startDate and endDate):
        eeFilterDate = ee.Filter.date(startDate, endDate)
        eeCollection = eeCollection.filter(eeFilterDate)
    eeFirstImage = ee.Image(eeCollection.mosaic())
    try:
        if(skipCloudMask == False):
            sID = ''
            if ("lc8" in lowerAsset):
                sID = 'OLI_TIRS'
            elif ("le7" in lowerAsset):
                sID = 'ETM'
            elif ("lt5" in lowerAsset):
                sID = 'TM'
            scored = ee.Algorithms.Landsat.simpleCloudScore(
                eeFirstImage.set('SENSOR_ID', sID))
            mask = scored.select(['cloud']).lte(20)
            masked = eeFirstImage.updateMask(mask)
            values = imageToMapId(masked, visParams)
        else:
            values = imageToMapId(eeFirstImage, visParams)
    except EEException as ine:
        imageToMapId(eeFirstImage, visParams)
    return values

########## ee.FeatureCollection ##########


def getFeatureCollectionTileUrl(featureCollection, field, matchID, visParams):
    fc = ee.FeatureCollection(featureCollection)
    single = fc.filter(ee.Filter.equals(field, matchID))
    mapId = ee.Image().paint(single, 0, 2).getMapId(visParams)
    return mapId['tile_fetcher'].url_format

########## Pre defined ee.ImageCollection ##########

# Index Image Collection


def lsMaskClouds(img, cloudThresh=10):
    score = ee.Image(1.0)
    # Clouds are reasonably bright in the blue band.
    blue_rescale = img.select('blue').subtract(ee.Number(0.1)).divide(
        ee.Number(0.3).subtract(ee.Number(0.1)))
    score = score.min(blue_rescale)

    # Clouds are reasonably bright in all visible bands.
    visible = img.select('red').add(
        img.select('green')).add(img.select('blue'))
    visible_rescale = visible.subtract(ee.Number(0.2)).divide(
        ee.Number(0.8).subtract(ee.Number(0.2)))
    score = score.min(visible_rescale)

    # Clouds are reasonably bright in all infrared bands.
    infrared = img.select('nir').add(
        img.select('swir1')).add(img.select('swir2'))
    infrared_rescale = infrared.subtract(ee.Number(0.3)).divide(
        ee.Number(0.8).subtract(ee.Number(0.3)))
    score = score.min(infrared_rescale)

    # Clouds are reasonably cool in temperature.
    temp_rescale = img.select('temp').subtract(ee.Number(300)).divide(
        ee.Number(290).subtract(ee.Number(300)))
    score = score.min(temp_rescale)

    # However, clouds are not snow.
    ndsi = img.normalizedDifference(['green', 'swir1'])
    ndsi_rescale = ndsi.subtract(ee.Number(0.8)).divide(
        ee.Number(0.6).subtract(ee.Number(0.8)))
    score = score.min(ndsi_rescale).multiply(100).byte()
    mask = score.lt(cloudThresh).rename(['cloudMask'])
    img = img.updateMask(mask)
    return img.addBands(score)


def s2MaskClouds(img):
    qa = img.select('QA60')

    # Bits 10 and 11 are clouds and cirrus, respectively.
    cloudBitMask = int(math.pow(2, 10))
    cirrusBitMask = int(math.pow(2, 11))

    # clear if both flags set to zero.
    clear = qa.bitwiseAnd(cloudBitMask).eq(0).And(
        qa.bitwiseAnd(cirrusBitMask).eq(0))

    return img.divide(10000).updateMask(clear).set('system:time_start', img.get('system:time_start'))


def bandPassAdjustment(img):
    keep = img.select(['temp'])
    bands = ['blue', 'green', 'red', 'nir', 'swir1', 'swir2']
    # linear regression coefficients for adjustment
    gain = ee.Array([[0.977], [1.005], [0.982], [1.001], [1.001], [0.996]])
    bias = ee.Array([[-0.00411], [-0.00093], [0.00094],
                    [-0.00029], [-0.00015], [-0.00097]])
    # Make an Array Image, with a 2-D Array per pixel.
    arrayImage2D = img.select(bands).toArray().toArray(1)

    # apply correction factors and reproject array to geographic image
    componentsImage = ee.Image(gain).multiply(arrayImage2D).add(ee.Image(bias)) \
        .arrayProject([0]).arrayFlatten([bands]).float()

    # .set('system:time_start',img.get('system:time_start'));
    return keep.addBands(componentsImage)


def getLandSatMergedCollection():
    sensorBandDictLandsatTOA = {'L8': [1, 2, 3, 4, 5, 9, 6],
                                'L7': [0, 1, 2, 3, 4, 5, 7],
                                'L5': [0, 1, 2, 3, 4, 5, 6],
                                'L4': [0, 1, 2, 3, 4, 5, 6],
                                'S2': [1, 2, 3, 7, 11, 10, 12]}
    bandNamesLandsatTOA = ['blue', 'green',
                           'red', 'nir', 'swir1', 'temp', 'swir2']
    metadataCloudCoverMax = 100
    lt4 = ee.ImageCollection('LANDSAT/LT4_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L4'], bandNamesLandsatTOA).map(lsMaskClouds)
    lt5 = ee.ImageCollection('LANDSAT/LT5_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L5'], bandNamesLandsatTOA).map(lsMaskClouds)
    le7 = ee.ImageCollection('LANDSAT/LE7_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L7'], bandNamesLandsatTOA).map(lsMaskClouds)
    lc8 = ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L8'], bandNamesLandsatTOA).map(lsMaskClouds)
    s2 = ee.ImageCollection('COPERNICUS/S2') \
        .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', metadataCloudCoverMax) \
        .map(s2MaskClouds).select(sensorBandDictLandsatTOA['S2'], bandNamesLandsatTOA) \
        .map(bandPassAdjustment)
    return ee.ImageCollection(lt4.merge(lt5).merge(le7).merge(lc8).merge(s2))


def filteredImageNDVIToMapId(startDate, endDate):
    def calcNDVI(img):
        return img.expression('(i.nir - i.red) / (i.nir + i.red)',  {'i': img}).rename(['NDVI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'c9c0bf,435ebf,eee8aa,006400'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDVI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageEVIToMapId(startDate, endDate):
    def calcEVI(img):
        return img.expression('2.5 * (i.nir - i.red) / (i.nir + 6.0 * i.red - 7.5 * i.blue + 1)',  {'i': img}).rename(['EVI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'F5F5F5,E6D3C5,C48472,B9CF63,94BF3D,6BB037,42A333,00942C,008729,007824,004A16'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcEVI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageEVI2ToMapId(startDate, endDate):
    def calcEVI2(img):
        return img.expression('2.5 * (i.nir - i.red) / (i.nir + 2.4 * i.red + 1)',  {'i': img}).rename(['EVI2']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'F5F5F5,E6D3C5,C48472,B9CF63,94BF3D,6BB037,42A333,00942C,008729,007824,004A16'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcEVI2).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageNDMIToMapId(startDate, endDate):
    def calcNDMI(img):
        return img.expression('(i.nir - i.swir1) / (i.nir + i.swir1)',  {'i': img}).rename(['NDMI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = '0000FE,2E60FD,31B0FD,00FEFE,50FE00,DBFE66,FEFE00,FFBB00,FF6F00,FE0000'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDMI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageNDWIToMapId(startDate, endDate):
    def calcNDWI(img):
        return img.expression('(i.green - i.nir) / (i.green + i.nir)',  {'i': img}).rename(['NDWI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = '505050,E8E8E8,00FF33,003300'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDWI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageByIndexToMapId(startDate, endDate, index):
    lowerIndex = index.lower()
    if (lowerIndex == 'ndvi'):
        return filteredImageNDVIToMapId(startDate, endDate)
    elif (lowerIndex == 'evi'):
        return filteredImageEVIToMapId(startDate, endDate)
    elif (lowerIndex == 'evi2'):
        return filteredImageEVI2ToMapId(startDate, endDate)
    elif (lowerIndex == 'ndmi'):
        return filteredImageNDMIToMapId(startDate, endDate)
    elif (lowerIndex == 'ndwi'):
        return filteredImageNDWIToMapId(startDate, endDate)


def filteredImageCompositeToMapId(assetId, visParams, startDate, endDate, metadataCloudCoverMax, simpleCompositeVariable):
    eeCollection = ee.ImageCollection(assetId)
    if (startDate and endDate):
        eeCollection = eeCollection.filterDate(startDate, endDate)
    eeCollection.filterMetadata(
        'CLOUD_COVER',
        'less_than',
        metadataCloudCoverMax
    )
    eeMosaicImage = ee.Algorithms.Landsat.simpleComposite(
        eeCollection,
        simpleCompositeVariable,
        10,
        40,
        True
    )
    return imageToMapId(eeMosaicImage, visParams)


def filteredSentinelComposite(visParams, startDate, endDate, metadataCloudCoverMax):
    def cloudScore(img):
        def rescale(img, exp, thresholds):
            return img.expression(exp, {'img': img}).subtract(thresholds[0]).divide(thresholds[1] - thresholds[0])

        score = ee.Image(1.0)
        score = score.min(rescale(img, 'img.B2', [0.1, 0.3]))
        score = score.min(rescale(img, 'img.B4 + img.B3 + img.B2', [0.2, 0.8]))
        score = score.min(
            rescale(img, 'img.B8 + img.B11 + img.B12', [0.3, 0.8]))
        ndsi = img.normalizedDifference(['B3', 'B11'])
        return score.min(rescale(ndsi, 'img', [0.8, 0.6]))

    def cloudScoreS2(img):
        rescale = img.divide(10000)
        score = cloudScore(rescale).multiply(100).rename('cloudscore')
        return img.addBands(score)

    sentinel2 = ee.ImageCollection('COPERNICUS/S2')
    f2017s2 = sentinel2.filterDate(startDate, endDate).filterMetadata(
        'CLOUDY_PIXEL_PERCENTAGE', 'less_than', metadataCloudCoverMax)
    m2017s2 = f2017s2.map(cloudScoreS2)
    m2017s3 = m2017s2.median()
    return imageToMapId(m2017s3, visParams)


def filteredSentinelSARComposite(visParams, startDate, endDate):
    def toNatural(img):
        return ee.Image(10).pow(img.divide(10))

    def addRatioBands(img):
        # not using angle band
        vv = img.select('VV')
        vh = img.select('VH')
        vv_vh = vv.divide(vh).rename('VV/VH')
        vh_vv = vh.divide(vv).rename('VH/VV')
        return vv.addBands(vh).addBands(vv_vh).addBands(vh_vv)

    sentinel1 = ee.ImageCollection('COPERNICUS/S1_GRD')
    sentinel1 = sentinel1.filterDate(startDate, endDate) \
        .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')) \
        .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH')) \
        .filter(ee.Filter.eq('instrumentMode', 'IW'))

    sentinel1 = sentinel1.map(toNatural)
    sentinel1 = sentinel1.map(addRatioBands)
    median = sentinel1.median()
    return imageToMapId(median, visParams)

########## Time Series ##########


def getTimeSeriesByCollectionAndIndex(assetId, indexName, scale, coords, startDate, endDate, reducer):
    geometry = None
    indexCollection = None
    if isinstance(coords[0], list):
        geometry = ee.Geometry.Polygon(coords)
    else:
        geometry = ee.Geometry.Point(coords)
    if indexName != None:
        indexCollection = ee.ImageCollection(assetId).filterDate(
            startDate, endDate).select(indexName)
    else:
        indexCollection = ee.ImageCollection(
            assetId).filterDate(startDate, endDate)

    def getIndex(image):
        theReducer = getReducer(reducer)
        if indexName != None:
            indexValue = image.reduceRegion(
                theReducer, geometry, scale).get(indexName)
        else:
            indexValue = image.reduceRegion(theReducer, geometry, scale)
        date = image.get('system:time_start')
        indexImage = ee.Image().set(
            'indexValue', [ee.Number(date), indexValue])
        return indexImage

    def getClipped(image):
        return image.clip(geometry)

    clippedcollection = indexCollection.map(getClipped)
    indexCollection1 = clippedcollection.map(getIndex)
    indexCollection2 = indexCollection1.aggregate_array('indexValue')
    return indexCollection2.getInfo()


def getTimeSeriesByIndex(indexName, scale, coords, startDate, endDate, reducer):
    bandsByCollection = {
        'LANDSAT/LC08/C01/T1_TOA': ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
        'LANDSAT/LC08/C01/T2_TOA': ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
        'LANDSAT/LE07/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LE07/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT05/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT05/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT04/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT04/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7']
    }
    indexes = {
        'NDVI': '(nir - red) / (nir + red)',
        'EVI': '2.5 * (nir - red) / (nir + 6.0 * red - 7.5 * blue + 1)',
        'EVI2': '2.5 * (nir - red) / (nir + 2.4 * red + 1)',
        'NDMI': '(nir - swir1) / (nir + swir1)',
        'NDWI': '(green - nir) / (green + nir)',
        'NBR': '(nir - swir2) / (nir + swir2)',
        'LSAVI': '((nir - red) / (nir + red + 0.5)) * (1 + 0.5)'
    }

    def create(name):
        def maskClouds(image):
            def isSet(types):
                """ https://landsat.usgs.gov/collectionqualityband """
                typeByValue = {
                    'badPixels': 15,
                    'cloud': 16,
                    'shadow': 256,
                    'snow': 1024,
                    'cirrus': 4096
                }
                anySet = ee.Image(0)
                for Type in types:
                    anySet = anySet.Or(image.select(
                        'BQA').bitwiseAnd(typeByValue[Type]).neq(0))
                return anySet
            return image.updateMask(isSet(['badPixels', 'cloud', 'shadow', 'cirrus']).Not())

        def toIndex(image):
            bands = bandsByCollection[name]
            return image.expression(indexes[indexName], {
                'blue': image.select(bands[0]),
                'green': image.select(bands[1]),
                'red': image.select(bands[2]),
                'nir': image.select(bands[3]),
                'swir1': image.select(bands[4]),
                'swir2': image.select(bands[5]),
            }).clamp(-1, 1).rename(['index'])

        def toIndexWithTimeStart(image):
            time = image.get('system:time_start')
            image = maskClouds(image)
            return toIndex(image).set('system:time_start', time)
        #
        if startDate and endDate:
            return ee.ImageCollection(name).filterDate(startDate, endDate).filterBounds(geometry).map(toIndexWithTimeStart, True)
        else:
            return ee.ImageCollection(name).filterBounds(geometry).map(toIndexWithTimeStart, True)

    def reduceRegion(image):
        theReducer = getReducer(reducer)
        reduced = image.reduceRegion(
            theReducer, geometry=geometry, scale=scale, maxPixels=1e6)
        return ee.Feature(None, {
            'index': reduced.get('index'),
            'timeIndex': [image.get('system:time_start'), reduced.get('index')]
        })

    geometry = None
    if isinstance(coords[0], list) or isinstance(coords[0], tuple):
        geometry = ee.Geometry.Polygon(coords)
    else:
        geometry = ee.Geometry.Point(coords)
    collection = ee.ImageCollection([])
    for name in bandsByCollection:
        collection = collection.merge(create(name))
    return ee.ImageCollection(ee.ImageCollection(collection).sort('system:time_start').distinct('system:time_start')) \
        .map(reduceRegion) \
        .filterMetadata('index', 'not_equals', None) \
        .aggregate_array('timeIndex') \
        .getInfo()


########## Degradation##########

def getDegradationTileUrlByDateS1(geometry, date, visParams):
    imDate = datetime.datetime.strptime(date, "%Y-%m-%d")
    befDate = imDate - datetime.timedelta(days=1)
    aftDate = imDate + datetime.timedelta(days=1)

    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)

    sentinel1Data = getS1({
        "targetBands": ['VV', 'VH', 'VV/VH'],
        'region': geometry})

    start = befDate.strftime('%Y-%m-%d')
    end = aftDate.strftime('%Y-%m-%d')

    selectedImage = sentinel1Data.filterDate(start, end).first()

    selectedImage = ee.Image(selectedImage)
    mapparams = selectedImage.getMapId(visParams)
    return mapparams['tile_fetcher'].url_format


def getDegradationPlotsByPointS1(geometry, start, end):
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)

    sentinel1Data = getS1({
        "targetBands": ['VV', 'VH', 'VV/VH'],
        'region': geometry
    }).filterDate(start, end)

    def myimageMapper(img):
        theReducer = ee.Reducer.mean()
        indexValue = img.reduceRegion(theReducer, geometry, 30)
        date = img.get('system:time_start')
        visParams = {'bands': ['VV', 'VH', 'ratioVVVH'],
                     'min': [-15, -25, .40], 'max': [0, -10, 1], 'gamma': 1.6}
        indexImage = ee.Image().set(
            'indexValue', [ee.Number(date), indexValue])
        return indexImage
    lsd = sentinel1Data.map(myimageMapper, True)
    indexCollection2 = lsd.aggregate_array('indexValue')
    values = indexCollection2.getInfo()
    return values


def getDegradationTileUrlByDate(geometry, date, visParams):
    imDate = datetime.datetime.strptime(date, "%Y-%m-%d")
    startDate = imDate - datetime.timedelta(days=1)
    endDate = imDate + datetime.timedelta(days=1)
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)
    landsatData = getLandsat({
        "start": startDate.strftime('%Y-%m-%d'),
        "end": endDate.strftime('%Y-%m-%d'),
        "targetBands": ['RED', 'GREEN', 'BLUE', 'SWIR1', 'NIR'],
        "region": geometry,
        "sensors": {"l4": False, "l5": False, "l7": False, "l8": True}
    })

    selectedImage = landsatData.first()
    unmasked = ee.Image(selectedImage).multiply(10000).toInt16().unmask()
    mapparams = unmasked.getMapId(visParams)
    return mapparams['tile_fetcher'].url_format


def getDegradationPlotsByPoint(geometry, start, end, band):
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)
    landsatData = getLandsat({
        "start": start,
        "end": end,
        "targetBands": [band],
        "region": geometry,
        "sensors": {"l4": True, "l5": True, "l7": True, "l8": True}
    })

    def myImageMapper(img):
        theReducer = ee.Reducer.mean()
        indexValue = img.reduceRegion(theReducer, geometry, 30)
        date = img.get('system:time_start')
        indexImage = ee.Image().set(
            'indexValue',
            [ee.Number(date), indexValue]
        )
        return indexImage
    lsd = landsatData.map(myImageMapper, True)
    indexCollection2 = lsd.aggregate_array('indexValue')
    values = indexCollection2.getInfo()
    return values


########## Stats ##########

def getStatistics(extent):
    extentGeom = ee.Geometry.Polygon(extent)
    elev = ee.Image('USGS/GTOPO30')
    minmaxElev = elev.reduceRegion(
        ee.Reducer.minMax(), extentGeom, 1000, maxPixels=500000000)
    minElev = minmaxElev.get('elevation_min').getInfo()
    maxElev = minmaxElev.get('elevation_max').getInfo()
    ciesinPopGrid = ee.Image('CIESIN/GPWv4/population-count/2020')
    popDict = ciesinPopGrid.reduceRegion(
        ee.Reducer.sum(), extentGeom, maxPixels=500000000)
    pop = popDict.get('population-count').getInfo()
    pop = int(pop)
    return {
        'minElev': minElev,
        'maxElev': maxElev,
        'pop': pop
    }

[((96, 14, 96, 29), 'ee.Image', 'ee.Image', ({(96, 23, 96, 28): 'image'}, {}), '(image)', False, 'import ee\n'), ((107, 19, 107, 46), 'ee.ImageCollection', 'ee.ImageCollection', ({(107, 38, 107, 45): 'assetId'}, {}), '(assetId)', False, 'import ee\n'), ((120, 19, 120, 46), 'ee.ImageCollection', 'ee.ImageCollection', ({(120, 38, 120, 45): 'assetId'}, {}), '(assetId)', False, 'import ee\n'), ((160, 9, 160, 48), 'ee.FeatureCollection', 'ee.FeatureCollection', ({(160, 30, 160, 47): 'featureCollection'}, {}), '(featureCollection)', False, 'import ee\n'), ((171, 12, 171, 25), 'ee.Image', 'ee.Image', ({(171, 21, 171, 24): '1.0'}, {}), '(1.0)', False, 'import ee\n'), ((224, 11, 224, 75), 'ee.Array', 'ee.Array', ({(224, 20, 224, 74): '[[0.977], [1.005], [0.982], [1.001], [1.001], [0.996]]'}, {}), '([[0.977], [1.005], [0.982], [1.001], [1.001], [0.996]])', False, 'import ee\n'), ((225, 11, 226, 56), 'ee.Array', 'ee.Array', ({(225, 20, 226, 55): '[[-0.00411], [-0.00093], [0.00094], [-0.00029], [-0.00015], [-0.00097]]'}, {}), '([[-0.00411], [-0.00093], [0.00094], [-0.00029], [-0.00015], [-\n    0.00097]])', False, 'import ee\n'), ((346, 19, 346, 46), 'ee.ImageCollection', 'ee.ImageCollection', ({(346, 38, 346, 45): 'assetId'}, {}), '(assetId)', False, 'import ee\n'), ((354, 20, 360, 5), 'ee.Algorithms.Landsat.simpleComposite', 'ee.Algorithms.Landsat.simpleComposite', ({(355, 8, 355, 20): 'eeCollection', (356, 8, 356, 31): 'simpleCompositeVariable', (357, 8, 357, 10): '10', (358, 8, 358, 10): '40', (359, 8, 359, 12): 'True'}, {}), '(eeCollection, simpleCompositeVariable,\n    10, 40, True)', False, 'import ee\n'), ((382, 16, 382, 51), 'ee.ImageCollection', 'ee.ImageCollection', ({(382, 35, 382, 50): '"""COPERNICUS/S2"""'}, {}), "('COPERNICUS/S2')", False, 'import ee\n'), ((402, 16, 402, 55), 'ee.ImageCollection', 'ee.ImageCollection', ({(402, 35, 402, 54): '"""COPERNICUS/S1_GRD"""'}, {}), "('COPERNICUS/S1_GRD')", False, 'import ee\n'), ((525, 17, 525, 39), 'ee.ImageCollection', 'ee.ImageCollection', ({(525, 36, 525, 38): '[]'}, {}), '([])', False, 'import ee\n'), ((538, 13, 538, 57), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(538, 40, 538, 44): 'date', (538, 46, 538, 56): '"""%Y-%m-%d"""'}, {}), "(date, '%Y-%m-%d')", False, 'import datetime\n'), ((547, 20, 549, 28), 'gee.inputs.getS1', 'getS1', ({(547, 26, 549, 27): "{'targetBands': ['VV', 'VH', 'VV/VH'], 'region': geometry}"}, {}), "({'targetBands': ['VV', 'VH', 'VV/VH'], 'region': geometry})", False, 'from gee.inputs import getLandsat, getS1\n'), ((556, 20, 556, 43), 'ee.Image', 'ee.Image', ({(556, 29, 556, 42): 'selectedImage'}, {}), '(selectedImage)', False, 'import ee\n'), ((588, 13, 588, 57), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(588, 40, 588, 44): 'date', (588, 46, 588, 56): '"""%Y-%m-%d"""'}, {}), "(date, '%Y-%m-%d')", False, 'import datetime\n'), ((614, 18, 620, 6), 'gee.inputs.getLandsat', 'getLandsat', ({(614, 29, 620, 5): "{'start': start, 'end': end, 'targetBands': [band], 'region': geometry,\n    'sensors': {'l4': True, 'l5': True, 'l7': True, 'l8': True}}"}, {}), "({'start': start, 'end': end, 'targetBands': [band], 'region':\n    geometry, 'sensors': {'l4': True, 'l5': True, 'l7': True, 'l8': True}})", False, 'from gee.inputs import getLandsat, getS1\n'), ((640, 17, 640, 44), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(640, 37, 640, 43): 'extent'}, {}), '(extent)', False, 'import ee\n'), ((641, 11, 641, 35), 'ee.Image', 'ee.Image', ({(641, 20, 641, 34): '"""USGS/GTOPO30"""'}, {}), "('USGS/GTOPO30')", False, 'import ee\n'), ((646, 20, 646, 66), 'ee.Image', 'ee.Image', ({(646, 29, 646, 65): '"""CIESIN/GPWv4/population-count/2020"""'}, {}), "('CIESIN/GPWv4/population-count/2020')", False, 'import ee\n'), ((28, 15, 28, 31), 'ee.Reducer.min', 'ee.Reducer.min', ({}, {}), '()', False, 'import ee\n'), ((86, 18, 86, 32), 'ee.Image', 'ee.Image', ({(86, 27, 86, 31): 'name'}, {}), '(name)', False, 'import ee\n'), ((109, 23, 109, 57), 'ee.Filter.date', 'ee.Filter.date', ({(109, 38, 109, 47): 'startDate', (109, 49, 109, 56): 'endDate'}, {}), '(startDate, endDate)', False, 'import ee\n'), ((133, 23, 133, 57), 'ee.Filter.date', 'ee.Filter.date', ({(133, 38, 133, 47): 'startDate', (133, 49, 133, 56): 'endDate'}, {}), '(startDate, endDate)', False, 'import ee\n'), ((161, 23, 161, 55), 'ee.Filter.equals', 'ee.Filter.equals', ({(161, 40, 161, 45): 'field', (161, 47, 161, 54): 'matchID'}, {}), '(field, matchID)', False, 'import ee\n'), ((210, 23, 210, 38), 'math.pow', 'math.pow', ({(210, 32, 210, 33): '2', (210, 35, 210, 37): '10'}, {}), '(2, 10)', False, 'import math\n'), ((211, 24, 211, 39), 'math.pow', 'math.pow', ({(211, 33, 211, 34): '2', (211, 36, 211, 38): '11'}, {}), '(2, 11)', False, 'import math\n'), ((369, 16, 369, 29), 'ee.Image', 'ee.Image', ({(369, 25, 369, 28): '1.0'}, {}), '(1.0)', False, 'import ee\n'), ((406, 16, 406, 52), 'ee.Filter.eq', 'ee.Filter.eq', ({(406, 29, 406, 45): '"""instrumentMode"""', (406, 47, 406, 51): '"""IW"""'}, {}), "('instrumentMode', 'IW')", False, 'import ee\n'), ((420, 19, 420, 46), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(420, 39, 420, 45): 'coords'}, {}), '(coords)', False, 'import ee\n'), ((422, 19, 422, 44), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(422, 37, 422, 43): 'coords'}, {}), '(coords)', False, 'import ee\n'), ((522, 19, 522, 46), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(522, 39, 522, 45): 'coords'}, {}), '(coords)', False, 'import ee\n'), ((524, 19, 524, 44), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(524, 37, 524, 43): 'coords'}, {}), '(coords)', False, 'import ee\n'), ((539, 23, 539, 49), 'datetime.timedelta', 'datetime.timedelta', (), '', False, 'import datetime\n'), ((540, 23, 540, 49), 'datetime.timedelta', 'datetime.timedelta', (), '', False, 'import datetime\n'), ((543, 19, 543, 48), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(543, 39, 543, 47): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((545, 19, 545, 46), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(545, 37, 545, 45): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((563, 19, 563, 48), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(563, 39, 563, 47): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((565, 19, 565, 46), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(565, 37, 565, 45): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((573, 21, 573, 38), 'ee.Reducer.mean', 'ee.Reducer.mean', ({}, {}), '()', False, 'import ee\n'), ((589, 25, 589, 51), 'datetime.timedelta', 'datetime.timedelta', (), '', False, 'import datetime\n'), ((590, 23, 590, 49), 'datetime.timedelta', 'datetime.timedelta', (), '', False, 'import datetime\n'), ((592, 19, 592, 48), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(592, 39, 592, 47): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((594, 19, 594, 46), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(594, 37, 594, 45): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((611, 19, 611, 48), 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', ({(611, 39, 611, 47): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((613, 19, 613, 46), 'ee.Geometry.Point', 'ee.Geometry.Point', ({(613, 37, 613, 45): 'geometry'}, {}), '(geometry)', False, 'import ee\n'), ((623, 21, 623, 38), 'ee.Reducer.mean', 'ee.Reducer.mean', ({}, {}), '()', False, 'import ee\n'), ((643, 8, 643, 27), 'ee.Reducer.minMax', 'ee.Reducer.minMax', ({}, {}), '()', False, 'import ee\n'), ((648, 8, 648, 24), 'ee.Reducer.sum', 'ee.Reducer.sum', ({}, {}), '()', False, 'import ee\n'), ((16, 42, 16, 69), 'os.path.exists', 'os.path.exists', ({(16, 57, 16, 68): 'ee_key_path'}, {}), '(ee_key_path)', False, 'import os\n'), ((17, 26, 17, 79), 'ee.ServiceAccountCredentials', 'ee.ServiceAccountCredentials', ({(17, 55, 17, 65): 'ee_account', (17, 67, 17, 78): 'ee_key_path'}, {}), '(ee_account, ee_key_path)', False, 'import ee\n'), ((18, 12, 18, 38), 'ee.Initialize', 'ee.Initialize', ({(18, 26, 18, 37): 'credentials'}, {}), '(credentials)', False, 'import ee\n'), ((20, 12, 20, 27), 'ee.Initialize', 'ee.Initialize', ({}, {}), '()', False, 'import ee\n'), ((30, 15, 30, 31), 'ee.Reducer.max', 'ee.Reducer.max', ({}, {}), '()', False, 'import ee\n'), ((70, 19, 70, 34), 'json.loads', 'json.loads', ({(70, 30, 70, 33): 'val'}, {}), '(val)', False, 'import json\n'), ((174, 32, 174, 46), 'ee.Number', 'ee.Number', ({(174, 42, 174, 45): '0.1'}, {}), '(0.1)', False, 'import ee\n'), ((181, 32, 181, 46), 'ee.Number', 'ee.Number', ({(181, 42, 181, 45): '0.2'}, {}), '(0.2)', False, 'import ee\n'), ((188, 32, 188, 46), 'ee.Number', 'ee.Number', ({(188, 42, 188, 45): '0.3'}, {}), '(0.3)', False, 'import ee\n'), ((193, 32, 193, 46), 'ee.Number', 'ee.Number', ({(193, 42, 193, 45): '300'}, {}), '(300)', False, 'import ee\n'), ((199, 32, 199, 46), 'ee.Number', 'ee.Number', ({(199, 42, 199, 45): '0.8'}, {}), '(0.8)', False, 'import ee\n'), ((567, 20, 570, 6), 'gee.inputs.getS1', 'getS1', ({(567, 26, 570, 5): "{'targetBands': ['VV', 'VH', 'VV/VH'], 'region': geometry}"}, {}), "({'targetBands': ['VV', 'VH', 'VV/VH'], 'region': geometry})", False, 'from gee.inputs import getLandsat, getS1\n'), ((32, 15, 32, 32), 'ee.Reducer.mean', 'ee.Reducer.mean', ({}, {}), '()', False, 'import ee\n'), ((84, 18, 84, 42), 'ee.ImageCollection', 'ee.ImageCollection', ({(84, 37, 84, 41): 'name'}, {}), '(name)', False, 'import ee\n'), ((173, 47, 173, 61), 'ee.Number', 'ee.Number', ({(173, 57, 173, 60): '0.1'}, {}), '(0.1)', False, 'import ee\n'), ((174, 8, 174, 22), 'ee.Number', 'ee.Number', ({(174, 18, 174, 21): '0.3'}, {}), '(0.3)', False, 'import ee\n'), ((180, 39, 180, 53), 'ee.Number', 'ee.Number', ({(180, 49, 180, 52): '0.2'}, {}), '(0.2)', False, 'import ee\n'), ((181, 8, 181, 22), 'ee.Number', 'ee.Number', ({(181, 18, 181, 21): '0.8'}, {}), '(0.8)', False, 'import ee\n'), ((187, 41, 187, 55), 'ee.Number', 'ee.Number', ({(187, 51, 187, 54): '0.3'}, {}), '(0.3)', False, 'import ee\n'), ((188, 8, 188, 22), 'ee.Number', 'ee.Number', ({(188, 18, 188, 21): '0.8'}, {}), '(0.8)', False, 'import ee\n'), ((192, 47, 192, 61), 'ee.Number', 'ee.Number', ({(192, 57, 192, 60): '300'}, {}), '(300)', False, 'import ee\n'), ((193, 8, 193, 22), 'ee.Number', 'ee.Number', ({(193, 18, 193, 21): '290'}, {}), '(290)', False, 'import ee\n'), ((198, 33, 198, 47), 'ee.Number', 'ee.Number', ({(198, 43, 198, 46): '0.8'}, {}), '(0.8)', False, 'import ee\n'), ((199, 8, 199, 22), 'ee.Number', 'ee.Number', ({(199, 18, 199, 21): '0.6'}, {}), '(0.6)', False, 'import ee\n'), ((392, 15, 392, 27), 'ee.Image', 'ee.Image', ({(392, 24, 392, 26): '(10)'}, {}), '(10)', False, 'import ee\n'), ((405, 16, 405, 79), 'ee.Filter.listContains', 'ee.Filter.listContains', ({(405, 39, 405, 72): '"""transmitterReceiverPolarisation"""', (405, 74, 405, 78): '"""VH"""'}, {}), "('transmitterReceiverPolarisation', 'VH')", False, 'import ee\n'), ((427, 26, 428, 20), 'ee.ImageCollection', 'ee.ImageCollection', ({(428, 12, 428, 19): 'assetId'}, {}), '(assetId)', False, 'import ee\n'), ((438, 21, 438, 31), 'ee.Image', 'ee.Image', ({}, {}), '()', False, 'import ee\n'), ((439, 27, 439, 42), 'ee.Number', 'ee.Number', ({(439, 37, 439, 41): 'date'}, {}), '(date)', False, 'import ee\n'), ((483, 25, 483, 36), 'ee.Image', 'ee.Image', ({(483, 34, 483, 35): '0'}, {}), '(0)', False, 'import ee\n'), ((578, 21, 578, 31), 'ee.Image', 'ee.Image', ({}, {}), '()', False, 'import ee\n'), ((579, 27, 579, 42), 'ee.Number', 'ee.Number', ({(579, 37, 579, 41): 'date'}, {}), '(date)', False, 'import ee\n'), ((626, 21, 626, 31), 'ee.Image', 'ee.Image', ({}, {}), '()', False, 'import ee\n'), ((628, 13, 628, 28), 'ee.Number', 'ee.Number', ({(628, 23, 628, 27): 'date'}, {}), '(date)', False, 'import ee\n'), ((34, 15, 34, 32), 'ee.Reducer.mode', 'ee.Reducer.mode', ({}, {}), '()', False, 'import ee\n'), ((162, 12, 162, 22), 'ee.Image', 'ee.Image', ({}, {}), '()', False, 'import ee\n'), ((36, 15, 36, 33), 'ee.Reducer.first', 'ee.Reducer.first', ({}, {}), '()', False, 'import ee\n'), ((404, 16, 404, 79), 'ee.Filter.listContains', 'ee.Filter.listContains', ({(404, 39, 404, 72): '"""transmitterReceiverPolarisation"""', (404, 74, 404, 78): '"""VV"""'}, {}), "('transmitterReceiverPolarisation', 'VV')", False, 'import ee\n'), ((424, 26, 424, 53), 'ee.ImageCollection', 'ee.ImageCollection', ({(424, 45, 424, 52): 'assetId'}, {}), '(assetId)', False, 'import ee\n'), ((38, 15, 38, 32), 'ee.Reducer.last', 'ee.Reducer.last', ({}, {}), '()', False, 'import ee\n'), ((247, 10, 247, 51), 'ee.ImageCollection', 'ee.ImageCollection', ({(247, 29, 247, 50): '"""LANDSAT/LT4_L1T_TOA"""'}, {}), "('LANDSAT/LT4_L1T_TOA')", False, 'import ee\n'), ((250, 10, 250, 51), 'ee.ImageCollection', 'ee.ImageCollection', ({(250, 29, 250, 50): '"""LANDSAT/LT5_L1T_TOA"""'}, {}), "('LANDSAT/LT5_L1T_TOA')", False, 'import ee\n'), ((253, 10, 253, 51), 'ee.ImageCollection', 'ee.ImageCollection', ({(253, 29, 253, 50): '"""LANDSAT/LE7_L1T_TOA"""'}, {}), "('LANDSAT/LE7_L1T_TOA')", False, 'import ee\n'), ((256, 10, 256, 55), 'ee.ImageCollection', 'ee.ImageCollection', ({(256, 29, 256, 54): '"""LANDSAT/LC08/C01/T1_TOA"""'}, {}), "('LANDSAT/LC08/C01/T1_TOA')", False, 'import ee\n'), ((509, 19, 509, 43), 'ee.ImageCollection', 'ee.ImageCollection', ({(509, 38, 509, 42): 'name'}, {}), '(name)', False, 'import ee\n'), ((604, 15, 604, 38), 'ee.Image', 'ee.Image', ({(604, 24, 604, 37): 'selectedImage'}, {}), '(selectedImage)', False, 'import ee\n'), ((40, 15, 40, 31), 'ee.Reducer.sum', 'ee.Reducer.sum', ({}, {}), '()', False, 'import ee\n'), ((42, 15, 42, 34), 'ee.Reducer.median', 'ee.Reducer.median', ({}, {}), '()', False, 'import ee\n'), ((231, 64, 231, 78), 'ee.Image', 'ee.Image', ({(231, 73, 231, 77): 'bias'}, {}), '(bias)', False, 'import ee\n'), ((259, 9, 259, 44), 'ee.ImageCollection', 'ee.ImageCollection', ({(259, 28, 259, 43): '"""COPERNICUS/S2"""'}, {}), "('COPERNICUS/S2')", False, 'import ee\n'), ((507, 19, 507, 43), 'ee.ImageCollection', 'ee.ImageCollection', ({(507, 38, 507, 42): 'name'}, {}), '(name)', False, 'import ee\n'), ((231, 22, 231, 36), 'ee.Image', 'ee.Image', ({(231, 31, 231, 35): 'gain'}, {}), '(gain)', False, 'import ee\n'), ((528, 30, 528, 60), 'ee.ImageCollection', 'ee.ImageCollection', ({(528, 49, 528, 59): 'collection'}, {}), '(collection)', False, 'import ee\n')]