├── prjFireInsect.png ├── data ├── threeStates.dbf ├── threeStates.shp ├── threeStates.shx ├── focalFireInsect.rds ├── focalFireInsect.tif ├── prjFireInsect.kmz ├── prjFireInsect.tif ├── val_fireStates.rds └── threeStates.prj ├── edwebinar_mar19_ornldaac_tutorial.pdf ├── edwebinar_mar19.Rproj ├── .gitignore ├── LICENSE.md ├── README.md ├── edwebinar_mar19_ornldaac_supplemental.Rmd ├── edwebinar_mar19_ornldaac_supplemental.md ├── edwebinar_mar19_ornldaac_tutorial.Rmd └── edwebinar_mar19_ornldaac_tutorial.md /prjFireInsect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/prjFireInsect.png -------------------------------------------------------------------------------- /data/threeStates.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/threeStates.dbf -------------------------------------------------------------------------------- /data/threeStates.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/threeStates.shp -------------------------------------------------------------------------------- /data/threeStates.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/threeStates.shx -------------------------------------------------------------------------------- /data/focalFireInsect.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/focalFireInsect.rds -------------------------------------------------------------------------------- /data/focalFireInsect.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/focalFireInsect.tif -------------------------------------------------------------------------------- /data/prjFireInsect.kmz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/prjFireInsect.kmz -------------------------------------------------------------------------------- /data/prjFireInsect.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/prjFireInsect.tif -------------------------------------------------------------------------------- /data/val_fireStates.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/data/val_fireStates.rds -------------------------------------------------------------------------------- /edwebinar_mar19_ornldaac_tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornldaac/r-geospatial-webinar/HEAD/edwebinar_mar19_ornldaac_tutorial.pdf -------------------------------------------------------------------------------- /data/threeStates.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /edwebinar_mar19.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # Data Files 9 | /data/ 10 | prjFireInsect.png 11 | 12 | # Example code in package build process 13 | *-Ex.R 14 | 15 | # Output files from R CMD build 16 | /*.tar.gz 17 | 18 | # Output files from R CMD check 19 | /*.Rcheck/ 20 | 21 | # RStudio files 22 | .Rproj.user/ 23 | 24 | # Produced vignettes 25 | vignettes/*.html 26 | vignettes/*.pdf 27 | 28 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 29 | .httr-oauth 30 | 31 | # knitr and R markdown default cache directories 32 | /*_cache/ 33 | /cache/ 34 | 35 | # Temporary files created by R markdown 36 | *.utf8.md 37 | *.knit.md 38 | 39 | # Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html 40 | rsconnect/ 41 | .Rproj.user 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright , UT-Battelle, LLC 2 | 3 | All rights reserved 4 | 5 | # Introduction to Geospatial Analysis in R 6 | 7 | ## OPEN SOURCE LICENSE 8 | 9 | Subject to the conditions of this License, UT-Battelle, LLC (the “Licensor”) hereby grants, free of charge, to any person (the “Licensee”) obtaining a copy of this software and associated documentation files (the "Software"), a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to use, copy, modify, merge, publish, distribute, and/or sublicense copies of the Software. 10 | 11 | 1. Redistributions of Software must retain the above open source license grant, copyright and license notices, this list of conditions, and the disclaimer listed below. Changes or modifications to, or derivative works of the Software must be noted with comments and the contributor and organization’s name. 12 | 13 | 2. Neither the names of Licensor, the Department of Energy, or their employees may be used to endorse or promote products derived from this Software without their specific prior written permission. 14 | 15 | 3. If the Software is protected by a proprietary trademark owned by Licensor or the Department of Energy, then derivative works of the Software may not be distributed using the trademark without the prior written approval of the trademark owner. 16 | 17 | 18 | --- 19 | 20 | ## DISCLAIMER 21 | 22 | UT-Battelle, LLC AND THE GOVERNMENT MAKE NO REPRESENTATIONS AND DISCLAIM ALL WARRANTIES, BOTH EXPRESSED AND IMPLIED. THERE ARE NO EXPRESS OR IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY PATENT, COPYRIGHT, TRADEMARK, OR OTHER PROPRIETARY RIGHTS, OR THAT THE SOFTWARE WILL ACCOMPLISH THE INTENDED RESULTS OR THAT THE SOFTWARE OR ITS USE WILL NOT RESULT IN INJURY OR DAMAGE. The user assumes responsibility for all liabilities, penalties, fines, claims, causes of action, and costs and expenses, caused by, resulting from or arising out of, in whole or in part the use, storage or disposal of the SOFTWARE. 23 | 24 | --- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Geospatial Analysis in R 2 | 3 | ### NASA Earthdata Webinar 4 | 5 | ### *Presented by the ORNL DAAC* 6 | 7 | ### *March 13, 2019* 8 | 9 | ### **Keywords: R, Carbon Monitoring System, GIS** 10 | 11 | *** 12 | **UPDATE:** The entire repo was updated 2021-12-03 and again on 2024-01-25. The recent update includes a revision of the primary tutorial to replace deprecated packages (e.g., rgdal). 13 | 14 | # 1. Overview 15 | 16 | The open-source software environment R is gaining popularity among many scientists, including geologists, biologists, and environmental scientists. R provides an alternative to traditional GIS software with numerous packages for geospatial analysis. This webinar will begin with a brief introduction to an example geospatial dataset from the ORNL DAAC and an overview of common geospatial operations in R. Next, we will demonstrate how to import files into R, overlay layers, reduce spatial extent, select and reclassify values, and make a map. 17 | 18 | # 2. Dataset 19 | 20 | ## 2.1 CMS: Forest Carbon Stocks, Emissions, and Net Flux for the Conterminous US: 2005-2010 21 | 22 | This dataset provides maps of estimated carbon in forests of the 48 conterminous states of the United States (CONUS) for the years 2005-2010. Committed carbon stocks were estimated for forest aboveground biomass, belowground biomass, standing dead stems, and litter for the year 2005. Carbon emissions were estimated from land use conversion to agriculture, fire damage, insect damage, logging, wind, and weather events in the forests for the years 2006-2010. Committed net carbon flux was estimated as the sum of carbon emissions and sequestration. The maps are provided at 100 m spatial resolution in GeoTIFF format. Average annual carbon estimates, by United States county, are provided in shapefile format for (1) emissions for the multiple disturbance sources, (2) sequestration, and (3) the committed net carbon flux. 23 | 24 | Hagen, S., N. Harris, S.S. Saatchi, T. Pearson, C.W. Woodall, S. Ganguly, G.M. Domke, B.H. Braswell, B.F. Walters, J.C. Jenkins, S. Brown, W.A. Salas, A. Fore, Y. Yu, R.R. Nemani, C. Ipsan, and K.R. Brown. 2016. **CMS: Forest Carbon Stocks, Emissions, and Net Flux for the Conterminous US: 2005-2010.** ORNL DAAC, Oak Ridge, Tennessee, USA. 25 | 26 | ## 2.2 Carbon Monitoring System (CMS) 27 | 28 | The Carbon Monitoring System (CMS) is designed to make significant contributions in characterizing, quantifying, understanding, and predicting the evolution of global carbon sources and sinks through improved monitoring of carbon stocks and fluxes. CMS will use the full range of NASA satellite observations and modeling/analysis capabilities to establish the accuracy, quantitative uncertainties, and utility of products for supporting national and international policy, regulatory, and management activities. CMS will maintain a global emphasis while providing finer scale regional information, utilizing space-based and surface-based data and will rapidly initiate generation and distribution of products both for user evaluation and to inform near-term policy development and planning. 29 | 30 | # 3. Prerequisites 31 | 32 | Participants should have a basic understanding of R and some exposure to geospatial data and analysis, such as in ArcGIS. 33 | 34 | ## 3.1 R 35 | 36 | 1. [Download R](https://cran.r-project.org/) 37 | 2. [Download RStudio](https://www.rstudio.com/products/rstudio/download/#download) *Recommended* 38 | 3. [Review R Manuals](https://cran.r-project.org/manuals.html) *Recommended* 39 | 40 | ## 3.2 Data 41 | 42 | 1. [Sign in to NASA Earthdata](https://urs.earthdata.nasa.gov/users/new) 43 | 2. [Download GrossEmissions_v101_USA_Fire.tif](https://daac.ornl.gov/daacdata/cms/CMS_Forest_Carbon_Fluxes/data//GrossEmissions_v101_USA_Fire.tif) 44 | 3. [Download GrossEmissions_v101_USA_Insect.tif](https://daac.ornl.gov/daacdata/cms/CMS_Forest_Carbon_Fluxes/data//GrossEmissions_v101_USA_Insect.tif) 45 | 46 | # 4. Procedure 47 | 48 | ## 4.1 Tutorial 49 | 50 | 1. [R Markdown](https://github.com/jessnicwelch/edwebinar_mar19/blob/master/edwebinar_mar19_ornldaac_tutorial.Rmd) 51 | 2. [Markdown](https://github.com/jessnicwelch/edwebinar_mar19/blob/master/edwebinar_mar19_ornldaac_tutorial.md) 52 | 53 | ## 4.2 Supplemental 54 | 55 | 1. [R Markdown](https://github.com/jessnicwelch/edwebinar_mar19/blob/master/edwebinar_mar19_ornldaac_supplemental.Rmd) 56 | 2. [Markdown](https://github.com/jessnicwelch/edwebinar_mar19/blob/master/edwebinar_mar19_ornldaac_supplemental.md) 57 | 58 | ## 4.3 Webinar 59 | 60 | 1. [Presentation Slides](https://daac.ornl.gov/resources/tutorials/r-geospatial-webinar/Earthdata_R_Geospatial_webinar_Mar2019_v20190312.pdf) 61 | 2. [Video](https://daac.ornl.gov/resources/tutorials/r-geospatial-webinar/) 62 | 63 | # 5. Credits 64 | 65 | * [R](https://www.r-project.org/) - 4.3.2 (2023-10-31) -- "Eye Holes" 66 | * [RStudio](https://www.rstudio.com/products/rstudio/) - IDE and notebook construction 67 | * [raster package](https://CRAN.R-project.org/package=raster) - geospatial data manipulations 68 | * [sf package](https://cran.r-project.org/web/packages/sf/index.html) - support for simple features 69 | * [terra package](https://cran.r-project.org/package=terra) - spatial data analysis with raster (grid) data 70 | * [tigris](https://CRAN.R-project.org/package=tigris) - states function 71 | -------------------------------------------------------------------------------- /edwebinar_mar19_ornldaac_supplemental.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Geospatial Analysis in R" 3 | subtitle: "Supplemental Tutorial: Neighborhood analysis" 4 | author: 'Presented by the ORNL DAAC https://daac.ornl.gov' 5 | date: "January 9, 2024" 6 | output: 7 | html_document: 8 | keep_md: yes 9 | number_sections: yes 10 | toc: yes 11 | html_notebook: 12 | number_sections: yes 13 | toc: yes 14 | editor_options: 15 | chunk_output_type: inline 16 | --- 17 | *** 18 | 19 | 20 | 21 | This supplemental tutorial will demonstrate how perform a **neighborhood analysis** to identify locations disturbed by insects are adjacent to fire disturbance. The tutorial will use two raster maps created during the main tutorial. 22 | 23 | # Install and Load Packages 24 | 25 | As with the main portion of the tutorial, the *terra* and *sf* must be installed along with their dependencies. 26 | 27 | ```{r install_packages, message=FALSE, warning=FALSE, eval=FALSE} 28 | install.packages("terra", dependencies = TRUE) 29 | install.packages("sf", dependencies = TRUE) 30 | ``` 31 | 32 | Load the *terra* and *sf* packages. 33 | 34 | ```{r load_packages, message=FALSE, warning=FALSE} 35 | library(terra) # provides most functions 36 | library(sf) # provides function for saving files 37 | ``` 38 | 39 | # Load Data 40 | 41 | > *Functions featured in this section:* 42 | > **sf::st_read()** 43 | > reads a shapefile and loads it as a simple feature object 44 | 45 | We must load two objects, *threeStates* and *prjFireInsect*, that were created from manipulations described in the main part of the tutorial and saved in the "./data" folder. To load a shapefile, use the `st_read()` function from the *sf* package. We will create a third object, *focalFireInsect*, later in this supplemental tutorial. 46 | 47 | ```{r read_maps, message=FALSE, warning=FALSE, results="hide"} 48 | threeStates <- st_read(dsn="./data/threeStates.shp", quiet=TRUE) 49 | prjFireInsect <- rast("./data/prjFireInsect.tif") 50 | ``` 51 | 52 | # Perform a Focal Analysis: a simple demonstration 53 | 54 | > *Functions featured in this section:* 55 | > **terra::focal()** 56 | > calculates values from the neighborhood of cells ("moving window") around a target cell using a matrix 57 | 58 | A neighborhood analysis computes the value of a single focal cell using information from nearby cells. Imagine a donut (i.e., an annulus) where the focal cell is in the donut hole and values from the ring of surrounding cells are used to derive a value for the focal cell. 59 | 60 | Users can specify neighborhoods that best fit the spatial processes under analysis. The neighborhood may or may not include the focal cell, and the width of the band of surrounding cells can be specified to alternatively enlarge or shrink the neighborhood. Moreover, the neighborhood does not have to take the form of ring. It can be rectangular and/or expanded on one side but not the others. For example, imagine estimating air pollution values for a cells in a landscape with westerly winds. The analysis could use a triangular neighborhood that fans out to the west of the focal cell to use values from upwind cells while ignoring downwind values to the east. All cells within the neighborhood can be treated equally. Alternatively, the relative influence of cells can be weighted by their distance from the focal cell. 61 | 62 | The `focal()` function is used in the *terra* package for neighborhood analysis. We begin with a demonstration of how the `focal()` behaves using a small SpatRaster object. 63 | 64 | First, we create a matrix object similar to the raster grid of a SpatRaster object. Like *prjFireInsect*, the matrix object is made up of cell values (1's code for insect damage and 2's code for fire damage) and NA's (non-values). Unlike *prjFireInsect*, it is much smaller with only eight rows and eight columns. 65 | 66 | ```{r demo_1, message=FALSE, warning=FALSE} 67 | demo_mat <- matrix(data = c(1, NA, NA, NA, NA, NA, NA, 1, 68 | 1, NA, NA, NA, 1, NA, 2, 1, 69 | NA, 2, 1, NA, 1, NA, 1, NA, 70 | NA, 1, NA, NA, NA, 2, NA, 1, 71 | 2, 2, NA, 1, 2, NA, NA, 1, 72 | NA, NA, NA, 1, NA, 2, NA, NA, 73 | 1, NA, 1, 1, 2, NA, NA, NA, 74 | 1, NA, NA, NA, NA, NA, NA, 1), 75 | ncol = 8, byrow = TRUE) 76 | print(demo_mat) 77 | ``` 78 | 79 | Printing the matrix object shows how it resembles an 8 by 8 grid. 80 | 81 | Next, convert the matrix object to a raster object using *terra*'s `rast()` function. Calling `print()` shows the properties of a SpatRaster object with no CRS. 82 | 83 | ```{r demo_2, message=FALSE, warning=FALSE} 84 | demo_rast <- rast(demo_mat) 85 | print(demo_rast) 86 | ``` 87 | 88 | The `focal()` function is useful for raster analyses because it allows the user to compute the value of a focal (target) cell based upon the values of nearby cells. This utility has two components: (a) a definition of the window that identifies which cells to include in the neighborhood and (b) the function that evaluates the values of the nearby cells. 89 | 90 | First , we define the "neighborhood", that is, the locations and number of surrounding cells considered as a "neighbor" of the focal cell. We will use a rectangular neighborhood of eight cells so that every cell adjacent to the focal cell is considered a neighbor. This neighborhood is represented by a 3 x 3 matrix; it includes the focal cell in the center and 8 adjacent cells. 91 | 92 | ```{r demo_3, message=FALSE, warning=FALSE} 93 | demo_neigh <- matrix(rep(1,9), ncol = 3) # a 3 x 3 matrix of 1's 94 | print(demo_neigh) 95 | ``` 96 | 97 | We use 1's to represent all the neighboring cells because we want all cells weighted equally, and we also include the focal cell in our analysis. The *demo_neigh* matrix is passed to the `focal()` command below following "w = " argument below; the "w" is a reference to "moving window", a synonym for neighborhood. 98 | 99 | The options "expand = TRUE" and "fillValue = 0" are used because we want to consider **all** cells in *demo_rast*, even the "edge" cells that do not have a complete neighborhood of cells. In the case of edges, the "missing" neighbors will be considered zeros. 100 | 101 | The function used to evaluate cells in the neighborhood is set by "fun = 'max'", which tells R to find the maximum value for all cells in the window. The "na.rm = TRUE" specifies that NA cells in the neighborhood will be skipped. The "na.policy='omit'" tells `focal()` to not to compute values for focal cells that are NA. 102 | 103 | ```{r demo_4, message=FALSE, warning=FALSE} 104 | demo_foc <- focal(x=demo_rast, w = demo_neigh, fun = 'max', na.rm=TRUE, 105 | na.policy='omit', expand = TRUE, fillValue = 0) 106 | print(matrix(demo_foc[], ncol=8)) 107 | ``` 108 | 109 | Compare the *demo_foc* output with *demo_mat*. If a focal cell was 1, but adjacent to a 2, the focal cell became a 2. A focal cell that was a 2 remains a 2 because 2 is the maximum value of neighboring cells in *demo_rast*. 110 | 111 | Suppose we want to identify the cells in *demo_rast* that are 1's having at least one 2 in its neighborhood. We can draw on the information held in both *demo_rast* and *demo_foc*. First, combine the two rasters using raster algebra (i.e., adding the two SpatRaster objects). 112 | 113 | ```{r demo_5, message=FALSE, warning=FALSE} 114 | demo_temp <- demo_rast + demo_foc # adds values in the two rasters, cell by cell 115 | print(matrix(demo_temp[], ncol=8)) 116 | ``` 117 | 118 | Every cell that was a 1 in *demo_rast* but changed to a 2 in *demo_foc* is now a 3 (e.g., *demo_rast* = 1 and *demo_foc* = 2; therefore, *demo_rast* + *demo_foc* = 1 + 2 = 3). A cell that was a 2 in *demo_rast* and stayed a 2 in *demo_foc* is now a 4. So, the cells with a 3 in *demo_tmp* are the ones in the *demo_rast* cells with value 1 and at least one 2 in their neighborhood. 119 | 120 | We will use *terra*'s `app()`function (apply) to reclassify the values of *demo_temp* to get the final values we'd like to see represented. In the code below, we tell R that if a *demo_temp* value is 3, change it to a 1, and save every other value as NA. 121 | 122 | ```{r demo_6, message=FALSE, warning=FALSE} 123 | demo_tar <- app(demo_temp, 124 | fun = function(x) {ifelse( x == 3, 1, NA) } ) 125 | print(matrix(demo_tar[], ncol=8)) 126 | ``` 127 | 128 | This raster is our final product for the demonstration. The cells with value of 1 indicate 1-cells in *demo_rast* that were adjacent to a 2. We will apply this same logic to the "real" data. 129 | 130 | # Perform a Focal Analysis: identify locations adjacent fire and insect disturbances 131 | 132 | Using *prjFireInsect*, we will determine which forest locations were damaged by insects and were adjacent to forest locations damaged by fire. In other words, we will locate cells with the value 1 that are next to cells with the value 2. As explained in the main part of the tutorial, there are no cells that had both *fire* and *insect* disturbance, which is why we will use `focal()` here. 133 | 134 | The code below is the same as demonstrated above, but it uses a much larger SpatRaster object, *prjFireInsect*. 135 | The code runs the `focal()` analysis to create *focalFireInsect*. Then, that raster is reclassified to create *tarFireInsect*. 136 | 137 | ```{r focal_FI, message=FALSE, warning=FALSE} 138 | # this could take several minutes to run; use saved file if available. 139 | if(file.exists("./data/focalFireInsect.Rds")) { 140 | focalFireInsect <- readRDS("./data/focalFireInsect.rds") 141 | }else{ 142 | fireinsect_neigh <- matrix(rep(1,9), ncol = 3) 143 | focalFireInsect <- focal(prjFireInsect, w = fireinsect_neigh, 144 | fun = 'max', na.rm=TRUE, na.policy='omit', 145 | expand = TRUE, fillValue = 0) 146 | } 147 | print(focalFireInsect) 148 | ``` 149 | 150 | ```{r tarFI_map, message=FALSE, warning=FALSE, results="hide"} 151 | # this could take a few minutes to run 152 | temp <- focalFireInsect + prjFireInsect 153 | tarFireInsect <- app(temp, fun = function(x) {ifelse( x == 3, 1, NA) } ) 154 | print(tarFireInsect) 155 | cat("\nSummary\n", summary(tarFireInsect[]), "\n") 156 | rm(temp) # clean up 157 | ``` 158 | 159 | Notice that *tarFireInsect* has only the value 1 and NA's. The 1's identify the cells that meet the criterion: insect damage adjacent to at least one cell with fire damage. 160 | 161 | 162 | # Get Cell Coordinates 163 | 164 | > *Functions featured in this section:* 165 | > **terra::xyFromCell** 166 | > gets coordinates of the center of raster cells for a SpatRaster object 167 | 168 | In this section, we will get the geographic coordinates of the cells that were identified in the last section. First, we create a vector that stores the index of *tarFireInsect* cells that are 1. That is, we store the matrix or grid "locations" of the values across the extent of the SpatRaster object, as if counting from one to the total number of cells across the raster grid. The cell index number starts with 1 in upper left corner of raster grid. Index numbers increase from left to right across the row, and then from top to bottom. The cell index at the bottom right corner is equal to `ncell(tarFireInsect)`, the total number of cells in the raster layer. 169 | 170 | ```{r get_coord_1, message=FALSE, warning=FALSE} 171 | val_tar <- which(tarFireInsect[]==1) # returns index of cells with value of 1 172 | cat("Number of 1-cells: ", length(val_tar), "\n\n") # print number of cells with value of 1 173 | cat("First six index values: ", head(val_tar) ) 174 | ``` 175 | 176 | There are 11,487 cells that have value of 1. We use the function `head()` to view the first six values of *val_tar*. For example, the output tells us that the cell at position 18,468,526 of the "grid" has the value 1 177 | 178 | Using the cell "locations" provided by *val_tar* and the `xyFromCell()` function, we can extract the coordinates of those cells. The function outputs a matrix object, *loc_tarFireInsect*, and we name its columns. Because the CRS of this SpatRaster uses geographic coordinates, the x,y coordinates will be in longitude, latitude. 179 | 180 | ```{r get_coord_2, message=FALSE, warning=FALSE} 181 | loc_tarFireInsect <- xyFromCell(tarFireInsect, val_tar) 182 | colnames(loc_tarFireInsect) <- c("lon","lat") 183 | head(loc_tarFireInsect) 184 | ``` 185 | 186 | The first six rows of *loc_traFireInsect* shows the longitude and latitude of cells that have the value 1. In other words, we now know the exact locations of forested areas throughout Idaho, Montana, and Wyoming that had insect damage **and** were adjacent to a location that had fire damage. 187 | 188 | We save these coordinates in a comma-separated values (CSV) format for future use. If we wanted to, we could visit these locations in person! Try looking up one of these locations using a web-based map, like Google Maps. 189 | 190 | ```{r save_coord, message=FALSE, warning=FALSE, eval=FALSE} 191 | write.csv(loc_tarFireInsect, file = "loc_tarFireInsect.csv", row.names = FALSE) 192 | ``` 193 | 194 | 195 | # Plot Fire-Insect locations 196 | 197 | > *Functions featured in this section:* 198 | > **sf::st_as_sf** 199 | > gets coordinates of the center of raster cells for a SpatRaster object 200 | 201 | Using the coordinate, we can plot these locations on a dot map. The `st_as_sf()` function, as used below, creates a simple feature point object from a data frame holding point coordinates. The "coords=c(1,2)" and "crs =" arguments specify which columns of the data frame hold the x,y coordinates as well as their CRS. 202 | 203 | ```{r plot_FI_loc, message=FALSE, warning=FALSE} 204 | # create simple feature point object 205 | fi_pt <- st_as_sf(as.data.frame(loc_tarFireInsect), 206 | coords=c(1,2), crs = "+proj=longlat +datum=WGS84") 207 | plot(threeStates$geometry, main="Locations with fire-insect damage") 208 | plot(fi_pt, add=T, col="blue", cex=0.7) 209 | 210 | ``` 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /edwebinar_mar19_ornldaac_supplemental.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Geospatial Analysis in R" 3 | subtitle: "Supplemental Tutorial: Neighborhood analysis" 4 | author: 'Presented by the ORNL DAAC https://daac.ornl.gov' 5 | date: "January 9, 2024" 6 | output: 7 | html_document: 8 | keep_md: yes 9 | number_sections: yes 10 | toc: yes 11 | html_notebook: 12 | number_sections: yes 13 | toc: yes 14 | editor_options: 15 | chunk_output_type: inline 16 | --- 17 | *** 18 | 19 | 20 | 21 | This supplemental tutorial will demonstrate how perform a **neighborhood analysis** to identify locations disturbed by insects are adjacent to fire disturbance. The tutorial will use two raster maps created during the main tutorial. 22 | 23 | # Install and Load Packages 24 | 25 | As with the main portion of the tutorial, the *terra* and *sf* must be installed along with their dependencies. 26 | 27 | 28 | ```r 29 | install.packages("terra", dependencies = TRUE) 30 | install.packages("sf", dependencies = TRUE) 31 | ``` 32 | 33 | Load the *terra* and *sf* packages. 34 | 35 | 36 | ```r 37 | library(terra) # provides most functions 38 | library(sf) # provides function for saving files 39 | ``` 40 | 41 | # Load Data 42 | 43 | > *Functions featured in this section:* 44 | > **sf::st_read()** 45 | > reads a shapefile and loads it as a simple feature object 46 | 47 | We must load two objects, *threeStates* and *prjFireInsect*, that were created from manipulations described in the main part of the tutorial and saved in the "./data" folder. To load a shapefile, use the `st_read()` function from the *sf* package. We will create a third object, *focalFireInsect*, later in this supplemental tutorial. 48 | 49 | 50 | ```r 51 | threeStates <- st_read(dsn="./data/threeStates.shp", quiet=TRUE) 52 | prjFireInsect <- rast("./data/prjFireInsect.tif") 53 | ``` 54 | 55 | # Perform a Focal Analysis: a simple demonstration 56 | 57 | > *Functions featured in this section:* 58 | > **terra::focal()** 59 | > calculates values from the neighborhood of cells ("moving window") around a target cell using a matrix 60 | 61 | A neighborhood analysis computes the value of a single focal cell using information from nearby cells. Imagine a donut (i.e., an annulus) where the focal cell is in the donut hole and values from the ring of surrounding cells are used to derive a value for the focal cell. 62 | 63 | Users can specify neighborhoods that best fit the spatial processes under analysis. The neighborhood may or may not include the focal cell, and the width of the band of surrounding cells can be specified to alternatively enlarge or shrink the neighborhood. Moreover, the neighborhood does not have to take the form of ring. It can be rectangular and/or expanded on one side but not the others. For example, imagine estimating air pollution values for a cells in a landscape with westerly winds. The analysis could use a triangular neighborhood that fans out to the west of the focal cell to use values from upwind cells while ignoring downwind values to the east. All cells within the neighborhood can be treated equally. Alternatively, the relative influence of cells can be weighted by their distance from the focal cell. 64 | 65 | The `focal()` function is used in the *terra* package for neighborhood analysis. We begin with a demonstration of how the `focal()` behaves using a small SpatRaster object. 66 | 67 | First, we create a matrix object similar to the raster grid of a SpatRaster object. Like *prjFireInsect*, the matrix object is made up of cell values (1's code for insect damage and 2's code for fire damage) and NA's (non-values). Unlike *prjFireInsect*, it is much smaller with only eight rows and eight columns. 68 | 69 | 70 | ```r 71 | demo_mat <- matrix(data = c(1, NA, NA, NA, NA, NA, NA, 1, 72 | 1, NA, NA, NA, 1, NA, 2, 1, 73 | NA, 2, 1, NA, 1, NA, 1, NA, 74 | NA, 1, NA, NA, NA, 2, NA, 1, 75 | 2, 2, NA, 1, 2, NA, NA, 1, 76 | NA, NA, NA, 1, NA, 2, NA, NA, 77 | 1, NA, 1, 1, 2, NA, NA, NA, 78 | 1, NA, NA, NA, NA, NA, NA, 1), 79 | ncol = 8, byrow = TRUE) 80 | print(demo_mat) 81 | ``` 82 | 83 | ``` 84 | ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] 85 | ## [1,] 1 NA NA NA NA NA NA 1 86 | ## [2,] 1 NA NA NA 1 NA 2 1 87 | ## [3,] NA 2 1 NA 1 NA 1 NA 88 | ## [4,] NA 1 NA NA NA 2 NA 1 89 | ## [5,] 2 2 NA 1 2 NA NA 1 90 | ## [6,] NA NA NA 1 NA 2 NA NA 91 | ## [7,] 1 NA 1 1 2 NA NA NA 92 | ## [8,] 1 NA NA NA NA NA NA 1 93 | ``` 94 | 95 | Printing the matrix object shows how it resembles an 8 by 8 grid. 96 | 97 | Next, convert the matrix object to a raster object using *terra*'s `rast()` function. Calling `print()` shows the properties of a SpatRaster object with no CRS. 98 | 99 | 100 | ```r 101 | demo_rast <- rast(demo_mat) 102 | print(demo_rast) 103 | ``` 104 | 105 | ``` 106 | ## class : SpatRaster 107 | ## dimensions : 8, 8, 1 (nrow, ncol, nlyr) 108 | ## resolution : 1, 1 (x, y) 109 | ## extent : 0, 8, 0, 8 (xmin, xmax, ymin, ymax) 110 | ## coord. ref. : 111 | ## source(s) : memory 112 | ## name : lyr.1 113 | ## min value : 1 114 | ## max value : 2 115 | ``` 116 | 117 | The `focal()` function is useful for raster analyses because it allows the user to compute the value of a focal (target) cell based upon the values of nearby cells. This utility has two components: (a) a definition of the window that identifies which cells to include in the neighborhood and (b) the function that evaluates the values of the nearby cells. 118 | 119 | First , we define the "neighborhood", that is, the locations and number of surrounding cells considered as a "neighbor" of the focal cell. We will use a rectangular neighborhood of eight cells so that every cell adjacent to the focal cell is considered a neighbor. This neighborhood is represented by a 3 x 3 matrix; it includes the focal cell in the center and 8 adjacent cells. 120 | 121 | 122 | ```r 123 | demo_neigh <- matrix(rep(1,9), ncol = 3) # a 3 x 3 matrix of 1's 124 | print(demo_neigh) 125 | ``` 126 | 127 | ``` 128 | ## [,1] [,2] [,3] 129 | ## [1,] 1 1 1 130 | ## [2,] 1 1 1 131 | ## [3,] 1 1 1 132 | ``` 133 | 134 | We use 1's to represent all the neighboring cells because we want all cells weighted equally, and we also include the focal cell in our analysis. The *demo_neigh* matrix is passed to the `focal()` command below following "w = " argument below; the "w" is a reference to "moving window", a synonym for neighborhood. 135 | 136 | The options "expand = TRUE" and "fillValue = 0" are used because we want to consider **all** cells in *demo_rast*, even the "edge" cells that do not have a complete neighborhood of cells. In the case of edges, the "missing" neighbors will be considered zeros. 137 | 138 | The function used to evaluate cells in the neighborhood is set by "fun = 'max'", which tells R to find the maximum value for all cells in the window. The "na.rm = TRUE" specifies that NA cells in the neighborhood will be skipped. The "na.policy='omit'" tells `focal()` to not to compute values for focal cells that are NA. 139 | 140 | 141 | ```r 142 | demo_foc <- focal(x=demo_rast, w = demo_neigh, fun = 'max', na.rm=TRUE, 143 | na.policy='omit', expand = TRUE, fillValue = 0) 144 | print(matrix(demo_foc[], ncol=8)) 145 | ``` 146 | 147 | ``` 148 | ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] 149 | ## [1,] 1 2 NA NA 2 NA 1 1 150 | ## [2,] NA NA 2 2 2 NA NA NA 151 | ## [3,] NA NA 2 NA NA NA 1 NA 152 | ## [4,] NA NA NA NA 2 2 2 NA 153 | ## [5,] NA 1 2 NA 2 NA 2 NA 154 | ## [6,] NA NA NA 2 NA 2 NA NA 155 | ## [7,] NA 2 2 NA NA NA NA NA 156 | ## [8,] 2 2 NA 1 1 NA NA 1 157 | ``` 158 | 159 | Compare the *demo_foc* output with *demo_mat*. If a focal cell was 1, but adjacent to a 2, the focal cell became a 2. A focal cell that was a 2 remains a 2 because 2 is the maximum value of neighboring cells in *demo_rast*. 160 | 161 | Suppose we want to identify the cells in *demo_rast* that are 1's having at least one 2 in its neighborhood. We can draw on the information held in both *demo_rast* and *demo_foc*. First, combine the two rasters using raster algebra (i.e., adding the two SpatRaster objects). 162 | 163 | 164 | ```r 165 | demo_temp <- demo_rast + demo_foc # adds values in the two rasters, cell by cell 166 | print(matrix(demo_temp[], ncol=8)) 167 | ``` 168 | 169 | ``` 170 | ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] 171 | ## [1,] 2 3 NA NA 4 NA 2 2 172 | ## [2,] NA NA 4 3 4 NA NA NA 173 | ## [3,] NA NA 3 NA NA NA 2 NA 174 | ## [4,] NA NA NA NA 3 3 3 NA 175 | ## [5,] NA 2 3 NA 4 NA 4 NA 176 | ## [6,] NA NA NA 4 NA 4 NA NA 177 | ## [7,] NA 4 3 NA NA NA NA NA 178 | ## [8,] 3 3 NA 2 2 NA NA 2 179 | ``` 180 | 181 | Every cell that was a 1 in *demo_rast* but changed to a 2 in *demo_foc* is now a 3 (e.g., *demo_rast* = 1 and *demo_foc* = 2; therefore, *demo_rast* + *demo_foc* = 1 + 2 = 3). A cell that was a 2 in *demo_rast* and stayed a 2 in *demo_foc* is now a 4. So, the cells with a 3 in *demo_tmp* are the ones in the *demo_rast* cells with value 1 and at least one 2 in their neighborhood. 182 | 183 | We will use *terra*'s `app()`function (apply) to reclassify the values of *demo_temp* to get the final values we'd like to see represented. In the code below, we tell R that if a *demo_temp* value is 3, change it to a 1, and save every other value as NA. 184 | 185 | 186 | ```r 187 | demo_tar <- app(demo_temp, 188 | fun = function(x) {ifelse( x == 3, 1, NA) } ) 189 | print(matrix(demo_tar[], ncol=8)) 190 | ``` 191 | 192 | ``` 193 | ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] 194 | ## [1,] NA 1 NA NA NA NA NA NA 195 | ## [2,] NA NA NA 1 NA NA NA NA 196 | ## [3,] NA NA 1 NA NA NA NA NA 197 | ## [4,] NA NA NA NA 1 1 1 NA 198 | ## [5,] NA NA 1 NA NA NA NA NA 199 | ## [6,] NA NA NA NA NA NA NA NA 200 | ## [7,] NA NA 1 NA NA NA NA NA 201 | ## [8,] 1 1 NA NA NA NA NA NA 202 | ``` 203 | 204 | This raster is our final product for the demonstration. The cells with value of 1 indicate 1-cells in *demo_rast* that were adjacent to a 2. We will apply this same logic to the "real" data. 205 | 206 | # Perform a Focal Analysis: identify locations adjacent fire and insect disturbances 207 | 208 | Using *prjFireInsect*, we will determine which forest locations were damaged by insects and were adjacent to forest locations damaged by fire. In other words, we will locate cells with the value 1 that are next to cells with the value 2. As explained in the main part of the tutorial, there are no cells that had both *fire* and *insect* disturbance, which is why we will use `focal()` here. 209 | 210 | The code below is the same as demonstrated above, but it uses a much larger SpatRaster object, *prjFireInsect*. 211 | The code runs the `focal()` analysis to create *focalFireInsect*. Then, that raster is reclassified to create *tarFireInsect*. 212 | 213 | 214 | ```r 215 | # this could take several minutes to run; use saved file if available. 216 | if(file.exists("./data/focalFireInsect.Rds")) { 217 | focalFireInsect <- readRDS("./data/focalFireInsect.rds") 218 | }else{ 219 | fireinsect_neigh <- matrix(rep(1,9), ncol = 3) 220 | focalFireInsect <- focal(prjFireInsect, w = fireinsect_neigh, 221 | fun = 'max', na.rm=TRUE, na.policy='omit', 222 | expand = TRUE, fillValue = 0) 223 | } 224 | print(focalFireInsect) 225 | ``` 226 | 227 | ``` 228 | ## class : SpatRaster 229 | ## dimensions : 9169, 13781, 1 (nrow, ncol, nlyr) 230 | ## resolution : 0.001169079, 0.001169079 (x, y) 231 | ## extent : -119.2604, -103.1493, 39.61248, 50.33177 (xmin, xmax, ymin, ymax) 232 | ## coord. ref. : lon/lat WGS 84 (EPSG:4326) 233 | ## source(s) : memory 234 | ## varname : prjFireInsect 235 | ## name : focal_max 236 | ## min value : 1 237 | ## max value : 2 238 | ``` 239 | 240 | 241 | ```r 242 | # this could take a few minutes to run 243 | temp <- focalFireInsect + prjFireInsect 244 | tarFireInsect <- app(temp, fun = function(x) {ifelse( x == 3, 1, NA) } ) 245 | print(tarFireInsect) 246 | cat("\nSummary\n", summary(tarFireInsect[]), "\n") 247 | rm(temp) # clean up 248 | ``` 249 | 250 | Notice that *tarFireInsect* has only the value 1 and NA's. The 1's identify the cells that meet the criterion: insect damage adjacent to at least one cell with fire damage. 251 | 252 | 253 | # Get Cell Coordinates 254 | 255 | > *Functions featured in this section:* 256 | > **terra::xyFromCell** 257 | > gets coordinates of the center of raster cells for a SpatRaster object 258 | 259 | In this section, we will get the geographic coordinates of the cells that were identified in the last section. First, we create a vector that stores the index of *tarFireInsect* cells that are 1. That is, we store the matrix or grid "locations" of the values across the extent of the SpatRaster object, as if counting from one to the total number of cells across the raster grid. The cell index number starts with 1 in upper left corner of raster grid. Index numbers increase from left to right across the row, and then from top to bottom. The cell index at the bottom right corner is equal to `ncell(tarFireInsect)`, the total number of cells in the raster layer. 260 | 261 | 262 | ```r 263 | val_tar <- which(tarFireInsect[]==1) # returns index of cells with value of 1 264 | cat("Number of 1-cells: ", length(val_tar), "\n\n") # print number of cells with value of 1 265 | ``` 266 | 267 | ``` 268 | ## Number of 1-cells: 11487 269 | ``` 270 | 271 | ```r 272 | cat("First six index values: ", head(val_tar) ) 273 | ``` 274 | 275 | ``` 276 | ## First six index values: 18468526 18761010 18774794 18788574 18940070 18953851 277 | ``` 278 | 279 | There are 11,487 cells that have value of 1. We use the function `head()` to view the first six values of *val_tar*. For example, the output tells us that the cell at position 18,468,526 of the "grid" has the value 1 280 | 281 | Using the cell "locations" provided by *val_tar* and the `xyFromCell()` function, we can extract the coordinates of those cells. The function outputs a matrix object, *loc_tarFireInsect*, and we name its columns. Because the CRS of this SpatRaster uses geographic coordinates, the x,y coordinates will be in longitude, latitude. 282 | 283 | 284 | ```r 285 | loc_tarFireInsect <- xyFromCell(tarFireInsect, val_tar) 286 | colnames(loc_tarFireInsect) <- c("lon","lat") 287 | head(loc_tarFireInsect) 288 | ``` 289 | 290 | ``` 291 | ## lon lat 292 | ## [1,] -116.9392 48.76462 293 | ## [2,] -113.3349 48.74007 294 | ## [3,] -113.3314 48.73890 295 | ## [4,] -113.3326 48.73773 296 | ## [5,] -113.4436 48.72487 297 | ## [6,] -113.4436 48.72370 298 | ``` 299 | 300 | The first six rows of *loc_traFireInsect* shows the longitude and latitude of cells that have the value 1. In other words, we now know the exact locations of forested areas throughout Idaho, Montana, and Wyoming that had insect damage **and** were adjacent to a location that had fire damage. 301 | 302 | We save these coordinates in a comma-separated values (CSV) format for future use. If we wanted to, we could visit these locations in person! Try looking up one of these locations using a web-based map, like Google Maps. 303 | 304 | 305 | ```r 306 | write.csv(loc_tarFireInsect, file = "loc_tarFireInsect.csv", row.names = FALSE) 307 | ``` 308 | 309 | 310 | # Plot Fire-Insect locations 311 | 312 | > *Functions featured in this section:* 313 | > **sf::st_as_sf** 314 | > gets coordinates of the center of raster cells for a SpatRaster object 315 | 316 | Using the coordinate, we can plot these locations on a dot map. The `st_as_sf()` function, as used below, creates a simple feature point object from a data frame holding point coordinates. The "coords=c(1,2)" and "crs =" arguments specify which columns of the data frame hold the x,y coordinates as well as their CRS. 317 | 318 | 319 | ```r 320 | # create simple feature point object 321 | fi_pt <- st_as_sf(as.data.frame(loc_tarFireInsect), 322 | coords=c(1,2), crs = "+proj=longlat +datum=WGS84") 323 | plot(threeStates$geometry, main="Locations with fire-insect damage") 324 | plot(fi_pt, add=T, col="blue", cex=0.7) 325 | ``` 326 | 327 | ![](edwebinar_mar19_ornldaac_supplemental_files/figure-html/plot_FI_loc-1.png) 328 | 329 | 330 | 331 | 332 | -------------------------------------------------------------------------------- /edwebinar_mar19_ornldaac_tutorial.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Geospatial Analysis in R" 3 | author: "ORNL DAAC https://daac.ornl.gov" 4 | date: "January 25, 2024" 5 | output: 6 | pdf_document: 7 | toc: yes 8 | latex_engine: xelatex 9 | html_notebook: 10 | number_sections: yes 11 | toc: yes 12 | html_document: 13 | keep_md: yes 14 | number_sections: yes 15 | toc: yes 16 | header-includes: 17 | - \usepackage{fontspec} 18 | - \setmainfont{Calibri Light} 19 | subtitle: NASA Earthdata Webinar 20 | editor_options: 21 | chunk_output_type: inline 22 | --- 23 | *** 24 | 25 | 26 | 27 | # Install and Load Packages 28 | 29 | 30 | In addition to the built-in functionality of R, we will use four packages throughout this exercise. Packages are a collection of documentation, functions, and other items that someone has created and compiled for others to use in R. Install the packages, as well as their dependencies, using the function `install.packages()`. 31 | 32 | ```{r packages_install, message=FALSE, warning=FALSE, eval=FALSE} 33 | install.packages("terra", dependencies = TRUE) 34 | install.packages("sf", dependencies = TRUE) 35 | install.packages("tigris", dependencies = TRUE) 36 | install.packages("raster", dependencies = TRUE) 37 | ``` 38 | 39 | Most of the functions we will use are from the *terra* and *sf* packages. The *terra*, *sf*, and *stars* (not used here) packages are largely replacing older R packages, such as *raster*, for for managing and manipulating spatial data. However, we will use a *raster* function at the end of this tutorial to export the final object in KML/KMZ format. 40 | 41 | ```{r packages_load, message=FALSE, warning=FALSE, results="hide"} 42 | library(terra) 43 | library(sf) 44 | library(tigris) # provides access to TIGER shapefiles from US Census Bureau 45 | library(raster) 46 | ``` 47 | 48 | For package details, try `help()` (e.g., `help("terra")`), and to view the necessary arguments of a function try `args()` (e.g., `args(cover)`). 49 | 50 | # Load Data 51 | 52 | > *Functions featured in this section:* 53 | > **terra::rast()** 54 | > The `rast()` function from the terra package creates a 'SpatRaster' (spatial raster) object from a file 55 | > **tigris::states()** 56 | > The `states()` function from the tigris package downloads a shapefile of the United States that will be loaded as a SpatialPolygonsDataFrame object 57 | 58 | Two GeoTiff files are needed to complete this tutorial, both from the dataset titled "CMS: Forest Carbon Stocks, Emissions, and Net Flux for the Conterminous US: 2005-2010" and freely available through the ORNL DAAC at 59 | [https://doi.org/10.3334/ORNLDAAC/1313](https://doi.org/10.3334/ORNLDAAC/1313). 60 | 61 | The dataset provides maps of estimated carbon emissions in forests of the conterminous United States for the years 2006-2010. We will use the maps of carbon emissions caused by fire (*GrossEmissions_v101_USA_Fire.tif*) and insect damage (*GrossEmissions_v101_USA_Insect.tif*). These maps are provided at 100-meter spatial resolution in GeoTIFF format using Albers North America projection. Refer to the accompanying "README.md" for instructions on how to download the data. 62 | 63 | To begin, be sure to set your working directory using `setwd()`. The code below assumes that the GeoTIFF files are saved in a folder called 'data' located under the working directory (e.g., "./data/GrossEmissions_v101_USA_Fire.tif"). 64 | 65 | With the `rast()` function, load *GrossEmissions_v101_USA_Fire.tif* and name it *fire*, then load *GrossEmissions_v101_USA_Insect.tif* and name it *insect*. The contents of these two files are stored as SpatRaster objects; *fire* and *insect* are the primary focus of our manipulations throughout this exercise. 66 | 67 | The function `states()` downloads a shapefile of the United States from the United States Census Bureau. It will be stored as a simple feature data frame object named *myStates*. 68 | 69 | ```{r load_data, message=FALSE, warning=FALSE, results="hide"} 70 | fire <- rast("./data/GrossEmissions_v101_USA_Fire.tif") # read GeoTIFF, store as SpatRaster object 71 | insect <- rast("./data/GrossEmissions_v101_USA_Insect.tif") 72 | myStates <- states(cb = TRUE, progress_bar=FALSE) # will download a generalized (1:500k) file 73 | ``` 74 | 75 | # Check the Coordinate Reference System and Plot a Raster 76 | 77 | > *Functions featured in this section:* 78 | > **terra::crs()** 79 | > gets the coordinate reference system of a raster object 80 | 81 | Use `print()` to view details about the internal data structure of the raster object we named *fire*. 82 | 83 | ```{r data_info, message=FALSE, warning=FALSE} 84 | print(fire) 85 | ``` 86 | 87 | The output lists important attributes of *fire*, like its dimensions, resolution, spatial extent, coordinate reference system, and the minimum and maximum values of the cells (i.e., carbon emissions). 88 | 89 | The next two commands retrieve the coordinate reference system (CRS) of *fire* and display the CRS in 'WKT' (Well Known Text) and 'Proj4' formats. 90 | 91 | ```{r data_crs_1, message=FALSE, warning=FALSE} 92 | fire_wkt <- crs(fire) # CRS in WKT format 93 | writeLines(strwrap(fire_wkt, width = 70, exdent = 5)) # needed to control text wrapping 94 | ``` 95 | 96 | ```{r data_crs_2, message=FALSE, warning=FALSE} 97 | fire_prj <- crs(fire, proj=T) # CRS as proj4 string 98 | writeLines(strwrap(fire_prj, width = 70, exdent = 5)) 99 | ``` 100 | In the PROJ.4 representation, the first argument is "+proj=" and defines the projection. "aea" refers to the `NAD83 / Albers NorthAm` projection (EPSG 42303), and "+units=m" tells us that the resolution of the raster object is in meters. Refer to the attributes of *fire* provided by `print()` above. The resolution of the raster is "100, 100 (x, y)" meaning that each cell is 100 meters by 100 meters, or one hectare (ha). 101 | 102 | Use the `plot()` function to make a simple image of *fire* and visualize the carbon emissions from fire damage across the forests of the conterminous United States between 2006 and 2010. According to the documentation for the dataset, gross carbon emissions were measured in megagrams of carbon per year (Mg C/y) per cell. 103 | 104 | ```{r raster_plot_1, message=FALSE, warning=FALSE} 105 | plot(fire, 106 | main = "Gross Carbon Emissions from Fire Damage\n across CONUS Forests (2006-2010)", 107 | xlab = "horizontal extent (m)", 108 | ylab = "vertical extent (m)", 109 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 110 | colNA = "black", 111 | box = FALSE) 112 | ``` 113 | 114 | The spatial extent of the raster object is displayed on the x- and y-axes. All NA cells (i.e., cells that have no values) are colored black for better visualization of fire damage. The legend offers the range of cell values and represents them using a default color theme. Because the extent of this object covers the conterminous US, we cannot see much spatial detail in this figure. 115 | 116 | Let's examine the raster object we named *insect*. The function `crs()` retrieves the CRS information for each raster object. Then, we use `identical()` to determine if *fire* and *insect* have the same CRS. 117 | 118 | ```{r compare_crs, message=FALSE, warning=FALSE} 119 | identical(crs(fire), crs(insect)) 120 | ``` 121 | 122 | "TRUE" indicates that the CRS for the two raster objects have the same CRS. 123 | 124 | Plot *insect* but change the content for the argument "main = ", which defines the main title of the plot. 125 | 126 | ```{r raster_plot_2, message=FALSE, warning=FALSE} 127 | plot(insect, 128 | main = "Gross Carbon Emissions from Insect Damage\n across CONUS Forests (2006-2010)", 129 | xlab = "horizontal extent (m)", 130 | ylab = "vertical extent (m)", 131 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 132 | colNA = "black", 133 | box = FALSE) 134 | ``` 135 | 136 | You can likely imagine an outline of the United States given the distribution of spatial data in the two raster objects. 137 | 138 | # Select Data Within a Region of Interest 139 | 140 | > *Functions featured in this section:* 141 | > **terra::crs()** 142 | > retrieves or sets the CRS for a spatial object 143 | > **sf::st_transform()** 144 | > provides re-projection given a CRS 145 | > **sf::st_bbox()** 146 | > retrieves the bounding box of a simple feature (sf) spatial object 147 | > **terra::crop()** 148 | > returns a geographic subset of an object as specified by an Extent object 149 | > **terra::mask()** 150 | > creates a new raster object with the same values as the input object, except for the cells that are NA in the second object (the 'mask') 151 | 152 | Next, we reduce the spatial extent of *fire* and *insect* to focus on three states in the western US. We can select the states from the *myStates* to create a new feature then use it to crop the *fire* and *insect* rasters. 153 | 154 | First, use `print()` to view details about the internal data structure of the simple feature we named *myStates*. 155 | 156 | ```{r states_1, message=FALSE, warning=FALSE} 157 | print(myStates) 158 | ``` 159 | 160 | *myStates* has 56 features (i.e., polygons) each with 9 attribute fields. This *sf* object can be thought of a dataframe with 56 rows and ten columns (variables or features). The columns include the 9 attribute fields plus a "geometry" column. 161 | 162 | For this exercise, we will focus on carbon emissions for Idaho, Montana, and Wyoming. We can use column referencing and indexing to select all column information contained in *myStates*, but for only three rows (polygons). This code selects the polygons for which the 'NAME' is either Idaho, Montana, or Wyoming. These three polygons are saved in the resultant simple feature *threeStates*. 163 | 164 | ```{r states_2, message=FALSE, warning=FALSE} 165 | threeStates <- myStates[myStates$NAME == "Idaho" | 166 | myStates$NAME == "Montana" | 167 | myStates$NAME == "Wyoming", ] 168 | threeStates <- threeStates[order(threeStates$NAME),] # sort by name: ID, MT, WY 169 | print(threeStates) 170 | ``` 171 | 172 | *threeStates* has only three rows, but the same number of columns as *myStates*. 173 | 174 | What does *threeStates* look like plotted? We'll plot the geometry (i.e., the 10th column) of *threeStates* so we only see the outline. 175 | 176 | ```{r states_3, message=FALSE, warning=FALSE} 177 | plot(threeStates$geometry) 178 | ``` 179 | 180 | We can get the *fire* and *insect* data that occurs "within" *threeStates*. First, we must confirm that the three objects share the same CRS before we can overlay or otherwise combine information from them. Some GIS software, such as QGIS or ArcGIS, can automatically combine data layers having different CRS, but that is not a default capability with R. 181 | 182 | ```{r states_4, message=FALSE, warning=FALSE} 183 | identical(crs(fire), crs(threeStates)) 184 | ``` 185 | 186 | "FALSE" indicates that *threeStates* does not have the same CRS as *fire*, so we will make a new version of these state polygons that has *fire*'s CRS. The `st_transform()` from the *sf* package will work. This function uses `crs=crs(fire)` to set the CRS of the new simple feature object to the same projection as *fire*. 187 | 188 | ```{r states_5, message=FALSE, warning=FALSE} 189 | transStates <- st_transform(threeStates, crs=crs(fire) ) 190 | plot(transStates$geometry) # draw outline of the state polygons 191 | ``` 192 | 193 | Plotting the geometry of the new object *transStates* shows that the projection has changed. Notice how the orientation of the polygons has shifted to match the *NAD83 / Albers NorthAm projection*. 194 | 195 | Now that our objects share a CRS, we will compare the extent of *fire* and *transStates*. Use the `st_bbox()` function to view the bounding (i.e., bounding box) of *transStates* and *fire*. 196 | 197 | ```{r states_6, message=FALSE, warning=FALSE} 198 | cat("fire extent\n"); st_bbox(fire) 199 | cat("\ntransStates extent\n"); st_bbox(transStates) 200 | ``` 201 | 202 | To visualize the bounding boxes, use the `ext()` (i.e., extent) function with `plot()`. 203 | 204 | ```{r states_7, message=FALSE, warning=FALSE} 205 | plot(ext(fire), col='grey') # bounding box of fire (grey) 206 | plot(ext(transStates), col='blue', add=T) # bounding box of tranStates (blue) 207 | ``` 208 | 209 | The raster *fire* has a much larger extent than *transStates* because *fire* covers the entire conterminous US. 210 | 211 | Reducing the extent of the *fire* and *insect* rasters will greatly speed up processing and reduce the size of saved files. The `crop()` function will serve this purpose. Cropping will create a geographic subset of *fire* and *insect* as specified by the extent of *transStates*. We prepend "crop" to the names of the new raster objects to reflect this manipulation. 212 | 213 | ```{r croppping, message=FALSE, warning=FALSE, results="hide"} 214 | # this will take a minute to run 215 | cropFire <- crop(fire, transStates) # crop(raster object, extent object) 216 | cropInsect <- crop(insect, transStates) 217 | ``` 218 | 219 | Now when we plot *cropFire* and *cropInsect*, we will also plot *transStates* "on top" to envision how carbon emissions are distributed across the three states. 220 | 221 | ```{r crop_plot, message=FALSE, warning=FALSE} 222 | par(mfrow=c(1,1)) 223 | mar.val <- c(3.1, 3.1, 3.1, 3.1) # set plot margin values for maps: bot,lft,top,rgt 224 | plot(cropFire, 225 | main = "Gross Carbon Emissions from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 226 | xlab = "horizontal extent (m)", 227 | ylab = "vertical extent (m)", 228 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 229 | colNA = "black", 230 | mar=mar.val, 231 | box = FALSE) 232 | plot(transStates$geometry, 233 | border = "white", 234 | add = TRUE) 235 | plot(cropInsect, 236 | main = "Gross Carbon Emissions from Insect Damage\n across ID, MT, WY Forests (2005-2010)", 237 | # cex.main=0.85, 238 | xlab = "horizontal extent (m)", 239 | ylab = "vertical extent (m)", 240 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 241 | colNA = "black", 242 | mar=mar.val, 243 | box = FALSE) 244 | plot(transStates$geometry, 245 | border = "white", 246 | add = TRUE) 247 | ``` 248 | 249 | If you look closely at the cells "outside" the boundary of the *transStates* polygons, you can still see cell values. That's because `crop()` changed the extent of the two raster objects to match that of the simple feature object, but `crop()` did not change cells outside of the polygon boundaries. 250 | 251 | To remove those extraneous cell values, use the `mask()` function to create two new rasters, one for fire damage and one for insect damage. The *terra* function `mask()` will convert raster cells outside of the state polygons to NA. **Note:** You can use `mask()` or `crop()` in either order. 252 | 253 | ```{r masking, message=FALSE, warning=FALSE, results="hide"} 254 | # this will take a couple of minutes to run 255 | maskFire <- mask(cropFire, transStates) # mask(raster object, mask object) 256 | maskInsect <- mask(cropInsect, transStates) 257 | ``` 258 | 259 | Plot *maskFire* and *maskInsect*. 260 | 261 | ```{r mask_plot, message=FALSE, warning=FALSE} 262 | plot(maskFire, 263 | main = "Gross Carbon Emissions from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 264 | xlab = "horizontal extent (m)", 265 | ylab = "vertical extent (m)", 266 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 267 | colNA = "black", 268 | mar=mar.val, 269 | box = FALSE) 270 | plot(transStates$geometry, 271 | border = "white", 272 | add = TRUE) 273 | plot(maskInsect, 274 | main = "Gross Carbon Emissions from Insect Damage\n across ID, MT, WY Forests (2005-2010)", 275 | xlab = "horizontal extent (m)", 276 | ylab = "vertical extent (m)", 277 | plg=list( title="\\n Mg C/ha/yr", size=c(0.7,1) ), 278 | colNA = "black", 279 | mar=mar.val, 280 | box = FALSE) 281 | plot(transStates$geometry, 282 | border = "white", 283 | add = TRUE) 284 | ``` 285 | 286 | These plots demonstrate that all cell values outside of the *transStates* polygons are now NA. 287 | 288 | # Examine Summaries of Raster Values 289 | 290 | > *Functions featured in this section:* 291 | > **terra::extract()** 292 | > extracts values from a raster object at the locations of other spatial data 293 | 294 | In this section, we will compare the three states by their carbon emissions from fire damage. 295 | 296 | We will use *terra*'s `extract()` function to collect the cell values of *maskFire* where the *transStates* simple feature object overlaps the raster object. We will use `summary()` to examine the distribution of cell values that we collect. 297 | 298 | ```{r fire_summary_1, message=FALSE, warning=FALSE} 299 | # this can take up to an hour to run, so load a saved copy for the demonstration 300 | if(file.exists("./data/val_fireStates.Rds")) { 301 | val_fireStates <- readRDS("./data/val_fireStates.rds") 302 | summary(val_fireStates) 303 | }else{ 304 | val_fireStates <- extract(maskFire, transStates, df = TRUE) # extract(raster object, extent object) 305 | summary(val_fireStates) 306 | # saveRDS(val_fireStates, "./data/val_fireStates.Rds") # uncomment this line to save to file 307 | } 308 | ``` 309 | 310 | There are two columns in this summary of *val_fireStates*. One is ID, which corresponds with the three states; 1 = Idaho, 2 = Montana, and 3 = Wyoming. The ID value corresponds to the row order of the polygons. That is why it was important to sort *threeStates* object by 'NAME' in the code above. Still, it is a good idea to verify this order. 311 | 312 | ```{r fire_summary_2, message=FALSE, warning=FALSE} 313 | cbind("ID"=1:3, "state"=transStates$NAME) 314 | ``` 315 | 316 | The second column is a summary of all cell values across those three states. On average (mean), 56 megagrams of carbon per ha per year are a result of forest destruction by fire damage for all states combined. 317 | 318 | To look at the summary for cell values by state, we will use `subset()` to split *val_fireStates* into three data frames, one for each state. In the code below, we subset *val_fireStates* so that only the rows with ID == "1" (representing Idaho) will be returned. We name the new object with the prefix "temp" and suffix "id" (Idaho). 319 | 320 | ```{r fire_summary_3, message=FALSE, warning=FALSE} 321 | temp_val_id <- subset(val_fireStates, subset = ID %in% 1) # Idaho values 322 | summary(temp_val_id) 323 | ``` 324 | 325 | The summary demonstrates that there is now only a single value in the ID column, and that the distribution of cell values has changed. This resultant data frame object is quite large and has more information than we need. We need only the second column and don't care for the large number of NA's. 326 | 327 | To create a new vector of cells values from *temp_val_id*, use the `is.na()` function to eliminate the rows with NA cells and select the second column holding emissions values. 328 | 329 | ```{r fire_summary_4, message=FALSE, warning=FALSE} 330 | val_id <- temp_val_id[!is.na(temp_val_id$GrossEmissions_v101_USA_Fire),2] 331 | summary(val_id) 332 | ``` 333 | 334 | The resultant object, *val_id*, is a vector object (a single column of numbers) with no NA's. 335 | 336 | We will do the same with *val_fire* for the states Montana and Wyoming. 337 | 338 | ```{r fire_summary_5, message=FALSE, warning=FALSE} 339 | temp_val_mt <- subset(val_fireStates, subset = ID %in% 2) # Montana 340 | val_mt <- temp_val_mt[!is.na(temp_val_mt$GrossEmissions_v101_USA_Fire), 2] 341 | temp_val_wy <- subset(val_fireStates, subset = ID %in% 3) # Wyoming 342 | val_wy <- temp_val_wy[!is.na(temp_val_wy$GrossEmissions_v101_USA_Fire), 2] 343 | rm(temp_val_id, temp_val_mt, temp_val_wy) # clean up 344 | ``` 345 | 346 | What's the total carbon emissions from fire for each state and the range of values within each state for the period 2006 to 2010? 347 | 348 | ```{r fire_summary_6, message=FALSE, warning=FALSE} 349 | cat("Number of cells burned (ha)\n") # divide totals by 1000 to improve ability to compare 350 | cat("Idaho: ", length(val_id), " Montana: ", length(val_mt), 351 | " Wyoming: ", length(val_wy),"\n") 352 | 353 | cat("\nTotal emissions (/1000)\n") # divide totals by 1000 to improve ability to compare 354 | cat("Idaho: ", sum(val_id)/1000, " Montana: ", sum(val_mt)/1000, 355 | " Wyoming: ", sum(val_wy)/1000,"\n") 356 | 357 | cat("\nDistribution of cell values\n") 358 | cat("Idaho\n"); summary(val_id); cat("Montana\n"); summary(val_mt); cat("Wyoming\n"); summary(val_wy) 359 | ``` 360 | 361 | Idaho has the greatest number of cells burned, the highest total carbon emissions, and the maximum gross carbon emissions from a single cell. In contrast, the largest mean emissions from cells occurred in Montana. Wyoming had the least amount of carbon emissions due to fire. 362 | 363 | In addition to using `summary()`, we can create graphs to visualize carbon emissions from fire damage within each of the three states. The function `hist()` plots the frequency of cell values. We will set some arguments of the plot so that we can compare carbon emissions across all three states. 364 | 365 | ```{r fire_summary_7, message=FALSE, warning=FALSE} 366 | par(mfrow=c(2,2), mar=c(3,3,3,3)) 367 | hist(val_id, 368 | main = "Idaho", 369 | ylab = "number of cells", 370 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 371 | ylim = c(0, 120000), # same y-axis limit for all three states 372 | xlim = c(0, 350)) # same x-axis limit for all three states 373 | hist(val_mt, 374 | main = "Montana", 375 | ylab = "number of cells", 376 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 377 | ylim = c(0, 120000), 378 | xlim = c(0, 350)) 379 | hist(val_wy, 380 | main = "Wyoming", 381 | ylab = "number of cells", 382 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 383 | ylim = c(0, 120000), 384 | xlim = c(0, 350)) 385 | ``` 386 | 387 | The histograms show the number of times (on the y-axis) each unique cell value (on the x-axis) occurs in each state. In other words, these plots illustrate the variation in carbon emissions from fire damage within the three different states and provide a visual display of the numerical summaries above. 388 | 389 | # Reclassify Raster Values 390 | 391 | > *Functions featured in this section:* 392 | > **reclassify()** 393 | > reclassifies groups of values of a raster object to other values 394 | > **calc()** 395 | > calculates values for a new raster object from another raster object using a formula 396 | 397 | Now we are going to change the values of our two raster objects using different methods. The goal is to generate a single map that shows the cells where fire and insect disturbances occurred. Fire and insect maps are reclassified individually then combined. 398 | 399 | Beginning with *maskFire*, we convert all cells with values >0 to the value 2 and save the new raster object to *reclassFire* . 400 | 401 | ```{r reclass_1, message=FALSE, warning=FALSE, results="hide"} 402 | reclassFire <- maskFire 403 | reclassFire[reclassFire >0] <- 2 404 | ``` 405 | 406 | Check that our reclassification of *maskFire* worked as expected using `summary()`. The pair of square brackets ("[]") after the raster name tells the summary command to read all values of the raster. 407 | 408 | ```{r reclass_2, message=FALSE, warning=FALSE} 409 | summary(reclassFire[]) 410 | ``` 411 | 412 | Yes, all values are either 2 or NA. 413 | 414 | All cell values of *reclassFire* should be at the same locations as *maskFire* but with a single value. 415 | 416 | ```{r reclass_3, message=FALSE, warning=FALSE} 417 | plot(reclassFire, 418 | main = "Locations of Forest Disturbance from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 419 | xlab = "horizontal extent (m)", 420 | ylab = "vertical extent (m)", 421 | legend = FALSE, 422 | col = "red", colNA = "black", 423 | mar=c(3.1, 1.1, 2.8, 1.1), # bot,lft,top,rgt 424 | box = FALSE) 425 | plot(transStates$geometry, 426 | border = "white", 427 | add = TRUE) 428 | ``` 429 | 430 | The plot of *reclassFire* now illustrates locations where there were carbon emissions due to forest fire. Notice that we chose a single color to represent the presence of values using the argument "col = "red"". 431 | 432 | Now we will reclassify all values of *maskInsect* that are greater than zero to be 1. Let's check the range of values for this raster. 433 | 434 | ```{r reclass_4, message=FALSE, warning=FALSE, results="hide"} 435 | range(maskInsect[], na.rm=TRUE) 436 | ``` 437 | 438 | This time, we will use the `classify()` function in the *terra* package. This function uses a matrix to identify the target cell values and to what value those cells will change. 439 | 440 | ```{r reclass_5, message=FALSE, warning=FALSE, results="hide"} 441 | reclassInsect <- classify(maskInsect, 442 | rcl = matrix(data = c(0, 285, 1), # c(from value, to value, becomes) 443 | nrow = 1, ncol = 3)) 444 | ``` 445 | 446 | The argument following "rcl =" tells R that values from 1 to 285 should be reclassified as one. Essentially, we are making the presence of insect damage equal one. 447 | 448 | Check the reclassification of *maskInsect* using `summary()`. 449 | 450 | ```{r reclass_6, message=FALSE, warning=FALSE} 451 | summary(reclassInsect[]) 452 | ``` 453 | 454 | All values are 1 or NA. 455 | 456 | Plot *reclassInsect*. All the cell values should be at the same locations as *maskInsect* but will all be the value one. 457 | 458 | ```{r reclass_plot, message=FALSE, warning=FALSE} 459 | plot(reclassInsect, 460 | main = "Locations of Forest Disturbance from Insect Damage\n across ID, MT, WY Forests (2006-2010)", 461 | xlab = "horizontal extent (m)", 462 | ylab = "vertical extent (m)", 463 | legend = FALSE, 464 | col = "dark green", 465 | colNA = "black", 466 | mar=c(3.1, 1.1, 3.1, 1.1), 467 | box = FALSE) 468 | plot(transStates$geometry, 469 | border = "white", 470 | add = TRUE) 471 | ``` 472 | 473 | The plot illustrates locations where there were carbon emissions due to insect damage in forests, so now the information conveyed by the *maskInsect* raster object is presence or absence of insect damage. 474 | 475 | # Combine Two Rasters 476 | 477 | > *Functions featured in this section:* 478 | > **cover()** 479 | > replaces NA values in the first raster object with the values of the second 480 | 481 | Next, we will join *reclassFire* and *reclassInsect* to form a single raster object. According to the documentation for this dataset, there are no overlapping, non-NA cells between the two raster objects. That is, if you were to combine the two rasters object, a cell could take only the value provided by *reclassFire* (i.e., 2) or *reclassInsect* (i.e., 1), or be NA. This allows us to use the `cover()` function to combine objects. `cover()` will replace NA values of *reclassFire* with non-NA values of *reclassInsect*. 482 | 483 | ```{r combine_1, message=FALSE, warning=FALSE, results="hide"} 484 | # this could take a couple of minutes to run 485 | fireInsect <- cover(reclassFire, reclassInsect) 486 | ``` 487 | 488 | Check the combination of *reclassFire* and *reclassInsect* using `summary()`. 489 | 490 | ```{r combine_2, message=FALSE, warning=FALSE} 491 | summary(fireInsect[]) 492 | ``` 493 | 494 | The data distribution of the new raster object shows that the minimum value is now 1 (i.e., the insect damage value we specified during reclassification) and the maximum value is 2 (i.e., the fire damage value). 495 | 496 | The plotting arguments below now reflect the "breaks" in the values we would like to see illustrated on the plot. Insect damage is displayed as green cells and fire damage as red. 497 | 498 | ```{r combine_3, message=FALSE, warning=FALSE} 499 | plot(fireInsect, 500 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 501 | xlab = "horizontal extent (m)", 502 | ylab = "vertical extent (m)", 503 | plg = list(legend=c("insect","fire"), title=" Disturbance"), 504 | col = c("dark green", "red"), 505 | colNA = "black", 506 | mar=c(3.1, 1.1, 3.1, 1.1), 507 | box = FALSE) 508 | plot(transStates$geometry, 509 | border = "white", 510 | add = TRUE) 511 | ``` 512 | 513 | # Reproject and Write a Raster 514 | 515 | > *Functions featured in this section:* 516 | > **projectRaster {raster}** 517 | > projects the values of a raster object to a new one with a different projection 518 | > **writeRaster {raster}** 519 | > writes an entire raster object to a file 520 | 521 | Reprojecting a raster in R is different than transforming the CRS as we did with the simple feature earlier in the exercise. To reproject a raster, we use the `project()` function and the `crs()` function to provide the CRS information. The nearest neighbor `method` ('near') is used to maintain cell values of 1 and 2. Other reprojection methods can produce cell values that are the average of nearby cells, a situation we want to avoid here. 522 | 523 | ```{r reproject_1, message=FALSE, warning=FALSE, results="hide"} 524 | # this may take several minutes to run 525 | prjFireInsect <- project(fireInsect, 526 | crs("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"), 527 | method="near") 528 | ``` 529 | 530 | Now, check the properties of this new raster object using `print()`. 531 | 532 | ```{r reproject_2, message=FALSE, warning=FALSE} 533 | print(prjFireInsect) 534 | ``` 535 | 536 | It's a new raster object named *prjFireInsect* that has the standard Geographic projection with latitude and longitude expressed in decimal degrees (DD) as its CRS. 537 | 538 | We will plot *prjFireInsect* with slightly different arguments than *fireInsect* to "zoom in" to the center of the plot. Also, we will use *threeStates* instead of *transStates* because *threeStates* also uses the Geographic projection. 539 | 540 | ```{r reproject_3, message=FALSE, warning=FALSE} 541 | plot(prjFireInsect, 542 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 543 | xlab = "longitude (DD)", 544 | ylab = "latitude (DD)", 545 | plg = list(legend=c("insect","fire"), title="\n Disturbance"), 546 | col = c("dark green", "red"), 547 | mar=c(3.1, 1.1, 2.8, 1.1), 548 | ext = st_bbox(prjFireInsect)/1.25, 549 | box = FALSE) 550 | 551 | plot(threeStates$geometry, 552 | border = "black", 553 | add = TRUE) 554 | ``` 555 | 556 | Let's use the `writeRaster()` function to save *prjFireInsect* to the data directory. We will save the file in GeoTIFF (\*.tif) format so that the geographic information of the raster object is retrievable outside of R. 557 | 558 | ```{r reproject_save_1, message=FALSE, warning=FALSE, results="hide"} 559 | writeRaster(prjFireInsect, filename = "./data/prjFireInsect.tif", overwrite=TRUE) 560 | ``` 561 | 562 | Use the function `file.exists()`, which tests for the existence of a given file, to ensure that *prjFireInsect* was successfully saved to our working directory. 563 | 564 | ```{r reproject_save_2, message=FALSE, warning=FALSE } 565 | file.exists("./data/prjFireInsect.tif") 566 | ``` 567 | 568 | Now we are able to share the raster with others or open it in another program. 569 | 570 | # Export a Plot as PNG and Raster as KML 571 | 572 | > *Functions featured in this section:* 573 | > **KML()** 574 | > exports raster object data to a KML file 575 | 576 | To save an imaage of the final plot, we use `png()` and repeat the plot() command. The png() function will open a graphics device that will save the plot we run in \*.png format. We will use the function `dev.off()` to tell R when we are finished plotting and want to close the graphics device. 577 | 578 | ```{r reproject_plot, message=FALSE, warning=FALSE, results="hide"} 579 | png("prjFireInsect.png", width=650, res=80) 580 | plot(prjFireInsect, 581 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 582 | xlab = "longitude (DD)", 583 | ylab = "latitude (DD)", 584 | plg = list(legend=c("insect","fire"), title="\n Disturbance"), 585 | col = c("dark green", "red"), 586 | mar=c(3.1, 1.1, 2.8, 1.1), 587 | ext = st_bbox(prjFireInsect)/1.25, 588 | box = FALSE) 589 | plot(threeStates$geometry, 590 | border = "black", 591 | add = TRUE) 592 | dev.off() 593 | ``` 594 | 595 | It might be useful to save *prjFireInsect* in \*.kmz format. KML stands for Keyhole Markup Language, and KMZ is the compressed version of KML format. These formats were developed for geographic visualization in Google Earth. 596 | 597 | At present, the *terra* package does not include an option to export a SpatRaster object as a KML/KMZ; it is necessary to use the *raster* package. First, *prjFireInsect* must be converted to a Raster object then saved using the raster package's KML() function. 598 | 599 | ```{r reproject_kmz, message=FALSE, warning=FALSE, results="hide"} 600 | prjFireInsect.r <- as(prjFireInsect, "Raster") # convert to a 'Raster' object 601 | KML(prjFireInsect.r, "./data/prjFireInsect.kmz", col = c("dark green", "red"), overwrite=TRUE) 602 | ``` 603 | 604 | We successfully saved the raster object as a KMZ file. 605 | 606 | *** 607 | 608 | This is the end to the tutorial. If you liked this tutorial, please tell us on [EarthData Forum](https://forum.earthdata.nasa.gov/). If you would like to make a suggestion for a new tutorial, please email uso@ornl.gov. 609 | 610 | There is a supplemental document included on GitHub that offers two additional sections, *Perform a Focal Analysis* and *Get Cell Coordinates*. 611 | 612 | 613 | -------------------------------------------------------------------------------- /edwebinar_mar19_ornldaac_tutorial.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Geospatial Analysis in R" 3 | author: "ORNL DAAC https://daac.ornl.gov" 4 | date: "January 25, 2024" 5 | output: 6 | html_document: 7 | keep_md: yes 8 | number_sections: yes 9 | toc: yes 10 | html_notebook: 11 | number_sections: yes 12 | toc: yes 13 | pdf_document: 14 | toc: yes 15 | latex_engine: xelatex 16 | header-includes: 17 | - \usepackage{fontspec} 18 | - \setmainfont{Calibri Light} 19 | subtitle: NASA Earthdata Webinar 20 | editor_options: 21 | chunk_output_type: inline 22 | --- 23 | *** 24 | 25 | 26 | 27 | # Install and Load Packages 28 | 29 | 30 | In addition to the built-in functionality of R, we will use four packages throughout this exercise. Packages are a collection of documentation, functions, and other items that someone has created and compiled for others to use in R. Install the packages, as well as their dependencies, using the function `install.packages()`. 31 | 32 | 33 | ```r 34 | install.packages("terra", dependencies = TRUE) 35 | install.packages("sf", dependencies = TRUE) 36 | install.packages("tigris", dependencies = TRUE) 37 | install.packages("raster", dependencies = TRUE) 38 | ``` 39 | 40 | Most of the functions we will use are from the *terra* and *sf* packages. The *terra*, *sf*, and *stars* (not used here) packages are largely replacing older R packages, such as *raster*, for for managing and manipulating spatial data. However, we will use a *raster* function at the end of this tutorial to export the final object in KML/KMZ format. 41 | 42 | 43 | ```r 44 | library(terra) 45 | library(sf) 46 | library(tigris) # provides access to TIGER shapefiles from US Census Bureau 47 | library(raster) 48 | ``` 49 | 50 | For package details, try `help()` (e.g., `help("terra")`), and to view the necessary arguments of a function try `args()` (e.g., `args(cover)`). 51 | 52 | # Load Data 53 | 54 | > *Functions featured in this section:* 55 | > **terra::rast()** 56 | > The `rast()` function from the terra package creates a 'SpatRaster' (spatial raster) object from a file 57 | > **tigris::states()** 58 | > The `states()` function from the tigris package downloads a shapefile of the United States that will be loaded as a SpatialPolygonsDataFrame object 59 | 60 | Two GeoTiff files are needed to complete this tutorial, both from the dataset titled "CMS: Forest Carbon Stocks, Emissions, and Net Flux for the Conterminous US: 2005-2010" and freely available through the ORNL DAAC at 61 | [https://doi.org/10.3334/ORNLDAAC/1313](https://doi.org/10.3334/ORNLDAAC/1313). 62 | 63 | The dataset provides maps of estimated carbon emissions in forests of the conterminous United States for the years 2006-2010. We will use the maps of carbon emissions caused by fire (*GrossEmissions_v101_USA_Fire.tif*) and insect damage (*GrossEmissions_v101_USA_Insect.tif*). These maps are provided at 100-meter spatial resolution in GeoTIFF format using Albers North America projection. Refer to the accompanying "README.md" for instructions on how to download the data. 64 | 65 | To begin, be sure to set your working directory using `setwd()`. The code below assumes that the GeoTIFF files are saved in a folder called 'data' located under the working directory (e.g., "./data/GrossEmissions_v101_USA_Fire.tif"). 66 | 67 | With the `rast()` function, load *GrossEmissions_v101_USA_Fire.tif* and name it *fire*, then load *GrossEmissions_v101_USA_Insect.tif* and name it *insect*. The contents of these two files are stored as SpatRaster objects; *fire* and *insect* are the primary focus of our manipulations throughout this exercise. 68 | 69 | The function `states()` downloads a shapefile of the United States from the United States Census Bureau. It will be stored as a simple feature data frame object named *myStates*. 70 | 71 | 72 | ```r 73 | fire <- rast("./data/GrossEmissions_v101_USA_Fire.tif") # read GeoTIFF, store as SpatRaster object 74 | insect <- rast("./data/GrossEmissions_v101_USA_Insect.tif") 75 | myStates <- states(cb = TRUE, progress_bar=FALSE) # will download a generalized (1:500k) file 76 | ``` 77 | 78 | # Check the Coordinate Reference System and Plot a Raster 79 | 80 | > *Functions featured in this section:* 81 | > **terra::crs()** 82 | > gets the coordinate reference system of a raster object 83 | 84 | Use `print()` to view details about the internal data structure of the raster object we named *fire*. 85 | 86 | 87 | ```r 88 | print(fire) 89 | ``` 90 | 91 | ``` 92 | ## class : SpatRaster 93 | ## dimensions : 32818, 59444, 1 (nrow, ncol, nlyr) 94 | ## resolution : 100, 100 (x, y) 95 | ## extent : -2972184, 2972216, 36233.75, 3318034 (xmin, xmax, ymin, ymax) 96 | ## coord. ref. : USA_Contiguous_Albers_Equal_Area_Conic_USGS_version 97 | ## source : GrossEmissions_v101_USA_Fire.tif 98 | ## name : GrossEmissions_v101_USA_Fire 99 | ## min value : 2 100 | ## max value : 373 101 | ``` 102 | 103 | The output lists important attributes of *fire*, like its dimensions, resolution, spatial extent, coordinate reference system, and the minimum and maximum values of the cells (i.e., carbon emissions). 104 | 105 | The next two commands retrieve the coordinate reference system (CRS) of *fire* and display the CRS in 'WKT' (Well Known Text) and 'Proj4' formats. 106 | 107 | 108 | ```r 109 | fire_wkt <- crs(fire) # CRS in WKT format 110 | writeLines(strwrap(fire_wkt, width = 70, exdent = 5)) # needed to control text wrapping 111 | ``` 112 | 113 | ``` 114 | ## PROJCRS["USA_Contiguous_Albers_Equal_Area_Conic_USGS_version", 115 | ## BASEGEOGCRS["NAD83", DATUM["North American Datum 1983", 116 | ## ELLIPSOID["GRS 1980",6378137,298.257222101004, 117 | ## LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, 118 | ## ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]], 119 | ## CONVERSION["Albers Equal Area", METHOD["Albers Equal Area", 120 | ## ID["EPSG",9822]], PARAMETER["Latitude of false origin",23, 121 | ## ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8821]], 122 | ## PARAMETER["Longitude of false origin",-96, 123 | ## ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8822]], 124 | ## PARAMETER["Latitude of 1st standard parallel",29.5, 125 | ## ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8823]], 126 | ## PARAMETER["Latitude of 2nd standard parallel",45.5, 127 | ## ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8824]], 128 | ## PARAMETER["Easting at false origin",0, LENGTHUNIT["metre",1], 129 | ## ID["EPSG",8826]], PARAMETER["Northing at false origin",0, 130 | ## LENGTHUNIT["metre",1], ID["EPSG",8827]]], CS[Cartesian,2], 131 | ## AXIS["easting",east, ORDER[1], LENGTHUNIT["metre",1, 132 | ## ID["EPSG",9001]]], AXIS["northing",north, ORDER[2], 133 | ## LENGTHUNIT["metre",1, ID["EPSG",9001]]]] 134 | ``` 135 | 136 | 137 | ```r 138 | fire_prj <- crs(fire, proj=T) # CRS as proj4 string 139 | writeLines(strwrap(fire_prj, width = 70, exdent = 5)) 140 | ``` 141 | 142 | ``` 143 | ## +proj=aea +lat_0=23 +lon_0=-96 +lat_1=29.5 +lat_2=45.5 +x_0=0 +y_0=0 144 | ## +datum=NAD83 +units=m +no_defs 145 | ``` 146 | In the PROJ.4 representation, the first argument is "+proj=" and defines the projection. "aea" refers to the `NAD83 / Albers NorthAm` projection (EPSG 42303), and "+units=m" tells us that the resolution of the raster object is in meters. Refer to the attributes of *fire* provided by `print()` above. The resolution of the raster is "100, 100 (x, y)" meaning that each cell is 100 meters by 100 meters, or one hectare (ha). 147 | 148 | Use the `plot()` function to make a simple image of *fire* and visualize the carbon emissions from fire damage across the forests of the conterminous United States between 2006 and 2010. According to the documentation for the dataset, gross carbon emissions were measured in megagrams of carbon per year (Mg C/y) per cell. 149 | 150 | 151 | ```r 152 | plot(fire, 153 | main = "Gross Carbon Emissions from Fire Damage\n across CONUS Forests (2006-2010)", 154 | xlab = "horizontal extent (m)", 155 | ylab = "vertical extent (m)", 156 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 157 | colNA = "black", 158 | box = FALSE) 159 | ``` 160 | 161 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/raster_plot_1-1.png) 162 | 163 | The spatial extent of the raster object is displayed on the x- and y-axes. All NA cells (i.e., cells that have no values) are colored black for better visualization of fire damage. The legend offers the range of cell values and represents them using a default color theme. Because the extent of this object covers the conterminous US, we cannot see much spatial detail in this figure. 164 | 165 | Let's examine the raster object we named *insect*. The function `crs()` retrieves the CRS information for each raster object. Then, we use `identical()` to determine if *fire* and *insect* have the same CRS. 166 | 167 | 168 | ```r 169 | identical(crs(fire), crs(insect)) 170 | ``` 171 | 172 | ``` 173 | ## [1] TRUE 174 | ``` 175 | 176 | "TRUE" indicates that the CRS for the two raster objects have the same CRS. 177 | 178 | Plot *insect* but change the content for the argument "main = ", which defines the main title of the plot. 179 | 180 | 181 | ```r 182 | plot(insect, 183 | main = "Gross Carbon Emissions from Insect Damage\n across CONUS Forests (2006-2010)", 184 | xlab = "horizontal extent (m)", 185 | ylab = "vertical extent (m)", 186 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 187 | colNA = "black", 188 | box = FALSE) 189 | ``` 190 | 191 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/raster_plot_2-1.png) 192 | 193 | You can likely imagine an outline of the United States given the distribution of spatial data in the two raster objects. 194 | 195 | # Select Data Within a Region of Interest 196 | 197 | > *Functions featured in this section:* 198 | > **terra::crs()** 199 | > retrieves or sets the CRS for a spatial object 200 | > **sf::st_transform()** 201 | > provides re-projection given a CRS 202 | > **sf::st_bbox()** 203 | > retrieves the bounding box of a simple feature (sf) spatial object 204 | > **terra::crop()** 205 | > returns a geographic subset of an object as specified by an Extent object 206 | > **terra::mask()** 207 | > creates a new raster object with the same values as the input object, except for the cells that are NA in the second object (the 'mask') 208 | 209 | Next, we reduce the spatial extent of *fire* and *insect* to focus on three states in the western US. We can select the states from the *myStates* to create a new feature then use it to crop the *fire* and *insect* rasters. 210 | 211 | First, use `print()` to view details about the internal data structure of the simple feature we named *myStates*. 212 | 213 | 214 | ```r 215 | print(myStates) 216 | ``` 217 | 218 | ``` 219 | ## Simple feature collection with 56 features and 9 fields 220 | ## Geometry type: MULTIPOLYGON 221 | ## Dimension: XY 222 | ## Bounding box: xmin: -179.1489 ymin: -14.5487 xmax: 179.7785 ymax: 71.36516 223 | ## Geodetic CRS: NAD83 224 | ## First 10 features: 225 | ## STATEFP STATENS AFFGEOID GEOID STUSPS NAME LSAD ALAND 226 | ## 1 56 01779807 0400000US56 56 WY Wyoming 00 2.514587e+11 227 | ## 2 02 01785533 0400000US02 02 AK Alaska 00 1.478943e+12 228 | ## 3 24 01714934 0400000US24 24 MD Maryland 00 2.515199e+10 229 | ## 4 60 01802701 0400000US60 60 AS American Samoa 00 1.977591e+08 230 | ## 5 05 00068085 0400000US05 05 AR Arkansas 00 1.346608e+11 231 | ## 6 38 01779797 0400000US38 38 ND North Dakota 00 1.786943e+11 232 | ## 7 10 01779781 0400000US10 10 DE Delaware 00 5.046732e+09 233 | ## 8 66 01802705 0400000US66 66 GU Guam 00 5.435558e+08 234 | ## 9 35 00897535 0400000US35 35 NM New Mexico 00 3.141986e+11 235 | ## 10 49 01455989 0400000US49 49 UT Utah 00 2.133551e+11 236 | ## AWATER geometry 237 | ## 1 1867503716 MULTIPOLYGON (((-111.0546 4... 238 | ## 2 245378425142 MULTIPOLYGON (((179.4825 51... 239 | ## 3 6979074857 MULTIPOLYGON (((-76.05015 3... 240 | ## 4 1307243751 MULTIPOLYGON (((-168.1458 -... 241 | ## 5 3121950081 MULTIPOLYGON (((-94.61792 3... 242 | ## 6 4414779956 MULTIPOLYGON (((-104.0487 4... 243 | ## 7 1399179670 MULTIPOLYGON (((-75.56555 3... 244 | ## 8 934337453 MULTIPOLYGON (((144.6454 13... 245 | ## 9 726482113 MULTIPOLYGON (((-109.0502 3... 246 | ## 10 6529973239 MULTIPOLYGON (((-114.053 37... 247 | ``` 248 | 249 | *myStates* has 56 features (i.e., polygons) each with 9 attribute fields. This *sf* object can be thought of a dataframe with 56 rows and ten columns (variables or features). The columns include the 9 attribute fields plus a "geometry" column. 250 | 251 | For this exercise, we will focus on carbon emissions for Idaho, Montana, and Wyoming. We can use column referencing and indexing to select all column information contained in *myStates*, but for only three rows (polygons). This code selects the polygons for which the 'NAME' is either Idaho, Montana, or Wyoming. These three polygons are saved in the resultant simple feature *threeStates*. 252 | 253 | 254 | ```r 255 | threeStates <- myStates[myStates$NAME == "Idaho" | 256 | myStates$NAME == "Montana" | 257 | myStates$NAME == "Wyoming", ] 258 | threeStates <- threeStates[order(threeStates$NAME),] # sort by name: ID, MT, WY 259 | print(threeStates) 260 | ``` 261 | 262 | ``` 263 | ## Simple feature collection with 3 features and 9 fields 264 | ## Geometry type: MULTIPOLYGON 265 | ## Dimension: XY 266 | ## Bounding box: xmin: -117.243 ymin: 40.99475 xmax: -104.0396 ymax: 49.00139 267 | ## Geodetic CRS: NAD83 268 | ## STATEFP STATENS AFFGEOID GEOID STUSPS NAME LSAD ALAND 269 | ## 18 16 01779783 0400000US16 16 ID Idaho 00 214049931578 270 | ## 27 30 00767982 0400000US30 30 MT Montana 00 376973729130 271 | ## 1 56 01779807 0400000US56 56 WY Wyoming 00 251458712294 272 | ## AWATER geometry 273 | ## 18 2391569647 MULTIPOLYGON (((-117.2427 4... 274 | ## 27 3866634365 MULTIPOLYGON (((-116.0491 4... 275 | ## 1 1867503716 MULTIPOLYGON (((-111.0546 4... 276 | ``` 277 | 278 | *threeStates* has only three rows, but the same number of columns as *myStates*. 279 | 280 | What does *threeStates* look like plotted? We'll plot the geometry (i.e., the 10th column) of *threeStates* so we only see the outline. 281 | 282 | 283 | ```r 284 | plot(threeStates$geometry) 285 | ``` 286 | 287 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/states_3-1.png) 288 | 289 | We can get the *fire* and *insect* data that occurs "within" *threeStates*. First, we must confirm that the three objects share the same CRS before we can overlay or otherwise combine information from them. Some GIS software, such as QGIS or ArcGIS, can automatically combine data layers having different CRS, but that is not a default capability with R. 290 | 291 | 292 | ```r 293 | identical(crs(fire), crs(threeStates)) 294 | ``` 295 | 296 | ``` 297 | ## [1] FALSE 298 | ``` 299 | 300 | "FALSE" indicates that *threeStates* does not have the same CRS as *fire*, so we will make a new version of these state polygons that has *fire*'s CRS. The `st_transform()` from the *sf* package will work. This function uses `crs=crs(fire)` to set the CRS of the new simple feature object to the same projection as *fire*. 301 | 302 | 303 | ```r 304 | transStates <- st_transform(threeStates, crs=crs(fire) ) 305 | plot(transStates$geometry) # draw outline of the state polygons 306 | ``` 307 | 308 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/states_5-1.png) 309 | 310 | Plotting the geometry of the new object *transStates* shows that the projection has changed. Notice how the orientation of the polygons has shifted to match the *NAD83 / Albers NorthAm projection*. 311 | 312 | Now that our objects share a CRS, we will compare the extent of *fire* and *transStates*. Use the `st_bbox()` function to view the bounding (i.e., bounding box) of *transStates* and *fire*. 313 | 314 | 315 | ```r 316 | cat("fire extent\n"); st_bbox(fire) 317 | ``` 318 | 319 | ``` 320 | ## fire extent 321 | ``` 322 | 323 | ``` 324 | ## xmin ymin xmax ymax 325 | ## -2972184.50 36233.75 2972215.50 3318033.75 326 | ``` 327 | 328 | ```r 329 | cat("\ntransStates extent\n"); st_bbox(transStates) 330 | ``` 331 | 332 | ``` 333 | ## 334 | ## transStates extent 335 | ``` 336 | 337 | ``` 338 | ## xmin ymin xmax ymax 339 | ## -1715671.1 2027603.7 -595594.4 3059862.0 340 | ``` 341 | 342 | To visualize the bounding boxes, use the `ext()` (i.e., extent) function with `plot()`. 343 | 344 | 345 | ```r 346 | plot(ext(fire), col='grey') # bounding box of fire (grey) 347 | plot(ext(transStates), col='blue', add=T) # bounding box of tranStates (blue) 348 | ``` 349 | 350 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/states_7-1.png) 351 | 352 | The raster *fire* has a much larger extent than *transStates* because *fire* covers the entire conterminous US. 353 | 354 | Reducing the extent of the *fire* and *insect* rasters will greatly speed up processing and reduce the size of saved files. The `crop()` function will serve this purpose. Cropping will create a geographic subset of *fire* and *insect* as specified by the extent of *transStates*. We prepend "crop" to the names of the new raster objects to reflect this manipulation. 355 | 356 | 357 | ```r 358 | # this will take a minute to run 359 | cropFire <- crop(fire, transStates) # crop(raster object, extent object) 360 | cropInsect <- crop(insect, transStates) 361 | ``` 362 | 363 | Now when we plot *cropFire* and *cropInsect*, we will also plot *transStates* "on top" to envision how carbon emissions are distributed across the three states. 364 | 365 | 366 | ```r 367 | par(mfrow=c(1,1)) 368 | mar.val <- c(3.1, 3.1, 3.1, 3.1) # set plot margin values for maps: bot,lft,top,rgt 369 | plot(cropFire, 370 | main = "Gross Carbon Emissions from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 371 | xlab = "horizontal extent (m)", 372 | ylab = "vertical extent (m)", 373 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 374 | colNA = "black", 375 | mar=mar.val, 376 | box = FALSE) 377 | plot(transStates$geometry, 378 | border = "white", 379 | add = TRUE) 380 | ``` 381 | 382 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/crop_plot-1.png) 383 | 384 | ```r 385 | plot(cropInsect, 386 | main = "Gross Carbon Emissions from Insect Damage\n across ID, MT, WY Forests (2005-2010)", 387 | # cex.main=0.85, 388 | xlab = "horizontal extent (m)", 389 | ylab = "vertical extent (m)", 390 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 391 | colNA = "black", 392 | mar=mar.val, 393 | box = FALSE) 394 | plot(transStates$geometry, 395 | border = "white", 396 | add = TRUE) 397 | ``` 398 | 399 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/crop_plot-2.png) 400 | 401 | If you look closely at the cells "outside" the boundary of the *transStates* polygons, you can still see cell values. That's because `crop()` changed the extent of the two raster objects to match that of the simple feature object, but `crop()` did not change cells outside of the polygon boundaries. 402 | 403 | To remove those extraneous cell values, use the `mask()` function to create two new rasters, one for fire damage and one for insect damage. The *terra* function `mask()` will convert raster cells outside of the state polygons to NA. **Note:** You can use `mask()` or `crop()` in either order. 404 | 405 | 406 | ```r 407 | # this will take a couple of minutes to run 408 | maskFire <- mask(cropFire, transStates) # mask(raster object, mask object) 409 | maskInsect <- mask(cropInsect, transStates) 410 | ``` 411 | 412 | Plot *maskFire* and *maskInsect*. 413 | 414 | 415 | ```r 416 | plot(maskFire, 417 | main = "Gross Carbon Emissions from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 418 | xlab = "horizontal extent (m)", 419 | ylab = "vertical extent (m)", 420 | plg=list( title="\n Mg C/ha/yr", size=c(0.7,1) ), 421 | colNA = "black", 422 | mar=mar.val, 423 | box = FALSE) 424 | plot(transStates$geometry, 425 | border = "white", 426 | add = TRUE) 427 | ``` 428 | 429 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/mask_plot-1.png) 430 | 431 | ```r 432 | plot(maskInsect, 433 | main = "Gross Carbon Emissions from Insect Damage\n across ID, MT, WY Forests (2005-2010)", 434 | xlab = "horizontal extent (m)", 435 | ylab = "vertical extent (m)", 436 | plg=list( title="\\n Mg C/ha/yr", size=c(0.7,1) ), 437 | colNA = "black", 438 | mar=mar.val, 439 | box = FALSE) 440 | plot(transStates$geometry, 441 | border = "white", 442 | add = TRUE) 443 | ``` 444 | 445 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/mask_plot-2.png) 446 | 447 | These plots demonstrate that all cell values outside of the *transStates* polygons are now NA. 448 | 449 | # Examine Summaries of Raster Values 450 | 451 | > *Functions featured in this section:* 452 | > **terra::extract()** 453 | > extracts values from a raster object at the locations of other spatial data 454 | 455 | In this section, we will compare the three states by their carbon emissions from fire damage. 456 | 457 | We will use *terra*'s `extract()` function to collect the cell values of *maskFire* where the *transStates* simple feature object overlaps the raster object. We will use `summary()` to examine the distribution of cell values that we collect. 458 | 459 | 460 | ```r 461 | # this can take up to an hour to run, so load a saved copy for the demonstration 462 | if(file.exists("./data/val_fireStates.Rds")) { 463 | val_fireStates <- readRDS("./data/val_fireStates.rds") 464 | summary(val_fireStates) 465 | }else{ 466 | val_fireStates <- extract(maskFire, transStates, df = TRUE) # extract(raster object, extent object) 467 | summary(val_fireStates) 468 | # saveRDS(val_fireStates, "./data/val_fireStates.Rds") # uncomment this line to save to file 469 | } 470 | ``` 471 | 472 | ``` 473 | ## ID GrossEmissions_v101_USA_Fire 474 | ## Min. :1.000 Min. : 2 475 | ## 1st Qu.:1.000 1st Qu.: 27 476 | ## Median :2.000 Median : 47 477 | ## Mean :1.807 Mean : 56 478 | ## 3rd Qu.:3.000 3rd Qu.: 76 479 | ## Max. :3.000 Max. :333 480 | ## NA's :84454561 481 | ``` 482 | 483 | There are two columns in this summary of *val_fireStates*. One is ID, which corresponds with the three states; 1 = Idaho, 2 = Montana, and 3 = Wyoming. The ID value corresponds to the row order of the polygons. That is why it was important to sort *threeStates* object by 'NAME' in the code above. Still, it is a good idea to verify this order. 484 | 485 | 486 | ```r 487 | cbind("ID"=1:3, "state"=transStates$NAME) 488 | ``` 489 | 490 | ``` 491 | ## ID state 492 | ## [1,] "1" "Idaho" 493 | ## [2,] "2" "Montana" 494 | ## [3,] "3" "Wyoming" 495 | ``` 496 | 497 | The second column is a summary of all cell values across those three states. On average (mean), 56 megagrams of carbon per ha per year are a result of forest destruction by fire damage for all states combined. 498 | 499 | To look at the summary for cell values by state, we will use `subset()` to split *val_fireStates* into three data frames, one for each state. In the code below, we subset *val_fireStates* so that only the rows with ID == "1" (representing Idaho) will be returned. We name the new object with the prefix "temp" and suffix "id" (Idaho). 500 | 501 | 502 | ```r 503 | temp_val_id <- subset(val_fireStates, subset = ID %in% 1) # Idaho values 504 | summary(temp_val_id) 505 | ``` 506 | 507 | ``` 508 | ## ID GrossEmissions_v101_USA_Fire 509 | ## Min. :1 Min. : 3 510 | ## 1st Qu.:1 1st Qu.: 27 511 | ## Median :1 Median : 49 512 | ## Mean :1 Mean : 58 513 | ## 3rd Qu.:1 3rd Qu.: 79 514 | ## Max. :1 Max. :254 515 | ## NA's :37907760 516 | ``` 517 | 518 | The summary demonstrates that there is now only a single value in the ID column, and that the distribution of cell values has changed. This resultant data frame object is quite large and has more information than we need. We need only the second column and don't care for the large number of NA's. 519 | 520 | To create a new vector of cells values from *temp_val_id*, use the `is.na()` function to eliminate the rows with NA cells and select the second column holding emissions values. 521 | 522 | 523 | ```r 524 | val_id <- temp_val_id[!is.na(temp_val_id$GrossEmissions_v101_USA_Fire),2] 525 | summary(val_id) 526 | ``` 527 | 528 | ``` 529 | ## Min. 1st Qu. Median Mean 3rd Qu. Max. 530 | ## 3.00 27.00 49.00 58.06 79.00 254.00 531 | ``` 532 | 533 | The resultant object, *val_id*, is a vector object (a single column of numbers) with no NA's. 534 | 535 | We will do the same with *val_fire* for the states Montana and Wyoming. 536 | 537 | 538 | ```r 539 | temp_val_mt <- subset(val_fireStates, subset = ID %in% 2) # Montana 540 | val_mt <- temp_val_mt[!is.na(temp_val_mt$GrossEmissions_v101_USA_Fire), 2] 541 | temp_val_wy <- subset(val_fireStates, subset = ID %in% 3) # Wyoming 542 | val_wy <- temp_val_wy[!is.na(temp_val_wy$GrossEmissions_v101_USA_Fire), 2] 543 | rm(temp_val_id, temp_val_mt, temp_val_wy) # clean up 544 | ``` 545 | 546 | What's the total carbon emissions from fire for each state and the range of values within each state for the period 2006 to 2010? 547 | 548 | 549 | ```r 550 | cat("Number of cells burned (ha)\n") # divide totals by 1000 to improve ability to compare 551 | ``` 552 | 553 | ``` 554 | ## Number of cells burned (ha) 555 | ``` 556 | 557 | ```r 558 | cat("Idaho: ", length(val_id), " Montana: ", length(val_mt), 559 | " Wyoming: ", length(val_wy),"\n") 560 | ``` 561 | 562 | ``` 563 | ## Idaho: 176544 Montana: 49758 Wyoming: 379739 564 | ``` 565 | 566 | ```r 567 | cat("\nTotal emissions (/1000)\n") # divide totals by 1000 to improve ability to compare 568 | ``` 569 | 570 | ``` 571 | ## 572 | ## Total emissions (/1000) 573 | ``` 574 | 575 | ```r 576 | cat("Idaho: ", sum(val_id)/1000, " Montana: ", sum(val_mt)/1000, 577 | " Wyoming: ", sum(val_wy)/1000,"\n") 578 | ``` 579 | 580 | ``` 581 | ## Idaho: 10250.22 Montana: 2660.297 Wyoming: 20961.93 582 | ``` 583 | 584 | ```r 585 | cat("\nDistribution of cell values\n") 586 | ``` 587 | 588 | ``` 589 | ## 590 | ## Distribution of cell values 591 | ``` 592 | 593 | ```r 594 | cat("Idaho\n"); summary(val_id); cat("Montana\n"); summary(val_mt); cat("Wyoming\n"); summary(val_wy) 595 | ``` 596 | 597 | ``` 598 | ## Idaho 599 | ``` 600 | 601 | ``` 602 | ## Min. 1st Qu. Median Mean 3rd Qu. Max. 603 | ## 3.00 27.00 49.00 58.06 79.00 254.00 604 | ``` 605 | 606 | ``` 607 | ## Montana 608 | ``` 609 | 610 | ``` 611 | ## Min. 1st Qu. Median Mean 3rd Qu. Max. 612 | ## 4.00 24.00 43.00 53.46 70.00 230.00 613 | ``` 614 | 615 | ``` 616 | ## Wyoming 617 | ``` 618 | 619 | ``` 620 | ## Min. 1st Qu. Median Mean 3rd Qu. Max. 621 | ## 2.0 27.0 47.0 55.2 75.0 333.0 622 | ``` 623 | 624 | Idaho has the greatest number of cells burned, the highest total carbon emissions, and the maximum gross carbon emissions from a single cell. In contrast, the largest mean emissions from cells occurred in Montana. Wyoming had the least amount of carbon emissions due to fire. 625 | 626 | In addition to using `summary()`, we can create graphs to visualize carbon emissions from fire damage within each of the three states. The function `hist()` plots the frequency of cell values. We will set some arguments of the plot so that we can compare carbon emissions across all three states. 627 | 628 | 629 | ```r 630 | par(mfrow=c(2,2), mar=c(3,3,3,3)) 631 | hist(val_id, 632 | main = "Idaho", 633 | ylab = "number of cells", 634 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 635 | ylim = c(0, 120000), # same y-axis limit for all three states 636 | xlim = c(0, 350)) # same x-axis limit for all three states 637 | hist(val_mt, 638 | main = "Montana", 639 | ylab = "number of cells", 640 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 641 | ylim = c(0, 120000), 642 | xlim = c(0, 350)) 643 | hist(val_wy, 644 | main = "Wyoming", 645 | ylab = "number of cells", 646 | xlab = "megagrams of carbon per ha per year (Mg C/ha/yr)", 647 | ylim = c(0, 120000), 648 | xlim = c(0, 350)) 649 | ``` 650 | 651 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/fire_summary_7-1.png) 652 | 653 | The histograms show the number of times (on the y-axis) each unique cell value (on the x-axis) occurs in each state. In other words, these plots illustrate the variation in carbon emissions from fire damage within the three different states and provide a visual display of the numerical summaries above. 654 | 655 | # Reclassify Raster Values 656 | 657 | > *Functions featured in this section:* 658 | > **reclassify()** 659 | > reclassifies groups of values of a raster object to other values 660 | > **calc()** 661 | > calculates values for a new raster object from another raster object using a formula 662 | 663 | Now we are going to change the values of our two raster objects using different methods. The goal is to generate a single map that shows the cells where fire and insect disturbances occurred. Fire and insect maps are reclassified individually then combined. 664 | 665 | Beginning with *maskFire*, we convert all cells with values >0 to the value 2 and save the new raster object to *reclassFire* . 666 | 667 | 668 | ```r 669 | reclassFire <- maskFire 670 | reclassFire[reclassFire >0] <- 2 671 | ``` 672 | 673 | Check that our reclassification of *maskFire* worked as expected using `summary()`. The pair of square brackets ("[]") after the raster name tells the summary command to read all values of the raster. 674 | 675 | 676 | ```r 677 | summary(reclassFire[]) 678 | ``` 679 | 680 | ``` 681 | ## GrossEmissions_v101_USA_Fire 682 | ## Min. :2 683 | ## 1st Qu.:2 684 | ## Median :2 685 | ## Mean :2 686 | ## 3rd Qu.:2 687 | ## Max. :2 688 | ## NA's :115010680 689 | ``` 690 | 691 | Yes, all values are either 2 or NA. 692 | 693 | All cell values of *reclassFire* should be at the same locations as *maskFire* but with a single value. 694 | 695 | 696 | ```r 697 | plot(reclassFire, 698 | main = "Locations of Forest Disturbance from Fire Damage\n across ID, MT, WY Forests (2006-2010)", 699 | xlab = "horizontal extent (m)", 700 | ylab = "vertical extent (m)", 701 | legend = FALSE, 702 | col = "red", colNA = "black", 703 | mar=c(3.1, 1.1, 2.8, 1.1), # bot,lft,top,rgt 704 | box = FALSE) 705 | plot(transStates$geometry, 706 | border = "white", 707 | add = TRUE) 708 | ``` 709 | 710 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/reclass_3-1.png) 711 | 712 | The plot of *reclassFire* now illustrates locations where there were carbon emissions due to forest fire. Notice that we chose a single color to represent the presence of values using the argument "col = "red"". 713 | 714 | Now we will reclassify all values of *maskInsect* that are greater than zero to be 1. Let's check the range of values for this raster. 715 | 716 | 717 | ```r 718 | range(maskInsect[], na.rm=TRUE) 719 | ``` 720 | 721 | This time, we will use the `classify()` function in the *terra* package. This function uses a matrix to identify the target cell values and to what value those cells will change. 722 | 723 | 724 | ```r 725 | reclassInsect <- classify(maskInsect, 726 | rcl = matrix(data = c(0, 285, 1), # c(from value, to value, becomes) 727 | nrow = 1, ncol = 3)) 728 | ``` 729 | 730 | The argument following "rcl =" tells R that values from 1 to 285 should be reclassified as one. Essentially, we are making the presence of insect damage equal one. 731 | 732 | Check the reclassification of *maskInsect* using `summary()`. 733 | 734 | 735 | ```r 736 | summary(reclassInsect[]) 737 | ``` 738 | 739 | ``` 740 | ## GrossEmissions_v101_USA_Insect 741 | ## Min. :1 742 | ## 1st Qu.:1 743 | ## Median :1 744 | ## Mean :1 745 | ## 3rd Qu.:1 746 | ## Max. :1 747 | ## NA's :113254371 748 | ``` 749 | 750 | All values are 1 or NA. 751 | 752 | Plot *reclassInsect*. All the cell values should be at the same locations as *maskInsect* but will all be the value one. 753 | 754 | 755 | ```r 756 | plot(reclassInsect, 757 | main = "Locations of Forest Disturbance from Insect Damage\n across ID, MT, WY Forests (2006-2010)", 758 | xlab = "horizontal extent (m)", 759 | ylab = "vertical extent (m)", 760 | legend = FALSE, 761 | col = "dark green", 762 | colNA = "black", 763 | mar=c(3.1, 1.1, 3.1, 1.1), 764 | box = FALSE) 765 | plot(transStates$geometry, 766 | border = "white", 767 | add = TRUE) 768 | ``` 769 | 770 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/reclass_plot-1.png) 771 | 772 | The plot illustrates locations where there were carbon emissions due to insect damage in forests, so now the information conveyed by the *maskInsect* raster object is presence or absence of insect damage. 773 | 774 | # Combine Two Rasters 775 | 776 | > *Functions featured in this section:* 777 | > **cover()** 778 | > replaces NA values in the first raster object with the values of the second 779 | 780 | Next, we will join *reclassFire* and *reclassInsect* to form a single raster object. According to the documentation for this dataset, there are no overlapping, non-NA cells between the two raster objects. That is, if you were to combine the two rasters object, a cell could take only the value provided by *reclassFire* (i.e., 2) or *reclassInsect* (i.e., 1), or be NA. This allows us to use the `cover()` function to combine objects. `cover()` will replace NA values of *reclassFire* with non-NA values of *reclassInsect*. 781 | 782 | 783 | ```r 784 | # this could take a couple of minutes to run 785 | fireInsect <- cover(reclassFire, reclassInsect) 786 | ``` 787 | 788 | Check the combination of *reclassFire* and *reclassInsect* using `summary()`. 789 | 790 | 791 | ```r 792 | summary(fireInsect[]) 793 | ``` 794 | 795 | ``` 796 | ## GrossEmissions_v101_USA_Fire 797 | ## Min. :1 798 | ## 1st Qu.:1 799 | ## Median :1 800 | ## Mean :1 801 | ## 3rd Qu.:1 802 | ## Max. :2 803 | ## NA's :112648329 804 | ``` 805 | 806 | The data distribution of the new raster object shows that the minimum value is now 1 (i.e., the insect damage value we specified during reclassification) and the maximum value is 2 (i.e., the fire damage value). 807 | 808 | The plotting arguments below now reflect the "breaks" in the values we would like to see illustrated on the plot. Insect damage is displayed as green cells and fire damage as red. 809 | 810 | 811 | ```r 812 | plot(fireInsect, 813 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 814 | xlab = "horizontal extent (m)", 815 | ylab = "vertical extent (m)", 816 | plg = list(legend=c("insect","fire"), title=" Disturbance"), 817 | col = c("dark green", "red"), 818 | colNA = "black", 819 | mar=c(3.1, 1.1, 3.1, 1.1), 820 | box = FALSE) 821 | plot(transStates$geometry, 822 | border = "white", 823 | add = TRUE) 824 | ``` 825 | 826 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/combine_3-1.png) 827 | 828 | # Reproject and Write a Raster 829 | 830 | > *Functions featured in this section:* 831 | > **projectRaster {raster}** 832 | > projects the values of a raster object to a new one with a different projection 833 | > **writeRaster {raster}** 834 | > writes an entire raster object to a file 835 | 836 | Reprojecting a raster in R is different than transforming the CRS as we did with the simple feature earlier in the exercise. To reproject a raster, we use the `project()` function and the `crs()` function to provide the CRS information. The nearest neighbor `method` ('near') is used to maintain cell values of 1 and 2. Other reprojection methods can produce cell values that are the average of nearby cells, a situation we want to avoid here. 837 | 838 | 839 | ```r 840 | # this may take several minutes to run 841 | prjFireInsect <- project(fireInsect, 842 | crs("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"), 843 | method="near") 844 | ``` 845 | 846 | Now, check the properties of this new raster object using `print()`. 847 | 848 | 849 | ```r 850 | print(prjFireInsect) 851 | ``` 852 | 853 | ``` 854 | ## class : SpatRaster 855 | ## dimensions : 9169, 13781, 1 (nrow, ncol, nlyr) 856 | ## resolution : 0.001169079, 0.001169079 (x, y) 857 | ## extent : -119.2604, -103.1493, 39.61248, 50.33177 (xmin, xmax, ymin, ymax) 858 | ## coord. ref. : +proj=longlat +datum=WGS84 +no_defs 859 | ## source : spat_y9S0rklDm0ll97v_2068.tif 860 | ## name : GrossEmissions_v101_USA_Fire 861 | ## min value : 1 862 | ## max value : 2 863 | ``` 864 | 865 | It's a new raster object named *prjFireInsect* that has the standard Geographic projection with latitude and longitude expressed in decimal degrees (DD) as its CRS. 866 | 867 | We will plot *prjFireInsect* with slightly different arguments than *fireInsect* to "zoom in" to the center of the plot. Also, we will use *threeStates* instead of *transStates* because *threeStates* also uses the Geographic projection. 868 | 869 | 870 | ```r 871 | plot(prjFireInsect, 872 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 873 | xlab = "longitude (DD)", 874 | ylab = "latitude (DD)", 875 | plg = list(legend=c("insect","fire"), title="\n Disturbance"), 876 | col = c("dark green", "red"), 877 | mar=c(3.1, 1.1, 2.8, 1.1), 878 | ext = st_bbox(prjFireInsect)/1.25, 879 | box = FALSE) 880 | 881 | plot(threeStates$geometry, 882 | border = "black", 883 | add = TRUE) 884 | ``` 885 | 886 | ![](edwebinar_mar19_ornldaac_tutorial_files/figure-html/reproject_3-1.png) 887 | 888 | Let's use the `writeRaster()` function to save *prjFireInsect* to the data directory. We will save the file in GeoTIFF (\*.tif) format so that the geographic information of the raster object is retrievable outside of R. 889 | 890 | 891 | ```r 892 | writeRaster(prjFireInsect, filename = "./data/prjFireInsect.tif", overwrite=TRUE) 893 | ``` 894 | 895 | Use the function `file.exists()`, which tests for the existence of a given file, to ensure that *prjFireInsect* was successfully saved to our working directory. 896 | 897 | 898 | ```r 899 | file.exists("./data/prjFireInsect.tif") 900 | ``` 901 | 902 | ``` 903 | ## [1] TRUE 904 | ``` 905 | 906 | Now we are able to share the raster with others or open it in another program. 907 | 908 | # Export a Plot as PNG and Raster as KML 909 | 910 | > *Functions featured in this section:* 911 | > **KML()** 912 | > exports raster object data to a KML file 913 | 914 | To save an imaage of the final plot, we use `png()` and repeat the plot() command. The png() function will open a graphics device that will save the plot we run in \*.png format. We will use the function `dev.off()` to tell R when we are finished plotting and want to close the graphics device. 915 | 916 | 917 | ```r 918 | png("prjFireInsect.png", width=650, res=80) 919 | plot(prjFireInsect, 920 | main = "Locations of Forest Disturbance\n across ID, MT, WY Forests (2006-2010)", 921 | xlab = "longitude (DD)", 922 | ylab = "latitude (DD)", 923 | plg = list(legend=c("insect","fire"), title="\n Disturbance"), 924 | col = c("dark green", "red"), 925 | mar=c(3.1, 1.1, 2.8, 1.1), 926 | ext = st_bbox(prjFireInsect)/1.25, 927 | box = FALSE) 928 | plot(threeStates$geometry, 929 | border = "black", 930 | add = TRUE) 931 | dev.off() 932 | ``` 933 | 934 | It might be useful to save *prjFireInsect* in \*.kmz format. KML stands for Keyhole Markup Language, and KMZ is the compressed version of KML format. These formats were developed for geographic visualization in Google Earth. 935 | 936 | At present, the *terra* package does not include an option to export a SpatRaster object as a KML/KMZ; it is necessary to use the *raster* package. First, *prjFireInsect* must be converted to a Raster object then saved using the raster package's KML() function. 937 | 938 | 939 | ```r 940 | prjFireInsect.r <- as(prjFireInsect, "Raster") # convert to a 'Raster' object 941 | KML(prjFireInsect.r, "./data/prjFireInsect.kmz", col = c("dark green", "red"), overwrite=TRUE) 942 | ``` 943 | 944 | We successfully saved the raster object as a KMZ file. 945 | 946 | *** 947 | 948 | This is the end to the tutorial. If you liked this tutorial, please tell us on [EarthData Forum](https://forum.earthdata.nasa.gov/). If you would like to make a suggestion for a new tutorial, please email uso@ornl.gov. 949 | 950 | There is a supplemental document included on GitHub that offers two additional sections, *Perform a Focal Analysis* and *Get Cell Coordinates*. 951 | 952 | 953 | --------------------------------------------------------------------------------