Visualize daily weather forecast from GFS using Google Earth Engine
Since few years ago NOAA Global Forecast Data (GFS) available at Google Earth Engine (GEE), which makes it easier to access even though there are only 9 variables out of hundreds available.
Accessing GFS data is tricky, because it can get pretty confusing with the timing. I have discussed it before on access GFS via GRIB filter and getting GFS rainfall forecast using GEE.
Let’s start write the code. I would like to visualize all the 9-variable of GFS data using GEE.
First step is to define the geographic domain and write the symbology for all variable. Each variable will have different min-max value and palette.
// INITIALISATION AND SYMBOLOGY //----------------------------- ////// // Center of the map Map.setCenter(118, -2.5, 6); // Define geographic domain. //--- var rectangle = ee.Geometry.Rectangle(92, -12, 145, 9); // Indonesia var bbox = ee.Feature(rectangle).geometry(); //Color-codes based on Color-Brewer https://colorbrewer2.org/ //--- // Temperature visualisation var visTave = { min: 20, max: 35, palette: ['ffffcc','ffeda0','fed976','feb24c', 'fd8d3c','fc4e2a','e31a1c','bd0026','800026'] }; // Specific Humidity visualisation var visSH = { min: 0, max: 30, palette: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', 'abd9e9', '74add1', '4575b4', '313695'], }; // Relative Humidity visualisation var visRH = { min: 50, max: 100, palette: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', 'abd9e9', '74add1', '4575b4', '313695'], }; // Wind speed visualisation var visWind = { min: 0, max: 10, palette: ['3288bd', '66c2a5', 'abdda4', 'e6f598', 'fee08b', 'fdae61', 'f46d43', 'd53e4f'], }; // Total precipitation visualisation var visPcp = { min: 0, max: 50, palette: [ 'cccccc','f9f3d5','dce2a8','a8c58d','77a87d','ace8f8', '4cafd9','1d5ede','001bc0','9131f1','e983f3','f6c7ec' ]}; // Precipitable water visualisation var visPW = { min: 0, max: 50, palette: [ 'cccccc','f9f3d5','dce2a8','a8c58d','77a87d','ace8f8', '4cafd9','1d5ede','001bc0','9131f1','e983f3','f6c7ec' ]}; // Total cloud coverage visualisation var visCC = { min: 0, max: 100, // dataset max is 100 palette: ['black','white'] }; // Shortwave radiation visualisation var visSR = { min: 0, max: 1230, palette: ['blue', 'purple', 'cyan', 'green', 'yellow', 'red'], };
Now set the date and data
// DATE AND DATA //-------------------------------- ////// // Strip time off current date/time var today = ee.Date(new Date().toISOString().split('T')[0]); print(today); // GFS Dataset Availability: // https://developers.google.com/earth-engine/datasets/catalog/NOAA_GFS0P25#citations var start_period = ee.Date('2019-11-07'); // First GFS data 1 Jul 2015 // MAIN INPUT //----------- ////// // Create function to import GFS data - https://developers.google.com/earth-engine/datasets/catalog/NOAA_GFS0P25 // 24-hours/1-day forecast from selected date var GFS0P25 = ee.ImageCollection('NOAA/GFS0P25');
Now as the initial process when map loaded, create new variable for each band, select the bands and do filtering based on date and “forecast_hours”. The focus is only grab 1-day forecast, so I will use forecast hours 6,12,18,24.
I will adapt script from Gennadii Donchyts to visualize Wind Direction as a vector.
// INITIAL PROCESS WHEN MAP LOADED //-------------------------------- ////// // Add today's GFS data to the map // Below is the example on 24-hours data using forecast_hours 6,12,18,24 // To get data for day-2 from the forecast date, use forecast_hours 30,36,42,48 // To get data for day-3 from the forecast date, use forecast_hours 54,60,66,72 // 2m Temperature var tave_d1_draft = GFS0P25 .select('temperature_2m_above_ground') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var tave_d1 = tave_d1_draft.clip(bbox); // 2m Specific Humidity var sh_d1_draft = GFS0P25 .select('specific_humidity_2m_above_ground') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection // SH unit in GFS is kg/kg, I want to convert it to g/kg // This means that such humidity is expressed as the amount of // water vapour (in grams) present in each kilogram of air var sh_d1 = sh_d1_draft.expression( 'SHg = SHkg * 1000', { 'SHkg': sh_d1_draft.select('specific_humidity_2m_above_ground') }).float().clip(bbox); // 2m Relative Humidity var rh_d1_draft = GFS0P25 .select('relative_humidity_2m_above_ground') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var rh_d1 = rh_d1_draft.clip(bbox); // 10m wind var uv_d1_draft = GFS0P25 .select(['u_component_of_wind_10m_above_ground', 'v_component_of_wind_10m_above_ground']) .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection // Wind speed and direction //--- // Based on code from Gennadii Donchyts // https://code.earthengine.google.com/320ee5bc81f2de3ae49f348f8ec9a6d7 var uv_d1_0 = uv_d1_draft.clip(bbox); var scale = Map.getScale() * 10; //25000 var numPixels = 1e10; var samples = uv_d1_0.rename(['u10', 'v10']).sample({ region: bbox, scale: scale, numPixels: numPixels, geometries: true }); var scaleVector = 0.1; var vectors_d1 = samples.map(function(f) { var u = ee.Number(f.get('u10')).multiply(scaleVector); var v = ee.Number(f.get('v10')).multiply(scaleVector); var origin = f.geometry(); // translate var proj = origin.projection().translate(u, v); var end = ee.Geometry.Point(origin.transform(proj).coordinates()); // construct line var geom = ee.Algorithms.GeometryConstructors.LineString([origin, end], null, true); return f.setGeometry(geom); }); var uv_d1 = uv_d1_0.pow(2).reduce(ee.Reducer.sum()).sqrt(); // Precipitation var pcp_d1_draft = GFS0P25 .select('total_precipitation_surface') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .sum(); // make a composite of the collection var pcp_d1 = pcp_d1_draft.updateMask(pcp_d1_draft.gt(1)).clip(bbox); // Precipitable Water var pw_d1_draft = GFS0P25 .select('precipitable_water_entire_atmosphere') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var pw_d1 = pw_d1_draft.clip(bbox); // Total Cloud Cover var cc_d1_draft = GFS0P25 .select('total_cloud_cover_entire_atmosphere') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var cc_d1 = cc_d1_draft.clip(bbox); // Downward Shortwave Radiation Flux var sr_d1_draft = GFS0P25 .select('downward_shortwave_radiation_flux') .filterDate(today, today.advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var sr_d1 = sr_d1_draft.clip(bbox);
Next script will be adding all layer to the display.
I am planning to use Date Slider widget, so the user can easily change the date then the map will automatically change. To support that plan, I need to add Reset procedure, so every change on the data, all layers will get refresh.
// ui Map Layer // Load the image into map using standard symbology var layer_sr_d1 = ui.Map.Layer(sr_d1,visSR,'SRF - Shortwave Radiation Flux (W/m^2)', false); var layer_cc_d1 = ui.Map.Layer(cc_d1,visCC,'CC - Total Cloud Coverage (%)', false); var layer_pw_d1 = ui.Map.Layer(pw_d1,visPW,'PW - Precipitable Water (mm)', false); var layer_pcp_d1 = ui.Map.Layer(pcp_d1,visPcp,'Pcp - Precipitation (mm)', false); var layer_uv_d1 = ui.Map.Layer(uv_d1,visWind,'WS - Wind Speed (m/s)', false); var layer_vectors_d1 = ui.Map.Layer(vectors_d1.style({ color: 'white', width: 1 }), {}, 'WD - Wind Directions', false); var layer_rh_d1 = ui.Map.Layer(rh_d1,visRH,'RH - Relative Humidity (%)', false); var layer_sh_d1 = ui.Map.Layer(sh_d1,visSH,'SH - Specific Humidity (g/kg)', false); var layer_tave_d1 = ui.Map.Layer(tave_d1,visTave,'Temp - Temperature (°C)', true); // Reset all layers Map.layers().reset([ layer_sr_d1,layer_cc_d1,layer_pw_d1,layer_pcp_d1, layer_uv_d1,layer_vectors_d1,layer_rh_d1,layer_sh_d1,layer_tave_d1 ]);
Let’s add Date Slider configuration to Map.
According to the reference, Date Slider required some arguments: start, end, value, period, onChange, disabled, style
// DATE SLIDER CONFIGURATION //-------------------------- ////// var startDate = start_period.getInfo(); var endDate = today.advance(24,'hour').getInfo(); // Slider function var slider = ui.DateSlider({ start: startDate.value, end: endDate.value, period: 1, // Every 5 days style: {width: '300px', padding: '10px'}, onChange: renderDateRange }); Map.add(slider);
Then the following code will follow to describe the renderDateRange function when the Date Slide onChange is active. The code is similar with initial process when map loaded above..
// Render date range function function renderDateRange(dateRange) { // 24-hours/1-day/Day-1 forecast from selected date // 2m Temperature var tave_d1_draft = GFS0P25 .select('temperature_2m_above_ground') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var tave_d1 = tave_d1_draft.clip(bbox); // 2m Specific Humidity var sh_d1_draft = GFS0P25 .select('specific_humidity_2m_above_ground') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection // SH unit in GFS is kg/kg, I want to convert it to g/kg // This means that such humidity is expressed as the amount of // water vapour (in grams) present in each kilogram of air var sh_d1 = sh_d1_draft.expression( 'SHg = SHkg * 1000', { 'SHkg': sh_d1_draft.select('specific_humidity_2m_above_ground') }).float().clip(bbox); // 2m Relative Humidity var rh_d1_draft = GFS0P25 .select('relative_humidity_2m_above_ground') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var rh_d1 = rh_d1_draft.clip(bbox); // 10m wind var uv_d1_draft = GFS0P25 .select(['u_component_of_wind_10m_above_ground', 'v_component_of_wind_10m_above_ground']) .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection // Wind speed and direction //--- // Based on code from Gennadii Donchyts // https://code.earthengine.google.com/320ee5bc81f2de3ae49f348f8ec9a6d7 var uv_d1_0 = uv_d1_draft.clip(bbox); var scale = Map.getScale() * 10; //25000 var numPixels = 1e10; var samples = uv_d1_0.rename(['u10', 'v10']).sample({ region: bbox, scale: scale, numPixels: numPixels, geometries: true }); var scaleVector = 0.1; var vectors_d1 = samples.map(function(f) { var u = ee.Number(f.get('u10')).multiply(scaleVector); var v = ee.Number(f.get('v10')).multiply(scaleVector); var origin = f.geometry(); // translate var proj = origin.projection().translate(u, v); var end = ee.Geometry.Point(origin.transform(proj).coordinates()); // construct line var geom = ee.Algorithms.GeometryConstructors.LineString([origin, end], null, true); return f.setGeometry(geom); }); var uv_d1 = uv_d1_0.pow(2).reduce(ee.Reducer.sum()).sqrt(); // Precipitation var pcp_d1_draft = GFS0P25 .select('total_precipitation_surface') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .sum(); // make a composite of the collection var pcp_d1 = pcp_d1_draft.updateMask(pcp_d1_draft.gt(1)).clip(bbox); // Precipitable Water var pw_d1_draft = GFS0P25 .select('precipitable_water_entire_atmosphere') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var pw_d1 = pw_d1_draft.clip(bbox); // Total Cloud Cover var cc_d1_draft = GFS0P25 .select('total_cloud_cover_entire_atmosphere') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var cc_d1 = cc_d1_draft.clip(bbox); // Downward Shortwave Radiation Flux var sr_d1_draft = GFS0P25 .select('downward_shortwave_radiation_flux') .filterDate(dateRange.start(), dateRange.end().advance(6,'hour')) .filter(ee.Filter.lt('forecast_time',today.advance(1,'day').millis())) .filter(ee.Filter.inList('forecast_hours', [6,12,18,24])) .mean(); // make a composite of the collection var sr_d1 = sr_d1_draft.clip(bbox); // Load the image into map using standard symbology var layer_sr_d1 = ui.Map.Layer(sr_d1,visSR,'SRF - Shortwave Radiation Flux (W/m^2)', false); var layer_cc_d1 = ui.Map.Layer(cc_d1,visCC,'CC - Total Cloud Coverage (%)', false); var layer_pw_d1 = ui.Map.Layer(pw_d1,visPW,'PW - Precipitable Water (mm)', false); var layer_pcp_d1 = ui.Map.Layer(pcp_d1,visPcp,'Pcp - Precipitation (mm)', false); var layer_uv_d1 = ui.Map.Layer(uv_d1,visWind,'WS - Wind Speed (m/s)', false); var layer_vectors_d1 = ui.Map.Layer(vectors_d1.style({ color: 'white', width: 1 }), {}, 'WD - Wind Directions', false); var layer_rh_d1 = ui.Map.Layer(rh_d1,visRH,'RH - Relative Humidity (%)', false); var layer_sh_d1 = ui.Map.Layer(sh_d1,visSH,'SH - Specific Humidity (g/kg)', false); var layer_tave_d1 = ui.Map.Layer(tave_d1,visTave,'Temp - Temperature (°C)', true); // Reset all layers Map.layers().reset([ layer_sr_d1,layer_cc_d1,layer_pw_d1,layer_pcp_d1, layer_uv_d1,layer_vectors_d1,layer_rh_d1,layer_sh_d1,layer_tave_d1 ]); }
Next, I would like to add Download button, so user can easily download all the variable and save it into Google Drive for whatever date they choose. The filename convention will be NameOfVariable_YYYY-MM-DD
// DATA DOWNLOAD //--------------------- ////// // Add download button to panel var download = ui.Button({ label: 'Downloads the data', style: {position: 'bottom-right', color: 'black'}, onClick: function() { // As the download request probably takes some time, we need to warn the user, // so they not repeating click the download button alert("If you see this message, the download request is started! Please go to Tasks, wait until the download list appear. Click RUN for each data you want to download"); // Downloading data // Export the result to Google Drive var dt = ee.Date(slider.getValue()[0]); // Temperature Export.image.toDrive({ image:tave_d1, description:'Temperature_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Specific Humidity Export.image.toDrive({ image:sh_d1, description:'SpecificHumidity_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Relative Humidity Export.image.toDrive({ image:rh_d1, description:'RelativeHumidity_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Wind Speed Export.image.toDrive({ image:uv_d1, description:'WindSpeed_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Precipitation Export.image.toDrive({ image:pcp_d1, description:'Precipitation_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Precipitable Water Export.image.toDrive({ image:pw_d1, description:'Precipitable_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Cloud Cover Export.image.toDrive({ image:cc_d1, description:'CloudCover_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); // Shortwave Radiation Flux Export.image.toDrive({ image:sr_d1, description:'ShortwaveRadiationFlux_' + dt.format('yyyy-MM-dd').getInfo(), folder:'GEE_GFS', scale:projection.nominalScale(), region:bbox, maxPixels:1e12 }); print('Data Downloaded!', dt); } }); // Add the button to the map and the panel to root. Map.add(download);
I would like to display all legend for all layer in a one place. To achieve the goal, I need to define some parameters: position in the panel, widget for each feature (title, unit, min-max value and a vertical ramp color as thumbnail image.
// LEGEND CONFIGURATION //--------------------- ////// // Temperature legend // Create legend title var TaveLegendTitle = ui.Label({value: 'Temp',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var Taveunit = ui.Label({value:'(°C)'}); var TaveunitMax = ui.Label({value: '35'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var TaveGradient = lon.multiply((visTave.max-visTave.min)/100.0).add(visTave.min); var TaveLegendImage = TaveGradient.visualize(visTave); var TaveThumb = ui.Thumbnail({image: TaveLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var TaveunitMin = ui.Label({value: '20'}); // Set position of panel var TaveLegend = ui.Panel({ widgets: [TaveLegendTitle, Taveunit, TaveunitMax, TaveThumb, TaveunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Specific Humidity legend // Create legend title var SHLegendTitle = ui.Label({value: 'SH',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var SHunit = ui.Label({value:'(g/kg)'}); var SHunitMax = ui.Label({value: '30'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var SHGradient = lon.multiply((visSH.max-visSH.min)/100.0).add(visSH.min); var SHLegendImage = SHGradient.visualize(visSH); var SHThumb = ui.Thumbnail({image: SHLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var SHunitMin = ui.Label({value: '0'}); // Set position of panel var SHLegend = ui.Panel({ widgets: [SHLegendTitle, SHunit, SHunitMax, SHThumb, SHunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Relative Humidity legend // Create legend title var RHLegendTitle = ui.Label({value: 'RH',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var RHunit = ui.Label({value:'(%)'}); var RHunitMax = ui.Label({value: '100'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var RHGradient = lon.multiply((visRH.max-visRH.min)/100.0).add(visRH.min); var RHLegendImage = RHGradient.visualize(visRH); var RHThumb = ui.Thumbnail({image: RHLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var RHunitMin = ui.Label({value: '0'}); // Set position of panel var RHLegend = ui.Panel({ widgets: [RHLegendTitle, RHunit, RHunitMax, RHThumb, RHunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Wind Speed legend // Create legend title var WSLegendTitle = ui.Label({value: 'WS',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var WSunit = ui.Label({value:'(m/s)'}); var WSunitMax = ui.Label({value: '10'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var WSGradient = lon.multiply((visWind.max-visWind.min)/100.0).add(visWind.min); var WSLegendImage = WSGradient.visualize(visWind); var WSThumb = ui.Thumbnail({image: WSLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var WSunitMin = ui.Label({value: '0'}); // Set position of panel var WSLegend = ui.Panel({ widgets: [WSLegendTitle, WSunit, WSunitMax, WSThumb, WSunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Precipitation and Precipitable Water legend // Create legend title var PcpLegendTitle = ui.Label({value: 'Pcp/PW',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var Pcpunit = ui.Label({value:'(mm)'}); var PcpunitMax = ui.Label({value: '50'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var PcpGradient = lon.multiply((visPcp.max-visPcp.min)/100.0).add(visPcp.min); var PcpLegendImage = PcpGradient.visualize(visPcp); var PcpThumb = ui.Thumbnail({image: PcpLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var PcpunitMin = ui.Label({value: '0'}); // Set position of panel var PcpLegend = ui.Panel({ widgets: [PcpLegendTitle, Pcpunit, PcpunitMax, PcpThumb, PcpunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Cloud Coverage legend // Create legend title var CCLegendTitle = ui.Label({value: 'CC',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var CCunit = ui.Label({value:'(%)'}); var CCunitMax = ui.Label({value: '100'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var CCGradient = lon.multiply((visCC.max-visCC.min)/100.0).add(visCC.min); var CCLegendImage = CCGradient.visualize(visCC); var CCThumb = ui.Thumbnail({image: CCLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var CCunitMin = ui.Label({value: '0'}); // Set position of panel var CCLegend = ui.Panel({ widgets: [CCLegendTitle, CCunit, CCunitMax, CCThumb, CCunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); ////// ////// // Shortwave Radiation Flux legend // Create legend title var SRLegendTitle = ui.Label({value: 'SRF',style: {fontWeight: 'bold',fontSize: '14px', margin: '0 0 4px 10',padding: '0'}}); // Create text on top of legend var SRunit = ui.Label({value:'(W/m^2)'}); var SRunitMax = ui.Label({value: '1230'}); // Create the legend image var lon = ee.Image.pixelLonLat().select('latitude'); var SRGradient = lon.multiply((visSR.max-visSR.min)/100.0).add(visSR.min); var SRLegendImage = SRGradient.visualize(visSR); var SRThumb = ui.Thumbnail({image: SRLegendImage,params: {bbox:'0,0,20,100', dimensions:'20x250'}, style: {padding: '1px', position: 'bottom-center'}}); var SRunitMin = ui.Label({value: '0'}); // Set position of panel var SRLegend = ui.Panel({ widgets: [SRLegendTitle, SRunit, SRunitMax, SRThumb, SRunitMin], style: {position: 'bottom-left',padding: '8px 4px'}}); //////
Last configuration will be adding a panel for Legends. As I will display all the legend together, it will take up a space. So activate the panel through the button click are good idea, then I need to add additional button to hide the legend panel if not needed.
////// // Panel Legends // Vertical ramp color var vertRamp = ui.Panel({ widgets: [TaveLegend, SHLegend, RHLegend, WSLegend, PcpLegend, CCLegend, SRLegend], layout: ui.Panel.Layout.Flow('horizontal') }); var panel_legends = ui.Panel(); panel_legends.style().set({ width: '420px', height: '500px', position: 'top-right', color: 'black', shown: false }); Map.add(panel_legends); // Legend title var Lintro = ui.Panel([ ui.Label({ value: 'Legends', style: {fontSize: '20px', fontWeight: 'bold'} }), ]); // Add a button to close legend // Add a button to hide the Panel. var Closebut = ui.Button({label: 'Close', style: {color: 'black'}, onClick: function() { panel_legends.style().set('shown', false); button.style().set('shown', true); } }); // // Add a button to hide the Panel. var buts = ui.Panel({layout: ui.Panel.Layout.flow('horizontal'), widgets:[Lintro, Closebut], style: {width: '420px', height: '60px', position:'top-right'}}); panel_legends.add(buts); panel_legends.add(vertRamp); // Create a button to unhide the panel. var button = ui.Button({ label: 'Show Legends', style: {position: 'bottom-right', color: 'black'}, onClick: function() { // Hide the button. button.style().set('shown', false); // Display the panel. panel_legends.style().set('shown', true); // Temporarily make a map click hide the panel // and show the button. var listenerId = Map.onClick(function() { panel_legends.style().set('shown', false); button.style().set('shown', true); // Once the panel is hidden, the map should not try to close it by listening for clicks. Map.unlisten(listenerId); }); } }); // Add the button to the map and the panel to root. Map.add(button);
Link for the full code
Notes: The code was compiled from various source (GEE help, GEE Groups, StackExchange)