## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ## ----install, eval=FALSE------------------------------------------------------ # # From CRAN: # install.packages("rDeckgl") # # # Development version from GitHub: # # remotes::install_github("TiRizvanov/rDeckgl") ## ----scatterplot-------------------------------------------------------------- # library(rDeckgl) # library(scales) # # # Generate a spaced-out grid of ~10K points around San Francisco # set.seed(42) # grid_size <- 100L # # lon_seq <- seq(-122.515, -122.355, length.out = grid_size) # lat_seq <- seq(37.70, 37.82, length.out = grid_size) # grid <- expand.grid(lon = lon_seq, lat = lat_seq) # grid$value <- rnorm(nrow(grid), mean = 0, sd = 1) # # # Add light jitter # jitter_strength <- 0.0007 # grid$lon <- grid$lon + runif(nrow(grid), -jitter_strength, jitter_strength) # grid$lat <- grid$lat + runif(nrow(grid), -jitter_strength, jitter_strength) # grid$radius <- runif(nrow(grid), 25, 80) # # points_data <- grid # # # Map 'value' to viridis color palette # domain_range <- range(points_data$value) # palette_fun <- col_numeric(viridis_pal(option = "B")(256), domain_range) # rgba <- col2rgb(palette_fun(points_data$value)) # points_data$color_r <- rgba[1, ] # points_data$color_g <- rgba[2, ] # points_data$color_b <- rgba[3, ] # # spec <- list( # `@@type` = "DeckGL", # initialViewState = list( # longitude = mean(range(points_data$lon)), # latitude = mean(range(points_data$lat)), # zoom = 11.5, # pitch = 20, # bearing = 0 # ), # tooltip = list( # html = "
Value: {value}
Radius: {radius} m
", # style = list( # backgroundColor = "#0e1119", # color = "#FFFFFF", # fontSize = "12px" # ) # ), # views = list( # list( # `@@type` = "MapView", # controller = TRUE, # mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" # ) # ), # layers = list( # list( # `@@type` = "ScatterplotLayer", # id = "scatterplot", # data = list( # type = "duckdb", # query = "SELECT lon, lat, radius, value, color_r, color_g, color_b FROM points" # ), # getPosition = "@@=[lon, lat]", # getRadius = "@@=radius", # getFillColor = "@@=[color_r, color_g, color_b, 200]", # pickable = TRUE, # autoHighlight = TRUE, # radiusUnits = "meters" # ) # ) # ) # # deckgl( # spec = spec, # data = list(points = points_data), # width = "100%", # height = "600px" # ) ## ----hexagon------------------------------------------------------------------ # library(rDeckgl) # # hexagon_spec <- list( # initialViewState = list( # longitude = -1.4157267858730052, # latitude = 52.232395363869415, # zoom = 6.6, # pitch = 40.5, # bearing = -27.396674584323023 # ), # views = list( # list( # `@@type` = "MapView", # controller = TRUE, # mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json" # ) # ), # layers = list( # list( # `@@type` = "HexagonLayer", # id = "heatmap", # data = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv", # coverage = 1, # pickable = TRUE, # elevationRange = c(0, 3000), # elevationScale = 50, # extruded = TRUE, # getPosition = "@@=[lng,lat]", # radius = 1000, # colorRange = list( # c(1, 152, 189), # c(73, 227, 206), # c(216, 254, 181), # c(254, 237, 177), # c(254, 173, 84), # c(209, 55, 78) # ) # ) # ) # ) # # deckgl( # spec = hexagon_spec, # width = "100%", # height = "600px" # ) ## ----polygons----------------------------------------------------------------- # library(rDeckgl) # library(DBI) # library(duckdb) # # # ── 1. Generate mock spatial omics data ──────────────────────────────────────── # set.seed(42) # n_cells <- 300 # field_w <- 8000 # microns # field_h <- 6000 # # # Random centroids # cx <- runif(n_cells, 200, field_w - 200) # cy <- runif(n_cells, 200, field_h - 200) # # # Simulated per-cell metrics # total_expr <- pmax(0, round(rnorm(n_cells, 200, 80))) # cluster <- paste0("c", sample.int(6L, n_cells, replace = TRUE)) # # # Colour centroids by cluster # pal <- grDevices::hcl.colors(6, "Dark 3") # rgb_ <- grDevices::col2rgb(pal[as.integer(factor(cluster))]) # cr <- as.integer(rgb_[1, ]) # cg <- as.integer(rgb_[2, ]) # cb <- as.integer(rgb_[3, ]) # # centroids <- data.frame( # cell_ID = sprintf("CELL_%04d", seq_len(n_cells)), # x = cx, y = cy, # total_expr, cluster, r = cr, g = cg, b = cb # ) # # # Build irregular hexagonal WKT polygons # angles <- seq(0, 2 * pi, length.out = 7)[-7] # 6 vertices # wkt_polygons <- vapply(seq_len(n_cells), function(i) { # radius <- runif(1, 80, 200) # jitter <- runif(6, 0.80, 1.20) # vx <- cx[i] + radius * jitter * cos(angles) # vy <- cy[i] + radius * jitter * sin(angles) # pts <- paste(sprintf("%f %f", c(vx, vx[1]), c(vy, vy[1])), collapse = ", ") # sprintf("POLYGON((%s))", pts) # }, character(1)) # # polygon_df <- data.frame( # cell_ID = centroids$cell_ID, # wkt = wkt_polygons # ) # # # ── 2. Load data into in-memory DuckDB with spatial extension ────────────────── # con <- dbConnect(duckdb(), dbdir = ":memory:") # # for (sql in c("INSTALL spatial", "LOAD spatial", # "INSTALL nanoarrow FROM community", "LOAD nanoarrow", # "CALL register_geoarrow_extensions()")) { # try(dbExecute(con, sql), silent = TRUE) # } # # dbWriteTable(con, "poly_raw", polygon_df, overwrite = TRUE) # dbExecute(con, " # CREATE TABLE cells AS # SELECT cell_ID, # ST_GeomFromText(wkt) AS geometry # FROM poly_raw # WHERE wkt IS NOT NULL # ") # # # ── 3. Build the deck.gl spec ───────────────────────────────────────────────── # # Orthographic (Cartesian) view — same as spatial omics viewers # cx_mid <- field_w / 2 # cy_mid <- field_h / 2 # zoom <- log2(600 / field_w) # fit ~600 px wide # # polygon_query <- "SELECT cell_ID, geometry FROM cells WHERE geometry IS NOT NULL" # # spec <- list( # views = list( # list( # `@@type` = "OrthographicView", # controller = TRUE # ) # ), # initialViewState = list( # target = list(cx_mid, cy_mid, 0), # zoom = zoom, # minZoom = -5, # maxZoom = 8 # ), # layers = list( # list( # `@@type` = "GeoArrowSolidPolygonLayer", # id = "cell-polygons", # data = list( # type = "duckdb", # query = polygon_query, # format = "geoarrow" # ), # geometryColumn = "geometry", # getFillColor = c(100L, 80L, 220L, 100L), # stroked = FALSE, # coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", # positionFormat = "XY", # pickable = TRUE # ), # list( # `@@type` = "ScatterplotLayer", # id = "cell-centroids", # data = list( # type = "duckdb", # query = "SELECT cell_ID, x, y, r, g, b FROM centroids" # ), # getPosition = "@@=[x, y]", # getFillColor = "@@=[r, g, b]", # getRadius = 2, # radiusUnits = "pixels", # coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", # positionFormat = "XY", # opacity = 0.85, # pickable = TRUE # ) # ) # ) # # widget <- deckgl( # spec = spec, # con = con, # data = list(centroids = centroids), # width = "100%", # height = "600px" # ) # # DBI::dbDisconnect(con) # widget ## ----shiny, eval=FALSE-------------------------------------------------------- # library(shiny) # library(rDeckgl) # library(scales) # # ui <- fluidPage( # titlePanel("Deck.gl in Shiny"), # deckglOutput("myDeckgl", width = "100%", height = "600px") # ) # # server <- function(input, output, session) { # output$myDeckgl <- renderDeckgl({ # # Generate sample data # set.seed(42) # n_points <- 500 # # points_data <- data.frame( # lon = runif(n_points, -122.5, -122.3), # lat = runif(n_points, 37.7, 37.85), # value = rnorm(n_points), # radius = runif(n_points, 50, 150) # ) # # # Add colors # palette_fun <- col_numeric(viridis_pal(option = "B")(256), range(points_data$value)) # rgba <- col2rgb(palette_fun(points_data$value)) # points_data$color_r <- rgba[1, ] # points_data$color_g <- rgba[2, ] # points_data$color_b <- rgba[3, ] # # # Create spec # spec <- list( # `@@type` = "DeckGL", # initialViewState = list( # longitude = -122.4, # latitude = 37.78, # zoom = 11, # pitch = 0, # bearing = 0 # ), # views = list( # list( # `@@type` = "MapView", # controller = TRUE, # mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" # ) # ), # layers = list( # list( # `@@type` = "ScatterplotLayer", # id = "points", # data = list( # type = "duckdb", # query = "SELECT lon, lat, radius, color_r, color_g, color_b FROM points" # ), # getPosition = "@@=[lon, lat]", # getRadius = "@@=radius", # getFillColor = "@@=[color_r, color_g, color_b, 180]", # pickable = TRUE, # radiusUnits = "meters" # ) # ) # ) # # deckgl(spec = spec, data = list(points = points_data)) # }) # } # # shinyApp(ui, server) ## ----geoarrow-scatterplot, eval=FALSE----------------------------------------- # library(rDeckgl) # # set.seed(42) # n_points <- 1000 # # points_data <- data.frame( # id = 1:n_points, # lon = runif(n_points, -122.5, -122.3), # lat = runif(n_points, 37.7, 37.85), # value = rnorm(n_points, mean = 0.5, sd = 0.3), # radius = runif(n_points, 50, 200) # ) # # points_data$value <- pmax(0, pmin(1, points_data$value)) # # spec <- list( # `@@type` = "DeckGL", # initialViewState = list( # longitude = -122.4, # latitude = 37.78, # zoom = 12, # pitch = 0, # bearing = 0 # ), # views = list( # list( # `@@type` = "MapView", # controller = TRUE, # mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" # ) # ), # layers = list( # list( # `@@type` = "ScatterplotLayer", # id = "points", # data = list( # type = "duckdb", # query = "SELECT id, lon, lat, value, radius FROM points_data" # ), # getPosition = "@@=[lon, lat]", # getRadius = "@@=radius", # getFillColor = "@@=[value * 255, (1 - value) * 255, 100, 200]", # radiusUnits = "meters", # pickable = TRUE, # autoHighlight = TRUE # ) # ) # ) # # deckgl( # spec = spec, # data = list(points_data = points_data), # width = "100%", # height = "600px" # ) ## ----geoarrow-utah, eval=FALSE------------------------------------------------ # library(rDeckgl) # library(arrow) # # parquet_path <- "Utah.parquet" # # if (!file.exists(parquet_path)) { # stop("Parquet file not found: ", parquet_path) # } # # parquet_data <- arrow::read_parquet(parquet_path) # # max_rows <- 1000000 # sample_data <- if (nrow(parquet_data) > max_rows) { # head(parquet_data, max_rows) # } else { # parquet_data # } # # # Locate the densest cluster of buildings to centre the initial view # bbox_data <- sample_data$GEOMETRY_bbox # if (!is.null(bbox_data)) { # cx <- (as.numeric(bbox_data$xmin) + as.numeric(bbox_data$xmax)) / 2 # cy <- (as.numeric(bbox_data$ymin) + as.numeric(bbox_data$ymax)) / 2 # # grid_key <- paste(floor(cx / 0.1), floor(cy / 0.1), sep = ",") # densest_cell <- names(which.max(table(grid_key))) # idx <- which(grid_key == densest_cell) # # center_lon <- mean(cx[idx], na.rm = TRUE) # center_lat <- mean(cy[idx], na.rm = TRUE) # } else { # center_lon <- -110.4144 # center_lat <- 39.4991 # } # # # Encode the Arrow table as base64 IPC for inline delivery # arrow_table <- arrow::arrow_table(sample_data) # temp_file <- tempfile(fileext = ".arrows") # arrow::write_ipc_stream(arrow_table, temp_file) # arrow_bytes <- readBin(temp_file, "raw", n = file.info(temp_file)$size) # unlink(temp_file) # arrow_b64 <- base64enc::base64encode(arrow_bytes) # # if (length(arrow_bytes) / 1024 / 1024 > 50) { # warning("Encoded payload exceeds 50 MB and may cause browser issues.") # } # # spec <- list( # `@@type` = "DeckGL", # width = 1024, # height = 768, # initialViewState = list( # longitude = center_lon, # latitude = center_lat, # zoom = 12, # pitch = 0, # bearing = 0 # ), # controller = TRUE, # layers = list( # list( # `@@type` = "GeoArrowSolidPolygonLayer", # id = "utah-buildings", # data = list(`__arrow` = arrow_b64), # geometryColumn = "GEOMETRY", # getFillColor = c(255, 100, 0, 200), # extruded = FALSE, # pickable = TRUE, # `_normalize` = FALSE, # `_windingOrder` = "CCW" # ) # ) # ) # # deckgl(spec = spec, width = 1024, height = 768) ## ----shiny-native-geoarrow, eval=FALSE---------------------------------------- # library(shiny) # library(rDeckgl) # library(arrow) # # # Prepare the data file # parquet_path <- "Utah.parquet" # # if (!file.exists(parquet_path)) { # stop("Parquet file not found: ", parquet_path) # } # # # Read Parquet and create Arrow IPC file # parquet_data <- arrow::read_parquet(parquet_path) # # # Sample for reasonable performance # max_rows <- 1000000 # if (nrow(parquet_data) > max_rows) { # sample_data <- head(parquet_data, max_rows) # } else { # sample_data <- parquet_data # } # # # Compute density-based center # bbox_data <- sample_data$GEOMETRY_bbox # if (!is.null(bbox_data)) { # xmin <- as.numeric(bbox_data$xmin) # xmax <- as.numeric(bbox_data$xmax) # ymin <- as.numeric(bbox_data$ymin) # ymax <- as.numeric(bbox_data$ymax) # # cx <- (xmin + xmax) / 2 # cy <- (ymin + ymax) / 2 # # grid_size <- 0.1 # grid_x <- floor(cx / grid_size) # grid_y <- floor(cy / grid_size) # grid_key <- paste(grid_x, grid_y, sep = ",") # # grid_counts <- table(grid_key) # densest_cell <- names(which.max(grid_counts)) # densest_indices <- which(grid_key == densest_cell) # # center_lon <- mean(cx[densest_indices], na.rm = TRUE) # center_lat <- mean(cy[densest_indices], na.rm = TRUE) # } else { # center_lon <- -110.4144 # center_lat <- 39.4991 # } # # # Write Arrow IPC file to temporary location # temp_arrow <- tempfile(fileext = ".arrows") # arrow_table <- arrow::arrow_table(sample_data) # arrow::write_ipc_stream(arrow_table, temp_arrow) # # # Set up static file serving # static_root <- dirname(temp_arrow) # static_name <- "utahdata" # addResourcePath(static_name, static_root) # arrow_url <- paste0(static_name, "/", basename(temp_arrow)) # # # UI # ui <- fluidPage( # titlePanel("Utah Buildings - Native GeoArrow Rendering"), # deckglOutput("map", width = "100%", height = "600px") # ) # # # Server # server <- function(input, output, session) { # output$map <- renderDeckgl({ # spec <- list( # `@@type` = "DeckGL", # width = 1024, # height = 600, # initialViewState = list( # longitude = center_lon, # latitude = center_lat, # zoom = 12, # pitch = 0, # bearing = 0 # ), # controller = TRUE, # layers = list( # list( # # Use GeoArrowPolygonLayer for native rendering (no binary conversion) # `@@type` = "GeoArrowPolygonLayer", # id = "utah-buildings-native", # data = list(`__arrow_url` = arrow_url), # geometryColumn = "GEOMETRY", # getFillColor = c(255, 100, 0, 200), # getLineColor = c(255, 255, 255, 100), # filled = TRUE, # stroked = TRUE, # extruded = FALSE, # pickable = TRUE, # autoHighlight = TRUE # ) # ) # ) # # deckgl(spec = spec, width = "100%", height = "600px") # }) # # # Cleanup on session end # onSessionEnded(function() { # if (file.exists(temp_arrow)) { # unlink(temp_arrow) # } # }) # } # # # Run app # shinyApp(ui, server)