├── 19235 LCD_NOSA 1.3.doc ├── .gitattributes ├── .gitignore ├── README.txt └── 2017Fall_PacificSouthwestCCII_Code.txt /19235 LCD_NOSA 1.3.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NASA-DEVELOP/LCD/HEAD/19235 LCD_NOSA 1.3.doc -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ========= 2 | LADT - Landscape Anomaly Detection Tool 3 | ========= 4 | 5 | Date Created: November 16, 2017 6 | 7 | Purpose: 8 | This script calculates Relative Green, percent change in Relative Green, and Normalized Burn Ratio, and displays NAIP Imagery and USDA Croplands data layers to help the United 9 | States Fish and Wildlife Service detect land use changes and preliminarily explore potential causes of the changes. 10 | 11 | Description: 12 | The HCP study areas are located within the Pacific Southwest, a region that has highly variable land cover. Therefore, the main index used to evaluate vegetation cover was 13 | Relative Green (RG), which compares the NDVI in the study period to a historical baseline of NDVI values (Eq.1 and 2). To use the tool, the user selects the HCP of interest, and 14 | the map zooms to the selected location. Then the user types in the month and year of interest. These two initial inputs are used by the tool when the user clicks other buttons 15 | to run calculations and display other map layers. 16 | 17 | In order to help the user preliminarily explain the potential causes of the land use changes, three ancillary datasets have been added. NBR (Eq. 3) establishes whether a fire 18 | is likely to have occurred anywhere in the HCP, so the user can determine whether a fire could have been the cause of land use changes. The second ancillary dataset is the USDA 19 | Croplands Data layers, which can help establish if change in crop type may have caused any land use changes. The third ancillary datset is the National Agriculture Imagery Program 20 | data, which is high resolution imagery that can give the user a better context of what is happening in the HCP of interest. 21 | 22 | For the RG related images, low vegetation/vegetation loss is displayed in purple, little change is displayed in white, and high vegetation/vegetation gain is displayed in green. 23 | These colors were chosen instead of a more intuitive red/green color ramp to avoid challenges for people who are colorblind. For NBR, likelihood occurrence of fire (very low NBR) 24 | is displayed in red and all other areas are beige. 25 | 26 | NDVI = (NIR - Red) / (NIR + Red) 27 | Equation 1: Normalized Difference Vegetation Index (NDVI) where NIR is the Near Infrared band and Red is the Red band in Visible Light. 28 | 29 | RGi,j = (NDVIi,j - NDVImin,j) / (NDVImax,j - NDVImin,j) 30 | Equation 2: where i,j is the recorded NDVI at time i at pixel j and NDVI max,j and NDVI min,j are the 'historical' max and min NDVI for that pixel from the baseline, which is 31 | currently set to 2000-2010. 32 | 33 | NBR = (NIR - SWIR) / (NIR + SWIR) 34 | Equation 3: Normalized Burn Ratio (NBR) where NIR is the Near Infrared band and SWIR is the Short Range Infrared band. 35 | 36 | 37 | Required Packages 38 | ================= 39 | Google Earth Engine 40 | 41 | 42 | Workflow: 43 | 1) Copy code into Google Earth Engine API. 44 | 2) Hover your cursor over the part of the code at the very beginning that is underlined in yellow, and click Convert when the option appears. 45 | 3) Click 'Run' on the top right part of the code editor. The user interface will load on the right side of the screen. 46 | 4) Drag the map up to fill the screen and hide the code editor. Use the user interface to run analyses and display ancillary data. 47 | **Use the Word and video tutorials as needed** 48 | 49 | 50 | Contact 51 | ======= 52 | Authors: 53 | Kimberly Johnson, Jarell Perez, Michaela Britt, Justin Herbst, Emily Gotschalk, Zachery Stout 54 | Created by NASA DEVELOP Pacific Southwest Cross-Cutting II Team Fall 2017 55 | 56 | Contact: 57 | Kimberly Johnson 58 | kimberlybuskjohnson@gmail.com 59 | -------------------------------------------------------------------------------- /2017Fall_PacificSouthwestCCII_Code.txt: -------------------------------------------------------------------------------- 1 | var l5 = ee.ImageCollection("LANDSAT/LT05/C01/T1_SR"), 2 | l8 = ee.ImageCollection("LANDSAT/LC08/C01/T1_SR"), 3 | l7 = ee.ImageCollection("LANDSAT/LE07/C01/T1_SR"), 4 | sentinel = ee.ImageCollection("COPERNICUS/S2"); 5 | 6 | ///US Fish and Wildlife Service Change Detection Tool/// 7 | ////////////////by: NASA DEVELOP/////////////////////// 8 | 9 | //a visual describing our product// 10 | /* ,aa, ,aa 11 | d" "b ,d",`b 12 | ,dP a "b,ad8' 8 8 13 | d8' 8 ,88888a 8 8 14 | d8baa8ba888888888a8 15 | ,ad888888888YYYY888YYY, 16 | ,a888888888888" "8P" "b 17 | ,aad8888tt,8888888b (0 `8, 0 8 18 | ____________________________,,aadd888ttt8888ttt"8"I "Yb, `Ya 8 19 | ,aad8888b888888aab8888888888b, ,aatPt888ttt8888tt 8,`b, "Ya,. `"aP 20 | ,ad88tttt8888888888888888888888888ttttt888ttd88888ttt8888tt,t "ba,. `"`d888 21 | ,d888tttttttttttttt888888888888888888888888ttt8888888888ttt888ttt, "a, `88' 22 | a888tttttttttttttttttttttttttt8888888888888ttttt88888ttt888888888tt, `""8"' 23 | d8P"' ,tttttttttttttttttttttttttttttttttt88tttttt888tttttttt8a"8888ttt, ,8' 24 | d8tb " ,tt" ""tttttttttttttttttttttttttttttttttt88ttttttttttt, Y888tt" ,8' <---- helping wolves like these... 25 | 88tt) "t" ttttt" """ """ "" tttttYttttttttttttt, " 8ttb,a8' 26 | 88tt `"b' ""t'ttttttttttt"t"t t taP" 27 | 8tP `b ,tttttt' " " "tt, ,8" 28 | (8tb b, `b, a, tttttt' ""dP' 29 | I88tb `8, `b d' tttttt ,aP" 30 | 8888tb `8, ,P d' "tt "t' ,a8P" 31 | I888ttt, "b ,8' ,8 "tt" ,d"d"' 32 | ,888tttt' 8b ,dP""""""""""""""""Y8 tt ,d",d' 33 | ,d888ttttP d"8b ,dP' "b, "ttP' d' 34 | ,d888ttttPY ,d' dPb, ,dP' Pacific "b, t8' 8 35 | d888tttt8" ,d" ,d" 8 ,d"' Southwest `b "P 8 36 | d888tt88888d" ,d" ,d" ,d" Cross-cutting 8 I 8 37 | d888888888P' ,d" ,d" ,d" II 8 I 8 38 | 88888888P' ,d" (P' d" 8 8 8 39 | "8P"'"8 ,8' Ib d" Y 8 8 40 | 8 d" `8 8 `b 8 Y 41 | 8 8 8, 8, 8 Y `b 42 | 8 Y, `b `b Y `b `b 43 | Y, "ba, `b `b, `b 8, `"ba, 44 | "b, "8 `b `""b `b `Yaa,adP' 45 | """""' `baaaaaaP `YaaaadP"' 46 | 47 | 48 | 49 | . . . . . . . 50 | . . . . . _______ 51 | . . . //////// 52 | . . ________ . . ///////// . . 53 | . |.____. /\ .///////// . 54 | . .// \/ |\ ///////// 55 | . . .// \ | \ ///////// . . . 56 | ||. . .| | ///////// . . 57 | . . || | |//`,///// . <---- using tools like this... 58 | . \\ ./ // / \/ . 59 | . \\.___./ //\` ' ,_\ . . 60 | . . \ //////\ , / \ . . 61 | . ///////// \| ' | . 62 | . . ///////// . \ _ / . 63 | ///////// . 64 | . .///////// . . 65 | . --------- . .. . 66 | . . . . . 67 | ________________________ 68 | ____________------------ -------------_________ 69 | */ 70 | 71 | //This script allows a user to detect land use change in Habitat Conservation Plan areas in California, Nevada, and Oregon 72 | //It calculates relative green to detect changes in the landscape to help the USFWS better target field visits 73 | //The tool requires imports that include the Landsat 5, 7, and 8 TOA Reflectance with Fmask collections, and Sentinel-2 MSI 74 | 75 | //BEGIN importing the HCP area polygons// 76 | //We will use these polygons throughout the script to filterBounds and clip raster data to// 77 | //Import fusion table that includes Habitat Conservation Plan boundaries 78 | var fc = ee.FeatureCollection("ft:1XPuR1Ed50oFQsww3bZfXIk7nz9sQh-D19A9mWU3D"); 79 | 80 | //Create array of HCP names so that they are searchable 81 | var names = fc.aggregate_array('name'); 82 | //END importing the HCP area polygons// 83 | 84 | //BEGIN the creation of a merged imageCollection that allows us to access imagery from 1984-2017// 85 | //Step 1: Create merged collection from Landsat 5, 7, & 8 and Sentinel-2 86 | 87 | //Create mergeCollection function 88 | var mergeCollection = function() { 89 | // list the bands as described in the metadata for each specific satelitte 90 | var l5names = ee.List(["B1","B2","B3","B4","B5","B6","B7"]); 91 | var l7names = ee.List(["B1","B2","B3","B4","B5","B6","B7"]); 92 | var l8names = ee.List(["B1","B2","B3","B4","B5","B6","B7","B10","B11"]); 93 | var snames = ee.List(["B1","B2","B3","B4","B5","B6","B7","B8","B8A","B11","B12"]); 94 | 95 | // list the common band names which will allow us to easily merge the bands 96 | var l5Bands = ee.List(['blue','green','red','nir','swir1','thermal1','swir2']); 97 | var l7Bands = ee.List(['blue','green','red','nir','swir1','thermal1', 'swir2']); 98 | var l8Bands = ee.List(['b1','blue','green','red','nir','swir1','swir2','thermal1','thermal2']); 99 | var sBands = ee.List(['aerosols','blue','green','red','red_edge1','red_edge2','red_edge3','nir','red_edge4','swir1','swir2']); 100 | 101 | //create a cloud masking function for Landsat 102 | //var applyMask = function(image) { 103 | // return image.updateMask(image.select('fmask').lt(2));}; 104 | 105 | //create a cloud masking function for Sentinel-2 106 | // var sentMask = function(im) { 107 | // Opaque and cirrus cloud masks cause bits 10 and 11 in QA60 to be set, so values less than 1024 are cloud-free 108 | // var mask = ee.Image(0).where(im.select('QA60').gte(1024), 1).not(); 109 | // return im.updateMask(mask); 110 | // }; 111 | 112 | //BEGIN the creation of new bands to add to our merged imageCollection// 113 | //create NDVI function 114 | var NDVI; 115 | var makeNDVI = function(image){ 116 | NDVI = image.normalizedDifference(['nir', 'red']).rename('NDVI'); 117 | //print('done with NDVI'); 118 | return image.addBands(NDVI); 119 | }; 120 | 121 | //create NBR function 122 | var makeNBR = function(image){ 123 | var NBR = image.normalizedDifference(['nir', 'swir2']).rename('NBR'); 124 | //print('finished NBR'); 125 | return image.addBands(NBR); 126 | }; 127 | //END the creation of new bands to add to merged imageCollection// 128 | 129 | // Change the bands to common band names so that they are searchable across the different collections 130 | var l5images = l5.select(l5names, l5Bands) 131 | //.map(applyMask) 132 | .map(makeNDVI) 133 | .map(makeNBR); 134 | var l7images = l7.select(l7names, l7Bands) 135 | // .map(applyMask) 136 | .map(makeNDVI) 137 | .map(makeNBR); 138 | var l8images = l8.select(l8names, l8Bands) 139 | //.map(applyMask) 140 | .map(makeNDVI) 141 | .map(makeNBR); 142 | var sentImages = sentinel.select(snames, sBands) 143 | //.map(sentMask) 144 | .map(makeNDVI) 145 | .map(makeNBR); 146 | 147 | //create merged collection from Landsat 5, 7, & 8 and Sentinel-2 148 | var myCollection = ee.ImageCollection(l5images.merge(l7images.merge(l8images.merge(sentImages)))); 149 | return myCollection; 150 | }; 151 | 152 | //by creating this next variable, we are able to call the collection outside of the function 153 | //create LandsatCollection by calling mergeCollection function 154 | LandsatCollection = mergeCollection(); 155 | //END the creation of a merged imageCollection that allows us to access imagery from 1984-2017// 156 | 157 | 158 | //BEGIN the creation of the drop down menu which allows the user to select the HCP, and the creation of the baseline which our analyses will compare against// 159 | //Step 2: Create selection widget that subsets the spatial extent to a HCP selection and creates the NDVI baseline for the relative greenness analysis 160 | 161 | //create variables that are accessible by other widgets; created outside of the function to make them global variables 162 | var col; 163 | var byMonthmax; 164 | var byMonthmin; 165 | var LandsatCollection; 166 | var conversion; 167 | 168 | //Create HCP selection widget that searches fusion table by array of names 169 | var select = ui.Select({ 170 | items: names.getInfo(), 171 | placeholder: ('Choose Area of Interest'), 172 | style: {width: '290px'}, 173 | onChange: function(key) { 174 | Map.clear(); //clears previous calculations from map upon new selection 175 | var selection = ee.Feature(fc.filter(ee.Filter.eq('name', key)).first()); 176 | Map.centerObject(selection); //centers map on HCP selection 177 | // show HCP 178 | var layer = ui.Map.Layer(selection, {color:'blue'}, 'HCP'); 179 | Map.layers().set(0, layer); //adds HCP selection to the map 180 | conversion = ee.FeatureCollection(selection); //converts the HCP selection into a feature collection for future analyses 181 | 182 | //BEGIN NDVI Baseline creation for Relative Greenness// 183 | //Create NDVI baseline variable by filtering LandsatCollection by date and spatial resolution 184 | var NDVIbaseline = ee.ImageCollection(LandsatCollection.filterDate('2000-01-01', '2010-12-31') //filter date to 2000-2010 185 | .filterBounds(conversion) //filter spatially to HCP selection 186 | .select('NDVI')); //select NDVI band 187 | //End NDVI Baseline creation for Relative Greenness// 188 | 189 | //In addition to NDVI baseline, Relative Greenness requires a baseline min and max 190 | //Create list of months for min and max collections 191 | var months = ee.List.sequence(1,12); 192 | 193 | //create monthly NDVI minimums 194 | byMonthmin = ee.ImageCollection.fromImages( 195 | months.map(function (m) { //for each month in baseline, calculate min NDVI value 196 | return NDVIbaseline.filter(ee.Filter.calendarRange(m, m, 'month')) 197 | .select('NDVI').min() 198 | .set('month', m); 199 | })); 200 | 201 | //create monthly NDVI maximums 202 | byMonthmax = ee.ImageCollection.fromImages( 203 | months.map(function (m) { //for each month in baseline, calculate max NDVI value 204 | return NDVIbaseline.filter(ee.Filter.calendarRange(m, m, 'month')) 205 | .select('NDVI').max() 206 | .set('month', m); 207 | })); 208 | 209 | //Set the month and year properties in metadata so that they are searchable later 210 | var NDVIBaselinetest = NDVIbaseline.map(function(img) { 211 | img = img.clip(conversion); 212 | var d = ee.Date(ee.Number(img.get('system:time_start'))); 213 | var m = ee.Number(d.get('month')); 214 | var y = ee.Number(d.get('year')); 215 | return img.set({'month':m, 'year':y}); 216 | }); 217 | } 218 | }); 219 | //END the creation of the drop down menu which allows the user to select the HCP, and the creation of the baseline which our analyses will compare against// 220 | 221 | //BEGIN Relative Greeness calculations// 222 | //Step 3: Calculate Relative Greenness based on NDVI baseline, min, and max from Step 2 223 | 224 | //create year and month selection textboxes 225 | var selectYr = ui.Textbox({placeholder: 'Year', value: '2016', 226 | style: {width: '100px'}}); //defaults to 2016, textbox displays ‘Year’ if textbox is empty 227 | var selectMnth = ui.Textbox({placeholder: 'Month', value: '2', 228 | style: {width: '100px'}}); 229 | 230 | //Create final Relative Greenness variable 231 | var ndviRelGre; 232 | //user needs to select month and year of interest from 2010-2017 233 | //create year and month variables 234 | var yr; 235 | var mnth; 236 | //Create widget that applies temporal selection and loads Relative Greenness to map 237 | var load = ui.Button({ 238 | label: ('Calculate Relative Greenness'), //adds button label 239 | onClick: function() 240 | { 241 | //create function that will parse textbox inputs into Relative Greenness equations 242 | function fillTextbox(){ 243 | //assign the varible 'yr' to the current string in the selectYr textbox 244 | yr = selectYr.getValue(); 245 | // if 'yr' is 'yr' then turn that string into a number 246 | if (yr) yr = ee.Number.parse(yr); 247 | mnth = selectMnth.getValue(); 248 | if (mnth) mnth = ee.Number.parse(mnth); 249 | } 250 | //run fillTextbox function 251 | fillTextbox(); 252 | 253 | //Next, create a current NDVI collection to use in Relative Greenness equation - this will be used when selecting current time of interest 254 | var NDVICurrent = ee.ImageCollection(LandsatCollection.filterDate('2010-01-01', '2017-12-31') //filter date to 2000-2010 255 | .filterBounds(conversion) //filter to HCP selection 256 | .select('NDVI')); //select NDVI 257 | 258 | //Set the month and year properties in metadata so that they are searchable later 259 | var NDVICurrenttest = NDVICurrent.map(function(img) { 260 | img = img.clip(conversion); 261 | var d = ee.Date(ee.Number(img.get('system:time_start'))); 262 | var m = ee.Number(d.get('month')); 263 | var y = ee.Number(d.get('year')); 264 | return img.set({'month':m, 'year':y}); 265 | }); 266 | 267 | //Create filterBaseLine function that filters NDVI collections to month and year selection and outputs Relative Greenness 268 | var filterBaseLine = function(year, month, conversion){ 269 | // calculates the current NDVI filtered by input year and month 270 | var ndvi_cur = ee.Image(NDVICurrenttest 271 | .filterMetadata('year', 'equals', year) //filter by year of interest 272 | .filterMetadata('month', 'equals', month) //filter by month of interest 273 | .filterBounds(conversion) //filter to HCP selection 274 | .mean()); 275 | 276 | //Filters baseline min and max by input month, reduced using mean 277 | var min_ndvi = ee.Image(byMonthmin 278 | .filterMetadata('month', 'equals', month) //filter by month of interest 279 | .filterBounds(conversion) //filter to HCP selection 280 | .min()); 281 | 282 | var max_ndvi = ee.Image(byMonthmax 283 | .filterMetadata('month', 'equals', month) //filter by month of interest 284 | .filterBounds(conversion) //filter to HCP selection 285 | .max()); 286 | 287 | //The relative greeness equation (current - min / max - min) 288 | ndviRelGre = ndvi_cur.subtract(min_ndvi).divide(max_ndvi.subtract(min_ndvi)); 289 | }; //finish filterBaseLine 290 | 291 | //call the function with year, month, and HCP selection as inputs 292 | filterBaseLine(yr, mnth, conversion); 293 | 294 | //add Relative Greenness to map 295 | Map.addLayer(ndviRelGre.clip(conversion),{min:0, max:1, palette: ['f1a340', 'FFFFFF', '556B2F']},'Relative Green'); 296 | var relgreencol = ee.ImageCollection(ndviRelGre); //adds final result to image collection for export function 297 | col = ee.ImageCollection(relgreencol); //merges with col variable 298 | 299 | //generate chart ndvi 300 | //Currently trying to figure out how to point this towards our ndvi data and our selected month/year 301 | var startdate = ee.Date.fromYMD(yr, 1, 1); 302 | var enddate = ee.Date.fromYMD(yr.add(1), 1, 1); 303 | 304 | //use our merged Landsat Collection 305 | var NDVIchartdoi = ee.ImageCollection(LandsatCollection.filterDate(startdate, enddate)); 306 | var ndviChart = ui.Chart.image.doySeriesByYear( 307 | NDVIchartdoi, 'NDVI', conversion, ee.Reducer.mean(), 500); 308 | 309 | panel.widgets().set(9, ndviChart); 310 | } 311 | } 312 | ); 313 | //END Relative Greeness calculations// 314 | 315 | //BEGIN make a legend for Relative Greenness that is added to the user interface// 316 | //list visualization parameters 317 | var LGND = {"opacity":1,"min":-90,"max":60, 318 | "palette":['f1a340', 'FFFFFF', '556B2F']}; 319 | 320 | //make a function for displaying Relative Greenness legend 321 | function makeLegend(vis) { 322 | var lon = ee.Image.pixelLonLat().select('longitude'); 323 | var gradient = lon.multiply((vis.max-vis.min)/100.0).add(vis.min); 324 | var legendImage = gradient.visualize(vis); 325 | 326 | //Coefficient legend 327 | var thumb = ui.Thumbnail({ 328 | image: legendImage, 329 | params: {bbox:'0,0,100,8', dimensions:'300x15'}, 330 | style: {position: 'bottom-center'} 331 | }); 332 | var text = ui.Panel({ 333 | widgets: [ 334 | ui.Label(String('Historical Minimum')), 335 | ui.Label({style: {stretch: 'horizontal'}}), 336 | ui.Label(String('Historical Maximum')), 337 | ], 338 | layout: ui.Panel.Layout.flow('horizontal'), 339 | style: { 340 | padding: '0px', 341 | stretch: 'horizontal', 342 | fontSize: '12px', 343 | color: 'gray', 344 | textAlign: 'center' 345 | } 346 | }); 347 | 348 | return ui.Panel({style:{position: 'bottom-left'}}) 349 | .add(text).add(thumb); 350 | } 351 | //END make a legend for Relative Greenness// 352 | 353 | //BEGIN calculating percent change in Relative Greenness// 354 | //Create percent change in Relative Greenness variables 355 | var pastRelGre; 356 | var changeRelGreen; 357 | 358 | //Create widget that applies temporal selection and loads percent change in Relative Greenness to map 359 | var percentChange = ui.Button({ 360 | label: ('Calculate Percent Change in Relative Greenness'), //adds button label 361 | onClick: function() 362 | { 363 | //user needs to select month and year of interest from 2010-2017 364 | //create year and month variables 365 | var yr; 366 | var mnth; 367 | //create function that will parse textbox inputs into Relative Greenness equations 368 | function fillTextbox(){ 369 | //assign the varible 'yr' to the current string in the selectYr textbox 370 | yr = selectYr.getValue(); 371 | // if 'yr' is 'yr' then turn that string into a number 372 | if (yr) yr = ee.Number.parse(yr); 373 | mnth = selectMnth.getValue(); 374 | if (mnth) mnth = ee.Number.parse(mnth); 375 | } 376 | //run fillTextbox function 377 | fillTextbox(); 378 | 379 | //creates variable for year prior to year of interest 380 | var prevYr = yr.subtract(1); 381 | 382 | //Next, create a previous year NDVI collection to use in percent change in Relative Greenness equation 383 | var NDVICurrent = ee.ImageCollection(LandsatCollection.filterDate('2010-01-01', '2017-12-31') //filter date to 2000-2010 384 | .filterBounds(conversion) //filter to HCP selection 385 | .select('NDVI')); //select NDVI 386 | 387 | //Set the month and year properties in metadata so that they are searchable later 388 | var NDVICurrenttest = NDVICurrent.map(function(img) { 389 | img = img.clip(conversion); 390 | var d = ee.Date(ee.Number(img.get('system:time_start'))); 391 | var m = ee.Number(d.get('month')); 392 | var y = ee.Number(d.get('year')); 393 | return img.set({'month':m, 'year':y}); 394 | }); 395 | 396 | //Create filterBaseLine function that filter NDVI collections to month and year selection and outputs for previous year Relative Greenness 397 | var filterBaseLine = function(year, month, conversion){ 398 | // calculates the current NDVI filtered by input year and month 399 | var ndvi_past = ee.Image(NDVICurrenttest 400 | .filterMetadata('year', 'equals', year) //filter by year of interest 401 | .filterMetadata('month', 'equals', month) //filter by month of interest 402 | .filterBounds(conversion) //filter to HCP selection 403 | .mean()); 404 | 405 | //Filters baseline min and max by input month, reduced using mean 406 | var pastmin_ndvi = ee.Image(byMonthmin 407 | .filterMetadata('month', 'equals', month) //filter by month of interest 408 | .filterBounds(conversion) //filter to HCP selection 409 | .min()); 410 | 411 | var pastmax_ndvi = ee.Image(byMonthmax 412 | .filterMetadata('month', 'equals', month) //filter by month of interest 413 | .filterBounds(conversion) //filter to HCP selection 414 | .max()); 415 | 416 | //The relative greeness equation (current - min / max - min) - creates relative greenness for previous year of interest 417 | pastRelGre = ndvi_past.subtract(pastmin_ndvi).divide(pastmax_ndvi.subtract(pastmin_ndvi)); 418 | }; 419 | 420 | //call the function with previous year, month, and HCP selection as inputs 421 | filterBaseLine(prevYr, mnth, conversion); 422 | changeRelGreen = (ndviRelGre.subtract(pastRelGre)).divide(pastRelGre); 423 | //add percent change in Relative Greenness to map 424 | Map.addLayer(changeRelGreen.clip(conversion),{min:-1, max:1, palette: ['f1a340', 'FFFFFF', '556B2F']},'Percent Change Relative Green'); 425 | var changeColl = ee.ImageCollection(changeRelGreen); //makes image collection from percent change variable for exporting 426 | col = ee.ImageCollection(col.merge(changeColl)); //merges with col variable 427 | } 428 | }); 429 | //END calcuating percent change in Relative Greenness// 430 | 431 | //BEGIN adding ancillary datasets to the map 432 | //Step 4: Add ancillary datasets to help explain change 433 | 434 | //BEGIN add NASS Cropland data 435 | //loads croplands data for 2008 - 2016 436 | var nass08 = ee.Image('USDA/NASS/CDL/2008'); 437 | var nass09 = ee.Image('USDA/NASS/CDL/2009'); 438 | var nass10 = ee.Image('USDA/NASS/CDL/2010'); 439 | var nass11 = ee.Image('USDA/NASS/CDL/2011'); 440 | var nass12 = ee.Image('USDA/NASS/CDL/2012'); 441 | var nass13 = ee.Image('USDA/NASS/CDL/2013').select('cropland'); 442 | var nass14 = ee.Image('USDA/NASS/CDL/2014').select('cropland'); 443 | var nass15 = ee.Image('USDA/NASS/CDL/2015').select('cropland'); 444 | var nass16 = ee.Image('USDA/NASS/CDL/2016').select('cropland'); 445 | 446 | //Create drop-down to select cropland data layer to display 447 | var preseq; 448 | var onlypresent; 449 | var nass = ui.Select({ 450 | items: [ 451 | {label: '2016', value: nass16}, 452 | {label: '2015', value: nass15}, 453 | {label: '2014', value: nass14}, 454 | {label: '2013', value: nass13}, 455 | {label: '2012', value: nass12}, 456 | {label: '2011', value: nass11}, 457 | {label: '2010', value: nass10}, 458 | {label: '2009', value: nass09}, 459 | {label: '2008', value: nass08},], 460 | placeholder: ('Choose Cropland Data by Year (2008-2016)'), 461 | onChange: function(value){ 462 | //show cropland layer 463 | var nalayer = ee.Image(value.clip(conversion)); 464 | var nalayerName = value.id().getInfo(); 465 | print(nalayerName, 'layer name'); 466 | Map.addLayer(nalayer, {}, 'Cropland ' + nalayerName); //add cropland data to map 467 | 468 | //convert croplands data to vector, this is used to identify the specific crop rather than raster value 469 | var navector = nalayer.reduceToVectors({ 470 | geometry: conversion, 471 | scale: 1000, 472 | maxPixels: 1e13, 473 | geometryType: 'polygon', 474 | eightConnected: false, 475 | labelProperty: 'Cropland_Type', 476 | }); 477 | Map.addLayer(navector, {}, "Cropland Vectors - IGNORE", {}, 0); //sets vector layer to be transparent, so the user only sees the raster layer 478 | 479 | //converts the properties in the raster to lists 480 | var vectsize = navector.size(); 481 | var vectlist = navector.toList(vectsize); 482 | var croplandnames = ee.List(nalayer.get("cropland_class_names")); 483 | var croplandvals = ee.List(nalayer.get("cropland_class_values")); 484 | var crop_palettes = ee.List(nalayer.get("cropland_class_palette")); 485 | var croplandcombo = croplandnames.zip(croplandvals); 486 | var numcrops = ee.Number(croplandcombo.size()); 487 | var cropseq = ee.List.sequence(0,numcrops.subtract(1)); 488 | 489 | function addCropland(callFeature) 490 | { 491 | var curr = ee.Feature(callFeature); 492 | var cropland = ee.Number(curr.get("Cropland_Type")); 493 | 494 | var x = cropseq.map(findName); 495 | 496 | function findName(callNum) 497 | { 498 | var ind = ee.Number(callNum); 499 | var elm = ee.List(croplandcombo.get(ind)); 500 | var cname = ee.String(elm.get(0)); 501 | var cval = ee.Number(elm.get(1)); 502 | 503 | var check = ee.Algorithms.If(ee.Algorithms.IsEqual(cropland,cval),cname,0); 504 | 505 | return check; 506 | } 507 | 508 | var allz = ee.List.repeat(0,135); 509 | var remtest = x.removeAll(allz); 510 | var cropkind = ee.String(remtest.get(0)); 511 | 512 | 513 | var findind = ee.Number(x.indexOf(cropkind)); 514 | var pal = crop_palettes.get(findind); 515 | 516 | 517 | return curr.set('crop_type', cropkind).set('color',pal); 518 | } 519 | 520 | var tryaddcops = vectlist.map(addCropland); 521 | var featcollvect = ee.FeatureCollection(tryaddcops); 522 | 523 | //filters by cropland value 524 | //so that cropland of the same cropland value can be manipulated together 525 | function cropFilt(callEl) 526 | { 527 | var curr = ee.Number(callEl); 528 | var eachtype = featcollvect.filter(ee.Filter.eq('Cropland_Type',curr)); 529 | return eachtype.set('Cropland_Type', callEl); 530 | } 531 | 532 | var filtype = ee.FeatureCollection(croplandvals.map(cropFilt)); 533 | 534 | var filttypesize = filtype.size(); 535 | var filtypelist = filtype.toList(filttypesize); 536 | 537 | //then adds those properties to the overall feature collection of each cropland 538 | function addFCprops(callFC) 539 | { 540 | var curr = ee.FeatureCollection(callFC); 541 | var cropland = ee.Number(curr.get("Cropland_Type")); 542 | var size = ee.Number(curr.size()); 543 | 544 | var x = cropseq.map(findName); 545 | 546 | function findName(callNum) 547 | { 548 | var ind = ee.Number(callNum); 549 | var elm = ee.List(croplandcombo.get(ind)); 550 | var cname = ee.String(elm.get(0)); 551 | var cval = ee.Number(elm.get(1)); 552 | 553 | var check = ee.Algorithms.If(ee.Algorithms.IsEqual(cropland,cval),cname,0); 554 | 555 | return check; 556 | } 557 | 558 | var allz = ee.List.repeat(0,135); 559 | var remtest = x.removeAll(allz); 560 | var cropkind = ee.String(remtest.get(0)); 561 | 562 | return curr.set('crop_type', cropkind).set('size', size); 563 | 564 | } 565 | 566 | var byfc = ee.FeatureCollection(filtypelist.map(addFCprops)); 567 | 568 | //returns feature collections that aren't empty, the croplands that are actually present 569 | //within the given HCP 570 | function isPres(callFC) 571 | { 572 | var curr = ee.FeatureCollection(callFC); 573 | var noempty = curr.filter(ee.Filter.greaterThan('size',0)); 574 | return noempty; 575 | } 576 | 577 | var fcnotempty = ee.FeatureCollection(isPres(byfc)); 578 | 579 | var onlypresize = ee.Number(fcnotempty.size()); 580 | onlypresent = fcnotempty.toList(onlypresize); 581 | preseq = ee.List.sequence(0,onlypresize.subtract(1)); 582 | 583 | //Turns Cursor into crosshair during cropland layer selected that returns specific pixel selection 584 | Map.style().set('cursor', 'crosshair'); 585 | var inspector = ui.Panel([ui.Label('Click to get cropland type')]); 586 | Map.add(inspector); 587 | 588 | Map.onClick(function(coords) { 589 | 590 | inspector.widgets().set(0, ui.Label 591 | ({ 592 | value: 'Loading...', 593 | style: {color: 'gray'} 594 | }) 595 | ); 596 | 597 | //coords from the location where the cursor clicked 598 | var point = ee.Geometry.Point(coords.lon, coords.lat); 599 | 600 | var x = preseq.map(isWithin); 601 | 602 | //finds and displays the name of the feature collection the point is contained within 603 | 604 | function isWithin(callInd) 605 | { 606 | var ind = ee.Number(callInd); 607 | var curr = ee.FeatureCollection(onlypresent.get(callInd)); 608 | var tru = ee.Algorithms.If(point.containedIn(curr),ind,0); 609 | return tru; 610 | } 611 | 612 | var sortx = x.sort().reverse(); 613 | var getind = ee.Number(sortx.get(0)); 614 | 615 | var geo = ee.FeatureCollection(onlypresent.get(getind)); 616 | var getValue = ee.String(geo.get('crop_type')); 617 | 618 | function year1(result) 619 | { 620 | inspector.widgets().set(0, ui.Label({value: 'Cropland: ' +result,})); 621 | } 622 | 623 | getValue.evaluate(year1); 624 | 625 | }); 626 | } 627 | }); 628 | //END add NASS Cropland data// 629 | 630 | //BEGIN add NAIP Imagery// 631 | var naip = ui.Button({ 632 | label: ('National Agriculture Imagery Program Data'), 633 | onClick: function(){ 634 | //user needs to select month and year of interest from 2010-2017 635 | //create year and month variables 636 | var yr; 637 | var mnth; 638 | //create function that will parse textbox inputs into Relative Greenness equations 639 | function fillTextbox(){ 640 | //assign the varible 'yr' to the current string in the selectYr textbox 641 | yr = selectYr.getValue(); 642 | // if 'yr' is 'yr' then turn that string into a number 643 | if (yr) yr = ee.Number.parse(yr); 644 | mnth = selectMnth.getValue(); 645 | if (mnth) mnth = ee.Number.parse(mnth); 646 | } 647 | //run fillTextbox function 648 | fillTextbox(); 649 | 650 | //adds NAIP collection 651 | var icnaip = ee.ImageCollection('USDA/NAIP/DOQQ'); 652 | 653 | //set the month and year properties in metadata to allow for searching 654 | var naipCurrenttest = icnaip.map(function(img) { 655 | img = img.clip(conversion); 656 | var d = ee.Date(ee.Number(img.get('system:time_start'))); 657 | var m = ee.Number(d.get('month')); 658 | var y = ee.Number(d.get('year')); 659 | return img.set({'month':m, 'year':y}); 660 | }); 661 | 662 | var reducer = ee.Reducer.first(); 663 | //create mean NBR image from NBR current collection 664 | var naipmean = ee.Image(naipCurrenttest 665 | .filterMetadata('year', 'equals', yr) 666 | //.filterMetadata('month', 'equals', mnth) 667 | .filterBounds(conversion) 668 | .median()); 669 | 670 | var naiplayer = ee.Image(naipmean.clip(conversion)); 671 | var naipcol = ee.ImageCollection(naiplayer); 672 | col = ee.ImageCollection(col.merge(naipcol)); 673 | Map.addLayer(naiplayer, {}, 'NAIP'); 674 | }}); 675 | //END add NAIP Imagery// 676 | 677 | //BEGIN calculate NBR// 678 | //create button to calculate NBR 679 | var NBR = ui.Button({ 680 | label: ('Calculate Normalized Burn Ratio'), 681 | onClick: function() 682 | { 683 | //user needs to select month and year of interest from 2010-2017 684 | //create year and month variables 685 | var yr; 686 | var mnth; 687 | //create function that will parse textbox inputs into NBR calculation 688 | function fillTextbox(){ 689 | //assign the varible 'yr' to the current string in the selectYr textbox 690 | yr = selectYr.getValue(); 691 | // if 'yr' is 'yr' then turn that string into a number 692 | if (yr) yr = ee.Number.parse(yr); 693 | mnth = selectMnth.getValue(); 694 | if (mnth) mnth = ee.Number.parse(mnth); 695 | } 696 | //run fillTextbox function 697 | fillTextbox(); 698 | 699 | //create current NBR image collection (2010-2017) 700 | var NBRCurrent = ee.ImageCollection(LandsatCollection.filterDate('2010-01-01', '2017-12-31') 701 | .filterBounds(conversion) //filter by HCP selection 702 | .select('NBR')); //select NBR band 703 | 704 | //set the month and year properties in metadata to allow for searching 705 | var NBRCurrenttest = NBRCurrent.map(function(img) { 706 | img = img.clip(conversion); 707 | var d = ee.Date(ee.Number(img.get('system:time_start'))); 708 | var m = ee.Number(d.get('month')); 709 | var y = ee.Number(d.get('year')); 710 | return img.set({'month':m, 'year':y}); 711 | }); 712 | 713 | //create mean NBR image from NBR current collection 714 | var NBRmean = ee.Image(NBRCurrenttest 715 | .filterMetadata('year', 'equals', yr) 716 | //.filterMetadata('month', 'equals', mnth) 717 | .filterBounds(conversion) 718 | .mean()); 719 | 720 | //adds NBR output to map 721 | Map.addLayer(NBRmean, {min:-0.35, max:-0.15, palette: ['FF0000', 'F2EAD8']}, 'NBR'); 722 | var NBRcoll = ee.ImageCollection(NBRmean); //adds NBR output to image collection for exporting 723 | col = ee.ImageCollection(col.merge(NBRcoll)); //merge with col variable 724 | }}); 725 | 726 | //make a legend for NBR 727 | //list visualization parameters 728 | var LGND2 = {"opacity":1, "min":-0.35, "max":-0.15, 729 | "palette":['FF0000', 'F2EAD8']}; 730 | 731 | //make a function for displaying NBR legend 732 | function makeLegend2(vis) { 733 | var lon = ee.Image.pixelLonLat().select('longitude'); 734 | var gradient = lon.multiply((vis.max-vis.min)/100.0).add(vis.min); 735 | var legendImage = gradient.visualize(vis); 736 | 737 | //Coefficient legend 738 | var thumb2 = ui.Thumbnail({ 739 | image: legendImage, 740 | params: {bbox:'0,0,100,8', dimensions:'300x15'}, 741 | style: {position: 'bottom-center'} 742 | }); 743 | var text2 = ui.Panel({ 744 | widgets: [ 745 | ui.Label(String('Fire Likely')), 746 | ui.Label({style: {stretch: 'horizontal'}}), 747 | ui.Label(String('Fire Unlikely')), 748 | ], 749 | layout: ui.Panel.Layout.flow('horizontal'), 750 | style: { 751 | padding: '0px', 752 | stretch: 'horizontal', 753 | fontSize: '12px', 754 | color: 'gray', 755 | textAlign: 'center' 756 | } 757 | }); 758 | 759 | return ui.Panel({style:{position: 'bottom-left'}}) 760 | .add(text2).add(thumb2); 761 | } 762 | //END calculate NBR// 763 | 764 | //BEGIN creating panel for the user interface// 765 | //Step 4: Create panel for user interface 766 | //Create master panel in which to arrange widgets 767 | var panel = ui.Panel({style: {width: '320px'}}) 768 | .add(ui.Label({ 769 | value: 'BETA: US Fish and Wildlife Service Change Detection Tool', //create title for whole UI 770 | style: {fontWeight: 'bold', fontSize: '22px', color: '#228B22'} 771 | }) 772 | ); 773 | 774 | //Create drop-down for HCP selection 775 | var hcpbtn = ui.Panel({ 776 | widgets: [select], //include select widget 777 | layout: ui.Panel.Layout.flow('vertical'), 778 | }); 779 | 780 | //Create labels for step one and include HCP selection button 781 | var stepone = ui.Panel({ 782 | widgets: [ 783 | ui.Label('1) Select Area of Interest', {fontWeight: 'bold'}), //title for Step 1 784 | ui.Label('If your area of interest is not listed, contact support to get your area added.', 785 | {fontSize: '12px'}), 786 | ui.Label('Updating this selection will clear the map of all previous outputs.', {fontSize: '12px'}), 787 | select, //adds HCP selection section 788 | ], 789 | }); 790 | 791 | //create labels for step two 792 | var steptwo = ui.Panel({ 793 | widgets: [ 794 | ui.Label('2) Calculate Relative Greenness', {fontWeight: 'bold'}), //title for Step 2 795 | ui.Label('Select year and month of interest for the Relative Greenness calculation.', {fontSize: '12px'}), 796 | ], 797 | }); 798 | 799 | //create labels for year and month selections 800 | var stepTwoLabels = ui.Panel({ 801 | widgets: [ 802 | ui.Label('Year selection', {fontSize: '12px'}), 803 | ui.Label({style: {stretch: 'horizontal'}}), 804 | ui.Label('Month selection', {fontSize: '12px'}), 805 | ], 806 | layout: ui.Panel.Layout.flow('horizontal'), 807 | }); 808 | 809 | //create panel with year and month selection textboxes 810 | var stepTwoText = ui.Panel({ 811 | widgets: [ 812 | selectYr, 813 | ui.Label({style: {stretch: 'horizontal'}}), 814 | selectMnth, 815 | ], 816 | layout: ui.Panel.Layout.flow('horizontal'), 817 | }); 818 | 819 | //create panel with relative greenness calculation button and relative greenness legend 820 | var stepTwoAction = ui.Panel({ 821 | widgets: [load, makeLegend(LGND), percentChange] 822 | }); 823 | 824 | //Create panel for step three and include cropland and NBR buttons 825 | var stepthree = ui.Panel({ 826 | widgets: [ 827 | ui.Label('3) Select Ancillary Products', {fontWeight: 'bold'}), //title for Step 3 828 | NBR, 829 | makeLegend2(LGND2), 830 | nass, 831 | naip, 832 | ], 833 | }); 834 | 835 | //BEGIN export function// 836 | //export button uses a combination of javascript and python 837 | var exprt = ui.Button({ 838 | label: 'Export Image', 839 | onClick: function() { 840 | print(col); 841 | var ExportCol = function(col, folder, scale, type, 842 | nimg, maxPixels, region) { 843 | type = type || "float"; 844 | nimg = nimg || 500; 845 | scale = scale || 1000; 846 | maxPixels = maxPixels || 1e10; 847 | 848 | var colList = col.toList(nimg); 849 | var n = colList.size().getInfo(); 850 | 851 | for (var i = 0; i < n; i++) { 852 | var img = ee.Image(colList.get(i)); 853 | var name = Map.layers().get(i) 854 | var grab = name.get("name") 855 | var month = ee.String(mnth).getInfo(); 856 | var year = ee.String(yr).getInfo(); 857 | region = region || img.geometry().bounds().getInfo()["coordinates"]; 858 | 859 | var imgtype = {"float":img.toFloat(), 860 | "byte":img.toByte(), 861 | "int":img.toInt(), 862 | "double":img.toDouble() 863 | }; 864 | 865 | Export.image.toDrive({ 866 | image:imgtype[type], 867 | description: 'InsertProductName_'+month+'_'+year, 868 | folder: folder, 869 | fileNamePrefix: 'InsertProductName_'+month+'_'+year, 870 | region: conversion, 871 | scale: scale, 872 | maxPixels: maxPixels}); 873 | } 874 | }; 875 | ExportCol(col, "Land Use Change Detection Outputs", 30); 876 | }}); 877 | //END export function// 878 | 879 | //create panel for step four 880 | var stepfour = ui.Panel({ 881 | widgets: [ 882 | ui.Label('4) Export the Displayed Derived Product', {fontWeight: 'bold'}), //title for Step 4 883 | exprt, 884 | ], 885 | }); 886 | 887 | //create panel for step five 888 | var stepfive = ui.Panel([ 889 | ui.Label({ 890 | value: '5) Chart', 891 | style: {fontWeight: 'bold'} 892 | }), 893 | ui.Label('A chart will auto populate once relative green is calculated.') 894 | ]); 895 | //END creating panel for the user interface// 896 | 897 | //BEGIN call functions to create user interface// 898 | panel.add(stepone); //add step one to panel 899 | panel.add(steptwo); //add step two to panel 900 | panel.add(stepTwoLabels); //add step two labels to panel 901 | panel.add(stepTwoText); //add step two text to panel 902 | panel.add(stepTwoAction); //add step two action to panel 903 | panel.add(stepthree); //add step three to panel 904 | panel.add(stepfour); //add step four to panel 905 | panel.add(stepfive); //add step five to panel 906 | ui.root.add(panel); //add panel to root 907 | //END call functions to create user interface// 908 | --------------------------------------------------------------------------------