//------------------------------------------------------
//DESCRIPTION
//------------------------------------------------------
/*
Simple dashboard to select reservoirs in Zarafshan Province in Uzbekistan.
After selecting a year the user can display the Water Occurence Frequency within the area of interest.
The user can also select individual Sentinel-2 scenes to display RGB satellite images and the shoreline at the selected date.
Furthermore, this script can be used to iterate over many water bodies for exporting associated Water Occurence Images as Assets
*/

//------------------------------------------------------
// 1. Parameters
//------------------------------------------------------
//Analysis period of Water Occurence maps to be exported:
var start_date_4export = ee.Date.fromYMD(2017,1,1);
var end_date_4export = ee.Date.fromYMD(2022, 12, 31);
//cloud and nodata thresholds
var cc_thresh = 5;
var cc_pix=10;
var nodata_thresh = 10;
//water surface mapping parameters:
var cannyThreshold = 0.7;
var cannySigma = 0.7;
var minValue = -0.3;
var scale=30;
var maxValue=0.3;
var mndwi_thresh=-0.3;

var buffer = 500;
//------------------------------------------------------
// 2. Selection of Water Bodies
//------------------------------------------------------

var Lakes = ee.FeatureCollection("projects/ee-adriankreinerak/assets/Lakes_combination_v5");

//Select the water bodies that should be considered for the analysis.
var Lakes_Zarafshan=Lakes.map(function(feat){
  return ee.Feature(feat).set('Name',ee.String('ID_').cat(ee.Number.parse(feat.get('fid'))));//Define a name for each reservoir
});
//Get the list of IDs of selected water bodies (client side)
var allids=Lakes_Zarafshan.aggregate_array('fid').getInfo();
print('IDs of selected water bodies',allids);

//define asset paths and name
var freqMapName='SurfWaterFreq_S2_17to22_';//frequency map export base name

//asset path for frequency maps:
var path_Freq = 'projects/ee-adriankreinerak/assets/SurfFreqMaps_Lakes_combination_v2';

// ****************************************************************************************************************** //
// GET LIST OF IDs FOR SPECIFIC DATA TYPES
// ****************************************************************************************************************** //

// get the list of Images already exported:
var Table = ee.data.listAssets(path_Freq);
var l;//number of exported images
if (Table.assets===null){
  l=0;
} else {
  l =Table.assets.length;
}
print('number of already exported images',l);
print(Table.assets)

//get the list of IDs of exported Water Occurence images (client side variable)
var tableids=[];
for (var i=0; i<l; i++) {//l
  tableids.push(Table.assets[i].id.split(freqMapName)[1]);
}
print('Exported Water Bodies IDs',tableids);

//Get the list of IDs of selected water bodies whose Water Occurence Frequency has not yet been exported
var missingIDs=[];
for (var i=0; i<allids.length; i++) {//l
  if( tableids.indexOf(allids[i].toString())<0){
    missingIDs.push(allids[i]);
  }
}
print('N° of missing water bodies in exported images:',missingIDs.length);
print('missing IDs', missingIDs);

var lakes_fc =Lakes_Zarafshan;

//Selected reservoir for starting screen.
var selectedLake=ee.Feature(lakes_fc.filter(ee.Filter.eq('fid',ee.Number(allids[0]))).first()).geometry();
print('selected reservoir for starting screen',ee.Feature(lakes_fc.filter(ee.Filter.eq('fid',ee.Number(allids[0]))).first()));

//add a buffer around the shapes of each reservoir to obtain the initial area of interest
var area_of_interest=selectedLake.buffer(buffer);

//------------------------------------------------------
// 3. Variables that will be used across functions
//------------------------------------------------------
var img_list_s2;
var withbands_s2;
var s2_cc;
var start_date;
var end_date;
var debug = false;
var selected_year;

//------------------------------------------------------
/// 4. UI ELEMENTS:
//------------------------------------------------------

//Define the Root Panel (Background map settings)
var satelliteMap = ui.Map();
satelliteMap.setOptions("HYBRID");
var root_widgets = ui.root.widgets();
root_widgets.reset([satelliteMap]);

