├── .gitignore ├── CITATION.cff ├── README.md ├── LICENSE └── OT_RayShader.Rmd /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Nathaniel" 5 | given-names: "Quinn" 6 | - family-names: "Beckley" 7 | given-names: "Matthew" 8 | title: "OpenTopography Overview of RayShader Library" 9 | version: 1.0.0 10 | date-released: 2021-07-28 11 | repository-code: "https://github.com/OpenTopography/RayShader" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NSF-1948997](https://img.shields.io/badge/NSF-1948997-blue.svg)](https://nsf.gov/awardsearch/showAward?AWD_ID=1948997) 2 | [![NSF-1948994](https://img.shields.io/badge/NSF-1948994-blue.svg)](https://nsf.gov/awardsearch/showAward?AWD_ID=1948994) 3 | [![NSF-1948857](https://img.shields.io/badge/NSF-1948857-blue.svg)](https://nsf.gov/awardsearch/showAward?AWD_ID=1948857) 4 | 5 | # Creating 3D Landscapes with OpenTopography and Rayshader 6 | 7 | This is an R Markdown Notebook created by OpenTopography profiling the [RayShader](https://cran.r-project.org/web/packages/rayshader/rayshader.pdf) package. This package utilizes a combination of raytracing and multiple hill shading methods to produce 2D and 3D data visualizations and maps. 8 | 9 | OpenTopography Blog Post: [Creating 2D and 3D Visualizations with Rayshader](https://opentopography.org/blog/creating-2d-and-3d-visualizations-rayshader) 10 | 11 | Email: info@opentopography.org with any questions or comments. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, OpenTopography Facility 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /OT_RayShader.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OpenTopography Overview of RayShader Library" 3 | output: 4 | html_document: 5 | df_print: paged 6 | html_notebook: default 7 | pdf_document: default 8 | --- 9 | 10 | PURPOSE: 11 | 12 | This is a [R Markdown](http://rmarkdown.rstudio.com) Notebook created by [OpenTopography](https://opentopography.org/) profiling the [RayShader](https://cran.r-project.org/web/packages/rayshader/rayshader.pdf) package. This package utilizes a combination of raytracing and multiple hill shading methods to produce 2D and 3D data visualizations and maps. A variety of options are explored in this tutorial, however, please see full package documentation as many more features are available. 13 | 14 | TECHNICAL CONTRIBUTIONS: 15 | 16 | This notebook calls the [USGS 3DEPElevation Image Server](https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage) for lidar digital elevation models (DEMs) and [ArcGIS Rest Services Directory World_Imagery](https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer) for one meter satellite and aerial imagery. When selecting a region to visualize, note that these sources only support locations within the United States. 17 | 18 | The notebook culminates in a 3D model of the specified area overlaid with the imagery from that location. This creates an accurate model in both form and color of the DEM. 19 | 20 | FUNDING: 21 | 22 | OpenTopography is supported by the National Science Foundation under Award Numbers 1948997, 1948994 & 1948857 23 | 24 | ACKNOWLEGMENTS: 25 | 26 | We want to thank the RayShader creator [Tyler Morgan-Wall](https://www.tylermw.com/) for the continued package maintenance. As noted before, there are a vast number of ways to use the RayShader package, this notebook is only designed to get users started. Please let us know if you have any comments or suggestions and [Happy RayShading](https://cran.r-project.org/web/packages/rayshader/rayshader.pdf)! 27 | 28 | Notes: 29 | When code snippets are between two #comments that are hashtagged, this is an area that requires user input. 30 | 31 | --------- 32 | 33 | Load (or install if needed) necessary libraries. 34 | ```{r} 35 | library(rayshader) 36 | library(rayrender) 37 | library(shiny) 38 | library(leaflet) 39 | library(leaflet.extras) 40 | library(sf) 41 | library(tiff) 42 | library(rgl) 43 | ``` 44 | 45 | 46 | Run the following chunk to locate the area to be visualized. When it is run, a map will open, zoom to the area of interest, and use button with black square to place bounding box. 47 | ```{r} 48 | ui <- fluidPage( 49 | leafletOutput("map") 50 | ) 51 | 52 | server <- function(input, output, session) { 53 | output$map <- renderLeaflet({ 54 | m <- leaflet() %>% 55 | addTiles() %>% 56 | addDrawToolbar(polylineOptions = F, circleOptions = F, markerOptions = F, 57 | circleMarkerOptions = F, polygonOptions = F) 58 | }) 59 | 60 | observeEvent(input$map_draw_new_feature, { 61 | feat <- input$map_draw_new_feature 62 | coords <- unlist(feat$geometry$coordinates) 63 | coords <- matrix(coords, ncol = 2, byrow = T) 64 | poly <- st_sf(st_sfc(st_polygon(list(coords))), crs = 4326) 65 | print(st_bbox(poly)) 66 | }) 67 | } 68 | 69 | shinyApp(ui, server) 70 | ``` 71 | 72 | 73 | The coordinates in the above output will be used as the bounding box. Copy/paste the xmin, ymin, xmax, and ymax variables in following code chunk to create the bounding box. 74 | ```{r} 75 | # ADD YOUR COORDINATES HERE 76 | xmin = -121.79031 77 | ymin = 45.30387 78 | xmax = -121.58707 79 | ymax = 45.44375 80 | # ADD YOUR COORDINATES HERE 81 | 82 | bbox <- list( 83 | p1 = list(long = xmin, lat = ymin), 84 | p2 = list(long = xmax, lat = ymax) 85 | ) 86 | 87 | 88 | leaflet() %>% 89 | addTiles() %>% 90 | addRectangles( 91 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 92 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 93 | fillColor = "transparent" 94 | ) %>% 95 | fitBounds( 96 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 97 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 98 | ) 99 | ``` 100 | 101 | 102 | Make sure bounding box matches your area of interest in map above. If not, revise coordinates. 103 | 104 | Create function to define image size and resolution of the raster image and 3DEP data download. 105 | ```{r} 106 | define_image_size <- function(bbox, major_dim = 400) { 107 | aspect_ratio <- abs((bbox$p1$long - bbox$p2$long) / (bbox$p1$lat - bbox$p2$lat)) 108 | img_width <- ifelse(aspect_ratio > 1, major_dim, major_dim*aspect_ratio) %>% round() 109 | img_height <- ifelse(aspect_ratio < 1, major_dim, major_dim/aspect_ratio) %>% round() 110 | size_str <- paste(img_width, img_height, sep = ",") 111 | list(height = img_height, width = img_width, size = size_str) 112 | } 113 | ``` 114 | 115 | 116 | Note: Increasing the longest dimension variable increases resolution but slows rendering time. It is dependent on the size of your bounding box, however, recommend starting with 1000. 117 | ```{r} 118 | #ADD YOUR LONGEST DIMENSION HERE 119 | long_dim = 5000 120 | #ADD YOUR LONGEST DIMENSION HERE 121 | 122 | image_size <- define_image_size(bbox, long_dim) 123 | 124 | image_size 125 | ``` 126 | 127 | 128 | Call USGS 3DEP Elevation API with the following function. 129 | ```{r} 130 | get_usgs_elevation_data <- function(bbox, size = "5000,3000", file = NULL, 131 | sr_bbox = 4326, sr_image = 4326) { 132 | require(httr) 133 | url <- parse_url("https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage") 134 | res <- GET( 135 | url, 136 | query = list( 137 | bbox = paste(bbox$p1$long, bbox$p1$lat, bbox$p2$long, bbox$p2$lat, 138 | sep = ","), 139 | bboxSR = sr_bbox, 140 | imageSR = sr_image, 141 | size = size, 142 | format = "tiff", 143 | pixelType = "F64", 144 | noDataInterpretation = "esriNoDataMatchAny", 145 | interpolation = "+RSP_BilinearInterpolation", 146 | f = "json" 147 | ) 148 | ) 149 | 150 | if (status_code(res) == 200) { 151 | body <- content(res, type = "application/json") 152 | # TODO - check that bbox values are correct 153 | # message(jsonlite::toJSON(body, auto_unbox = TRUE, pretty = TRUE)) 154 | 155 | img_res <- GET(body$href) 156 | img_bin <- content(img_res, "raw") 157 | if (is.null(file)) 158 | file <- tempfile("elev_matrix", fileext = ".tif") 159 | writeBin(img_bin, file) 160 | message(paste("image saved to file:", file)) 161 | } else { 162 | warning(res) 163 | } 164 | invisible(file) 165 | } 166 | ``` 167 | 168 | 169 | Call the API function and save the DEM .tif of the specified area to your working directory. 170 | ```{r} 171 | elev_file <- file.path("3DEP-elevation.tif") 172 | get_usgs_elevation_data(bbox, size = image_size$size, file = elev_file, 173 | sr_bbox = 4326, sr_image = 4326) 174 | ``` 175 | 176 | 177 | Load the 3DEP data into raster format. 178 | ```{r} 179 | elev_img <- raster::raster(elev_file) 180 | elevation_matrix <- matrix( 181 | raster::extract(elev_img, raster::extent(elev_img), buffer = 1000), 182 | nrow = ncol(elev_img), ncol = nrow(elev_img) 183 | ) 184 | ``` 185 | 186 | 187 | 188 | Call the World_Imagery API with the following function. 189 | ```{r} 190 | get_arcgis_map_image <- function(bbox, map_type = "World_Imagery", file = NULL, 191 | width = 5000, height = 3000, sr_bbox = 4326) { 192 | require(httr) 193 | require(glue) 194 | require(jsonlite) 195 | 196 | url <- parse_url("https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task/execute") 197 | 198 | # define JSON query parameter 199 | web_map_param <- list( 200 | baseMap = list( 201 | baseMapLayers = list( 202 | list(url = jsonlite::unbox(glue("https://services.arcgisonline.com/ArcGIS/rest/services/{map_type}/MapServer", 203 | map_type = map_type))) 204 | ) 205 | ), 206 | exportOptions = list( 207 | outputSize = c(width, height) 208 | ), 209 | mapOptions = list( 210 | extent = list( 211 | spatialReference = list(wkid = jsonlite::unbox(sr_bbox)), 212 | xmax = jsonlite::unbox(max(bbox$p1$long, bbox$p2$long)), 213 | xmin = jsonlite::unbox(min(bbox$p1$long, bbox$p2$long)), 214 | ymax = jsonlite::unbox(max(bbox$p1$lat, bbox$p2$lat)), 215 | ymin = jsonlite::unbox(min(bbox$p1$lat, bbox$p2$lat)) 216 | ) 217 | ) 218 | ) 219 | 220 | res <- GET( 221 | url, 222 | query = list( 223 | f = "json", 224 | Format = "PNG32", 225 | Layout_Template = "MAP_ONLY", 226 | Web_Map_as_JSON = jsonlite::toJSON(web_map_param)) 227 | ) 228 | 229 | if (status_code(res) == 200) { 230 | body <- content(res, type = "application/json") 231 | message(jsonlite::toJSON(body, auto_unbox = TRUE, pretty = TRUE)) 232 | if (is.null(file)) 233 | file <- tempfile("/Downloads/overlay_img", fileext = ".png") 234 | 235 | img_res <- GET(body$results[[1]]$value$url) 236 | img_bin <- content(img_res, "raw") 237 | writeBin(img_bin, file) 238 | message(paste("image saved to file:", file)) 239 | } else { 240 | message(res) 241 | } 242 | invisible(file) 243 | } 244 | ``` 245 | 246 | 247 | Call the API function and save the satellite and aerial imagery of the specified area to your working directory. 248 | ```{r} 249 | gis_overlay_file <- "map.png" 250 | get_arcgis_map_image(bbox, map_type = "World_Imagery", file = gis_overlay_file, 251 | width = image_size$width, height = image_size$height, 252 | sr_bbox = 4326) 253 | overlay_img <- png::readPNG(gis_overlay_file) 254 | ``` 255 | 256 | 257 | Now we have all necessary data loaded to begin visualizing. RayShader includes a variety of different methods for visualization. In these two code examples, we create a 2D followed by a 3D visualization that overlays satellite imagery on the model. 258 | 259 | The 2D image of a 3D model is produced below. Note: Some models appear more realistic with water detection, some without. Do a test depending on your needs. The alphalayer integer can be set between 0 and 1, and dictates transparency of overlay imagery. Recommend starting at 0.5 and adjusting from there, with higher values reducing transparency. 260 | ```{r} 261 | elevation_matrix %>% 262 | sphere_shade(texture = "desert") %>% 263 | #add_water(detect_water(elevation_matrix), color = "desert") %>% 264 | add_shadow(ray_shade(elevation_matrix), 0.1) %>% 265 | add_shadow(ambient_shade(elevation_matrix), 0.1) %>% 266 | add_overlay(overlay_img, alphalayer = 0.8) %>% 267 | plot_map() 268 | ``` 269 | 270 | 271 | The next code snippet creates a 3Dimensional model of the specified area. This can be orbited for a variety of rendering views. Other attributes, such as shadow, water, overlay, are adjusted in similar manner as 2D graphic above. Adjust zscale depending on spatial extents to scale rendering accurately. 272 | ```{r} 273 | elevation_matrix %>% 274 | sphere_shade(texture ="desert") %>% 275 | #add_water(detect_water(elevation_matrix), color = "desert") %>% 276 | add_shadow(ray_shade(elevation_matrix), max_darken = 0.5) %>% 277 | add_shadow(ambient_shade(elevation_matrix), max_darken = 0.5) %>% 278 | add_overlay(overlay_img, alphalayer = 0.8) %>% 279 | plot_3d(elevation_matrix, zscale = .6, fov = 0, theta = 135, zoom = 0.8, phi = 45, windowsize = c(5000, 4000)) 280 | Sys.sleep(0.2) 281 | render_snapshot() 282 | ``` 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | --------------------------------------------------------------------------------