4 research outputs found

    Errors in Time-Series Remote Sensing and an Open Access Application for Detecting and Visualizing Spatial Data Outliers Using Google Earth Engine

    No full text

    MODIS product version comparison application for Google Earth Engine

    No full text
    MODIS product version comparison application for Google Earth Engine This is associated with an Accepted Manuscript of an article published by IEEE in IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing on 20 March 2019, available online at doi.org/10.1109/JSTARS.2019.2901404. Reference: Peter, B.G. and Messina, J.P., 2019. Errors in Time-Series Remote Sensing and an Open Access Application for Detecting and Visualizing Spatial Data Outliers Using Google Earth Engine. IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing, 12(4), pp.1165-1174. Link to manuscript https://ieeexplore.ieee.org/abstract/document/8672086 Interactive Google Earth Engine Application https://cartoscience.users.earthengine.app/view/versions Google Earth Engine Code // Version 1.1 Map.setCenter(30, 20, 2.5).setOptions('HYBRID').style().set('cursor', 'crosshair'); var countryList = ee.FeatureCollection('ft:1tdSwUL7MVpOauSgRzqVTOwdfy17KDbw-1d9omPw'); var stats = function(year) { Map.layers().reset(); var countrySelected = app.country.countrySelect.getValue(); var region = countryList.filterMetadata('Country', 'equals', countrySelected).geometry(); var versionOne = app.inputBox.productBox.getValue(); var versionTwo = app.inputBox.productBoxTwo.getValue(); var band = app.inputBox.bandBox.getValue(); var bandTwo = app.inputBox.bandBoxTwo.getValue(); if (app.inputBox.customCheckbox.getValue() === true) { var latCoord = ee.Number.parse(app.inputBox.latCoordBox.getValue()).getInfo(); var lonCoord = ee.Number.parse(app.inputBox.lonCoordBox.getValue()).getInfo(); var distBuffer = ee.Number.parse(app.inputBox.distBox.getValue()).getInfo(); var distNum = distBuffer*1000; region = ee.Geometry.Point([lonCoord,latCoord]).buffer(distNum).bounds(); } var modisCollectionOne = ee.ImageCollection(versionOne).select(band); var modisCollectionTwo = ee.ImageCollection(versionTwo).select(bandTwo); var imageOne = modisCollectionOne.filter(ee.Filter.calendarRange(year,year,'year')).mean(); var imageTwo = modisCollectionTwo.filter(ee.Filter.calendarRange(year,year,'year')).mean(); var abs = imageOne.select(band).subtract(imageTwo.select(bandTwo)).abs().rename("difference"); var percentilesOne = imageOne.reduceRegion({ reducer: ee.Reducer.percentile([10,90]), geometry: region, scale: 250, maxPixels: 1e13 }); var percentilesTwo = imageTwo.reduceRegion({ reducer: ee.Reducer.percentile([10,90]), geometry: region, scale: 250, maxPixels: 1e13 }); var percentilesAbs = abs.reduceRegion({ reducer: ee.Reducer.percentile([10,90]), geometry: region, scale: 250, maxPixels: 1e13 }); var minOne = ee.Number(percentilesOne.get(band+'_p10')).getInfo(); var maxOne = ee.Number(percentilesOne.get(band+'_p90')).getInfo(); var minTwo = ee.Number(percentilesTwo.get(bandTwo+'_p10')).getInfo(); var maxTwo = ee.Number(percentilesTwo.get(bandTwo+'_p90')).getInfo(); var minBoth = Math.min(minOne,minTwo); var maxBoth = Math.max(maxOne,maxTwo); var minAbs = ee.Number(percentilesAbs.get('difference_p10')).getInfo(); var maxAbs = ee.Number(percentilesAbs.get('difference_p90')).getInfo(); var grayscale = ['f7f7f7', 'cccccc', '969696', '525252','141414']; Map.addLayer(imageOne.select(band).rename(band+'_'+versionOne).clip(region),{min: minBoth, max: maxBoth, palette: grayscale},band+' • '+versionOne, false); Map.addLayer(imageTwo.select(bandTwo).rename(bandTwo+'_'+versionTwo).clip(region),{min: minBoth, max: maxBoth, palette: grayscale},band+' • '+versionTwo, false); Map.addLayer(abs.clip(region),{min: minAbs, max: maxAbs, palette: grayscale},"Difference"); var options = { title: year+' Histogram', fontSize: 11, legend: {position: 'none'}, series: {0: {color: '7100AA'}} }; var histogram = ui.Chart.image.histogram(imageOne, region, 10000).setOptions(options); var optionsTwo = { title: year+' Histogram', fontSize: 11, legend: {position: 'none'}, series: {0: {color: '0071AA'}} }; var histogramTwo = ui.Chart.image.histogram(imageTwo, region, 10000).setOptions(optionsTwo); var clickLabel = ui.Label('Click map to get pixel time-series', {fontWeight: '300', fontSize: '13px', margin: '10px 10px 15px 30px'}); var clickLabelTwo = ui.Label('Click map to get pixel time-series', {fontWeight: '300', fontSize: '13px', margin: '10px 10px 15px 30px'}); app.rootPanels.panelOne.widgets().set(1, ui.Label('temp')); app.rootPanels.panelTwo.widgets().set(1, ui.Label('temp')); app.rootPanels.panelOne.widgets().set(1, histogram); app.rootPanels.panelOne.widgets().set(2, clickLabel); app.rootPanels.panelTwo.widgets().set(1, histogramTwo); app.rootPanels.panelTwo.widgets().set(2, clickLabelTwo); Map.centerObject(region); Map.setOptions('HYBRID'); Map.onClick(function(coords) { var point = ee.Geometry.Point(coords.lon, coords.lat); var dot = ui.Map.Layer(point, {color: 'AA0000'}, "Inspector"); Map.layers().set(3, dot); var clickChart = ui.Chart.image.series(modisCollectionOne, point, ee.Reducer.mean(), 10000); clickChart.setOptions({ title: 'Pixel | X: ' + coords.lon.toFixed(2)+', '+'Y: ' + coords.lat.toFixed(2), fontSize: 10, legend: {position: 'none'}, curveType: 'function', series: {0: {color: '7100AA'}}, trendlines: {0:{color: '202020', lineWidth: 1}} }); var clickChartTwo = ui.Chart.image.series(modisCollectionTwo, point, ee.Reducer.mean(), 10000); clickChartTwo.setOptions({ title: 'Pixel | X: ' + coords.lon.toFixed(2)+', '+'Y: ' + coords.lat.toFixed(2), fontSize: 10, legend: {position: 'none'}, curveType: 'function', series: {0: {color: '0071AA'}}, trendlines: {0:{color: '202020', lineWidth: 1}} }); app.rootPanels.panelOne.widgets().set(2, clickChart); app.rootPanels.panelTwo.widgets().set(2, clickChartTwo); }); }; // Load parameter input panel var initialize = function(init) { Map.clear(); Map.setOptions('HYBRID').style().set('cursor', 'crosshair'); var countrySelected = app.country.countrySelect.getValue(); var region = countryList.filterMetadata('Country', 'equals', countrySelected).geometry(); var versionOne = app.inputBox.productBox.getValue(); var versionTwo = app.inputBox.productBoxTwo.getValue(); var band = app.inputBox.bandBox.getValue(); var bandTwo = app.inputBox.bandBoxTwo.getValue(); var modisCollectionOne = ee.ImageCollection(versionOne).select(band); var modisCollectionTwo = ee.ImageCollection(versionTwo).select(bandTwo); var startYearOne = ee.Date(modisCollectionOne.first().get('system:time_start')).get('year'); var endYearOne = ee.Date(modisCollectionOne.sort('system:time_start',false).first().get('system:time_start')).get('year'); var startYearTwo = ee.Date(modisCollectionTwo.first().get('system:time_start')).get('year'); var endYearTwo = ee.Date(modisCollectionTwo.sort('system:time_start',false).first().get('system:time_start')).get('year'); var startYear = Math.max(ee.Number(startYearOne).getInfo(),ee.Number(startYearTwo).getInfo()); var endYear = Math.min(ee.Number(endYearOne).getInfo(),ee.Number(endYearTwo).getInfo()); ui.root.remove(app.rootPanels.panelOne); ui.root.remove(app.rootPanels.panelTwo); ui.root.insert(1,app.rootPanels.panelOne); ui.root.insert(2,app.rootPanels.panelTwo); var label = ui.Label('Select year'); var slider = ui.Slider({ min: startYear, max: endYear, step: 1, onChange: stats, style: {stretch: 'horizontal'} }); var panelSlider = ui.Panel({ widgets: [label, slider], layout: ui.Panel.Layout.flow('horizontal'), style: {fontSize: '10px', position: 'bottom-right', padding: '0'} }); var sliderStart = startYear+1; slider.setValue(sliderStart); Map.add(panelSlider); var display = { title: versionOne, fontSize: 11, curveType: 'function', series: {0: {color: '7100AA'}}, trendlines: {0:{color: '202020', lineWidth: 1}} }; var timeSeries = ui.Chart.image.series(modisCollectionOne.select(band), region, ee.Reducer.mean(), 10000).setOptions(display); var displayTwo = { title: versionTwo, fontSize: 11, curveType: 'function', series: {0: {color: '0071AA'}}, trendlines: {0:{color: '202020', lineWidth: 1}} }; var timeSeriesTwo = ui.Chart.image.series(modisCollectionTwo.select(bandTwo), region, ee.Reducer.mean(), 10000).setOptions(displayTwo); app.rootPanels.panelOne.widgets().set(0, timeSeries); app.rootPanels.panelTwo.widgets().set(0, timeSeriesTwo); }; // UI input panel layout var app = {}; app.createPanels = function() { app.intro = { panel: ui.Panel([ ui.Label({value: 'MODIS version comparison', style: {fontWeight: '700', fontSize: '20px', margin: '10px 5px 1px 10px'}}), ui.Label({value: "Enter fields to generate and compare time-series and histograms" , style: {fontWeight: '300', fontSize: '14px', margin: '5px 15px 10px 10px'}}) ]) }; app.rootPanels = { panelOne: ui.Panel({layout: ui.Panel.Layout.flow('vertical'), style: {width: '275px'}}), panelTwo: ui.Panel({layout: ui.Panel.Layout.flow('vertical'), style: {width: '275px'}}), }; // Input boxes app.inputBox = { customCheckbox: ui.Checkbox({label: 'Use custom boundary', style: {fontSize: '12px', margin: '5px 1px 1px 10px'}, value: false}), latCoordBox: ui.Textbox({placeholder: 'e.g., -14.9', style: {width: '80px', margin: '2px 1px 1px 10px'}}), lonCoordBox: ui.Textbox({placeholder: 'e.g., 35.5', style: {width: '80px', margin: '2px 1px 1px 1px'}}), distBox: ui.Textbox({placeholder: 'e.g., 100', style: {width: '75px', margin: '2px 1px 1px 1px'}}), productBox: ui.Textbox({placeholder: 'e.g., MODIS/006/MOD17A3H',style: {width: '260px', margin: '10px 1px 1px 10px'}}), productBoxTwo: ui.Textbox({placeholder: 'e.g., MODIS/055/MOD17A3',style: {width: '260px', margin: '10px 1px 1px 10px'}}), bandBox: ui.Textbox({placeholder: 'e.g., Npp', style: {width: '185px', margin: '5px 1px 1px 10px'}}), bandBoxTwo: ui.Textbox({placeholder: 'e.g., Npp', style: {width: '185px', margin: '5px 1px 1px 10px'}}), button: ui.Button('Click to map', initialize, false, {color: 'a81522', width: '275px'}) }; var xLabel = ui.Label({value: 'Latitude', style: {fontSize: '11px', margin: '2px 1px 1px 31px', color: '505050'}}); var yLabel = ui.Label({value: 'Longitude', style: {fontSize: '11px', margin: '2px 1px 1px 40px', color: '505050'}}); var distLabel = ui.Label({value: 'Buffer (km)', style: {fontSize: '11px', margin: '2px 1px 1px 29px', color: '505050'}}); // Country selection - dropdown List app.country = { label: ui.Label(), countrySelect: ui.Select({items: app.countryNames.getInfo()}) }; app.country.panel = ui.Panel({ widgets: [ ui.Label('1) Select country or create custom boundary', {fontWeight: '450', fontSize: '12px', margin: '7px 1px 1px 10px'}), ui.Label('Coarser spatial/temporal resolution data sets recommended for larger countries', {fontWeight: '450', fontSize: '10px', margin: '3px 3px 3px 10px'}), ui.Panel([app.country.countrySelect]), ui.Panel([app.inputBox.customCheckbox]), ui.Label('Recommended for finer spatial/temporal resolution data sets', {fontWeight: '450', fontSize: '10px', margin: '3px 3px 3px 10px'}), ui.Panel([app.inputBox.latCoordBox, app.inputBox.lonCoordBox, app.inputBox.distBox], ui.Panel.Layout.Flow('horizontal')), ui.Panel([xLabel, yLabel, distLabel], ui.Panel.Layout.Flow('horizontal')), ] }); app.country.countrySelect.setValue(app.country.countrySelect.items().get(183)); // Instructions app.inputBox.panel = ui.Panel({ widgets: [ ui.Label('2) Image collections to compare', {fontWeight: '450', fontSize: '12px', margin: '20px 3px 3px 10px'}), ui.Label('Visit https://developers.google.com/earth-engine/datasets/ to look up image collection IDs and band names', {fontWeight: '450', fontSize: '10px', margin: '3px 3px 3px 10px'}), ui.Panel([app.inputBox.productBox]), ui.Panel([ui.Label('Band name', {fontWeight: '450', fontSize: '11px', margin: '10px 3px 3px 15px'}), app.inputBox.bandBox], ui.Panel.Layout.Flow('horizontal')), ui.Panel([app.inputBox.productBoxTwo]), ui.Panel([ui.Label('Band name', {fontWeight: '450', fontSize: '11px', margin: '10px 3px 3px 15px'}), app.inputBox.bandBoxTwo], ui.Panel.Layout.Flow('horizontal')), ui.Label('3) Run MODIS comparison tool', {fontWeight: '450', fontSize: '12px', margin: '20px 3px 3px 10px'}), ui.Panel([app.inputBox.button]) ] }); // Citation app.authorCite = ui.Panel({ widgets: [ ui.Label({value: '*Calculations and layer loading may take a moment', style: {color: '177030', fontWeight: '700', fontSize: '11px', margin: '5px 15px 1px 10px'}}), ui.Label({value: "Hover over layers panel to view key and turn layers on/off.", style: {fontWeight: '450', fontSize: '11px', margin: '5px 2px 2px 10px'}}), ui.Label({value: "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", style: {color: '999999', fontWeight: '300', fontSize: '10px', margin: '5px 5px 5px 10px'}}), ui.Label({value: "Peter, B. G. and Messina, J. P. 2019. Errors in time-series remote sensing " + "and an open access application for detecting and visualizing spatial data outliers " + "using Google Earth Engine. IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing 12(4):1165–1174. " + "doi: 10.1109/JSTARS.2019.2901404", style: {fontWeight: '350', fontSize: '12px', margin: '5px 15px 10px 10px'}}) ] }); }; // Function to create database of countries for user selection app.createDB = function() { app.countryNames = countryList.sort('Country').aggregate_array('Country'); }; // Add panels to display app.boot = function() { app.createDB(); app.createPanels(); var panel = ui.Panel({style: {width: '310px'}}); var panelOne = ui.Panel({ layout: ui.Panel.Layout.flow('vertical') }); ui.root.insert(0, panel); panel.add(app.intro.panel).add(app.country.panel).add(app.inputBox.panel).add(app.authorCite); }; app.boot(); This content is made possible by the support of the American People provided to the Feed the Future Innovation Lab for Sustainable Intensification through the United States Agency for International Development (USAID). The contents are the sole responsibility of the authors and do not necessarily reflect the views of USAID or the United States Government. Program activities are funded by USAID under Cooperative Agreement No. AID-OAA-L-14-00006.</i

    Spatial data outlier detection and visualization using Google Earth Engine

    No full text
    Spatial data outlier detection and visualization using Google Earth Engine This is associated with an Accepted Manuscript of an article published by IEEE in IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing on 20 March 2019, available online at doi.org/10.1109/JSTARS.2019.2901404. Reference: Peter, B.G. and Messina, J.P., 2019. Errors in Time-Series Remote Sensing and an Open Access Application for Detecting and Visualizing Spatial Data Outliers Using Google Earth Engine. IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing, 12(4), pp.1165-1174. Link to manuscript https://ieeexplore.ieee.org/abstract/document/8672086 Interactive Google Earth Engine Application https://cartoscience.users.earthengine.app/view/outliers Google Earth Engine Code // Map view Map.setCenter(30, 20, 2.5).setOptions('HYBRID').style().set('cursor', 'crosshair'); // Mask options var countryList = ee.FeatureCollection('ft:1tdSwUL7MVpOauSgRzqVTOwdfy17KDbw-1d9omPw'), waterMask = ee.Image('UMD/hansen/global_forest_change_2015_v1_3').select('datamask').eq(1), forestMask = ee.ImageCollection('JAXA/ALOS/PALSAR/YEARLY/FNF').mode().eq(2), agModis = ee.ImageCollection('MODIS/006/MCD12Q1').select('LC_Type1').mode() .remap([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17], [0,0,0,0,0,0,0,0,0, 0, 0, 1, 0, 1, 0, 0, 0]), agGC = ee.Image('ESA/GLOBCOVER_L4_200901_200912_V2_3').select('landcover') .remap([11,14,20,30,40,50,60,70,90,100,110,120,130,140,150,160,170,180,190,200,210,220,230], [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), agMask = agModis.add(agGC).gt(0).eq(1); // Calculate stats var stats = function(year) { Map.layers().reset(); var scale = 10000; // Get input parameters var countrySelected = app.country.countrySelect.getValue(), region = countryList.filterMetadata('Country', 'equals', countrySelected).geometry(), version = app.inputBox.productBox.getValue(), band = app.inputBox.bandBox.getValue(); if (app.inputBox.customCheckbox.getValue() === true) { var latCoord = ee.Number.parse(app.inputBox.latCoordBox.getValue()).getInfo(), lonCoord = ee.Number.parse(app.inputBox.lonCoordBox.getValue()).getInfo(), distBuffer = ee.Number.parse(app.inputBox.distBox.getValue()).getInfo(), distNum = distBuffer*1000; region = ee.Geometry.Point([lonCoord,latCoord]).buffer(distNum).bounds(); } var image = ee.Image(version).select(band).clip(region); if (app.inputBox.imageCheckbox.getValue() === false) { var imageCollection = ee.ImageCollection(version).select(band); var startDate = ee.Date(imageCollection.first().get('system:time_start')).get('year'); var endDate = ee.Date(imageCollection.sort('system:time_start',false).first().get('system:time_start')).get('year'); imageCollection = ee.ImageCollection(version).select(band).filter(ee.Filter.calendarRange(startDate, endDate, 'year')).filterBounds(region); image = ee.Image(imageCollection.filter(ee.Filter.calendarRange(year, year, 'year')).mean()).clip(region); } if (app.inputBox.waterCheckbox.getValue() === true) { image = image.updateMask(waterMask); } if (app.inputBox.forestCheckbox.getValue() === true) { image = image.updateMask(forestMask); } if (app.inputBox.agricultureCheckbox.getValue() === true) { image = image.updateMask(agMask); } // Tukey's lower and upper fence var percentiles = image.reduceRegion({ reducer: ee.Reducer.percentile([10,25,50,75,90]), geometry: region, scale: scale, maxPixels: 1e12 }); var lowerQuartile = ee.Number(percentiles.get(band+'_p25')), median = ee.Number(percentiles.get(band+'_p50')), upperQuartile = ee.Number(percentiles.get(band+'_p75')); var IQR = upperQuartile.subtract(lowerQuartile), lowerFence = lowerQuartile.subtract(IQR.multiply(1.5)), upperFence = upperQuartile.add(IQR.multiply(1.5)); var quartiles = image.gt(lowerQuartile).add(image.gt(median)).add(image.gt(upperQuartile)) .remap([0,1,2,3],[1,2,3,4]).rename('quartile'), outliers = image.gt(lowerFence).add(image.gt(upperFence)).remap([0,1,2],[1,0,2]), tukeys = outliers.updateMask(outliers.neq(0)).rename('fence'); // Z-score var mean = ee.Number(image.reduceRegion({ reducer: ee.Reducer.mean(), geometry: region, scale: scale, maxPixels: 1e12 }).get(band)); var stdDev = ee.Number(image.reduceRegion({ reducer: ee.Reducer.stdDev(), geometry: region, scale: scale, maxPixels: 1e12 }).get(band)); var zScore = image.subtract(mean).divide(stdDev).rename('zscore'); // Modified Z-score var medAbsDev = image.subtract(median).abs(); var medianMedAbsDev = ee.Number(medAbsDev.reduceRegion({ reducer: ee.Reducer.median(), geometry: region, scale: scale, maxPixels: 1e12 }).get(band)); var zScoreMod = image.subtract(mean).multiply(0.6745).divide(medianMedAbsDev).abs().rename('zscore_mod'), zScoreModExp = zScoreMod.gt(3.5).multiply(3), zScoreModOutliers = zScoreModExp.updateMask(zScoreModExp.eq(3)).rename('zmod_outlier'); // Lower fence/upper fance + modified s-score outliers var combined = zScoreModExp.add(outliers).rename('combined_outliers').remap([0,1,2,3,4,5],[0,3,4,5,1,2]), // 0 = none // 1 = lower fence // 2 = upper fence // 3 = modified z-score // 4 = lower fence + modified z-score // 5 = upper fence + modified z-score combinedMask = combined.updateMask(combined.neq(0)); // Geary's C statistic var list = [1, 1, 1, 1, 1, 1, 1, 1, 1], centerList = [1, 1, 1, 1, 0, 1, 1, 1, 1], lists = [list, list, list, list, centerList, list, list, list, list], kernel = ee.Kernel.fixed(9, 9, lists, -4, -4, false), neighs = image.neighborhoodToBands(kernel), gearys = image.subtract(neighs).pow(2).reduce(ee.Reducer.sum()) .divide(Math.pow(9, 2)); var gearysQuartiles = gearys.reduceRegion({ reducer: ee.Reducer.percentile([10,25,75,90]), geometry: region, scale: scale, maxPixels: 1e12 }); var gearysLowerQuartile = ee.Number(gearysQuartiles.get('sum_p25')), gearysUpperQuartile = ee.Number(gearysQuartiles.get('sum_p75')), gearysIQR = gearysUpperQuartile.subtract(gearysLowerQuartile), gearysUpperFence = gearysUpperQuartile.add(gearysIQR.multiply(1.5)), gearysAccum = gearys.gt(gearysUpperFence).rename('gearys_outlier'), gearysOutlier = gearysAccum.updateMask(gearysAccum.eq(1)).rename('spatial_outlier'); // Locate pixels ≤ zero var lteZero = image.lte(0).updateMask(image.lte(0).eq(1)); // Layer design values var zScorePercentiles = zScore.reduceRegion({ reducer: ee.Reducer.percentile([10,90]), geometry: region, scale: scale, maxPixels: 1e12 }); var zScoreModPercentiles = zScoreMod.reduceRegion({ reducer: ee.Reducer.percentile([10,90]), geometry: region, scale: scale, maxPixels: 1e12 }); var raw10 = ee.Number(percentiles.get(band+'_p10')).getInfo(), raw90 = ee.Number(percentiles.get(band+'_p90')).getInfo(), zScore10 = ee.Number(zScorePercentiles.get('zscore_p10')).getInfo(), zScore90 = ee.Number(zScorePercentiles.get('zscore_p90')).getInfo(), zScoreMod10 = ee.Number(zScoreModPercentiles.get('zscore_mod_p10')).getInfo(), zScoreMod90 = ee.Number(zScoreModPercentiles.get('zscore_mod_p90')).getInfo(), gearys10 = ee.Number(gearysQuartiles.get('sum_p10')).getInfo(), gearys90 = ee.Number(gearysQuartiles.get('sum_p90')).getInfo(); // Region naming var regionName = countrySelected; if (app.inputBox.customCheckbox.getValue() === true) { regionName = 'Custom boundary'; } /* -- Make sure to change the scale to calculate accurate area percentages // Area % calculations var areaImage = image.multiply(0).rename('area'); var totalArea = ee.Number(areaImage.add(1).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')); var lfmzArea = ee.Number(areaImage.add(combinedMask.eq(1)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); var ufmzArea = ee.Number(areaImage.add(combinedMask.eq(2)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); var lfArea = ee.Number(areaImage.add(tukeys.eq(1)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); var ufArea = ee.Number(areaImage.add(tukeys.eq(2)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); var mzArea = ee.Number(areaImage.add(zScoreModOutliers.eq(3)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); var lteZeroArea = ee.Number(areaImage.add(lteZero.eq(0)).reduceRegion({ reducer: ee.Reducer.sum(), geometry: region, scale: scale, maxPixels: 1e12 }).get('area')).divide(totalArea).multiply(100).getInfo().toFixed(2); */ // Add layers to display var grayscale = ['f7f7f7', 'cccccc', '969696', '525252']; Map.addLayer(agMask.updateMask(agMask).clip(region), {palette: 'fdfdfd'}, 'Agriculture mask', false); Map.addLayer(image, {min: raw10, max: raw90, palette: grayscale}, version, false); Map.addLayer(quartiles, {min: 1, max: 4, palette: grayscale}, 'Quartiles', false); Map.addLayer(zScore, {min: zScore10, max: zScore90, palette: grayscale}, 'Z-score', false); Map.addLayer(zScoreMod, {min: zScoreMod10, max: zScoreMod90, palette: grayscale}, 'Modified Z-score', false); Map.addLayer(gearys, {min: gearys10, max: gearys90, palette: grayscale}, "Geary's C", false); Map.addLayer(tukeys, {min: 1, max: 2, palette: ['22a6ff','ffd400']}, "Tukey's outliers", false); Map.addLayer(zScoreModOutliers, {min: 3, max: 3, palette: '13e864'}, 'Modified Z-score outlier', false); Map.addLayer(gearysOutlier, {min: 1, max: 1, palette: ['bebebe']}, "Geary's C design layer", false); Map.addLayer(gearysOutlier, {min: 1, max: 1, palette: ['bebebe']}, "Geary's C outlier", false); Map.addLayer(combinedMask, {min: 1, max: 5, palette: ['6713e8','ff225a','22a6ff','ffd400','13e864']}, 'Combined outliers', true); Map.addLayer(lteZero, {min: 0, max: 0, palette: ['202020']}, 'Pixel ≤ zero', false); var empty = ee.Image().byte(); var regionVis = empty.paint({ featureCollection: region, width: 3 }); Map.addLayer(regionVis, {palette: '252525'}, regionName); Map.centerObject(region); // Charts panel var chartPanel = ui.Panel({ layout: ui.Panel.Layout.flow('vertical') }); chartPanel.style().set({width: '250px', position: 'bottom-left', margin: '0 0 0 0'}); Map.add(chartPanel); var yearLabel = ''; if (app.inputBox.imageCheckbox.getValue() === false) { // Chart time-series var timeSeriesDisplay = { fontSize: 11, width: '250px', curveType: 'function', format: 'short', hAxis: {textStyle: {fontSize: 10}, gridlines: {color: 'transparent'}}, vAxis: {textStyle: {fontSize: 10}, gridlines: {}}, trendlines: {0: {color: '202020', lineWidth: 0.5, visibleInLegend: false}}, series: {0: {color: '505050'}}}; var timeSeriesChart = ui.Chart.image.series(imageCollection, region, ee.Reducer.mean(), scale) .setOptions(timeSeriesDisplay).setSeriesNames([band]); chartPanel.add(timeSeriesChart); yearLabel = ' • '+year; } // Chart histogram var histogramDisplay = { fontSize: 11, hAxis: {format: 'short'}, vAxis: {format: 'short'}, series: {0: {color: '313131'}} }; var histogramChart = ui.Chart.image.histogram(image, region, scale) .setOptions(histogramDisplay).setSeriesNames([band+yearLabel]); chartPanel.add(histogramChart); // Create outlier legend var legend = ui.Panel({ style: { position: 'bottom-right', padding: '8px 15px' } }); var legendTitle = ui.Label({ value: 'Outlier type', style: { fontWeight: 'bold', fontSize: '18px', margin: '0 0 6px 30px', padding: '0' } }); legend.add(legendTitle); var makeRow = function(/*percName, */color, name) { /*var perc = ui.Label({ value: percName, style: {margin: '0 2px 6px 4px', fontSize: '13px'} });*/ var colorBox = ui.Label({ style: { backgroundColor: '#' + color, padding: '8px', margin: '0 0 0 0', border: '1px solid white' } }); var description = ui.Label({ value: name, style: {margin: '0 0 2px 4px', fontSize: '13px'} }); return ui.Panel({ widgets: [/*perc, */colorBox, description], layout: ui.Panel.Layout.Flow('horizontal'), style: {padding: '0'} }); }; // var percents = [lfmzArea+'%', ufmzArea+'%', lfArea+'%', ufArea+'%', mzArea+'%', lteZeroArea+'%']; var palette = ['6713e8','ff225a','22a6ff','ffd400','13e864','202020', 'bebebe']; var names = ['Lower fence & Modified z-score', 'Upper fence & Modified z-score', 'Lower fence ('+lowerFence.getInfo().toFixed(2)+')','Upper fence ('+upperFence.getInfo().toFixed(2)+')', 'Modified z-score', 'Pixel ≤ zero', "Geary's C"]; for (var i = 0; i < 7; i++) { legend.add(makeRow(/*percents[i], */palette[i], names[i])); } /* var gearyBox = ui.Label({ style: { backgroundColor: '#bebebe', padding: '8px', margin: '0 0 0 40px', border: '1px solid white' } }); var gearyLabel = ui.Label({ value: "Geary's C", style: {margin: '0 0 2px 4px', fontSize: '13px'} }); */ //legend.add(ui.Panel({widgets: [gearyBox,gearyLabel], layout: ui.Panel.Layout.Flow('horizontal')})); Map.add(legend); // Display Geary's C Map.onChangeZoom(function(zLevel) { if ((Map.getZoom() > 8) & (Map.getZoom() < 11)) { Map.layers().get(8).set('shown', true); } else { Map.layers().get(8).set('shown', false); } }); }; // Load parameter input panel var initialize = function(init) { if (app.inputBox.imageCheckbox.getValue() === true) { Map.clear(); Map.setOptions('HYBRID').style().set('cursor', 'crosshair'); stats(); } if (app.inputBox.imageCheckbox.getValue() === false) { Map.clear(); Map.setOptions('HYBRID').style().set('cursor', 'crosshair'); var version = app.inputBox.productBox.getValue(); var band = app.inputBox.bandBox.getValue(); var imageCollection = ee.ImageCollection(version).select(band); var minYear = ee.Date(imageCollection.first().get('system:time_start')).get('year').getInfo(); var maxYear = ee.Date(imageCollection.sort('system:time_start',false).first().get('system:time_start')).get('year').getInfo(); var label = ui.Label('Year'); var slider = ui.Slider({ min: minYear, max: maxYear, step: 1, onChange: stats, style: {stretch: 'horizontal'} }); var panelSlider = ui.Panel({ widgets: [label, slider], layout: ui.Panel.Layout.flow('horizontal'), style: {fontSize: '10px', position: 'top-left', padding: '0'} }); // Add slider var sliderStart = minYear+1; slider.setValue(sliderStart); Map.add(panelSlider); } }; // UI input panel layout var app = {}; app.createPanels = function() { app.intro = { panel: ui.Panel([ ui.Label({value: 'Outlier detection and visualization', style: {fontWeight: '700', fontSize: '20px', margin: '10px 5px 1px 10px'}}), ui.Label({value: "Enter fields to map Tukey's upper and lower fences, z-scores, modified z-scores, and Geary's C.", style: {fontWeight: '300', fontSize: '14px', margin: '5px 15px 10px 10px'}}) ]) }; // Input boxes app.inputBox = { customCheckbox: ui.Checkbox({label: 'Use custom boundary', style: {fontSize: '12px', margin: '5px 1px 1px 10px'}, value: false}), latCoordBox: ui.Textbox({placeholder: 'e.g., -14.9', style: {width: '85px', margin: '2px 1px 1px 10px'}}), lonCoordBox: ui.Textbox({placeholder: 'e.g., 35.5', style: {width: '85px', margin: '2px 1px 1px 1px'}}), distBox: ui.Textbox({placeholder: 'e.g., 100', style: {width: '75px', margin: '2px 1px 1px 1px'}}), productBox: ui.Textbox({placeholder: 'e.g., MODIS/055/MOD17A3',style: {width: '300px', margin: '10px 1px 1px 10px'}}), bandBox: ui.Textbox({placeholder: 'e.g., Npp', style: {width: '222px', margin: '5px 1px 1px 10px'}}), startBox: ui.Textbox({placeholder: 'e.g., 2001', style: {width: '75px', margin: '10px 1px 1px 10px'}}), endBox: ui.Textbox({placeholder: 'e.g., 2010', style: {width: '75px', margin: '10px 1px 1px 1px'}}), button: ui.Button('Click to map', initialize, false, {color: 'a81522', width: '150px'}), waterCheckbox: ui.Checkbox({label: 'Mask out water', style: {fontSize: '12px', margin: '0px 1px 1px 10px'}, value: false}), forestCheckbox: ui.Checkbox({label: 'Mask out forest', style: {fontSize: '12px', margin: '14px 1px 1px 10px'}, value: false}), agricultureCheckbox: ui.Checkbox({label: 'Mask to agriculture', style: {fontSize: '12px', margin: '14px 1px 1px 10px'}, value: false}), imageCheckbox: ui.Checkbox({label: 'Use single image', style: {fontSize: '12px', margin: '5px 1px 1px 10px'}, value: false}), }; var enDash = ui.Label({value: '–', style: {margin: '15px 1px 1px 1px', color: '757575'}}); var xLabel = ui.Label({value: 'Latitude', style: {fontSize: '11px', margin: '2px 1px 1px 31px', color: '505050'}}); var yLabel = ui.Label({value: 'Longitude', style: {fontSize: '11px', margin: '2px 1px 1px 40px', color: '505050'}}); var distLabel = ui.Label({value: 'Buffer (km)', style: {fontSize: '11px', margin: '2px 1px 1px 29px', color: '505050'}}); // Country selection - dropdown List app.country = { label: ui.Label(), countrySelect: ui.Select({items: app.countryNames.getInfo()}) }; app.country.panel = ui.Panel({ widgets: [ ui.Label('1) Select country or create custom boundary', {fontWeight: '450', fontSize: '13px', margin: '7px 1px 1px 10px'}), ui.Label('Coarser spatial/temporal resolution data sets recommended for larger countries', {fontWeight: '450', fontSize: '10px', margin: '3px 3px 3px 10px'}), ui.Panel([app.country.countrySelect]), ui.Panel([app.inputBox.customCheckbox]), ui.Label('Recommended for finer spatial/temporal resolution data sets', {fontWeight: '450', fontSize: '10px', margin: '3px 3px 3px 10px'}), ui.Panel([app.inputBox.latCoordBox, app.inputBox.lonCoordBox, app.inputBox.distBox], ui.Panel.Layout.Flow('horizontal')), ui.Panel([xLabel, yLabel, distLabel], ui.Panel.Layout.Flow('horizontal')), ui.Label('2) Select masking options', {fontWeight: '450', fontSize: '13px', margin: '12px 1px 10px 10px'}), ui.Panel([app.inputBox.waterCheckbox, app.inputBox.forestCheckbox, app.inputBox.agricultureCheckbox]) ] }); app.country.countrySelect.setValue(app.country.countrySelect.items().get(183)); // Instructions app.inputBox.panel = ui.Panel({ widgets: [ ui.Label('3) Image or image collection ID', {fontWeight: '450', fontSize: '13px', margin: '20px 3px 3px 10px'}), ui.Label('Visit https://developers.google.com/earth-engine/datasets/ to look up image or image collection ID and band name', {fontWeight: '450', fontSize: '10px', margin: '2px 3px 3px 10px'}), ui.Panel([app.inputBox.imageCheckbox, app.inputBox.productBox]), ui.Panel([ui.Label('Band name', {fontWeight: '450', fontSize: '12px', margin: '10px 3px 3px 15px'}), app.inputBox.bandBox], ui.Panel.Layout.Flow('horizontal')), ui.Label('4) Run outlier detection and visualization tool', {fontWeight: '450', fontSize: '13px', margin: '20px 3px 3px 10px'}), ui.Panel([app.inputBox.button]) ] }); // Citation app.authorCite = ui.Panel({ widgets: [ ui.Label({value: '*Calculations and layer loading may take some time depending on spatial and temporal resolutions selected', st
    corecore