├── .Rprofile ├── .gitattributes ├── .gitignore ├── .nojekyll ├── R ├── elevation-api.R ├── find-image-coordinates.R ├── image-size.R ├── map-image-api.R ├── rayshader-gif.R └── read-elevation.R ├── README.md ├── code-samples.R ├── data-raw ├── download-demo-file.R ├── generate-montereybay-gif.R └── sample-sf-images.R ├── data ├── dem_01.tif └── sf-elevation.tif ├── images ├── map-types.png ├── monetereybay-3D.png ├── montereybay.gif ├── one-does-not-simply-gif.jpg ├── raylogosmall.png ├── sf-2D-overlay-2000.png ├── sf-2D-overlay.png ├── sf-2D.png ├── sf-3D-overlay-label-2000.png ├── sf-3D-overlay-label.png ├── sf-3D-overlay.png ├── sf-flyby-small.gif ├── sf-flyby.gif ├── sf-map.png ├── sf-satellite.png ├── sf-street.png ├── sf-topo.png └── sutro-tower.jpg ├── index.Rmd ├── index.html ├── index_files ├── Proj4Leaflet-1.0.1 │ ├── proj4-compressed.js │ └── proj4leaflet.js ├── bowser-1.9.3 │ └── bowser.min.js ├── distill-2.2.21 │ └── template.v2.js ├── figure-html5 │ ├── 2D-map-1.png │ ├── load-elevation-1.png │ ├── map-types-1.png │ ├── rayshader-2D-overlay-1.png │ ├── rayshader-3D-overlay-label-1.png │ ├── unnamed-chunk-28-1.png │ ├── unnamed-chunk-5-1.png │ └── unnamed-chunk-7-1.png ├── htmlwidgets-1.3 │ └── htmlwidgets.js ├── jquery-1.12.4 │ └── jquery.min.js ├── leaflet-1.3.1 │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ ├── leaflet.css │ └── leaflet.js ├── leaflet-binding-2.0.2 │ └── leaflet.js ├── leafletfix-1.0.0 │ └── leafletfix.css ├── rstudio_leaflet-1.3.1 │ ├── images │ │ └── 1px.png │ └── rstudio_leaflet.css └── webcomponents-2.0.0 │ └── webcomponents.js ├── rayshader-demo.Rproj ├── renv.lock └── renv ├── .gitignore ├── activate.R └── settings.dcf /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.Rmd text 2 | *.R text 3 | 4 | # files to ignore 5 | index_files/* linguist-vendored 6 | images/* linguist-vendored 7 | *.html linguist-vendored 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | rayshader-demo_files/* 6 | temp/* 7 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/.nojekyll -------------------------------------------------------------------------------- /R/elevation-api.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Download USGS elevation data from the ArcGIS REST API. 4 | #' 5 | #' @param bbox bounding box coordinates (list of 2 points with long/lat values) 6 | #' @param size image size as a string with format "," 7 | #' @param file file path to save to. Default is NULL, which will create a temp file. 8 | #' @param sr_bbox Spatial Reference code for bounding box 9 | #' @param sr_image Spatial Reference code for elevation image 10 | #' 11 | #' @details This function uses the ArcGIS REST API, specifically the 12 | #' exportImage task. You can find links below to a web UI for this 13 | #' rest endpoint and API documentation. 14 | #' 15 | #' Web UI: https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage 16 | #' API docs: https://developers.arcgis.com/rest/services-reference/export-image.htm 17 | #' 18 | #' @return file path for downloaded elevation .tif file. This can be read with 19 | #' \code{read_elevation_file()}. 20 | #' 21 | #' @examples 22 | #' bbox <- list( 23 | #' p1 = list(long = -122.522, lat = 37.707), 24 | #' p2 = list(long = -122.354, lat = 37.84) 25 | #' ) 26 | #' image_size <- define_image_size(bbox, 600) 27 | #' elev_file <- get_usgs_elevation_data(bbox, size = image_size$size) 28 | #' 29 | get_usgs_elevation_data <- function(bbox, size = "400,400", file = NULL, 30 | sr_bbox = 4326, sr_image = 4326) { 31 | require(httr) 32 | 33 | # TODO - validate inputs 34 | 35 | url <- parse_url("https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage") 36 | res <- GET( 37 | url, 38 | query = list( 39 | bbox = paste(bbox$p1$long, bbox$p1$lat, bbox$p2$long, bbox$p2$lat, 40 | sep = ","), 41 | bboxSR = sr_bbox, 42 | imageSR = sr_image, 43 | size = size, 44 | format = "tiff", 45 | pixelType = "F32", 46 | noDataInterpretation = "esriNoDataMatchAny", 47 | interpolation = "+RSP_BilinearInterpolation", 48 | f = "json" 49 | ) 50 | ) 51 | 52 | if (status_code(res) == 200) { 53 | body <- content(res, type = "application/json") 54 | # TODO - check that bbox values are correct 55 | # message(jsonlite::toJSON(body, auto_unbox = TRUE, pretty = TRUE)) 56 | 57 | img_res <- GET(body$href) 58 | img_bin <- content(img_res, "raw") 59 | if (is.null(file)) 60 | file <- tempfile("elev_matrix", fileext = ".tif") 61 | writeBin(img_bin, file) 62 | message(paste("image saved to file:", file)) 63 | } else { 64 | warning(res) 65 | } 66 | invisible(file) 67 | } 68 | 69 | -------------------------------------------------------------------------------- /R/find-image-coordinates.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Translate the given long/lat coordinates into an image position (x, y). 4 | #' 5 | #' @param long longitude value 6 | #' @param lat latitude value 7 | #' @param bbox bounding box coordinates (list of 2 points with long/lat values) 8 | #' @param image_width image width, in pixels 9 | #' @param image_height image height, in pixels 10 | #' 11 | #' @return named list with elements "x" and "y" defining an image position 12 | #' 13 | find_image_coordinates <- function(long, lat, bbox, image_width, image_height) { 14 | x_img <- round(image_width * (long - min(bbox$p1$long, bbox$p2$long)) / abs(bbox$p1$long - bbox$p2$long)) 15 | y_img <- round(image_height * (lat - min(bbox$p1$lat, bbox$p2$lat)) / abs(bbox$p1$lat - bbox$p2$lat)) 16 | list(x = x_img, y = y_img) 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /R/image-size.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Define image size variables from the given bounding box coordinates. 4 | #' 5 | #' @param bbox bounding box coordinates (list of 2 points with long/lat values) 6 | #' @param major_dim major image dimension, in pixels. 7 | #' Default is 400 (meaning larger dimension will be 400 pixels) 8 | #' 9 | #' @return list with items "width", "height", and "size" (string of format ",") 10 | #' 11 | #' @examples 12 | #' bbox <- list( 13 | #' p1 = list(long = -122.522, lat = 37.707), 14 | #' p2 = list(long = -122.354, lat = 37.84) 15 | #' ) 16 | #' image_size <- define_image_size(bbox, 600) 17 | #' 18 | define_image_size <- function(bbox, major_dim = 400) { 19 | # calculate aspect ration (width/height) from lat/long bounding box 20 | aspect_ratio <- abs((bbox$p1$long - bbox$p2$long) / (bbox$p1$lat - bbox$p2$lat)) 21 | # define dimensions 22 | img_width <- ifelse(aspect_ratio > 1, major_dim, major_dim*aspect_ratio) %>% round() 23 | img_height <- ifelse(aspect_ratio < 1, major_dim, major_dim/aspect_ratio) %>% round() 24 | size_str <- paste(img_width, img_height, sep = ",") 25 | list(height = img_height, width = img_width, size = size_str) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /R/map-image-api.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Download a map image from the ArcGIS REST API 4 | #' 5 | #' @param bbox bounding box coordinates (list of 2 points with long/lat values) 6 | #' @param map_type map type to download - options are World_Street_Map, World_Imagery, World_Topo_Map 7 | #' @param file file path to save to. Default is NULL, which will create a temp file. 8 | #' @param width image width (pixels) 9 | #' @param height image height (pixels) 10 | #' @param sr_bbox Spatial Reference code for bounding box 11 | #' 12 | #' @details This function uses the ArcGIS REST API, specifically the 13 | #' "Execute Web Map Task" task. You can find links below to a web UI for this 14 | #' rest endpoint and API documentation. 15 | #' 16 | #' Web UI: https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task/execute 17 | #' API docs: https://developers.arcgis.com/rest/services-reference/export-web-map-task.htm 18 | #' 19 | #' @return file path for the downloaded .png map image 20 | #' 21 | #' @examples 22 | #' bbox <- list( 23 | #' p1 = list(long = -122.522, lat = 37.707), 24 | #' p2 = list(long = -122.354, lat = 37.84) 25 | #' ) 26 | #' image_size <- define_image_size(bbox, 600) 27 | #' overlay_file <- get_arcgis_map_image(bbox, width = image_size$width, 28 | #' height = image_size$height) 29 | #' 30 | get_arcgis_map_image <- function(bbox, map_type = "World_Street_Map", file = NULL, 31 | width = 400, height = 400, sr_bbox = 4326) { 32 | require(httr) 33 | require(glue) 34 | require(jsonlite) 35 | 36 | url <- parse_url("https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task/execute") 37 | 38 | # define JSON query parameter 39 | web_map_param <- list( 40 | baseMap = list( 41 | baseMapLayers = list( 42 | list(url = jsonlite::unbox(glue("https://services.arcgisonline.com/ArcGIS/rest/services/{map_type}/MapServer", 43 | map_type = map_type))) 44 | ) 45 | ), 46 | exportOptions = list( 47 | outputSize = c(width, height) 48 | ), 49 | mapOptions = list( 50 | extent = list( 51 | spatialReference = list(wkid = jsonlite::unbox(sr_bbox)), 52 | xmax = jsonlite::unbox(max(bbox$p1$long, bbox$p2$long)), 53 | xmin = jsonlite::unbox(min(bbox$p1$long, bbox$p2$long)), 54 | ymax = jsonlite::unbox(max(bbox$p1$lat, bbox$p2$lat)), 55 | ymin = jsonlite::unbox(min(bbox$p1$lat, bbox$p2$lat)) 56 | ) 57 | ) 58 | ) 59 | 60 | res <- GET( 61 | url, 62 | query = list( 63 | f = "json", 64 | Format = "PNG32", 65 | Layout_Template = "MAP_ONLY", 66 | Web_Map_as_JSON = jsonlite::toJSON(web_map_param)) 67 | ) 68 | 69 | if (status_code(res) == 200) { 70 | body <- content(res, type = "application/json") 71 | message(jsonlite::toJSON(body, auto_unbox = TRUE, pretty = TRUE)) 72 | if (is.null(file)) 73 | file <- tempfile("overlay_img", fileext = ".png") 74 | 75 | img_res <- GET(body$results[[1]]$value$url) 76 | img_bin <- content(img_res, "raw") 77 | writeBin(img_bin, file) 78 | message(paste("image saved to file:", file)) 79 | } else { 80 | message(res) 81 | } 82 | invisible(file) 83 | } 84 | 85 | -------------------------------------------------------------------------------- /R/rayshader-gif.R: -------------------------------------------------------------------------------- 1 | 2 | #' Build a gif of 3D rayshader plots 3 | #' 4 | #' @param hillshade Hillshade/image to be added to 3D surface map. 5 | #' @param heightmap A two-dimensional matrix, where each entry in the matrix is the elevation at that point. 6 | #' @param file file path for .gif 7 | #' @param duration gif duration in seconds (framerate will be duration/n_frames) 8 | #' @param ... additional arguments passed to rayshader::plot_3d(). See Details for more info. 9 | #' 10 | #' @details This function is designed to be a pipe-in replacement for rayshader::plot_3d(), 11 | #' but it will generate a 3D animated gif. Any inputs with lengths >1 will 12 | #' be interpreted as "animation" variables, which will be used to generate 13 | #' individual animation frames -- e.g. a vector of theta values would produce 14 | #' a rotating gif. Inputs to plot_3d() that are meant to have length >1 15 | #' (specifically "windowsize") will be excluded from this process. 16 | #' 17 | #' @return file path of .gif file created 18 | #' 19 | #' @examples 20 | #' # MONTEREREY BAY WATER DRAINING 21 | #' # ------------------------------ 22 | #' # define transition variables 23 | #' n_frames <- 180 24 | #' waterdepths <- transition_values(from = 0, to = min(montereybay), steps = n_frames) 25 | #' thetas <- transition_values(from = -45, to = -135, steps = n_frames) 26 | #' # generate gif 27 | #' zscale <- 50 28 | #' montereybay %>% 29 | #' sphere_shade(texture = "imhof1", zscale = zscale) %>% 30 | #' add_shadow(ambient_shade(montereybay, zscale = zscale), 0.5) %>% 31 | #' add_shadow(ray_shade(montereybay, zscale = zscale, lambert = TRUE), 0.5) %>% 32 | #' save_3d_gif(montereybay, file = "montereybay.gif", duration = 6, 33 | #' solid = TRUE, shadow = TRUE, water = TRUE, zscale = zscale, 34 | #' watercolor = "imhof3", wateralpha = 0.8, 35 | #' waterlinecolor = "#ffffff", waterlinealpha = 0.5, 36 | #' waterdepth = waterdepths/zscale, 37 | #' theta = thetas, phi = 45) 38 | #' 39 | save_3d_gif <- function(hillshade, heightmap, file, duration = 5, ...) { 40 | require(rayshader) 41 | require(magick) 42 | require(rgl) 43 | require(gifski) 44 | require(rlang) 45 | 46 | # capture dot arguments and extract variables with length > 1 for gif frames 47 | dots <- rlang::list2(...) 48 | var_exception_list <- c("windowsize") 49 | dot_var_lengths <- purrr::map_int(dots, length) 50 | gif_var_names <- names(dots)[dot_var_lengths > 1 & 51 | !(names(dots) %in% var_exception_list)] 52 | # split off dot variables to use on gif frames 53 | gif_dots <- dots[gif_var_names] 54 | static_dots <- dots[!(names(dots) %in% gif_var_names)] 55 | gif_var_lengths <- purrr::map_int(gif_dots, length) 56 | # build expressions for gif variables that include index 'i' (to use in the for loop) 57 | gif_expr_list <- purrr::map(names(gif_dots), ~rlang::expr(gif_dots[[!!.x]][i])) 58 | gif_exprs <- exprs(!!!gif_expr_list) 59 | names(gif_exprs) <- names(gif_dots) 60 | message(paste("gif variables found:", paste(names(gif_dots), collapse = ", "))) 61 | 62 | # TODO - can we recycle short vectors? 63 | if (length(unique(gif_var_lengths)) > 1) 64 | stop("all gif input vectors must be the same length") 65 | n_frames <- unique(gif_var_lengths) 66 | 67 | # generate temp .png images 68 | temp_dir <- tempdir() 69 | img_frames <- file.path(temp_dir, paste0("frame-", seq_len(n_frames), ".png")) 70 | on.exit(unlink(img_frames)) 71 | message(paste("Generating", n_frames, "temporary .png images...")) 72 | for (i in seq_len(n_frames)) { 73 | message(paste(" - image", i, "of", n_frames)) 74 | rgl::clear3d() 75 | hillshade %>% 76 | plot_3d_tidy_eval(heightmap, !!!append(gif_exprs, static_dots)) 77 | rgl::snapshot3d(img_frames[i]) 78 | } 79 | 80 | # build gif 81 | message("Generating .gif...") 82 | magick::image_write_gif(magick::image_read(img_frames), 83 | path = file, delay = duration/n_frames) 84 | message("Done!") 85 | invisible(file) 86 | } 87 | 88 | 89 | plot_3d_tidy_eval <- function(hillshade, ...) { 90 | dots <- rlang::enquos(...) 91 | plot_3d_call <- rlang::expr(plot_3d(hillshade, !!!dots)) 92 | rlang::eval_tidy(plot_3d_call) 93 | } 94 | 95 | 96 | #' Create a numeric vector of transition values. 97 | #' @description This function helps generate a sequence 98 | #' of numeric values to transition "from" a start point 99 | #' "to" some end point. The transition can be "one_way" 100 | #' (meaning it ends at the "to" point) or "two_way" (meaning 101 | #' we return back to end at the "from" point). 102 | #' 103 | #' @param from starting point for transition values 104 | #' @param to ending point (for one-way transitions) or turn-around point 105 | #' (for two-way transitions) 106 | #' @param steps the number of steps to take in the transation (i.e. the length 107 | #' of the returned vector) 108 | #' @param one_way logical value to determine if we should stop at the "to" value 109 | #' (TRUE) or turn around and return to the "from" value (FALSE) 110 | #' @param type string defining the transition type - currently suppoerts "cos" 111 | #' (for a cosine curve) and "lin" (for linear steps) 112 | #' 113 | #' @return a numeric vector of transition values 114 | #' 115 | transition_values <- function(from, to, steps = 10, 116 | one_way = FALSE, type = "cos") { 117 | if (!(type %in% c("cos", "lin"))) 118 | stop("type must be one of: 'cos', 'lin'") 119 | 120 | range <- c(from, to) 121 | middle <- mean(range) 122 | half_width <- diff(range)/2 123 | 124 | # define scaling vector starting at 1 (between 1 to -1) 125 | if (type == "cos") { 126 | scaling <- cos(seq(0, 2*pi / ifelse(one_way, 2, 1), length.out = steps)) 127 | } else if (type == "lin") { 128 | if (one_way) { 129 | xout <- seq(1, -1, length.out = steps) 130 | } else { 131 | xout <- c(seq(1, -1, length.out = floor(steps/2)), 132 | seq(-1, 1, length.out = ceiling(steps/2))) 133 | } 134 | scaling <- approx(x = c(-1, 1), y = c(-1, 1), xout = xout)$y 135 | } 136 | 137 | middle - half_width * scaling 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /R/read-elevation.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Read an elevation image file (like a .tif) into 4 | #' an elevation matrix. 5 | #' 6 | #' @param file file path 7 | #' 8 | #' @return A two-dimensional matrix, where each entry in the matrix is the 9 | #' elevation at that point. 10 | #' 11 | read_elevation_file <- function(file) { 12 | elev_img <- raster::raster(file) 13 | elev_matrix <- matrix( 14 | raster::extract(elev_img, raster::extent(elev_img), buffer = 1000), 15 | nrow = ncol(elev_img), ncol = nrow(elev_img) 16 | ) 17 | elev_matrix 18 | } 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rayshader-demo 2 | 3 | This repo is the source code for an exploration of the [`rayshader`](https://www.rayshader.com) package. 4 | 5 | The output is a **web article**, available via GitHub pages [here](https://wcmbishop.github.io/rayshader-demo/). 6 | 7 | You can find all the custom functions used in this article inside the `./R` folder. Also, the file `code-samples.R` is a collection of all the code samples used in the article, collected in one place. 8 | 9 | ![big-3D-san-francisco](images/sf-3D-overlay-label-2000.png) 10 | -------------------------------------------------------------------------------- /code-samples.R: -------------------------------------------------------------------------------- 1 | # Code samples presented in the radix web article 2 | # on making your own custom rayshader plots. 3 | 4 | library(httr) 5 | library(glue) 6 | library(jsonlite) 7 | library(leaflet) 8 | library(raster) 9 | library(rayshader) 10 | 11 | source(file.path("R", "map-image-api.R")) 12 | source(file.path("R", "elevation-api.R")) 13 | source(file.path("R", "image-size.R")) 14 | source(file.path("R", "rayshader-gif.R")) 15 | source(file.path("R", "read-elevation.R")) 16 | source(file.path("R", "find-image-coordinates.R")) 17 | 18 | 19 | # montererybay example -------------------------------------------------- 20 | montereybay %>% 21 | sphere_shade(zscale = 10, texture = "imhof1") %>% 22 | add_shadow(ray_shade(montereybay, zscale = 50)) %>% 23 | add_shadow(ambient_shade(montereybay, zscale = 50)) %>% 24 | plot_3d(montereybay, zscale = 50, theta = -45, phi = 45, water = TRUE, 25 | windowsize = c(1000,800), zoom = 0.75, waterlinealpha = 0.3, 26 | wateralpha = 0.5, watercolor = "lightblue", waterlinecolor = "white") 27 | render_snapshot() 28 | 29 | 30 | # selecting a map region -------------------------------------------------- 31 | # define bounding box with longitude/latitude coordinates 32 | bbox <- list( 33 | p1 = list(long = -122.522, lat = 37.707), 34 | p2 = list(long = -122.354, lat = 37.84) 35 | ) 36 | # display the bounding box 37 | leaflet() %>% 38 | addTiles() %>% 39 | addRectangles( 40 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 41 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 42 | fillColor = "transparent" 43 | ) %>% 44 | fitBounds( 45 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 46 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 47 | ) 48 | # define image size for map image files 49 | image_size <- define_image_size(bbox, major_dim = 600) 50 | 51 | 52 | # getting elevation data -------------------------------------------------- 53 | # download data from api 54 | elev_file <- file.path("data", "sf-elevation.tif") 55 | get_usgs_elevation_data(bbox, size = image_size$size, 56 | file = elev_file, 57 | sr_bbox = 4326, sr_image = 4326) 58 | # load data into an elevation matrix 59 | elev_matrix <- read_elevation_file(elev_file) 60 | 61 | 62 | # getting map overlay image -------------------------------------------------- 63 | overlay_file <- "images/sf-map.png" 64 | get_arcgis_map_image(bbox, map_type = "World_Topo_Map", file = overlay_file, 65 | width = image_size$width, height = image_size$height, 66 | sr_bbox = 4326) 67 | overlay_img <- png::readPNG(overlay_file) 68 | 69 | 70 | # plot 2D -------------------------------------------------- 71 | # calculate rayshader layers 72 | ambmat <- ambient_shade(elev_matrix, zscale = 30) 73 | raymat <- ray_shade(elev_matrix, zscale = 30, lambert = TRUE) 74 | watermap <- detect_water(elev_matrix) 75 | 76 | # plot 2D 77 | elev_matrix %>% 78 | sphere_shade(texture = "imhof4") %>% 79 | add_water(watermap, color = "imhof4") %>% 80 | add_shadow(raymat, max_darken = 0.5) %>% 81 | add_shadow(ambmat, max_darken = 0.5) %>% 82 | plot_map() 83 | 84 | 85 | # plot 2D with overlay -------------------------------------------------- 86 | elev_matrix %>% 87 | sphere_shade(texture = "imhof4") %>% 88 | add_water(watermap, color = "imhof4") %>% 89 | add_shadow(raymat, max_darken = 0.5) %>% 90 | add_shadow(ambmat, max_darken = 0.5) %>% 91 | add_overlay(overlay_img, alphalayer = 0.5) %>% 92 | plot_map() 93 | 94 | 95 | # plot 3D with overlay -------------------------------------------------- 96 | zscale <- 10 97 | rgl::clear3d() 98 | elev_matrix %>% 99 | sphere_shade(texture = "imhof4") %>% 100 | add_water(watermap, color = "imhof4") %>% 101 | add_overlay(overlay_img, alphalayer = 0.5) %>% 102 | add_shadow(raymat, max_darken = 0.5) %>% 103 | add_shadow(ambmat, max_darken = 0.5) %>% 104 | plot_3d(elev_matrix, zscale = zscale, windowsize = c(1200, 1000), 105 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, 106 | theta = 25, phi = 30, zoom = 0.65, fov = 60) 107 | render_snapshot() 108 | 109 | 110 | # plot 3D with overlay and label -------------------------------------------------- 111 | # define label 112 | label <- list(text = "Sutro Tower") 113 | label$pos <- find_image_coordinates( 114 | long = -122.452131, lat = 37.756735, bbox = bbox, 115 | image_width = image_size$width, image_height = image_size$height) 116 | 117 | # plot 3D 118 | zscale <- 10 119 | rgl::clear3d() 120 | elev_matrix %>% 121 | sphere_shade(texture = "imhof4") %>% 122 | add_water(watermap, color = "imhof4") %>% 123 | add_overlay(overlay_img, alphalayer = 0.5) %>% 124 | add_shadow(raymat, max_darken = 0.5) %>% 125 | add_shadow(ambmat, max_darken = 0.5) %>% 126 | plot_3d(elev_matrix, zscale = zscale, windowsize = c(1200, 1000), 127 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, wateralpha = 0, 128 | theta = 25, phi = 30, zoom = 0.65, fov = 60) 129 | # add label 130 | render_label(elev_matrix, x = label$pos$x, y = label$pos$y, z = 500, 131 | zscale = zscale, text = label$text, textsize = 2, linewidth = 5) 132 | render_snapshot() 133 | 134 | 135 | # montererybay gif -------------------------------------------------- 136 | # calculate input vectors for gif frames 137 | n_frames <- 180 138 | waterdepths <- transition_values(from = 0, to = min(montereybay), steps = n_frames) 139 | thetas <- transition_values(from = -45, to = -135, steps = n_frames) 140 | # generate gif 141 | zscale <- 50 142 | montereybay %>% 143 | sphere_shade(texture = "imhof1", zscale = zscale) %>% 144 | add_shadow(ambient_shade(montereybay, zscale = zscale), 0.5) %>% 145 | add_shadow(ray_shade(montereybay, zscale = zscale, lambert = TRUE), 0.5) %>% 146 | save_3d_gif(montereybay, file = "montereybay.gif", duration = 6, 147 | solid = TRUE, shadow = TRUE, water = TRUE, zscale = zscale, 148 | watercolor = "imhof3", wateralpha = 0.8, 149 | waterlinecolor = "#ffffff", waterlinealpha = 0.5, 150 | waterdepth = waterdepths/zscale, 151 | theta = thetas, phi = 45) 152 | 153 | 154 | # san francisco fly-by gif -------------------------------------------------- 155 | # create transition variables 156 | n_frames <- 180 157 | theta <- transition_values(from = 0, to = 360, steps = n_frames, 158 | one_way = TRUE, type = "lin") 159 | phi <- transition_values(from = 10, to = 70, steps = n_frames, 160 | one_way = FALSE, type = "cos") 161 | zoom <- transition_values(from = 0.4, to = 0.8, steps = n_frames, 162 | one_way = FALSE, type = "cos") 163 | 164 | # gif it! 165 | zscale <- 10 166 | elev_matrix %>% 167 | sphere_shade(texture = "imhof4") %>% 168 | add_water(watermap, color = "imhof4") %>% 169 | add_overlay(overlay_img, alphalayer = 0.5) %>% 170 | add_shadow(raymat, 0.4) %>% 171 | add_shadow(ambmat, 0.4) %>% 172 | save_3d_gif(elev_matrix, file = "images/sf-flyby.gif", duration = 6, 173 | zscale = zscale, windowsize = c(1200, 1000), wateralpha = 0, 174 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, 175 | theta = theta, phi = phi, zoom = zoom, fov = 60) 176 | 177 | -------------------------------------------------------------------------------- /data-raw/download-demo-file.R: -------------------------------------------------------------------------------- 1 | # download-demo-file.R 2 | # download demo elevation .tif from rayshader 3 | 4 | loadzip = file.path("data", "dem_01.tif.zip") 5 | download.file("https://tylermw.com/data/dem_01.tif.zip", loadzip) 6 | unzip(loadzip, exdir = "data", files = "dem_01.tif") 7 | unlink(loadzip) 8 | -------------------------------------------------------------------------------- /data-raw/generate-montereybay-gif.R: -------------------------------------------------------------------------------- 1 | # generate montereybay.gif 2 | library(httr) 3 | library(glue) 4 | library(jsonlite) 5 | library(raster) 6 | library(rayshader) 7 | source(file.path("R", "rayshader-gif.R")) 8 | 9 | # montery water gif - easier ==== 10 | n_frames <- 180 11 | zscale <- 50 12 | # calculate input vectors for gif frames 13 | waterdepths <- transition_values(start = 0, end = min(montereybay), steps = n_frames) 14 | thetas <- transition_values(start = -45, end = -135, steps = n_frames) 15 | # generate gif 16 | montereybay %>% 17 | sphere_shade(texture = "imhof1", zscale = 10) %>% 18 | add_shadow(ambient_shade(montereybay, zscale = zscale), 0.5) %>% 19 | add_shadow(ray_shade(montereybay, zscale = zscale, lambert = TRUE), 0.5) %>% 20 | create_3d_gif(montereybay, file = file.path("images", "montereybay.gif"), duration = 6, 21 | solid = TRUE, shadow = TRUE, water = TRUE, zscale = zscale, 22 | watercolor = "imhof3", wateralpha = 0.8, 23 | waterlinecolor = "#ffffff", waterlinealpha = 0.5, 24 | waterdepth = waterdepths/zscale, 25 | theta = thetas, phi = 45) 26 | -------------------------------------------------------------------------------- /data-raw/sample-sf-images.R: -------------------------------------------------------------------------------- 1 | library(httr) 2 | library(glue) 3 | library(jsonlite) 4 | source(file.path("R", "map-image-api.R")) 5 | source(file.path("R", "elevation-api.R")) 6 | source(file.path("R", "image-size.R")) 7 | 8 | 9 | # SF bounding box 10 | bbox <- list( 11 | p1 = list(long = -122.522, lat = 37.707), 12 | p2 = list(long = -122.354, lat = 37.84) #37.84, 37.817 13 | ) 14 | image_size <- define_image_size(bbox, major_dim = 400) 15 | 16 | 17 | # download different map types 18 | get_map_image(bbox, map_type = "World_Topo_Map", 19 | file = file.path("images", "sf-topo.png"), 20 | width = image_size$width, 21 | height = image_size$height) 22 | get_map_image(bbox, map_type = "World_Imagery", 23 | file = file.path("images", "sf-satellite.png"), 24 | width = image_size$width, 25 | height = image_size$height) 26 | get_map_image(bbox, map_type = "World_Street_Map", 27 | file = file.path("images", "sf-street.png"), 28 | width = image_size$width, 29 | height = image_size$height) 30 | 31 | -------------------------------------------------------------------------------- /data/dem_01.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/data/dem_01.tif -------------------------------------------------------------------------------- /data/sf-elevation.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/data/sf-elevation.tif -------------------------------------------------------------------------------- /images/map-types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/map-types.png -------------------------------------------------------------------------------- /images/monetereybay-3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/monetereybay-3D.png -------------------------------------------------------------------------------- /images/montereybay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/montereybay.gif -------------------------------------------------------------------------------- /images/one-does-not-simply-gif.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/one-does-not-simply-gif.jpg -------------------------------------------------------------------------------- /images/raylogosmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/raylogosmall.png -------------------------------------------------------------------------------- /images/sf-2D-overlay-2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-2D-overlay-2000.png -------------------------------------------------------------------------------- /images/sf-2D-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-2D-overlay.png -------------------------------------------------------------------------------- /images/sf-2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-2D.png -------------------------------------------------------------------------------- /images/sf-3D-overlay-label-2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-3D-overlay-label-2000.png -------------------------------------------------------------------------------- /images/sf-3D-overlay-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-3D-overlay-label.png -------------------------------------------------------------------------------- /images/sf-3D-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-3D-overlay.png -------------------------------------------------------------------------------- /images/sf-flyby-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-flyby-small.gif -------------------------------------------------------------------------------- /images/sf-flyby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-flyby.gif -------------------------------------------------------------------------------- /images/sf-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-map.png -------------------------------------------------------------------------------- /images/sf-satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-satellite.png -------------------------------------------------------------------------------- /images/sf-street.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-street.png -------------------------------------------------------------------------------- /images/sf-topo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sf-topo.png -------------------------------------------------------------------------------- /images/sutro-tower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/images/sutro-tower.jpg -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "I can rayshade, and so can you" 3 | description: | 4 | A beginner's tour of making custom maps with `rayshader` in R. 5 | author: 6 | - name: Will Bishop 7 | url: https://www.github.com/wcmbishop 8 | affiliation: "@wcmbishop" 9 | affiliation_url: https://twitter.com/wcmbishop 10 | date: "Feb 8, 2019" 11 | output: 12 | radix::radix_article: 13 | toc: true 14 | toc_depth: 3 15 | self_contained: false 16 | repository_url: https://www.github.com/wcmbishop/rayshader-demo 17 | base_url: https://wcmbishop.github.io/rayshader-demo 18 | preview: images/sf-2D-overlay.png 19 | creative_commons: CC BY 20 | --- 21 | 22 | ```{r setup, include=FALSE} 23 | knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE) 24 | 25 | library(httr) 26 | library(glue) 27 | library(jsonlite) 28 | library(raster) 29 | library(rayshader) 30 | 31 | source(file.path("R", "map-image-api.R")) 32 | source(file.path("R", "elevation-api.R")) 33 | source(file.path("R", "image-size.R")) 34 | source(file.path("R", "rayshader-gif.R")) 35 | source(file.path("R", "read-elevation.R")) 36 | source(file.path("R", "find-image-coordinates.R")) 37 | 38 | elev_file <- "data/sf-elevation.tif" 39 | overlay_file <- "images/sf-map.png" 40 | overlay_img <- png::readPNG(overlay_file) 41 | ``` 42 | 43 | # Introduction 44 | 45 | Last month in January, I attended the wonderful [rstudio::conf 2019](https://www.rstudio.com/conference/) in Austin, TX. One of the most interesting (and entertaining) presentations I saw at the conference was by [Tyler Morgan-Wall](https://twitter.com/tylermorganwall), talking about his magical `rayshader` package^[You can watch Tyler Morgan-Wall's full rstudio::conf 2019 presentation [online here](https://resources.rstudio.com/rstudio-conf-2019/3d-mapping-plotting-and-printing-with-rayshader)]. From Tyler's [rayshader website](https://www.rayshader.com): 46 | 47 | 52 | 53 | > rayshader is an open source R package for producing 2D and 3D hillshaded maps of elevation matrices using a combination of raytracing, spherical texture mapping, overlays, and ambient occlusion. 54 | 55 | Basically, `rayshader` makes maps -- *beautiful* 2D and 3D maps, built from raw elevation data. Like this one of Monterey Bay in California, using the code below: 56 | 57 | ```{r monterey-example, eval=FALSE} 58 | library(rayshader) 59 | 60 | montereybay %>% 61 | sphere_shade(zscale = 10, texture = "imhof1") %>% 62 | add_shadow(ray_shade(montereybay, zscale = 50)) %>% 63 | add_shadow(ambient_shade(montereybay, zscale = 50)) %>% 64 | plot_3d(montereybay, zscale = 50, theta = -45, phi = 45, water = TRUE, 65 | windowsize = c(1000,800), zoom = 0.75, waterlinealpha = 0.3, 66 | wateralpha = 0.5, watercolor = "lightblue", waterlinecolor = "white") 67 | render_snapshot() 68 | ``` 69 | 70 | ```{r monterey-example-view, echo=FALSE} 71 | knitr::include_graphics("images/monetereybay-3D.png") 72 | ``` 73 | 74 | I had seen images from `rayshader` floating around on Twitter, but it was amazing to get a (meme-filled) tour of the features from the package creator. I was inspired to try it out! I got up and running quickly with the package documentation, and I was spinning 3D plots of the included `montereybay` dataset in no time. But then I was stuck. How do I make my *own* maps? How do I add those nice image overlays? And how do I make those amazing gifs?! 75 | 76 | You see, I'm an "uneducated" `rayshader` user -- I don't know much about maps. Where do I get elevation data? What the heck is a `.tif`? Where can I download map images? How does one *simply* gif? The `rayshader` package falls on the right side of "magic" for the most part, but I found the documentation for these sorts of beginner questions lacking. So after stumbling through the darkness for a while and learning some things, I wrote this article for users like me, to help answer those questions. The message is this: **I can rayshade, and so can you**!^[Spoiler, I still don't know what a .tif is, but I'm still putting them to good use now.] 77 | 78 | ### Setup 79 | 80 | For this article, I'm using the latest dev version of `rayshader` from GitHub (version `r packageVersion("rayshader")` at the time of writing), which is significantly faster than the version I had installed from CRAN. You can download this version with the code below. Many of the code-snippets in this article also use custom functions I wrote for this post (marked with *⎈local*). You will need to source these functions locally to use them. All these functions are available in the [git source repo](https://github.com/wcmbishop/rayshader-demo) for this article, under the `R` folder, along with a single file with all the code samples from this post in `code-samples.R`.^[You can find the source code for this article, including all custom R functions (under the `R` folder), in the [git repo here](https://github.com/wcmbishop/rayshader-demo).] Now...let's get mapping. 81 | 82 | 85 | 86 | 87 | ```{r, eval = FALSE} 88 | # To install the latest version from Github: 89 | # install.packages("devtools") 90 | devtools::install_github("tylermorganwall/rayshader") 91 | ``` 92 | 93 | # Selecting a Region 94 | 95 | The first step in making My Own Map™ is to pick out a region. To keep it simple, let's stick with just a plain rectangular "bounding box" of somewhere in the world. I live in the Bay Area, so I thought I'd try out San Francisco to start with (plus, that's where rstudio::conf 2020 will be hosted!). Originally, I wanted to have a nice UI to manually draw a region on an interactive map...but that seemed hard. Instead, I settled on manually iterating on a set of longitude/latitude coordinates and displaying them in a map using the [`leaflet`](https://rstudio.github.io/leaflet/) package. The code and map below shows my selected bounding box for San Francisco. 96 | 97 | ```{r leaflet-map} 98 | library(leaflet) 99 | 100 | # define bounding box with longitude/latitude coordinates 101 | bbox <- list( 102 | p1 = list(long = -122.522, lat = 37.707), 103 | p2 = list(long = -122.354, lat = 37.84) 104 | ) 105 | 106 | leaflet() %>% 107 | addTiles() %>% 108 | addRectangles( 109 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 110 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 111 | fillColor = "transparent" 112 | ) %>% 113 | fitBounds( 114 | lng1 = bbox$p1$long, lat1 = bbox$p1$lat, 115 | lng2 = bbox$p2$long, lat2 = bbox$p2$lat, 116 | ) 117 | ``` 118 | 119 | ### Setting the Image Size 120 | 121 | Next is a small but important step. We need to define an **image size** to use based on our selected bounding box coordinates. Both our elevation data and our map overlay will be downloaded as image files (I think the term "image" file either means more than I know about, or it plays double-duty for data encoding with this spatial data). For our `rayshader` maps to work, two things are required: 122 | 123 | 1. the image dimensions need to match our bounding box coordinates (i.e have the same aspect ratio) 124 | 2. the image dimensions of the elevation data and the map overlay need to match exactly (i.e. produce the same size arrays) 125 | 126 | The custom function [`define_image_size()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/image-size.R)*⎈local* below calculates an image size to use in the rest of our code, with the results shown in json. We can set the overall size of the image by defining the major dimension (e.g. `600` pixels, which sets the larger image dimension). Larger sizes will download more elevation data and make more detailed maps. 127 | 128 | ```{r image_size} 129 | image_size <- define_image_size(bbox, major_dim = 600) 130 | ``` 131 | 132 | ```{r, echo=FALSE} 133 | jsonlite::toJSON(image_size, auto_unbox = TRUE, pretty = TRUE) 134 | ``` 135 | 136 | # Downloading Elevation Data 137 | 138 | Next we need to download some elevation data for our selected map region. This was probably the most difficult and intimidating part of this whole effort. As a person who is new to maps and layers and GIS and such, I had no idea where to start (and this field is *deep*). There are probably numerous great options out there^[Two seemingly nice paid mapping tools, with some level of free content, are [MapBox](https://www.mapbox.com) and [Planet](https://www.planet.com)], but I generally found them either confusing or costly. 139 | 140 | I wanted something **free** and **programmatic**. And the good news is, if you are looking for map regions in North America, there is an API for you! The USGS hosts a National Map tool that can export elevation data via a [web UI here](https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage?bbox=-122.522%2C37.707%2C-122.354%2C37.84&bboxSR=4326&size=600%2C480&imageSR=4326&time=&format=jpgpng&pixelType=F32&noData=&noDataInterpretation=esriNoDataMatchAny&interpolation=+RSP_BilinearInterpolation&compression=&compressionQuality=&bandIds=&mosaicRule=&renderingRule=&f=html), and they also support a REST API for the same data.^[You can find the USGS elevation data online [here](https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage), and there is good documentation on the ArcGIS REST API [here](https://developers.arcgis.com/rest/services-reference/export-image.htm)] 141 | 142 | 145 | 146 | I wrote the function [`get_usgs_elevation_data()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/elevation-api.R)*⎈local* to download elevation data using this API. The code below makes the process look easy! Now we have elevation measurements for our selected region (using our defined image size) in a .tif file, ready to raster (...whatever that means). 147 | 148 | ```{r elevation-api, eval=FALSE} 149 | # download elevation data 150 | elev_file <- file.path("data", "sf-elevation.tif") 151 | get_usgs_elevation_data(bbox, size = image_size$size, file = elev_file, 152 | sr_bbox = 4326, sr_image = 4326) 153 | ``` 154 | 155 | ### SR: Spatial Reference 156 | 157 | Before I move on, let's take a moment to talk about coordinate systems. You may have noticed the two numeric inputs above for `sr_bbox` and `sr_image`. The "sr" refers to "spatial reference". Apparently a map is not a map is not a map -- how it's drawn and what measurements it uses depend on its spatial reference system, each identified by an SR code. You can read a great introduction on this topic [here](https://developers.arcgis.com/documentation/core-concepts/spatial-references/)^[Read about Spatial Reference systems for mapping on the ArcGIS website [here](https://developers.arcgis.com/documentation/core-concepts/spatial-references/)]. For this article, I'm using SR 4326, which is the common GPS coordinate system, where positions are defined in *degrees* of longitude/latitude (as opposed to say the non-spherical mercator projections of SR 3857 used by `leaflet` and other web mapping packages). 158 | 159 | ### Plotting *My Own Map™* 160 | 161 | Now with this downloaded elevation data, I can make my very own map. In the code below, I first read in the elevation `.tif` file and load it into a 2D elevation matrix -- I show the code here for clarity, but this common step can be encapsulated in a function to make this a one-line call (which I've done in [`read_elevation_file()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/read-elevation.R)*⎈local*). Next is typical `rayshader` code -- I calculate some data layers and then build a 2D plot. I chose to calculate the initial data layers outside of the plot pipeline because I will re-use those pre-calculated layers in subsequent plots. 162 | 163 | ```{r load-elevation, results=FALSE} 164 | # load elevation data 165 | elev_img <- raster::raster(elev_file) 166 | elev_matrix <- matrix( 167 | raster::extract(elev_img, raster::extent(elev_img), buffer = 1000), 168 | nrow = ncol(elev_img), ncol = nrow(elev_img) 169 | ) 170 | 171 | # calculate rayshader layers 172 | ambmat <- ambient_shade(elev_matrix, zscale = 30) 173 | raymat <- ray_shade(elev_matrix, zscale = 30, lambert = TRUE) 174 | watermap <- detect_water(elev_matrix) 175 | 176 | # plot 2D 177 | elev_matrix %>% 178 | sphere_shade(texture = "imhof4") %>% 179 | add_water(watermap, color = "imhof4") %>% 180 | add_shadow(raymat, max_darken = 0.5) %>% 181 | add_shadow(ambmat, max_darken = 0.5) %>% 182 | plot_map() 183 | ``` 184 | 185 | 186 | # Overlaying Map Images 187 | 188 | Beautiful! San Francisco: the hilly, desert city by the bay. The vanilla `rayshader` plots can look striking, but sometimes they appear a bit barren. They are also missing context -- e.g. which one of those nobby hills is Nob Hill? Wouldn't it be nice to have a map overlayed on top to get our bearings? Or maybe a satellite image? That's possible with the `rayshader` `add_overlay()` function, but the *hard* part is getting the image. We need an image for the same bounding box region, AND (as mentioned above) the overlay image dimensions need to match our elevation data dimensions exactly. 189 | 190 | Good news! Once again, there's an API for that. ArcGISOnline hosts a web UI for exporting map images [here](https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task/execute), and this same url can also be accessed programmatically as a REST API.^[You can generate map images at arcgisonline.com via a web UI [here](https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task/execute), and there is good documentation on this ArcGIS REST API endpoint [here](https://developers.arcgis.com/rest/services-reference/export-web-map-task.htm).] 191 | 192 | I wrote the function [`get_arcgis_map_image()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/map-image-api.R)*⎈local* to interface with this API endpoint (which is actually a two-step process: one call to generate an image, another call to download the image). Once again, the code below makes this step look easy. You can see the downloaded map image to the right of the code. 193 | 194 | ```{r map-image-api, eval=FALSE} 195 | # fetch overlay image 196 | overlay_file <- "images/sf-map.png" 197 | get_arcgis_map_image(bbox, map_type = "World_Topo_Map", file = overlay_file, 198 | width = image_size$width, height = image_size$height, 199 | sr_bbox = 4326) 200 | overlay_img <- png::readPNG(overlay_file) 201 | ``` 202 | 203 | 208 | 209 | There are several base map types to choose from in the API, which are [listed here](https://services.arcgisonline.com/ArcGIS/rest/services) under "Services". Below are three options I enjoy. 210 | 211 | ```{r map-types, fig.width=7, fig.height=2, echo=FALSE, eval=FALSE, layout="l-body-outset"} 212 | library(ggplot2) 213 | library(patchwork) 214 | img_street <- png::readPNG(file.path("images", "sf-street.png")) 215 | img_topo <- png::readPNG(file.path("images", "sf-topo.png")) 216 | img_sat <- png::readPNG(file.path("images", "sf-satellite.png")) 217 | # build plots 218 | theme_size <- 6 219 | gg_street <- ggplot() + theme_minimal(theme_size) + 220 | annotation_custom(grid::rasterGrob(img_street, interpolate = TRUE), 221 | xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf) + 222 | ggtitle("World_Street_Map") 223 | gg_topo <- ggplot() + theme_minimal(theme_size) + 224 | annotation_custom(grid::rasterGrob(img_topo, interpolate = TRUE), 225 | xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf) + 226 | ggtitle("World_Topo_Map") 227 | gg_sat <- ggplot() + theme_minimal(theme_size) + 228 | annotation_custom(grid::rasterGrob(img_sat, interpolate = TRUE), 229 | xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf) + 230 | ggtitle("World_Imagery") 231 | # patchwork 232 | gg_street + gg_topo + gg_sat 233 | ggsave("images/map-types.png", height = 2, width = 7) 234 | ``` 235 | 236 | ```{r map-types-show, echo=FALSE, layout="l-body-outset"} 237 | knitr::include_graphics("images/map-types.png") 238 | ``` 239 | 240 | ### 2D Map Overlays 241 | 242 | Now that we have a map overlay image (with matching dimensions), We can add that to our `rayshader` plots. Here is a 2D overlay plot - the new layer `add_overlay()` adds the downloaded map image to the rendered `rayshader` plot. 243 | 244 | ```{r rayshader-2D-overlay, results=FALSE} 245 | # 2D plot with map overlay 246 | elev_matrix %>% 247 | sphere_shade(texture = "imhof4") %>% 248 | add_water(watermap, color = "imhof4") %>% 249 | add_shadow(raymat, max_darken = 0.5) %>% 250 | add_shadow(ambmat, max_darken = 0.5) %>% 251 | add_overlay(overlay_img, alphalayer = 0.5) %>% 252 | plot_map() 253 | ``` 254 | 255 | ### 3D Map Overlays 256 | 257 | And we can do the same thing with a 3D `rayshader` plot. Below is a static rendered snapshot, but locally you can interact with the 3D plot and manually spin and zoom to your heart's content. 258 | 259 | ```{r rayshader-3D-overlay, eval=FALSE} 260 | zscale <- 10 261 | rgl::clear3d() 262 | elev_matrix %>% 263 | sphere_shade(texture = "imhof4") %>% 264 | add_water(watermap, color = "imhof4") %>% 265 | add_overlay(overlay_img, alphalayer = 0.5) %>% 266 | add_shadow(raymat, max_darken = 0.5) %>% 267 | add_shadow(ambmat, max_darken = 0.5) %>% 268 | plot_3d(elev_matrix, zscale = zscale, windowsize = c(1200, 1000), 269 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, wateralpha = 0, 270 | theta = 25, phi = 30, zoom = 0.65, fov = 60) 271 | render_snapshot() 272 | ``` 273 | ```{r rayshader-3D-overlay-show, echo=FALSE} 274 | knitr::include_graphics("images/sf-3D-overlay.png") 275 | ``` 276 | 277 | We can even add map labels to our 3D plot with `render_label()`. Here's a marker for Sutro Tower, added after looking up the GPS coordinates on [latlong.net](https://www.latlong.net). I use the custom function [`find_image_coordinates()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/find-image-coordinates.R)*⎈local* to translate the GPS coordinates into an image x/y position. 278 | 279 | ```{r rayshader-3D-overlay-label, eval=FALSE, results=FALSE} 280 | # define label 281 | label <- list(text = "Sutro Tower") 282 | label$pos <- find_image_coordinates( 283 | long = -122.452131, lat = 37.756735, bbox = bbox, 284 | image_width = image_size$width, image_height = image_size$height) 285 | 286 | # plot 3D 287 | zscale <- 10 288 | rgl::clear3d() 289 | elev_matrix %>% 290 | sphere_shade(texture = "imhof4") %>% 291 | add_water(watermap, color = "imhof4") %>% 292 | add_overlay(overlay_img, alphalayer = 0.5) %>% 293 | add_shadow(raymat, max_darken = 0.5) %>% 294 | add_shadow(ambmat, max_darken = 0.5) %>% 295 | plot_3d(elev_matrix, zscale = zscale, windowsize = c(1200, 1000), 296 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, wateralpha = 0, 297 | theta = 25, phi = 30, zoom = 0.65, fov = 60) 298 | # add label 299 | render_label(elev_matrix, x = label$pos$x, y = label$pos$y, z = 500, 300 | zscale = zscale, text = label$text, textsize = 2, linewidth = 5) 301 | render_snapshot() 302 | ``` 303 | 304 | ```{r rayshader-3D-overlay-label-show, echo=FALSE} 305 | knitr::include_graphics("images/sf-3D-overlay-label.png") 306 | ``` 307 | 308 | 313 | 314 | 315 | # Into the 4th Dimension - GIFs 316 | 317 | Of course, one of the most eye-popping features of `rayshader` is the gifs it can make. We loved going from 2D to 3D, so why not animate? *People. Love. GIFs.* So, let's make some! 318 | 319 | ```{r, echo=FALSE, fig.align="center"} 320 | knitr::include_graphics("images/one-does-not-simply-gif.jpg") 321 | ``` 322 | 323 | Again this is possible, but I found the documentation on this a bit lacking. 324 | A good example can be found in [this blog post](https://www.tylermw.com/3d-maps-with-rayshader/) from Tyler Morgan-Wall's website -- the code below is pulled from that post. Basically, we loop through multiple plots to save individual images with `render_snapshot()`, then we stitch them into a gif with `image_write_gif()` (from the [`magick`](https://github.com/ropensci/magick) package). The code is readable and not *too* long....but what what if there was a *simpler* way? 325 | 326 | 331 | 332 | ```{r 3D-gif-basic, eval=FALSE} 333 | # montery water gif ==== 334 | elev_matrix <- montereybay 335 | n_frames <- 180 336 | zscale <- 50 337 | # frame transition variables 338 | waterdepthvalues <- min(elev_matrix)/2 - min(elev_matrix)/2 * cos(seq(0,2*pi,length.out = n_frames)) 339 | thetavalues <- -90 + 45 * cos(seq(0, 2*pi, length.out = n_frames)) 340 | # shadow layers 341 | ambmat <- ambient_shade(elev_matrix, zscale = zscale) 342 | raymat <- ray_shade(elev_matrix, zscale = zscale, lambert = TRUE) 343 | 344 | # generate .png frame images 345 | img_frames <- paste0("drain", seq_len(n_frames), ".png") 346 | for (i in seq_len(n_frames)) { 347 | message(paste(" - image", i, "of", n_frames)) 348 | elev_matrix %>% 349 | sphere_shade(texture = "imhof1") %>% 350 | add_shadow(ambmat, 0.5) %>% 351 | add_shadow(raymat, 0.5) %>% 352 | plot_3d(elev_matrix, solid = TRUE, shadow = TRUE, zscale = zscale, 353 | water = TRUE, watercolor = "imhof3", wateralpha = 0.8, 354 | waterlinecolor = "#ffffff", waterlinealpha = 0.5, 355 | waterdepth = waterdepthvalues[i]/zscale, 356 | theta = thetavalues[i], phi = 45) 357 | render_snapshot(img_frames[i]) 358 | rgl::clear3d() 359 | } 360 | 361 | # build gif 362 | magick::image_write_gif(magick::image_read(img_frames), 363 | path = "montereybay.gif", 364 | delay = 6/n_frames) 365 | ``` 366 | 367 | ### One can *simply* gif 368 | 369 | But wait! There's a simpler way to gif, using the custom function [`save_3d_gif()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/rayshader-gif.R)*⎈local*. You can pipe in plot layers to this function just like you would with `rayshader::plot_3d()`, with the addition of two new inputs: `file` (your gif file-path) and `duration` (your gif duration). But unlike `plot_3d()`, you can pass in any regular input as a vector (e.g. a bunch of `theta` values to generate a rotating gif). Any vector inputs with lengths >1 will be used to programatically generate intermediate .png images and then create a gif from these. This function is basically generating the same code as shown above, but it's dynamically made using [tidy evaluation](https://tidyeval.tidyverse.org). The result is the same (e.g. a beautiful gif of Monterey Bay, spinning and draining the water level), but the code is half as long and (hopefully) more readable. This includes using the helper function [`transition_values()`](https://github.com/wcmbishop/rayshader-demo/blob/master/R/rayshader-gif.R)*⎈local* to create gif input vectors for the animation frames. 370 | 371 | ```{r 3D-gif-better, eval=FALSE} 372 | # calculate input vectors for gif frames 373 | n_frames <- 180 374 | waterdepths <- transition_values(from = 0, to = min(montereybay), steps = n_frames) 375 | thetas <- transition_values(from = -45, to = -135, steps = n_frames) 376 | # generate gif 377 | zscale <- 50 378 | montereybay %>% 379 | sphere_shade(texture = "imhof1", zscale = zscale) %>% 380 | add_shadow(ambient_shade(montereybay, zscale = zscale), 0.5) %>% 381 | add_shadow(ray_shade(montereybay, zscale = zscale, lambert = TRUE), 0.5) %>% 382 | save_3d_gif(montereybay, file = "montereybay.gif", duration = 6, 383 | solid = TRUE, shadow = TRUE, water = TRUE, zscale = zscale, 384 | watercolor = "imhof3", wateralpha = 0.8, 385 | waterlinecolor = "#ffffff", waterlinealpha = 0.5, 386 | waterdepth = waterdepths/zscale, 387 | theta = thetas, phi = 45) 388 | ``` 389 | 390 | ```{r, echo=FALSE} 391 | knitr::include_graphics("images/montereybay.gif") 392 | ``` 393 | 394 | Here's another example, a gratuitous use of angles and zoom to build a San Francisco fly-by. 395 | 396 | ```{r, sf-flyby-gif, eval=FALSE} 397 | # gif transition variables 398 | n_frames <- 180 399 | theta <- transition_values(from = 0, to = 360, steps = n_frames, 400 | one_way = TRUE, type = "lin") 401 | phi <- transition_values(from = 10, to = 70, steps = n_frames, 402 | one_way = FALSE, type = "cos") 403 | zoom <- transition_values(from = 0.4, to = 0.8, steps = n_frames, 404 | one_way = FALSE, type = "cos") 405 | 406 | # GIF it! 407 | zscale <- 10 408 | elev_matrix %>% 409 | sphere_shade(texture = "imhof4") %>% 410 | add_water(watermap, color = "imhof4") %>% 411 | add_overlay(overlay_img, alphalayer = 0.5) %>% 412 | add_shadow(raymat, 0.4) %>% 413 | add_shadow(ambmat, 0.4) %>% 414 | save_3d_gif(elev_matrix, file = "images/sf-flyby.gif", duration = 6, 415 | zscale = zscale, windowsize = c(1200, 1000), wateralpha = 0, 416 | water = TRUE, soliddepth = -max(elev_matrix)/zscale, 417 | theta = theta, phi = phi, zoom = zoom, fov = 60) 418 | ``` 419 | 420 | ```{r, echo=FALSE} 421 | knitr::include_graphics("images/sf-flyby.gif") 422 | ``` 423 | 424 | # Big Maps 425 | 426 | For size and speed reasons while creating this article, I've been using an image size of 600 (on the major dimension). But if you increase that, you cake make some incredibly detailed maps. Here's what our San Francisco map looks like at a size of 2000, using the "World_Imagery" satellite map overlay. Stunning. 427 | 428 | **2D Big San Francisco** 429 | 430 | ```{r, echo=FALSE, layout="l-body-outset"} 431 | knitr::include_graphics("images/sf-2D-overlay-2000.png") 432 | ``` 433 | 434 | **3D Big San Francisco** 435 | 436 | ```{r, echo=FALSE, layout="l-body-outset"} 437 | knitr::include_graphics("images/sf-3D-overlay-label-2000.png") 438 | ``` 439 | 440 | # Conclusion 441 | 442 | That's all I've got. I hope you learned something that may save you a bit of pain and confusion if you try out `rayshader` for yourself. And maybe you're coming away with just a bit more excitement and inspiration to do so. I've certainly enjoyed my time with this package, and I'll be making more maps in the future (as well as 3D printing some of them!). 443 | 444 | If you have suggestions or edits or thoughts on this material, please feel free to [create an issue](https://www.github.com/wcmbishop/rayshader-demo/issues/new) in the git repo or tweet at me at [wcmbishop](https://twitter.com/wcmbishop). 445 | 446 | Thanks for reading. 447 | 448 | 449 | # Afterthoughts 450 | 451 | After writing this post, I found a few more leads on elevation data sources. I thought I'd share these here (though I think the REST API option I described above is still a great starting place). 452 | 453 | One is the `elevatr` R package ([vignette here](https://cran.r-project.org/web/packages/elevatr/vignettes/introduction_to_elevatr.html#usgs_elevation_point_query_service)), which uses the National Map [Elevation Point Query Service](http://ned.usgs.gov/epqs/). 454 | 455 | Tyler Morgan-Wall also posted some instructions for manually downloading elevation data from a National Map UI [here](https://viewer.nationalmap.gov/basic/?basemap=b1&category=ned,nedsrc&title=3DEP%20View) -- the instructions from this [reddit post](https://www.reddit.com/r/dataisbeautiful/comments/950vzg/visualizing_3d_maps_with_r_what_it_looks_like/) are quoted below: 456 | 457 | > Select File Format: IMG, and then zoom into where you want data. Click the big blue FIND PRODUCTS button at the top. In the Elevation Products (3DEP) pane, click the "results" link to expand the available topographic data available. Then click "Show Footprints" to show the areas that have data, and just hover over the area you want. It should give you a name like "n39w077"--find that in the results pane, and then click the download button. You should download a zip of an IMG file that should contain your data. 458 | > Then just load your data locally with the raster package: 459 | > ``` 460 | data = raster::raster("sampledata.img") 461 | ready_to_use_data = matrix( 462 | raster::extract(data, 463 | raster::extent(data), buffer=10000), nrow=ncol(data), ncol=nrow(data) 464 | ) 465 | ``` 466 | 467 | # Acknowledgments {.appendix} 468 | 469 | This article was built with [Radix](https://rstudio.github.io/radix) - thanks [RStudio](https://www.rstudio.com)!. 470 | 471 | And thanks for Tyler Morgan-Wall for creating the joyous [`rayshader`](https://www.rayshader.com) package. 472 | 473 | -------------------------------------------------------------------------------- /index_files/Proj4Leaflet-1.0.1/proj4leaflet.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | var L, proj4; 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD 5 | define(['leaflet', 'proj4'], factory); 6 | } else if (typeof module === 'object' && typeof module.exports === "object") { 7 | // Node/CommonJS 8 | L = require('leaflet'); 9 | proj4 = require('proj4'); 10 | module.exports = factory(L, proj4); 11 | } else { 12 | // Browser globals 13 | if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined') 14 | throw 'Leaflet and proj4 must be loaded first'; 15 | factory(window.L, window.proj4); 16 | } 17 | }(function (L, proj4) { 18 | if (proj4.__esModule && proj4.default) { 19 | // If proj4 was bundled as an ES6 module, unwrap it to get 20 | // to the actual main proj4 object. 21 | // See discussion in https://github.com/kartena/Proj4Leaflet/pull/147 22 | proj4 = proj4.default; 23 | } 24 | 25 | L.Proj = {}; 26 | 27 | L.Proj._isProj4Obj = function(a) { 28 | return (typeof a.inverse !== 'undefined' && 29 | typeof a.forward !== 'undefined'); 30 | }; 31 | 32 | L.Proj.Projection = L.Class.extend({ 33 | initialize: function(code, def, bounds) { 34 | var isP4 = L.Proj._isProj4Obj(code); 35 | this._proj = isP4 ? code : this._projFromCodeDef(code, def); 36 | this.bounds = isP4 ? def : bounds; 37 | }, 38 | 39 | project: function (latlng) { 40 | var point = this._proj.forward([latlng.lng, latlng.lat]); 41 | return new L.Point(point[0], point[1]); 42 | }, 43 | 44 | unproject: function (point, unbounded) { 45 | var point2 = this._proj.inverse([point.x, point.y]); 46 | return new L.LatLng(point2[1], point2[0], unbounded); 47 | }, 48 | 49 | _projFromCodeDef: function(code, def) { 50 | if (def) { 51 | proj4.defs(code, def); 52 | } else if (proj4.defs[code] === undefined) { 53 | var urn = code.split(':'); 54 | if (urn.length > 3) { 55 | code = urn[urn.length - 3] + ':' + urn[urn.length - 1]; 56 | } 57 | if (proj4.defs[code] === undefined) { 58 | throw 'No projection definition for code ' + code; 59 | } 60 | } 61 | 62 | return proj4(code); 63 | } 64 | }); 65 | 66 | L.Proj.CRS = L.Class.extend({ 67 | includes: L.CRS, 68 | 69 | options: { 70 | transformation: new L.Transformation(1, 0, -1, 0) 71 | }, 72 | 73 | initialize: function(a, b, c) { 74 | var code, 75 | proj, 76 | def, 77 | options; 78 | 79 | if (L.Proj._isProj4Obj(a)) { 80 | proj = a; 81 | code = proj.srsCode; 82 | options = b || {}; 83 | 84 | this.projection = new L.Proj.Projection(proj, options.bounds); 85 | } else { 86 | code = a; 87 | def = b; 88 | options = c || {}; 89 | this.projection = new L.Proj.Projection(code, def, options.bounds); 90 | } 91 | 92 | L.Util.setOptions(this, options); 93 | this.code = code; 94 | this.transformation = this.options.transformation; 95 | 96 | if (this.options.origin) { 97 | this.transformation = 98 | new L.Transformation(1, -this.options.origin[0], 99 | -1, this.options.origin[1]); 100 | } 101 | 102 | if (this.options.scales) { 103 | this._scales = this.options.scales; 104 | } else if (this.options.resolutions) { 105 | this._scales = []; 106 | for (var i = this.options.resolutions.length - 1; i >= 0; i--) { 107 | if (this.options.resolutions[i]) { 108 | this._scales[i] = 1 / this.options.resolutions[i]; 109 | } 110 | } 111 | } 112 | 113 | this.infinite = !this.options.bounds; 114 | 115 | }, 116 | 117 | scale: function(zoom) { 118 | var iZoom = Math.floor(zoom), 119 | baseScale, 120 | nextScale, 121 | scaleDiff, 122 | zDiff; 123 | if (zoom === iZoom) { 124 | return this._scales[zoom]; 125 | } else { 126 | // Non-integer zoom, interpolate 127 | baseScale = this._scales[iZoom]; 128 | nextScale = this._scales[iZoom + 1]; 129 | scaleDiff = nextScale - baseScale; 130 | zDiff = (zoom - iZoom); 131 | return baseScale + scaleDiff * zDiff; 132 | } 133 | }, 134 | 135 | zoom: function(scale) { 136 | // Find closest number in this._scales, down 137 | var downScale = this._closestElement(this._scales, scale), 138 | downZoom = this._scales.indexOf(downScale), 139 | nextScale, 140 | nextZoom, 141 | scaleDiff; 142 | // Check if scale is downScale => return array index 143 | if (scale === downScale) { 144 | return downZoom; 145 | } 146 | if (downScale === undefined) { 147 | return -Infinity; 148 | } 149 | // Interpolate 150 | nextZoom = downZoom + 1; 151 | nextScale = this._scales[nextZoom]; 152 | if (nextScale === undefined) { 153 | return Infinity; 154 | } 155 | scaleDiff = nextScale - downScale; 156 | return (scale - downScale) / scaleDiff + downZoom; 157 | }, 158 | 159 | distance: L.CRS.Earth.distance, 160 | 161 | R: L.CRS.Earth.R, 162 | 163 | /* Get the closest lowest element in an array */ 164 | _closestElement: function(array, element) { 165 | var low; 166 | for (var i = array.length; i--;) { 167 | if (array[i] <= element && (low === undefined || low < array[i])) { 168 | low = array[i]; 169 | } 170 | } 171 | return low; 172 | } 173 | }); 174 | 175 | L.Proj.GeoJSON = L.GeoJSON.extend({ 176 | initialize: function(geojson, options) { 177 | this._callLevel = 0; 178 | L.GeoJSON.prototype.initialize.call(this, geojson, options); 179 | }, 180 | 181 | addData: function(geojson) { 182 | var crs; 183 | 184 | if (geojson) { 185 | if (geojson.crs && geojson.crs.type === 'name') { 186 | crs = new L.Proj.CRS(geojson.crs.properties.name); 187 | } else if (geojson.crs && geojson.crs.type) { 188 | crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code); 189 | } 190 | 191 | if (crs !== undefined) { 192 | this.options.coordsToLatLng = function(coords) { 193 | var point = L.point(coords[0], coords[1]); 194 | return crs.projection.unproject(point); 195 | }; 196 | } 197 | } 198 | 199 | // Base class' addData might call us recursively, but 200 | // CRS shouldn't be cleared in that case, since CRS applies 201 | // to the whole GeoJSON, inluding sub-features. 202 | this._callLevel++; 203 | try { 204 | L.GeoJSON.prototype.addData.call(this, geojson); 205 | } finally { 206 | this._callLevel--; 207 | if (this._callLevel === 0) { 208 | delete this.options.coordsToLatLng; 209 | } 210 | } 211 | } 212 | }); 213 | 214 | L.Proj.geoJson = function(geojson, options) { 215 | return new L.Proj.GeoJSON(geojson, options); 216 | }; 217 | 218 | L.Proj.ImageOverlay = L.ImageOverlay.extend({ 219 | initialize: function (url, bounds, options) { 220 | L.ImageOverlay.prototype.initialize.call(this, url, null, options); 221 | this._projectedBounds = bounds; 222 | }, 223 | 224 | // Danger ahead: Overriding internal methods in Leaflet. 225 | // Decided to do this rather than making a copy of L.ImageOverlay 226 | // and doing very tiny modifications to it. 227 | // Future will tell if this was wise or not. 228 | _animateZoom: function (event) { 229 | var scale = this._map.getZoomScale(event.zoom); 230 | var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y); 231 | var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center); 232 | 233 | L.DomUtil.setTransform(this._image, offset, scale); 234 | }, 235 | 236 | _reset: function () { 237 | var zoom = this._map.getZoom(); 238 | var pixelOrigin = this._map.getPixelOrigin(); 239 | var bounds = L.bounds( 240 | this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin), 241 | this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin) 242 | ); 243 | var size = bounds.getSize(); 244 | 245 | L.DomUtil.setPosition(this._image, bounds.min); 246 | this._image.style.width = size.x + 'px'; 247 | this._image.style.height = size.y + 'px'; 248 | }, 249 | 250 | _projectedToNewLayerPoint: function (point, zoom, center) { 251 | var viewHalf = this._map.getSize()._divideBy(2); 252 | var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round(); 253 | var topLeft = newTopLeft.add(this._map._getMapPanePos()); 254 | 255 | return this._transform(point, zoom)._subtract(topLeft); 256 | }, 257 | 258 | _transform: function (point, zoom) { 259 | var crs = this._map.options.crs; 260 | var transformation = crs.transformation; 261 | var scale = crs.scale(zoom); 262 | 263 | return transformation.transform(point, scale); 264 | } 265 | }); 266 | 267 | L.Proj.imageOverlay = function (url, bounds, options) { 268 | return new L.Proj.ImageOverlay(url, bounds, options); 269 | }; 270 | 271 | return L.Proj; 272 | })); 273 | -------------------------------------------------------------------------------- /index_files/bowser-1.9.3/bowser.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bowser - a browser detector 3 | * https://github.com/ded/bowser 4 | * MIT License | (c) Dustin Diaz 2015 5 | */ 6 | !function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(t,n):e[t]=n()}(this,"bowser",function(){function t(t){function n(e){var n=t.match(e);return n&&n.length>1&&n[1]||""}function r(e){var n=t.match(e);return n&&n.length>1&&n[2]||""}function N(e){switch(e){case"NT":return"NT";case"XP":return"XP";case"NT 5.0":return"2000";case"NT 5.1":return"XP";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return undefined}}var i=n(/(ipod|iphone|ipad)/i).toLowerCase(),s=/like android/i.test(t),o=!s&&/android/i.test(t),u=/nexus\s*[0-6]\s*/i.test(t),a=!u&&/nexus\s*[0-9]+/i.test(t),f=/CrOS/.test(t),l=/silk/i.test(t),c=/sailfish/i.test(t),h=/tizen/i.test(t),p=/(web|hpw)os/i.test(t),d=/windows phone/i.test(t),v=/SamsungBrowser/i.test(t),m=!d&&/windows/i.test(t),g=!i&&!l&&/macintosh/i.test(t),y=!o&&!c&&!h&&!p&&/linux/i.test(t),b=r(/edg([ea]|ios)\/(\d+(\.\d+)?)/i),w=n(/version\/(\d+(\.\d+)?)/i),E=/tablet/i.test(t)&&!/tablet pc/i.test(t),S=!E&&/[^-]mobi/i.test(t),x=/xbox/i.test(t),T;/opera/i.test(t)?T={name:"Opera",opera:e,version:w||n(/(?:opera|opr|opios)[\s\/](\d+(\.\d+)?)/i)}:/opr\/|opios/i.test(t)?T={name:"Opera",opera:e,version:n(/(?:opr|opios)[\s\/](\d+(\.\d+)?)/i)||w}:/SamsungBrowser/i.test(t)?T={name:"Samsung Internet for Android",samsungBrowser:e,version:w||n(/(?:SamsungBrowser)[\s\/](\d+(\.\d+)?)/i)}:/coast/i.test(t)?T={name:"Opera Coast",coast:e,version:w||n(/(?:coast)[\s\/](\d+(\.\d+)?)/i)}:/yabrowser/i.test(t)?T={name:"Yandex Browser",yandexbrowser:e,version:w||n(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)}:/ucbrowser/i.test(t)?T={name:"UC Browser",ucbrowser:e,version:n(/(?:ucbrowser)[\s\/](\d+(?:\.\d+)+)/i)}:/mxios/i.test(t)?T={name:"Maxthon",maxthon:e,version:n(/(?:mxios)[\s\/](\d+(?:\.\d+)+)/i)}:/epiphany/i.test(t)?T={name:"Epiphany",epiphany:e,version:n(/(?:epiphany)[\s\/](\d+(?:\.\d+)+)/i)}:/puffin/i.test(t)?T={name:"Puffin",puffin:e,version:n(/(?:puffin)[\s\/](\d+(?:\.\d+)?)/i)}:/sleipnir/i.test(t)?T={name:"Sleipnir",sleipnir:e,version:n(/(?:sleipnir)[\s\/](\d+(?:\.\d+)+)/i)}:/k-meleon/i.test(t)?T={name:"K-Meleon",kMeleon:e,version:n(/(?:k-meleon)[\s\/](\d+(?:\.\d+)+)/i)}:d?(T={name:"Windows Phone",osname:"Windows Phone",windowsphone:e},b?(T.msedge=e,T.version=b):(T.msie=e,T.version=n(/iemobile\/(\d+(\.\d+)?)/i))):/msie|trident/i.test(t)?T={name:"Internet Explorer",msie:e,version:n(/(?:msie |rv:)(\d+(\.\d+)?)/i)}:f?T={name:"Chrome",osname:"Chrome OS",chromeos:e,chromeBook:e,chrome:e,version:n(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:/edg([ea]|ios)/i.test(t)?T={name:"Microsoft Edge",msedge:e,version:b}:/vivaldi/i.test(t)?T={name:"Vivaldi",vivaldi:e,version:n(/vivaldi\/(\d+(\.\d+)?)/i)||w}:c?T={name:"Sailfish",osname:"Sailfish OS",sailfish:e,version:n(/sailfish\s?browser\/(\d+(\.\d+)?)/i)}:/seamonkey\//i.test(t)?T={name:"SeaMonkey",seamonkey:e,version:n(/seamonkey\/(\d+(\.\d+)?)/i)}:/firefox|iceweasel|fxios/i.test(t)?(T={name:"Firefox",firefox:e,version:n(/(?:firefox|iceweasel|fxios)[ \/](\d+(\.\d+)?)/i)},/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(t)&&(T.firefoxos=e,T.osname="Firefox OS")):l?T={name:"Amazon Silk",silk:e,version:n(/silk\/(\d+(\.\d+)?)/i)}:/phantom/i.test(t)?T={name:"PhantomJS",phantom:e,version:n(/phantomjs\/(\d+(\.\d+)?)/i)}:/slimerjs/i.test(t)?T={name:"SlimerJS",slimer:e,version:n(/slimerjs\/(\d+(\.\d+)?)/i)}:/blackberry|\bbb\d+/i.test(t)||/rim\stablet/i.test(t)?T={name:"BlackBerry",osname:"BlackBerry OS",blackberry:e,version:w||n(/blackberry[\d]+\/(\d+(\.\d+)?)/i)}:p?(T={name:"WebOS",osname:"WebOS",webos:e,version:w||n(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)},/touchpad\//i.test(t)&&(T.touchpad=e)):/bada/i.test(t)?T={name:"Bada",osname:"Bada",bada:e,version:n(/dolfin\/(\d+(\.\d+)?)/i)}:h?T={name:"Tizen",osname:"Tizen",tizen:e,version:n(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i)||w}:/qupzilla/i.test(t)?T={name:"QupZilla",qupzilla:e,version:n(/(?:qupzilla)[\s\/](\d+(?:\.\d+)+)/i)||w}:/chromium/i.test(t)?T={name:"Chromium",chromium:e,version:n(/(?:chromium)[\s\/](\d+(?:\.\d+)?)/i)||w}:/chrome|crios|crmo/i.test(t)?T={name:"Chrome",chrome:e,version:n(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:o?T={name:"Android",version:w}:/safari|applewebkit/i.test(t)?(T={name:"Safari",safari:e},w&&(T.version=w)):i?(T={name:i=="iphone"?"iPhone":i=="ipad"?"iPad":"iPod"},w&&(T.version=w)):/googlebot/i.test(t)?T={name:"Googlebot",googlebot:e,version:n(/googlebot\/(\d+(\.\d+))/i)||w}:T={name:n(/^(.*)\/(.*) /),version:r(/^(.*)\/(.*) /)},!T.msedge&&/(apple)?webkit/i.test(t)?(/(apple)?webkit\/537\.36/i.test(t)?(T.name=T.name||"Blink",T.blink=e):(T.name=T.name||"Webkit",T.webkit=e),!T.version&&w&&(T.version=w)):!T.opera&&/gecko\//i.test(t)&&(T.name=T.name||"Gecko",T.gecko=e,T.version=T.version||n(/gecko\/(\d+(\.\d+)?)/i)),!T.windowsphone&&(o||T.silk)?(T.android=e,T.osname="Android"):!T.windowsphone&&i?(T[i]=e,T.ios=e,T.osname="iOS"):g?(T.mac=e,T.osname="macOS"):x?(T.xbox=e,T.osname="Xbox"):m?(T.windows=e,T.osname="Windows"):y&&(T.linux=e,T.osname="Linux");var C="";T.windows?C=N(n(/Windows ((NT|XP)( \d\d?.\d)?)/i)):T.windowsphone?C=n(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i):T.mac?(C=n(/Mac OS X (\d+([_\.\s]\d+)*)/i),C=C.replace(/[_\s]/g,".")):i?(C=n(/os (\d+([_\s]\d+)*) like mac os x/i),C=C.replace(/[_\s]/g,".")):o?C=n(/android[ \/-](\d+(\.\d+)*)/i):T.webos?C=n(/(?:web|hpw)os\/(\d+(\.\d+)*)/i):T.blackberry?C=n(/rim\stablet\sos\s(\d+(\.\d+)*)/i):T.bada?C=n(/bada\/(\d+(\.\d+)*)/i):T.tizen&&(C=n(/tizen[\/\s](\d+(\.\d+)*)/i)),C&&(T.osversion=C);var k=!T.windows&&C.split(".")[0];if(E||a||i=="ipad"||o&&(k==3||k>=4&&!S)||T.silk)T.tablet=e;else if(S||i=="iphone"||i=="ipod"||o||u||T.blackberry||T.webos||T.bada)T.mobile=e;return T.msedge||T.msie&&T.version>=10||T.yandexbrowser&&T.version>=15||T.vivaldi&&T.version>=1||T.chrome&&T.version>=20||T.samsungBrowser&&T.version>=4||T.firefox&&T.version>=20||T.safari&&T.version>=6||T.opera&&T.version>=10||T.ios&&T.osversion&&T.osversion.split(".")[0]>=6||T.blackberry&&T.version>=10.1||T.chromium&&T.version>=20?T.a=e:T.msie&&T.version<10||T.chrome&&T.version<20||T.firefox&&T.version<20||T.safari&&T.version<6||T.opera&&T.version<10||T.ios&&T.osversion&&T.osversion.split(".")[0]<6||T.chromium&&T.version<20?T.c=e:T.x=e,T}function r(e){return e.split(".").length}function i(e,t){var n=[],r;if(Array.prototype.map)return Array.prototype.map.call(e,t);for(r=0;r=0){if(n[0][t]>n[1][t])return 1;if(n[0][t]!==n[1][t])return-1;if(t===0)return 0}}function o(e,r,i){var o=n;typeof r=="string"&&(i=r,r=void 0),r===void 0&&(r=!1),i&&(o=t(i));var u=""+o.version;for(var a in e)if(e.hasOwnProperty(a)&&o[a]){if(typeof e[a]!="string")throw new Error("Browser version in the minVersion map should be a string: "+a+": "+String(e));return s([u,e[a]])<0}return r}function u(e,t,n){return!o(e,t,n)}var e=!0,n=t(typeof navigator!="undefined"?navigator.userAgent||"":"");return n.test=function(e){for(var t=0;t").addClass(errClass); 321 | errorSpan.text(err.message); 322 | $el.after(errorSpan); 323 | } 324 | } else if (display === "block") { 325 | // If block, add an error just after the el, set visibility:none on the 326 | // el, and position the error to be on top of the el. 327 | // Mark it with a unique ID and CSS class so we can remove it later. 328 | $el.css("visibility", "hidden"); 329 | if (err.message !== "") { 330 | var errorDiv = $("
").addClass(errClass).css("position", "absolute") 331 | .css("top", el.offsetTop) 332 | .css("left", el.offsetLeft) 333 | // setting width can push out the page size, forcing otherwise 334 | // unnecessary scrollbars to appear and making it impossible for 335 | // the element to shrink; so use max-width instead 336 | .css("maxWidth", el.offsetWidth) 337 | .css("height", el.offsetHeight); 338 | errorDiv.text(err.message); 339 | $el.after(errorDiv); 340 | 341 | // Really dumb way to keep the size/position of the error in sync with 342 | // the parent element as the window is resized or whatever. 343 | var intId = setInterval(function() { 344 | if (!errorDiv[0].parentElement) { 345 | clearInterval(intId); 346 | return; 347 | } 348 | errorDiv 349 | .css("top", el.offsetTop) 350 | .css("left", el.offsetLeft) 351 | .css("maxWidth", el.offsetWidth) 352 | .css("height", el.offsetHeight); 353 | }, 500); 354 | } 355 | } 356 | }, 357 | clearError: function(el) { 358 | var $el = $(el); 359 | var display = $el.data("restore-display-mode"); 360 | $el.data("restore-display-mode", null); 361 | 362 | if (display === "inline" || display === "inline-block") { 363 | if (display) 364 | $el.css("display", display); 365 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 366 | } else if (display === "block"){ 367 | $el.css("visibility", "inherit"); 368 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 369 | } 370 | }, 371 | sizing: {} 372 | }; 373 | 374 | // Called by widget bindings to register a new type of widget. The definition 375 | // object can contain the following properties: 376 | // - name (required) - A string indicating the binding name, which will be 377 | // used by default as the CSS classname to look for. 378 | // - initialize (optional) - A function(el) that will be called once per 379 | // widget element; if a value is returned, it will be passed as the third 380 | // value to renderValue. 381 | // - renderValue (required) - A function(el, data, initValue) that will be 382 | // called with data. Static contexts will cause this to be called once per 383 | // element; Shiny apps will cause this to be called multiple times per 384 | // element, as the data changes. 385 | window.HTMLWidgets.widget = function(definition) { 386 | if (!definition.name) { 387 | throw new Error("Widget must have a name"); 388 | } 389 | if (!definition.type) { 390 | throw new Error("Widget must have a type"); 391 | } 392 | // Currently we only support output widgets 393 | if (definition.type !== "output") { 394 | throw new Error("Unrecognized widget type '" + definition.type + "'"); 395 | } 396 | // TODO: Verify that .name is a valid CSS classname 397 | 398 | // Support new-style instance-bound definitions. Old-style class-bound 399 | // definitions have one widget "object" per widget per type/class of 400 | // widget; the renderValue and resize methods on such widget objects 401 | // take el and instance arguments, because the widget object can't 402 | // store them. New-style instance-bound definitions have one widget 403 | // object per widget instance; the definition that's passed in doesn't 404 | // provide renderValue or resize methods at all, just the single method 405 | // factory(el, width, height) 406 | // which returns an object that has renderValue(x) and resize(w, h). 407 | // This enables a far more natural programming style for the widget 408 | // author, who can store per-instance state using either OO-style 409 | // instance fields or functional-style closure variables (I guess this 410 | // is in contrast to what can only be called C-style pseudo-OO which is 411 | // what we required before). 412 | if (definition.factory) { 413 | definition = createLegacyDefinitionAdapter(definition); 414 | } 415 | 416 | if (!definition.renderValue) { 417 | throw new Error("Widget must have a renderValue function"); 418 | } 419 | 420 | // For static rendering (non-Shiny), use a simple widget registration 421 | // scheme. We also use this scheme for Shiny apps/documents that also 422 | // contain static widgets. 423 | window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; 424 | // Merge defaults into the definition; don't mutate the original definition. 425 | var staticBinding = extend({}, defaults, definition); 426 | overrideMethod(staticBinding, "find", function(superfunc) { 427 | return function(scope) { 428 | var results = superfunc(scope); 429 | // Filter out Shiny outputs, we only want the static kind 430 | return filterByClass(results, "html-widget-output", false); 431 | }; 432 | }); 433 | window.HTMLWidgets.widgets.push(staticBinding); 434 | 435 | if (shinyMode) { 436 | // Shiny is running. Register the definition with an output binding. 437 | // The definition itself will not be the output binding, instead 438 | // we will make an output binding object that delegates to the 439 | // definition. This is because we foolishly used the same method 440 | // name (renderValue) for htmlwidgets definition and Shiny bindings 441 | // but they actually have quite different semantics (the Shiny 442 | // bindings receive data that includes lots of metadata that it 443 | // strips off before calling htmlwidgets renderValue). We can't 444 | // just ignore the difference because in some widgets it's helpful 445 | // to call this.renderValue() from inside of resize(), and if 446 | // we're not delegating, then that call will go to the Shiny 447 | // version instead of the htmlwidgets version. 448 | 449 | // Merge defaults with definition, without mutating either. 450 | var bindingDef = extend({}, defaults, definition); 451 | 452 | // This object will be our actual Shiny binding. 453 | var shinyBinding = new Shiny.OutputBinding(); 454 | 455 | // With a few exceptions, we'll want to simply use the bindingDef's 456 | // version of methods if they are available, otherwise fall back to 457 | // Shiny's defaults. NOTE: If Shiny's output bindings gain additional 458 | // methods in the future, and we want them to be overrideable by 459 | // HTMLWidget binding definitions, then we'll need to add them to this 460 | // list. 461 | delegateMethod(shinyBinding, bindingDef, "getId"); 462 | delegateMethod(shinyBinding, bindingDef, "onValueChange"); 463 | delegateMethod(shinyBinding, bindingDef, "onValueError"); 464 | delegateMethod(shinyBinding, bindingDef, "renderError"); 465 | delegateMethod(shinyBinding, bindingDef, "clearError"); 466 | delegateMethod(shinyBinding, bindingDef, "showProgress"); 467 | 468 | // The find, renderValue, and resize are handled differently, because we 469 | // want to actually decorate the behavior of the bindingDef methods. 470 | 471 | shinyBinding.find = function(scope) { 472 | var results = bindingDef.find(scope); 473 | 474 | // Only return elements that are Shiny outputs, not static ones 475 | var dynamicResults = results.filter(".html-widget-output"); 476 | 477 | // It's possible that whatever caused Shiny to think there might be 478 | // new dynamic outputs, also caused there to be new static outputs. 479 | // Since there might be lots of different htmlwidgets bindings, we 480 | // schedule execution for later--no need to staticRender multiple 481 | // times. 482 | if (results.length !== dynamicResults.length) 483 | scheduleStaticRender(); 484 | 485 | return dynamicResults; 486 | }; 487 | 488 | // Wrap renderValue to handle initialization, which unfortunately isn't 489 | // supported natively by Shiny at the time of this writing. 490 | 491 | shinyBinding.renderValue = function(el, data) { 492 | Shiny.renderDependencies(data.deps); 493 | // Resolve strings marked as javascript literals to objects 494 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 495 | for (var i = 0; data.evals && i < data.evals.length; i++) { 496 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); 497 | } 498 | if (!bindingDef.renderOnNullValue) { 499 | if (data.x === null) { 500 | el.style.visibility = "hidden"; 501 | return; 502 | } else { 503 | el.style.visibility = "inherit"; 504 | } 505 | } 506 | if (!elementData(el, "initialized")) { 507 | initSizing(el); 508 | 509 | elementData(el, "initialized", true); 510 | if (bindingDef.initialize) { 511 | var result = bindingDef.initialize(el, el.offsetWidth, 512 | el.offsetHeight); 513 | elementData(el, "init_result", result); 514 | } 515 | } 516 | bindingDef.renderValue(el, data.x, elementData(el, "init_result")); 517 | evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); 518 | }; 519 | 520 | // Only override resize if bindingDef implements it 521 | if (bindingDef.resize) { 522 | shinyBinding.resize = function(el, width, height) { 523 | // Shiny can call resize before initialize/renderValue have been 524 | // called, which doesn't make sense for widgets. 525 | if (elementData(el, "initialized")) { 526 | bindingDef.resize(el, width, height, elementData(el, "init_result")); 527 | } 528 | }; 529 | } 530 | 531 | Shiny.outputBindings.register(shinyBinding, bindingDef.name); 532 | } 533 | }; 534 | 535 | var scheduleStaticRenderTimerId = null; 536 | function scheduleStaticRender() { 537 | if (!scheduleStaticRenderTimerId) { 538 | scheduleStaticRenderTimerId = setTimeout(function() { 539 | scheduleStaticRenderTimerId = null; 540 | window.HTMLWidgets.staticRender(); 541 | }, 1); 542 | } 543 | } 544 | 545 | // Render static widgets after the document finishes loading 546 | // Statically render all elements that are of this widget's class 547 | window.HTMLWidgets.staticRender = function() { 548 | var bindings = window.HTMLWidgets.widgets || []; 549 | forEach(bindings, function(binding) { 550 | var matches = binding.find(document.documentElement); 551 | forEach(matches, function(el) { 552 | var sizeObj = initSizing(el, binding); 553 | 554 | if (hasClass(el, "html-widget-static-bound")) 555 | return; 556 | el.className = el.className + " html-widget-static-bound"; 557 | 558 | var initResult; 559 | if (binding.initialize) { 560 | initResult = binding.initialize(el, 561 | sizeObj ? sizeObj.getWidth() : el.offsetWidth, 562 | sizeObj ? sizeObj.getHeight() : el.offsetHeight 563 | ); 564 | elementData(el, "init_result", initResult); 565 | } 566 | 567 | if (binding.resize) { 568 | var lastSize = { 569 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 570 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 571 | }; 572 | var resizeHandler = function(e) { 573 | var size = { 574 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 575 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 576 | }; 577 | if (size.w === 0 && size.h === 0) 578 | return; 579 | if (size.w === lastSize.w && size.h === lastSize.h) 580 | return; 581 | lastSize = size; 582 | binding.resize(el, size.w, size.h, initResult); 583 | }; 584 | 585 | on(window, "resize", resizeHandler); 586 | 587 | // This is needed for cases where we're running in a Shiny 588 | // app, but the widget itself is not a Shiny output, but 589 | // rather a simple static widget. One example of this is 590 | // an rmarkdown document that has runtime:shiny and widget 591 | // that isn't in a render function. Shiny only knows to 592 | // call resize handlers for Shiny outputs, not for static 593 | // widgets, so we do it ourselves. 594 | if (window.jQuery) { 595 | window.jQuery(document).on( 596 | "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", 597 | resizeHandler 598 | ); 599 | window.jQuery(document).on( 600 | "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", 601 | resizeHandler 602 | ); 603 | } 604 | 605 | // This is needed for the specific case of ioslides, which 606 | // flips slides between display:none and display:block. 607 | // Ideally we would not have to have ioslide-specific code 608 | // here, but rather have ioslides raise a generic event, 609 | // but the rmarkdown package just went to CRAN so the 610 | // window to getting that fixed may be long. 611 | if (window.addEventListener) { 612 | // It's OK to limit this to window.addEventListener 613 | // browsers because ioslides itself only supports 614 | // such browsers. 615 | on(document, "slideenter", resizeHandler); 616 | on(document, "slideleave", resizeHandler); 617 | } 618 | } 619 | 620 | var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); 621 | if (scriptData) { 622 | var data = JSON.parse(scriptData.textContent || scriptData.text); 623 | // Resolve strings marked as javascript literals to objects 624 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 625 | for (var k = 0; data.evals && k < data.evals.length; k++) { 626 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); 627 | } 628 | binding.renderValue(el, data.x, initResult); 629 | evalAndRun(data.jsHooks.render, initResult, [el, data.x]); 630 | } 631 | }); 632 | }); 633 | 634 | invokePostRenderHandlers(); 635 | } 636 | 637 | // Wait until after the document has loaded to render the widgets. 638 | if (document.addEventListener) { 639 | document.addEventListener("DOMContentLoaded", function() { 640 | document.removeEventListener("DOMContentLoaded", arguments.callee, false); 641 | window.HTMLWidgets.staticRender(); 642 | }, false); 643 | } else if (document.attachEvent) { 644 | document.attachEvent("onreadystatechange", function() { 645 | if (document.readyState === "complete") { 646 | document.detachEvent("onreadystatechange", arguments.callee); 647 | window.HTMLWidgets.staticRender(); 648 | } 649 | }); 650 | } 651 | 652 | 653 | window.HTMLWidgets.getAttachmentUrl = function(depname, key) { 654 | // If no key, default to the first item 655 | if (typeof(key) === "undefined") 656 | key = 1; 657 | 658 | var link = document.getElementById(depname + "-" + key + "-attachment"); 659 | if (!link) { 660 | throw new Error("Attachment " + depname + "/" + key + " not found in document"); 661 | } 662 | return link.getAttribute("href"); 663 | }; 664 | 665 | window.HTMLWidgets.dataframeToD3 = function(df) { 666 | var names = []; 667 | var length; 668 | for (var name in df) { 669 | if (df.hasOwnProperty(name)) 670 | names.push(name); 671 | if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { 672 | throw new Error("All fields must be arrays"); 673 | } else if (typeof(length) !== "undefined" && length !== df[name].length) { 674 | throw new Error("All fields must be arrays of the same length"); 675 | } 676 | length = df[name].length; 677 | } 678 | var results = []; 679 | var item; 680 | for (var row = 0; row < length; row++) { 681 | item = {}; 682 | for (var col = 0; col < names.length; col++) { 683 | item[names[col]] = df[names[col]][row]; 684 | } 685 | results.push(item); 686 | } 687 | return results; 688 | }; 689 | 690 | window.HTMLWidgets.transposeArray2D = function(array) { 691 | if (array.length === 0) return array; 692 | var newArray = array[0].map(function(col, i) { 693 | return array.map(function(row) { 694 | return row[i] 695 | }) 696 | }); 697 | return newArray; 698 | }; 699 | // Split value at splitChar, but allow splitChar to be escaped 700 | // using escapeChar. Any other characters escaped by escapeChar 701 | // will be included as usual (including escapeChar itself). 702 | function splitWithEscape(value, splitChar, escapeChar) { 703 | var results = []; 704 | var escapeMode = false; 705 | var currentResult = ""; 706 | for (var pos = 0; pos < value.length; pos++) { 707 | if (!escapeMode) { 708 | if (value[pos] === splitChar) { 709 | results.push(currentResult); 710 | currentResult = ""; 711 | } else if (value[pos] === escapeChar) { 712 | escapeMode = true; 713 | } else { 714 | currentResult += value[pos]; 715 | } 716 | } else { 717 | currentResult += value[pos]; 718 | escapeMode = false; 719 | } 720 | } 721 | if (currentResult !== "") { 722 | results.push(currentResult); 723 | } 724 | return results; 725 | } 726 | // Function authored by Yihui/JJ Allaire 727 | window.HTMLWidgets.evaluateStringMember = function(o, member) { 728 | var parts = splitWithEscape(member, '.', '\\'); 729 | for (var i = 0, l = parts.length; i < l; i++) { 730 | var part = parts[i]; 731 | // part may be a character or 'numeric' member name 732 | if (o !== null && typeof o === "object" && part in o) { 733 | if (i == (l - 1)) { // if we are at the end of the line then evalulate 734 | if (typeof o[part] === "string") 735 | o[part] = eval("(" + o[part] + ")"); 736 | } else { // otherwise continue to next embedded object 737 | o = o[part]; 738 | } 739 | } 740 | } 741 | }; 742 | 743 | // Retrieve the HTMLWidget instance (i.e. the return value of an 744 | // HTMLWidget binding's initialize() or factory() function) 745 | // associated with an element, or null if none. 746 | window.HTMLWidgets.getInstance = function(el) { 747 | return elementData(el, "init_result"); 748 | }; 749 | 750 | // Finds the first element in the scope that matches the selector, 751 | // and returns the HTMLWidget instance (i.e. the return value of 752 | // an HTMLWidget binding's initialize() or factory() function) 753 | // associated with that element, if any. If no element matches the 754 | // selector, or the first matching element has no HTMLWidget 755 | // instance associated with it, then null is returned. 756 | // 757 | // The scope argument is optional, and defaults to window.document. 758 | window.HTMLWidgets.find = function(scope, selector) { 759 | if (arguments.length == 1) { 760 | selector = scope; 761 | scope = document; 762 | } 763 | 764 | var el = scope.querySelector(selector); 765 | if (el === null) { 766 | return null; 767 | } else { 768 | return window.HTMLWidgets.getInstance(el); 769 | } 770 | }; 771 | 772 | // Finds all elements in the scope that match the selector, and 773 | // returns the HTMLWidget instances (i.e. the return values of 774 | // an HTMLWidget binding's initialize() or factory() function) 775 | // associated with the elements, in an array. If elements that 776 | // match the selector don't have an associated HTMLWidget 777 | // instance, the returned array will contain nulls. 778 | // 779 | // The scope argument is optional, and defaults to window.document. 780 | window.HTMLWidgets.findAll = function(scope, selector) { 781 | if (arguments.length == 1) { 782 | selector = scope; 783 | scope = document; 784 | } 785 | 786 | var nodes = scope.querySelectorAll(selector); 787 | var results = []; 788 | for (var i = 0; i < nodes.length; i++) { 789 | results.push(window.HTMLWidgets.getInstance(nodes[i])); 790 | } 791 | return results; 792 | }; 793 | 794 | var postRenderHandlers = []; 795 | function invokePostRenderHandlers() { 796 | while (postRenderHandlers.length) { 797 | var handler = postRenderHandlers.shift(); 798 | if (handler) { 799 | handler(); 800 | } 801 | } 802 | } 803 | 804 | // Register the given callback function to be invoked after the 805 | // next time static widgets are rendered. 806 | window.HTMLWidgets.addPostRenderHandler = function(callback) { 807 | postRenderHandlers.push(callback); 808 | }; 809 | 810 | // Takes a new-style instance-bound definition, and returns an 811 | // old-style class-bound definition. This saves us from having 812 | // to rewrite all the logic in this file to accomodate both 813 | // types of definitions. 814 | function createLegacyDefinitionAdapter(defn) { 815 | var result = { 816 | name: defn.name, 817 | type: defn.type, 818 | initialize: function(el, width, height) { 819 | return defn.factory(el, width, height); 820 | }, 821 | renderValue: function(el, x, instance) { 822 | return instance.renderValue(x); 823 | }, 824 | resize: function(el, width, height, instance) { 825 | return instance.resize(width, height); 826 | } 827 | }; 828 | 829 | if (defn.find) 830 | result.find = defn.find; 831 | if (defn.renderError) 832 | result.renderError = defn.renderError; 833 | if (defn.clearError) 834 | result.clearError = defn.clearError; 835 | 836 | return result; 837 | } 838 | })(); 839 | 840 | -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/leaflet-1.3.1/images/layers-2x.png -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/leaflet-1.3.1/images/layers.png -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/leaflet-1.3.1/images/marker-icon-2x.png -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/leaflet-1.3.1/images/marker-icon.png -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/leaflet-1.3.1/images/marker-shadow.png -------------------------------------------------------------------------------- /index_files/leaflet-1.3.1/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /index_files/leafletfix-1.0.0/leafletfix.css: -------------------------------------------------------------------------------- 1 | /* Work around CSS properties introduced on img by bootstrap */ 2 | img.leaflet-tile { 3 | padding: 0; 4 | margin: 0; 5 | border-radius: 0; 6 | border: none; 7 | } 8 | .info { 9 | padding: 6px 8px; 10 | font: 14px/16px Arial, Helvetica, sans-serif; 11 | background: white; 12 | background: rgba(255,255,255,0.8); 13 | box-shadow: 0 0 15px rgba(0,0,0,0.2); 14 | border-radius: 5px; 15 | } 16 | .legend { 17 | line-height: 18px; 18 | color: #555; 19 | } 20 | .legend svg text { 21 | fill: #555; 22 | } 23 | .legend svg line { 24 | stroke: #555; 25 | } 26 | .legend i { 27 | width: 18px; 28 | height: 18px; 29 | margin-right: 4px; 30 | opacity: 0.7; 31 | display: inline-block; 32 | vertical-align: top; 33 | /*For IE 7*/ 34 | zoom: 1; 35 | *display: inline; 36 | } 37 | -------------------------------------------------------------------------------- /index_files/rstudio_leaflet-1.3.1/images/1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcmbishop/rayshader-demo/38729b58a272c23cc0cd83e3f3a54d9787dc735a/index_files/rstudio_leaflet-1.3.1/images/1px.png -------------------------------------------------------------------------------- /index_files/rstudio_leaflet-1.3.1/rstudio_leaflet.css: -------------------------------------------------------------------------------- 1 | .leaflet-tooltip.leaflet-tooltip-text-only, 2 | .leaflet-tooltip.leaflet-tooltip-text-only:before, 3 | .leaflet-tooltip.leaflet-tooltip-text-only:after { 4 | background: none; 5 | border: none; 6 | box-shadow: none; 7 | } 8 | 9 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-left { 10 | margin-left: 5px; 11 | } 12 | 13 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-right { 14 | margin-left: -5px; 15 | } 16 | 17 | .leaflet-tooltip:after { 18 | border-right: 6px solid transparent; 19 | /* right: -16px; */ 20 | } 21 | 22 | .leaflet-popup-pane .leaflet-popup-tip-container { 23 | /* when the tooltip container is clicked, it is closed */ 24 | pointer-events: all; 25 | /* tooltips should display the "hand" icon, just like .leaflet-interactive*/ 26 | cursor: pointer; 27 | } 28 | 29 | /* have the widget be displayed in the right 'layer' */ 30 | .leaflet-map-pane { 31 | z-index: auto; 32 | } 33 | -------------------------------------------------------------------------------- /rayshader-demo.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: knitr 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "3.5.2", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cran.rstudio.com/" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "BH": { 13 | "Package": "BH", 14 | "Version": "1.69.0-1", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "88e64b38758666b85d283617231bb766" 18 | }, 19 | "Cairo": { 20 | "Package": "Cairo", 21 | "Version": "1.5-9", 22 | "Source": "Repository", 23 | "Repository": "CRAN", 24 | "Hash": "7d3116264280be09f4bd847bed0cab97" 25 | }, 26 | "MASS": { 27 | "Package": "MASS", 28 | "Version": "7.3-51.1", 29 | "Source": "Repository", 30 | "Repository": "CRAN", 31 | "Hash": "9beca15f32d56f1a5eacfa6419948eb9" 32 | }, 33 | "Matrix": { 34 | "Package": "Matrix", 35 | "Version": "1.2-15", 36 | "Source": "Repository", 37 | "Repository": "CRAN", 38 | "Hash": "9c5038aa0d5e8929162e780e42cf44fe" 39 | }, 40 | "R6": { 41 | "Package": "R6", 42 | "Version": "2.4.0", 43 | "Source": "Repository", 44 | "Repository": "CRAN", 45 | "Hash": "d42941d973f7f28b32f694bc89fb9d5f" 46 | }, 47 | "RColorBrewer": { 48 | "Package": "RColorBrewer", 49 | "Version": "1.1-2", 50 | "Source": "Repository", 51 | "Repository": "CRAN", 52 | "Hash": "cdfb92174501c241a0d4e13fcac600a4" 53 | }, 54 | "Rcpp": { 55 | "Package": "Rcpp", 56 | "Version": "1.0.1", 57 | "Source": "Repository", 58 | "Repository": "CRAN", 59 | "Hash": "542a8dbcf371718e4dee591341d28c43" 60 | }, 61 | "RcppArmadillo": { 62 | "Package": "RcppArmadillo", 63 | "Version": "0.9.200.7.0", 64 | "Source": "Repository", 65 | "Repository": "CRAN", 66 | "Hash": "9b8f08ff23c74c78a8dec3a617788743" 67 | }, 68 | "askpass": { 69 | "Package": "askpass", 70 | "Version": "1.1", 71 | "Source": "Repository", 72 | "Repository": "CRAN", 73 | "Hash": "ff8e30ba3e542c07372fca6de28bce3b" 74 | }, 75 | "assertthat": { 76 | "Package": "assertthat", 77 | "Version": "0.2.1", 78 | "Source": "Repository", 79 | "Repository": "CRAN", 80 | "Hash": "263effaa484b1dc3013cf1c1e8f6624e" 81 | }, 82 | "backports": { 83 | "Package": "backports", 84 | "Version": "1.1.4", 85 | "Source": "Repository", 86 | "Repository": "CRAN", 87 | "Hash": "8f863a45c5b6c9ccd5a71988138a48c2" 88 | }, 89 | "base64enc": { 90 | "Package": "base64enc", 91 | "Version": "0.1-3", 92 | "Source": "Repository", 93 | "Repository": "CRAN", 94 | "Hash": "eec0d710cee1455ba24eb6ab622a60e9" 95 | }, 96 | "bmp": { 97 | "Package": "bmp", 98 | "Version": "0.3", 99 | "Source": "Repository", 100 | "Repository": "CRAN", 101 | "Hash": "b8a09288aa891c4eb3103076158e1805" 102 | }, 103 | "bookdown": { 104 | "Package": "bookdown", 105 | "Version": "0.9", 106 | "Source": "Repository", 107 | "Repository": "CRAN", 108 | "Hash": "be0db70ddc5de27f01aea0c2f1ad24e7" 109 | }, 110 | "callr": { 111 | "Package": "callr", 112 | "Version": "3.1.1", 113 | "Source": "Repository", 114 | "Repository": "CRAN", 115 | "Hash": "383a4f023c2eb51d146f48762f6b79dd" 116 | }, 117 | "cli": { 118 | "Package": "cli", 119 | "Version": "1.1.0", 120 | "Source": "Repository", 121 | "Repository": "CRAN", 122 | "Hash": "c0a5f242aa2259cf7e40da04bb89f5d8" 123 | }, 124 | "codetools": { 125 | "Package": "codetools", 126 | "Version": "0.2-15", 127 | "Source": "Repository", 128 | "Repository": "CRAN", 129 | "Hash": "71fa8e1fac5dc255507f5ed253c6a367" 130 | }, 131 | "colorspace": { 132 | "Package": "colorspace", 133 | "Version": "1.4-1", 134 | "Source": "Repository", 135 | "Repository": "CRAN", 136 | "Hash": "70941d6476af18c149b5b6916f87a7c5" 137 | }, 138 | "crayon": { 139 | "Package": "crayon", 140 | "Version": "1.3.4", 141 | "Source": "Repository", 142 | "Repository": "CRAN", 143 | "Hash": "1e93bda8f2b60a0defc85c82a39e2891" 144 | }, 145 | "crosstalk": { 146 | "Package": "crosstalk", 147 | "Version": "1.0.0", 148 | "Source": "Repository", 149 | "Repository": "CRAN", 150 | "Hash": "751f5746f428e5c0e453a53dac3c85af" 151 | }, 152 | "curl": { 153 | "Package": "curl", 154 | "Version": "3.3", 155 | "Source": "Repository", 156 | "Repository": "CRAN", 157 | "Hash": "921ecc2db7bca3844daaec11fce12e76" 158 | }, 159 | "digest": { 160 | "Package": "digest", 161 | "Version": "0.6.19", 162 | "Source": "Repository", 163 | "Repository": "CRAN", 164 | "Hash": "b0ffa68c2a452ea6dafda0f7810b3e7d" 165 | }, 166 | "doParallel": { 167 | "Package": "doParallel", 168 | "Version": "1.0.14", 169 | "Source": "Repository", 170 | "Repository": "CRAN", 171 | "Hash": "0ea05ab6556ff10f3bed2a4f6caf23e2" 172 | }, 173 | "downloader": { 174 | "Package": "downloader", 175 | "Version": "0.4", 176 | "Source": "Repository", 177 | "Repository": "CRAN", 178 | "Hash": "3023de572f4e0c0b772fa4c9308c6cc8" 179 | }, 180 | "evaluate": { 181 | "Package": "evaluate", 182 | "Version": "0.13", 183 | "Source": "Repository", 184 | "Repository": "CRAN", 185 | "Hash": "1a4182c08eece9c529eec110119ac6a7" 186 | }, 187 | "fansi": { 188 | "Package": "fansi", 189 | "Version": "0.4.0", 190 | "Source": "Repository", 191 | "Repository": "CRAN", 192 | "Hash": "7e8ca3a2d400cbdaf72a602e9833c920" 193 | }, 194 | "foreach": { 195 | "Package": "foreach", 196 | "Version": "1.4.4", 197 | "Source": "Repository", 198 | "Repository": "CRAN", 199 | "Hash": "bd821384f7261ac6b892012ce2b82226" 200 | }, 201 | "ggplot2": { 202 | "Package": "ggplot2", 203 | "Version": "3.1.1", 204 | "Source": "Repository", 205 | "Repository": "CRAN", 206 | "Hash": "4afc1cad89d1a1acc0ed18e69ae71323" 207 | }, 208 | "gifski": { 209 | "Package": "gifski", 210 | "Version": "0.8.6", 211 | "Source": "Repository", 212 | "Repository": "CRAN", 213 | "Hash": "e40bd6c3e8cafc59d3899ebc5490694e" 214 | }, 215 | "glue": { 216 | "Package": "glue", 217 | "Version": "1.3.1", 218 | "Source": "Repository", 219 | "Repository": "CRAN", 220 | "Hash": "156f76da413ebe13f4d7e65ae6a5d19f" 221 | }, 222 | "gridExtra": { 223 | "Package": "gridExtra", 224 | "Version": "2.3", 225 | "Source": "Repository", 226 | "Repository": "CRAN", 227 | "Hash": "cd5e8e3a3950e27fad98d62feab5b0ba" 228 | }, 229 | "gtable": { 230 | "Package": "gtable", 231 | "Version": "0.3.0", 232 | "Source": "Repository", 233 | "Repository": "CRAN", 234 | "Hash": "120444406cc884baa3a2ff5c0566045b" 235 | }, 236 | "highr": { 237 | "Package": "highr", 238 | "Version": "0.8", 239 | "Source": "Repository", 240 | "Repository": "CRAN", 241 | "Hash": "2f5651aef7832e8eea60d3f3add2ffaa" 242 | }, 243 | "hms": { 244 | "Package": "hms", 245 | "Version": "0.4.2", 246 | "Source": "Repository", 247 | "Repository": "CRAN", 248 | "Hash": "c1a7ac1b51eb0f5ef51a4ea256eb9cbe" 249 | }, 250 | "htmltools": { 251 | "Package": "htmltools", 252 | "Version": "0.3.6", 253 | "Source": "Repository", 254 | "Repository": "CRAN", 255 | "Hash": "881e91b8b764a550de68ad18a98c05b5" 256 | }, 257 | "htmlwidgets": { 258 | "Package": "htmlwidgets", 259 | "Version": "1.3", 260 | "Source": "Repository", 261 | "Repository": "CRAN", 262 | "Hash": "f9779405095eb05656a9aa75e62606cb" 263 | }, 264 | "httpuv": { 265 | "Package": "httpuv", 266 | "Version": "1.4.5.1", 267 | "Source": "Repository", 268 | "Repository": "CRAN", 269 | "Hash": "95f47185730b736cd2af391d0c80aafa" 270 | }, 271 | "httr": { 272 | "Package": "httr", 273 | "Version": "1.4.0", 274 | "Source": "Repository", 275 | "Repository": "CRAN", 276 | "Hash": "f5dc99b1972d4e9eaf6f5c452339db08" 277 | }, 278 | "igraph": { 279 | "Package": "igraph", 280 | "Version": "1.2.2", 281 | "Source": "Repository", 282 | "Repository": "CRAN", 283 | "Hash": "037ab874c6af16d4274e7a12fd34d387" 284 | }, 285 | "imager": { 286 | "Package": "imager", 287 | "Version": "0.41.2", 288 | "Source": "Repository", 289 | "Repository": "CRAN", 290 | "Hash": "d52f681a900963068fbd5826dbb71261" 291 | }, 292 | "iterators": { 293 | "Package": "iterators", 294 | "Version": "1.0.10", 295 | "Source": "Repository", 296 | "Repository": "CRAN", 297 | "Hash": "14bee32d7116523ad5083d2147844208" 298 | }, 299 | "jpeg": { 300 | "Package": "jpeg", 301 | "Version": "0.1-8", 302 | "Source": "Repository", 303 | "Repository": "CRAN", 304 | "Hash": "9251bd09cb1fff9cf3b654f27b4633f3" 305 | }, 306 | "jsonlite": { 307 | "Package": "jsonlite", 308 | "Version": "1.6", 309 | "Source": "Repository", 310 | "Repository": "CRAN", 311 | "Hash": "9ec2b2fe0e874a66d0cb54d8ced5618e" 312 | }, 313 | "knitr": { 314 | "Package": "knitr", 315 | "Version": "1.22", 316 | "Source": "Repository", 317 | "Repository": "CRAN", 318 | "Hash": "b1ef4af5ce7c39dcba8da6ff39bfd7a1" 319 | }, 320 | "labeling": { 321 | "Package": "labeling", 322 | "Version": "0.3", 323 | "Source": "Repository", 324 | "Repository": "CRAN", 325 | "Hash": "379ec196f3f3bb312727d52b022fa8f9" 326 | }, 327 | "later": { 328 | "Package": "later", 329 | "Version": "0.8.0", 330 | "Source": "Repository", 331 | "Repository": "CRAN", 332 | "Hash": "718545eb0b5b354929864914c818ae53" 333 | }, 334 | "lattice": { 335 | "Package": "lattice", 336 | "Version": "0.20-38", 337 | "Source": "Repository", 338 | "Repository": "CRAN", 339 | "Hash": "16dfe2407fe485b6a9fb098120008721" 340 | }, 341 | "lazyeval": { 342 | "Package": "lazyeval", 343 | "Version": "0.2.2", 344 | "Source": "Repository", 345 | "Repository": "CRAN", 346 | "Hash": "8c343e48c3f58de2c70a641592abc0f1" 347 | }, 348 | "leaflet": { 349 | "Package": "leaflet", 350 | "Version": "2.0.2", 351 | "Source": "Repository", 352 | "Repository": "CRAN", 353 | "Hash": "06da4d9082beb344a3acd8fe9e45c7c0" 354 | }, 355 | "lubridate": { 356 | "Package": "lubridate", 357 | "Version": "1.7.4", 358 | "Source": "Repository", 359 | "Repository": "CRAN", 360 | "Hash": "4af93df0d50bb3919de67f36e7d3d00b" 361 | }, 362 | "magick": { 363 | "Package": "magick", 364 | "Version": "2.0", 365 | "Source": "Repository", 366 | "Repository": "CRAN", 367 | "Hash": "f682fcb032e61f81d0f5fc1d8cb099e3" 368 | }, 369 | "magrittr": { 370 | "Package": "magrittr", 371 | "Version": "1.5", 372 | "Source": "Repository", 373 | "Repository": "CRAN", 374 | "Hash": "cb6367fec3fd68ff41424fe9797b8eaf" 375 | }, 376 | "manipulateWidget": { 377 | "Package": "manipulateWidget", 378 | "Version": "0.10.0", 379 | "Source": "Repository", 380 | "Repository": "CRAN", 381 | "Hash": "def62119c485fd194b2b26ceed008ef7" 382 | }, 383 | "markdown": { 384 | "Package": "markdown", 385 | "Version": "0.9", 386 | "Source": "Repository", 387 | "Repository": "CRAN", 388 | "Hash": "5e5d7e3b37fbdb3b06e3f9484f80db0e" 389 | }, 390 | "mgcv": { 391 | "Package": "mgcv", 392 | "Version": "1.8-26", 393 | "Source": "Repository", 394 | "Repository": "CRAN", 395 | "Hash": "328a01ef69f40904edd7a7e7ebbadb06" 396 | }, 397 | "mime": { 398 | "Package": "mime", 399 | "Version": "0.7", 400 | "Source": "Repository", 401 | "Repository": "CRAN", 402 | "Hash": "054d829092f3869d99d1ac57e4fe21eb" 403 | }, 404 | "miniUI": { 405 | "Package": "miniUI", 406 | "Version": "0.1.1.1", 407 | "Source": "Repository", 408 | "Repository": "CRAN", 409 | "Hash": "78d83c530ce013174ce0cabbe3d743e1" 410 | }, 411 | "munsell": { 412 | "Package": "munsell", 413 | "Version": "0.5.0", 414 | "Source": "Repository", 415 | "Repository": "CRAN", 416 | "Hash": "381047af4d6b44ca4d9285dbdea42434" 417 | }, 418 | "nlme": { 419 | "Package": "nlme", 420 | "Version": "3.1-137", 421 | "Source": "Repository", 422 | "Repository": "CRAN", 423 | "Hash": "55f627741007cdb86b124a16183e6a9e" 424 | }, 425 | "openssl": { 426 | "Package": "openssl", 427 | "Version": "1.4", 428 | "Source": "Repository", 429 | "Repository": "CRAN", 430 | "Hash": "4557b03dff7c01f4e7cd10020b0667bc" 431 | }, 432 | "pillar": { 433 | "Package": "pillar", 434 | "Version": "1.4.1", 435 | "Source": "Repository", 436 | "Repository": "CRAN", 437 | "Hash": "6606112a90dbe4d2306acf618072af31" 438 | }, 439 | "pkgconfig": { 440 | "Package": "pkgconfig", 441 | "Version": "2.0.2", 442 | "Source": "Repository", 443 | "Repository": "CRAN", 444 | "Hash": "47f492c929356d52b15018b5827cf857" 445 | }, 446 | "plyr": { 447 | "Package": "plyr", 448 | "Version": "1.8.4", 449 | "Source": "Repository", 450 | "Repository": "CRAN", 451 | "Hash": "48b2c2a9c4a504ea9167e9252fd9fade" 452 | }, 453 | "png": { 454 | "Package": "png", 455 | "Version": "0.1-7", 456 | "Source": "Repository", 457 | "Repository": "CRAN", 458 | "Hash": "f4a3a0c1f8513a557dcc8dfb32239cd2" 459 | }, 460 | "prettyunits": { 461 | "Package": "prettyunits", 462 | "Version": "1.0.2", 463 | "Source": "Repository", 464 | "Repository": "CRAN", 465 | "Hash": "47d4646a3be22893c04093e8db164989" 466 | }, 467 | "processx": { 468 | "Package": "processx", 469 | "Version": "3.2.1", 470 | "Source": "Repository", 471 | "Repository": "CRAN", 472 | "Hash": "5a3d22c53ad090c87a85af0732631956" 473 | }, 474 | "progress": { 475 | "Package": "progress", 476 | "Version": "1.2.0", 477 | "Source": "Repository", 478 | "Repository": "CRAN", 479 | "Hash": "d72bac138cb24ed3a787f03a33954c46" 480 | }, 481 | "promises": { 482 | "Package": "promises", 483 | "Version": "1.0.1", 484 | "Source": "Repository", 485 | "Repository": "CRAN", 486 | "Hash": "30a46ce3d9fe3958f266181f8c6f541f" 487 | }, 488 | "ps": { 489 | "Package": "ps", 490 | "Version": "1.3.0", 491 | "Source": "Repository", 492 | "Repository": "CRAN", 493 | "Hash": "12c6a7e02239401d47599ddc44a484f4" 494 | }, 495 | "purrr": { 496 | "Package": "purrr", 497 | "Version": "0.3.2", 498 | "Source": "Repository", 499 | "Repository": "CRAN", 500 | "Hash": "19767ff705a04566e2f396e15ccc1b39" 501 | }, 502 | "radix": { 503 | "Package": "radix", 504 | "Version": "0.6", 505 | "Source": "Repository", 506 | "Repository": "CRAN", 507 | "Hash": "2fef7c3747d1422a6a8c9d91b0170ded" 508 | }, 509 | "raster": { 510 | "Package": "raster", 511 | "Version": "2.8-19", 512 | "Source": "Repository", 513 | "Repository": "CRAN", 514 | "Hash": "5f431eb5071179862b733e2cbcb2ea67" 515 | }, 516 | "rayshader": { 517 | "Package": "rayshader", 518 | "Version": "0.9.1", 519 | "Source": "GitHub", 520 | "RemoteType": "github", 521 | "RemoteHost": "api.github.com", 522 | "RemoteRepo": "rayshader", 523 | "RemoteUsername": "tylermorganwall", 524 | "RemoteRef": "master", 525 | "RemoteSha": "652a3bbb862465f1aee4bfbb9acd26cb0cf5fb1c", 526 | "Hash": "3512c3e1e95172a163bed9ad0d27d52b" 527 | }, 528 | "readbitmap": { 529 | "Package": "readbitmap", 530 | "Version": "0.1.5", 531 | "Source": "Repository", 532 | "Repository": "CRAN", 533 | "Hash": "209bbd6bc20c837daf86e12a92b393c3" 534 | }, 535 | "reshape2": { 536 | "Package": "reshape2", 537 | "Version": "1.4.3", 538 | "Source": "Repository", 539 | "Repository": "CRAN", 540 | "Hash": "a0b8854199dbfa29a71f514b3ffcddf0" 541 | }, 542 | "rgdal": { 543 | "Package": "rgdal", 544 | "Version": "1.3-6", 545 | "Source": "Repository", 546 | "Repository": "CRAN", 547 | "Hash": "c8ae4c6c434743b0312493cf08e984b2" 548 | }, 549 | "rgl": { 550 | "Package": "rgl", 551 | "Version": "0.99.16", 552 | "Source": "Repository", 553 | "Repository": "CRAN", 554 | "Hash": "d28b0e9d3be4bac5bbd36799c9d06f12" 555 | }, 556 | "rlang": { 557 | "Package": "rlang", 558 | "Version": "0.3.4", 559 | "Source": "Repository", 560 | "Repository": "CRAN", 561 | "Hash": "1367935e1052d25fd616368dea6d8bf5" 562 | }, 563 | "rmarkdown": { 564 | "Package": "rmarkdown", 565 | "Version": "1.12", 566 | "Source": "Repository", 567 | "Repository": "CRAN", 568 | "Hash": "6adbc9e47370f441b5ab2a4a4bd066df" 569 | }, 570 | "rprojroot": { 571 | "Package": "rprojroot", 572 | "Version": "1.3-2", 573 | "Source": "Repository", 574 | "Repository": "CRAN", 575 | "Hash": "75611db569353a873f987f858b6f06dd" 576 | }, 577 | "rstudioapi": { 578 | "Package": "rstudioapi", 579 | "Version": "0.10", 580 | "Source": "Repository", 581 | "Repository": "CRAN", 582 | "Hash": "6c03ab57831cbdae14d599151e832e18" 583 | }, 584 | "scales": { 585 | "Package": "scales", 586 | "Version": "1.0.0", 587 | "Source": "Repository", 588 | "Repository": "CRAN", 589 | "Hash": "17cfbe1a4fcc8d0440a2af68d9e07bf4" 590 | }, 591 | "shiny": { 592 | "Package": "shiny", 593 | "Version": "1.2.0", 594 | "Source": "Repository", 595 | "Repository": "CRAN", 596 | "Hash": "16d32dada859211298ca5f3cd5a76ab4" 597 | }, 598 | "sourcetools": { 599 | "Package": "sourcetools", 600 | "Version": "0.1.7", 601 | "Source": "Repository", 602 | "Repository": "CRAN", 603 | "Hash": "729278659af88ed36db42f46ccca0ebd" 604 | }, 605 | "sp": { 606 | "Package": "sp", 607 | "Version": "1.3-1", 608 | "Source": "Repository", 609 | "Repository": "CRAN", 610 | "Hash": "1a01377f70fbc6c380a712c61af85b0b" 611 | }, 612 | "stringi": { 613 | "Package": "stringi", 614 | "Version": "1.4.3", 615 | "Source": "Repository", 616 | "Repository": "CRAN", 617 | "Hash": "0472e71f2347e13cd398d5225ff3b0ea" 618 | }, 619 | "stringr": { 620 | "Package": "stringr", 621 | "Version": "1.4.0", 622 | "Source": "Repository", 623 | "Repository": "CRAN", 624 | "Hash": "01fb6c8262ac804d9bafa60cea8773da" 625 | }, 626 | "sys": { 627 | "Package": "sys", 628 | "Version": "3.2", 629 | "Source": "Repository", 630 | "Repository": "CRAN", 631 | "Hash": "22bf912f5ebfddff8992fdd4d01e9bb4" 632 | }, 633 | "tibble": { 634 | "Package": "tibble", 635 | "Version": "2.1.3", 636 | "Source": "Repository", 637 | "Repository": "CRAN", 638 | "Hash": "b28f74376db8b145e247e29e20bb7deb" 639 | }, 640 | "tiff": { 641 | "Package": "tiff", 642 | "Version": "0.1-5", 643 | "Source": "Repository", 644 | "Repository": "CRAN", 645 | "Hash": "c4038b6d34b80986dad3a6e2ebaa6fd3" 646 | }, 647 | "tinytex": { 648 | "Package": "tinytex", 649 | "Version": "0.11", 650 | "Source": "Repository", 651 | "Repository": "CRAN", 652 | "Hash": "d7ca18ea2e5d664fa6105881285c6b6e" 653 | }, 654 | "utf8": { 655 | "Package": "utf8", 656 | "Version": "1.1.4", 657 | "Source": "Repository", 658 | "Repository": "CRAN", 659 | "Hash": "557d1cc1d7b4ff362db5990acc00f2a0" 660 | }, 661 | "vctrs": { 662 | "Package": "vctrs", 663 | "Version": "0.1.0", 664 | "Source": "Repository", 665 | "Repository": "CRAN", 666 | "Hash": "a57267520709009929b09af57d668a2a" 667 | }, 668 | "viridis": { 669 | "Package": "viridis", 670 | "Version": "0.5.1", 671 | "Source": "Repository", 672 | "Repository": "CRAN", 673 | "Hash": "8b3bc31925862b55e20954eb3ba0f0c6" 674 | }, 675 | "viridisLite": { 676 | "Package": "viridisLite", 677 | "Version": "0.3.0", 678 | "Source": "Repository", 679 | "Repository": "CRAN", 680 | "Hash": "e62dbc33079a4e2ff7429032f0e46efd" 681 | }, 682 | "webshot": { 683 | "Package": "webshot", 684 | "Version": "0.5.1", 685 | "Source": "Repository", 686 | "Repository": "CRAN", 687 | "Hash": "13a7712cf6657cee13fda141e021c148" 688 | }, 689 | "whisker": { 690 | "Package": "whisker", 691 | "Version": "0.3-2", 692 | "Source": "Repository", 693 | "Repository": "CRAN", 694 | "Hash": "1e74aefd2c67890f504a1fa2d0834d70" 695 | }, 696 | "withr": { 697 | "Package": "withr", 698 | "Version": "2.1.2", 699 | "Source": "Repository", 700 | "Repository": "CRAN", 701 | "Hash": "ce9ffa6d865ecaf6a56cacc811565e07" 702 | }, 703 | "xfun": { 704 | "Package": "xfun", 705 | "Version": "0.6", 706 | "Source": "Repository", 707 | "Repository": "CRAN", 708 | "Hash": "bf185b73ebb313e62fd44ec7af5fc738" 709 | }, 710 | "xml2": { 711 | "Package": "xml2", 712 | "Version": "1.2.0", 713 | "Source": "Repository", 714 | "Repository": "CRAN", 715 | "Hash": "fe28830040ea8a0cd9d41749b8f9f715" 716 | }, 717 | "xtable": { 718 | "Package": "xtable", 719 | "Version": "1.8-3", 720 | "Source": "Repository", 721 | "Repository": "CRAN", 722 | "Hash": "240abc2788155021a3fc1684dfba4af0" 723 | }, 724 | "yaml": { 725 | "Package": "yaml", 726 | "Version": "2.2.0", 727 | "Source": "Repository", 728 | "Repository": "CRAN", 729 | "Hash": "e75dfa28ca59a73adfabf6899ce04472" 730 | }, 731 | "zeallot": { 732 | "Package": "zeallot", 733 | "Version": "0.1.0", 734 | "Source": "Repository", 735 | "Repository": "CRAN", 736 | "Hash": "c33bb7353728bd4547a079b2c351a021" 737 | } 738 | } 739 | } 740 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | python/ 3 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "0.7.0-126" 6 | 7 | # avoid recursion 8 | if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) 9 | return(invisible(TRUE)) 10 | 11 | # signal that we're loading renv during R startup 12 | Sys.setenv("RENV_R_INITIALIZING" = "true") 13 | on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) 14 | 15 | # signal that we've consented to use renv 16 | options(renv.consent = TRUE) 17 | 18 | # load the 'utils' package eagerly -- this ensures that renv shims, which 19 | # mask 'utils' packages, will come first on the search path 20 | library(utils, lib.loc = .Library) 21 | 22 | # check to see if renv has already been loaded 23 | if ("renv" %in% loadedNamespaces()) { 24 | 25 | # if renv has already been loaded, and it's the requested version of renv, 26 | # nothing to do 27 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") 28 | if (identical(spec[["version"]], version)) 29 | return(invisible(TRUE)) 30 | 31 | # otherwise, unload and attempt to load the correct version of renv 32 | unloadNamespace("renv") 33 | 34 | } 35 | 36 | # construct path to renv in library 37 | libpath <- local({ 38 | 39 | root <- Sys.getenv("RENV_PATHS_LIBRARY", unset = "renv/library") 40 | prefix <- paste("R", getRversion()[1, 1:2], sep = "-") 41 | 42 | # include SVN revision for development versions of R 43 | # (to avoid sharing platform-specific artefacts with released versions of R) 44 | devel <- 45 | identical(R.version[["status"]], "Under development (unstable)") || 46 | identical(R.version[["nickname"]], "Unsuffered Consequences") 47 | 48 | if (devel) 49 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 50 | 51 | file.path(root, prefix, R.version$platform) 52 | 53 | }) 54 | 55 | # try to load renv from the project library 56 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 57 | return(renv::load()) 58 | 59 | # failed to find renv locally; we'll try to install from GitHub. 60 | # first, set up download options as appropriate (try to use GITHUB_PAT) 61 | install_renv <- function() { 62 | 63 | message("Failed to find installation of renv -- attempting to bootstrap...") 64 | 65 | # ensure .Rprofile doesn't get executed 66 | rpu <- Sys.getenv("R_PROFILE_USER", unset = NA) 67 | Sys.setenv(R_PROFILE_USER = "") 68 | on.exit({ 69 | if (is.na(rpu)) 70 | Sys.unsetenv("R_PROFILE_USER") 71 | else 72 | Sys.setenv(R_PROFILE_USER = rpu) 73 | }, add = TRUE) 74 | 75 | # prepare download options 76 | pat <- Sys.getenv("GITHUB_PAT") 77 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 78 | fmt <- "--location --fail --header \"Authorization: token %s\"" 79 | extra <- sprintf(fmt, pat) 80 | saved <- options("download.file.method", "download.file.extra") 81 | options(download.file.method = "curl", download.file.extra = extra) 82 | on.exit(do.call(base::options, saved), add = TRUE) 83 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 84 | fmt <- "--header=\"Authorization: token %s\"" 85 | extra <- sprintf(fmt, pat) 86 | saved <- options("download.file.method", "download.file.extra") 87 | options(download.file.method = "wget", download.file.extra = extra) 88 | on.exit(do.call(base::options, saved), add = TRUE) 89 | } 90 | 91 | # fix up repos 92 | repos <- getOption("repos") 93 | on.exit(options(repos = repos), add = TRUE) 94 | repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" 95 | options(repos = repos) 96 | 97 | # check for renv on CRAN matching this version 98 | db <- as.data.frame(available.packages(), stringsAsFactors = FALSE) 99 | if ("renv" %in% rownames(db)) { 100 | entry <- db["renv", ] 101 | if (identical(entry$Version, version)) { 102 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 103 | dir.create(libpath, showWarnings = FALSE, recursive = TRUE) 104 | utils::install.packages("renv", lib = libpath, quiet = TRUE) 105 | message("Done!") 106 | return(TRUE) 107 | } 108 | } 109 | 110 | # try to download renv 111 | message("* Downloading renv ", version, " ... ", appendLF = FALSE) 112 | prefix <- "https://api.github.com" 113 | url <- file.path(prefix, "repos/rstudio/renv/tarball", version) 114 | destfile <- tempfile("renv-", fileext = ".tar.gz") 115 | on.exit(unlink(destfile), add = TRUE) 116 | utils::download.file(url, destfile = destfile, mode = "wb", quiet = TRUE) 117 | message("Done!") 118 | 119 | # attempt to install it into project library 120 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 121 | dir.create(libpath, showWarnings = FALSE, recursive = TRUE) 122 | 123 | # invoke using system2 so we can capture and report output 124 | bin <- R.home("bin") 125 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 126 | r <- file.path(bin, exe) 127 | args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(libpath), shQuote(destfile)) 128 | output <- system2(r, args, stdout = TRUE, stderr = TRUE) 129 | message("Done!") 130 | 131 | # check for successful install 132 | status <- attr(output, "status") 133 | if (is.numeric(status) && !identical(status, 0L)) { 134 | text <- c("Error installing renv", "=====================", output) 135 | writeLines(text, con = stderr()) 136 | } 137 | 138 | 139 | } 140 | 141 | try(install_renv()) 142 | 143 | # try again to load 144 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 145 | message("Successfully installed and loaded renv ", version, ".") 146 | return(renv::load()) 147 | } 148 | 149 | # failed to download or load renv; warn the user 150 | msg <- c( 151 | "Failed to find an renv installation: the project will not be loaded.", 152 | "Use `renv::activate()` to re-initialize the project." 153 | ) 154 | 155 | warning(paste(msg, collapse = "\n"), call. = FALSE) 156 | 157 | }) 158 | -------------------------------------------------------------------------------- /renv/settings.dcf: -------------------------------------------------------------------------------- 1 | external.libraries: 2 | ignored.packages: 3 | snapshot.type: packrat 4 | use.cache: TRUE 5 | --------------------------------------------------------------------------------