///OPACITY SLIDER AND LEGEND
var slider1 = ui.Slider({direction: 'horizontal',style: {width:'80px',fontWeight: '450', fontSize: '12px', margin: '1px 0px 1px 1px'}}).setValue(1);
slider1.onSlide(function(value) {
  satelliteMap.layers().get(4).setShown(true);
  satelliteMap.layers().get(4).setOpacity(value);
});
var slider2 = ui.Slider({style: {stretch: 'both',width:'80px',fontWeight: '450', fontSize: '12px', margin: '1px 1px 1px 1px'}}).setValue(0);
slider2.onSlide(function(value) {
  satelliteMap.layers().get(0).setShown(true);
  satelliteMap.layers().get(0).setOpacity(value);
});
var slider3 = ui.Slider({style: {stretch: 'both',width:'80px',fontWeight: '450', fontSize: '12px', margin: '1px 1px 1px 1px'}}).setValue(0);
slider3.onSlide(function(value) {
  satelliteMap.layers().get(3).setShown(true);
  satelliteMap.layers().get(3).setOpacity(value);
});
var sliderPanel = ui.Panel({style :{position : "top-left",maxWidth: "200px"}});//
var sliderPanel_base = ui.Panel({style :{}});//

sliderPanel_base.widgets().set(0,ui.Label('Frequency S2', {fontWeight: '450', fontSize: '10px', margin: '1px 1px 1px 1px'}));
sliderPanel_base.widgets().set(1,slider2);

sliderPanel.widgets().set(0,ui.Label('Opacity', {fontWeight: '450', fontSize: '14px', margin: '1px 1px 1px 1px'}));
sliderPanel.widgets().set(1,sliderPanel_base);

var thumbnail_s2 = ui.Thumbnail({
  image: ee.Image(1).visualize({min: 1, max: 1, palette: ['#008000']}),
  params: {bbox:'0,0,10,100', dimensions:'16x16'},
  style: {padding: '0px', margin: '1px 1px 1px 0px'}
});
var slider1_panel=ui.Panel([slider1,thumbnail_s2], ui.Panel.Layout.Flow('horizontal'));

//Define a Panel for the drop down lists
var selectPanel = ui.Panel({style :{position : "top-left",maxWidth: "250px"}});

//Drop Down list for selection of water bodies in main manu
var select_wb = ui.Select({items: [],
  onChange: function(key){
    selectedLake = ee.Feature(Lakes_Zarafshan.filter(ee.Filter.eq('Name', key)).first()).geometry();
    area_of_interest=selectedLake.buffer(buffer);//geometry;
    satelliteMap.centerObject(area_of_interest);
    dd = ee.Image().paint(area_of_interest, 0, 2);
    img_select_s2.setPlaceholder('Please wait...');
    slider1.setValue(0,false);
    slider2.setValue(0,false);
    slider3.setValue(0,false);
    sliderPanel.clear();
    sliderPanel.widgets().set(0,ui.Label('Opacity', {fontWeight: '450', fontSize: '14px', margin: '1px 1px 1px 1px'}));
    sliderPanel.add(sliderPanel_base);
    start_s2=0;
    if (selected_year>0){
      select_another_date(selected_year);
    }
  }
}).setPlaceholder('ID_'+allids[0]); 

//Define elements for water body selection drop down list
var names=[];
for (var i = 0; i <allids.length ; i++) { 
  var thisname='ID_'+ allids[i];
  names.push(thisname);
}
select_wb.items().reset(names);

//Drop Down list for selection of years in main manu
var select_year = ui.Select({items: [],
    onChange: select_another_date}); 

//identify current year:
var thisyear=new Date().getFullYear();
//list of all years since 2015:
var year_list=ee.List.sequence(2015,ee.Number(thisyear)).map(function(nb){
        return {label: ee.String(ee.Number(nb).int()), value: nb};
}).getInfo();//we need the list as a client side object: pull it over with getInfo()

//Define elements for year selection drop down list
select_year.items().reset(year_list);

//Add the UI elements to the main panel
selectPanel.add(ui.Label({value: 'Select a Lake', style : {color:'black', fontWeight: '600', fontSize: '12px',margin: '1px 1px 1px 7px'}}));
selectPanel.add(select_wb);
selectPanel.add(ui.Label({value: 'Select a Year', style : {color:'black', fontWeight: '600', fontSize: '12px',margin: '1px 1px 1px 7px'}}));
selectPanel.add(select_year);

//Drop down list for the selection of satellite images. 
var img_select_s2 = ui.Select({
    items: [],//the list is now empty. Items will be defined after selection of year.
    onChange: draw_s2,
    style: {position:'top-center',padding:'1px', fontWeight:'bold', backgroundColor: 'red'},
});
img_select_s2.setDisabled(true); 
img_select_s2.setPlaceholder('Please wait...');

//------------------------------------------------------
/// 5. Functions
//------------------------------------------------------

// Import external dependencies
var gee_codes = require('users/hydrosolutions/public_functions:SedimentBalance_T1L2');

//Add bands to Sentinel-2 images
var addbands_sen = function(image){
  var mndwi = image.normalizedDifference(['B3', 'B11']).rename('MNDWI');
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
  var bands = ['B2', 'B3', 'B4', 'B8','B11','QA60'];
  var newbands = ['B', 'G', 'R', 'NIR','SWIR1','QA60'];  
  return image.select(bands).rename(newbands).addBands(mndwi).addBands(ndvi)
    .set({'SENSING_TIME': ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')});
};

//Get the edge of water bodies based on the surface water mask
function getEdge10m(mask) {
  //get a buffer around edge +- 1m
  return mask.focal_max({radius: 10, units: 'meters'}).subtract(mask.focal_min({radius: 10, units: 'meters'}));
}

//Obtain the sensing time in yyyy-mm-dd format
var func_sensing_time = function(image) {
  return image.set({'SENSING_TIME': ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')});
};  

//Function to obtain a list of available Sentinel-2 images
function get_new_s2(){ 
  withbands_s2 = ee.ImageCollection('COPERNICUS/S2')
    .filterBounds(area_of_interest)
    .filterDate(start_date, end_date)
    .map(addbands_sen);//.map(slc_gapfill);
  withbands_s2=withbands_s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', cc_pix));
  s2_cc =ee.ImageCollection("COPERNICUS/S2_CLOUD_PROBABILITY")
    .filterBounds(area_of_interest)
    .filterDate(start_date, end_date)
    .map(func_sensing_time);

  var img_names_s2=withbands_s2.sort('system:time_start').aggregate_array('SENSING_TIME');
  img_list_s2=ee.Dictionary.fromLists(img_names_s2.distinct(), img_names_s2.distinct()).keys();

  var water_surface_probability = ee.Image(get_Water_Occurence(area_of_interest,start_date,end_date));
  satelliteMap.layers().set(0, ui.Map.Layer(water_surface_probability, {min:0,max:1,palette:'white,blue'}, 'water surface probability S2',false).setOpacity(0));

  //update the drop-down list of satellite images available for selection
  img_select_s2.items().reset();
  img_select_s2.setDisabled(true);  
  img_select_s2.setPlaceholder('Please wait...');
  print('img_list_s2',img_list_s2);
  img_list_s2.evaluate(function(result1b){
    img_select_s2.items().reset(result1b);
    img_select_s2.setPlaceholder('Select Sentinel-2 Scene');
    img_select_s2.setDisabled(false);
    img_select_s2.onChange(draw_s2);
  });  
}

// Function to obtain  Water Occurence maps
function get_Water_Occurence(aoi,start_date,end_date){
  aoi=ee.Geometry(aoi);
  var withbands_s2 = ee.ImageCollection('COPERNICUS/S2')
    .filterBounds(aoi)
    .filterDate(start_date, end_date)
    .map(addbands_sen)
    .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', cc_pix));
  var s2_cc =ee.ImageCollection("COPERNICUS/S2_CLOUD_PROBABILITY")
    .filterBounds(aoi)
    .filterDate(start_date, end_date)
    .map(func_sensing_time);
  var img_names_s2=withbands_s2.sort('system:time_start').aggregate_array('SENSING_TIME');
  var img_list_s2=ee.Dictionary.fromLists(img_names_s2.distinct(), img_names_s2.distinct()).keys();
  var s2_mosaicked=ee.ImageCollection(img_list_s2.map(function(x){
    var img1=ee.Image(withbands_s2.filter(ee.Filter.eq('SENSING_TIME', x)).first());
    var s2_cloud_col=s2_cc.filter(ee.Filter.eq('SENSING_TIME', x));
    var s2_cloud=ee.Image(s2_cloud_col.mosaic());
    //Mosaic the available Sentinel-2 images
    return ee.Image(withbands_s2.filter(ee.Filter.eq('SENSING_TIME', x)).mosaic())
    .addBands(ee.Image(ee.Algorithms.If(s2_cloud_col.size().gt(0),s2_cloud,ee.Image(0).rename('probability'))))
    .clip(aoi).copyProperties(img1)
    .set('system:time_start',img1.get('system:time_start'));
  })).map(gee_codes.cloudscore_sen)
    .filter(ee.Filter.lt('cloud_cover', cc_thresh)).filter(ee.Filter.lt('nodata_cover', nodata_thresh));

  var water_images = s2_mosaicked.map(function(img){
    var imgcloud = ee.Image(img).select('cloud');
    var mndwi = ee.Image(img).select('MNDWI');
    mndwi=mndwi.where(mndwi.gt(maxValue),maxValue);//bcs we do not want to detect edges between free water and vegetation overgrown water
    var th = gee_codes.computeThresholdUsingOtsu(mndwi.updateMask(imgcloud.lt(100)), scale, aoi, cannyThreshold, cannySigma, minValue, debug);
    return mndwi.updateMask(imgcloud.lt(100)).gt(th);
  });

  var summed = water_images.reduce(ee.Reducer.sum());

  var prob = summed.divide(ee.Image(s2_mosaicked.size()));

  return prob.clip(aoi);
}

//Function that is used when the user selects another year
function select_another_date(keyD){
  selected_year=keyD;
  start_date = ee.Date.fromYMD(keyD,1,1);
  end_date = ee.Date.fromYMD(keyD, 12, 31);
  satelliteMap.remove(img_select_s2);
  satelliteMap.remove(sliderPanel);
  satelliteMap.add(img_select_s2);
  satelliteMap.add(sliderPanel);
  slider1.setValue(0,false);
  slider2.setValue(0,false);
  slider3.setValue(0,false);
  get_new_s2();

  sliderPanel.clear();
  sliderPanel.widgets().set(0,ui.Label('Opacity', {fontWeight: '450', fontSize: '14px', margin: '1px 1px 1px 1px'}));
  sliderPanel.add(sliderPanel_base);
  start_s2=0;
  var layer_index = satelliteMap.layers().length();
  for (var i=0; i<layer_index; i++) {
    satelliteMap.layers().get(i).setShown(false);
  }
  satelliteMap.layers().set(1, ui.Map.Layer(dd,null, "area_of_interest"));
  satelliteMap.layers().set(2, ui.Map.Layer(dl,{color: 'blue'}, "Inventory").setOpacity(0.2));
}

//Function that is used when the user select another satellite image
var start_s2=0;
var draw_s2 = function(key){
          if (start_s2===0){
            start_s2=1;
            sliderPanel.clear();
              satelliteMap.layers().get(2).setShown(false);//Lake polygon
              sliderPanel.widgets().set(0,ui.Label('Opacity', {fontWeight: '450', fontSize: '14px', margin: '1px 1px 1px 1px'}));
              sliderPanel.widgets().set(1,ui.Label('Edge S-2', {fontWeight: '450', fontSize: '10px', margin: '1px 1px 1px 1px'}));
              sliderPanel.widgets().set(2,slider1_panel);
              sliderPanel.widgets().set(3,ui.Label('S2 RGB', {fontWeight: '450', fontSize: '10px', margin: '1px 1px 1px 1px'}));
              sliderPanel.widgets().set(4,slider3);
              sliderPanel.add(sliderPanel_base);
          }
          
          var img1 = withbands_s2.filter(ee.Filter.eq('SENSING_TIME', key)).mosaic();
          var s2_cloud_col=s2_cc.filter(ee.Filter.eq('SENSING_TIME', key));
          var s2_cloud=ee.Image(s2_cloud_col.mosaic());
          var probability_band=ee.Image(ee.Algorithms.If(s2_cloud_col.size().gt(0),s2_cloud,ee.Image(0).rename('probability')));
          var cloud_bands=ee.Image(gee_codes.cloudscore_sen(img1.clip(area_of_interest).addBands(probability_band)));

          var image=ee.Image(img1.addBands(cloud_bands.select(['cloud','probability'])).copyProperties(cloud_bands));
          print('selected image',image);

          satelliteMap.layers().set(3, ui.Map.Layer(image, {bands: (['R', 'G', 'B']), min: 0, max: 3000}, 's2 RGB'));

          // compute threshold using Otsu thresholding
          var bounds=area_of_interest;
          var mndwi = image.select('MNDWI');
          var imgcloud = image.select('cloud');
          var maxValue=0.3;
          mndwi=mndwi.where(mndwi.gt(maxValue),maxValue);//bcs we do not want to detect edges between free water and vegetation overgrown water
          //OTSU THRESHOLD
          var scale=ee.Number(30);
          var th = gee_codes.computeThresholdUsingOtsu(mndwi.updateMask(imgcloud.lt(100)), scale, bounds, cannyThreshold, cannySigma, minValue, debug);
          
          var dem_edge=ee.Image(1).mask(getEdge10m(mndwi.gt(th))).updateMask(imgcloud.lt(100));
          satelliteMap.layers().set(4, ui.Map.Layer(dem_edge.clip(area_of_interest), {palette:'#008000'}, 'Waterline'));
          
          slider1.setValue(1);
          slider3.setValue(1);

        };

//------------------------------------------------------
// 6. Add elements to the main map
//------------------------------------------------------

satelliteMap.add(selectPanel );
//Paint the shape of the reservoirs to the main map
var dd = ee.Image().paint(area_of_interest, 0, 2);
satelliteMap.centerObject(area_of_interest,14);
var dl = ee.Image().paint(ee.FeatureCollection(lakes_fc), 0, 2);
satelliteMap.addLayer(dd,null,'area_of_interest');
satelliteMap.addLayer(lakes_fc, {color: 'blue'}, 'Inventory');

//------------------------------------------------------
// 7. Iterate over all Water Bodies and export the Water Occurence maps to an Asset
//------------------------------------------------------

//We do not directly export to Google Drive because the Water Occurence Maps can be further used within GEE if they are available as assets
for (var id = 0; id <missingIDs.length ; id++) { 
  var thisname=missingIDs[id];
  var thisLake = ee.Feature(Lakes.filter(ee.Filter.eq('fid', thisname)).first()).geometry();
  var aoi = thisLake.buffer(buffer);//geometry;
  var new_water_surface_probability=get_Water_Occurence(aoi,start_date_4export,end_date_4export);
  Export.image.toAsset({
    image:new_water_surface_probability,
    description: freqMapName + thisname,// lake_name.getInfo(),
    assetId: path_Freq + '/'+freqMapName + thisname,
    scale: 10,
    maxPixels: 1e13,
    region:aoi
  }); 
}


// //Uncomment the following lines to export the images from assets to your Google Drive
// // get the list of Images already exported:
// var Table = ee.data.listAssets(path_Freq);
// var l;//number of exported images
// if (Table.assets===null){
//   l=0;
// } else {
//   l =Table.assets.length;
// }
// print('number of already exported images',l);
// for (var i=0; i<l; i++) {
//   var geom = ee.Geometry(ee.Image(Table.assets[i].id).get('system:footprint'));
//   //print('geom',geom);
//         Export.image.toDrive({
//         image:ee.Image(Table.assets[i].id),
//         description: Table.assets[i].id.split(path_Freq + '/')[1],
//         folder: 'Zarafshan_SurfFrequencyMaps',
//         scale: 10,
//         maxPixels: 1e13,
//         region:geom
//       }); 
// }


