cboettig commited on
Commit
92a8f8c
·
1 Parent(s): b761c8c

dynamic pmtiles filterer

Browse files
Files changed (1) hide show
  1. app.R +63 -36
app.R CHANGED
@@ -3,7 +3,8 @@ library(bslib)
3
  library(markdown)
4
  library(shinychat)
5
  library(mapgl)
6
- library(tidyverse)
 
7
  library(duckdbfs)
8
  library(fontawesome)
9
  library(bsicons)
@@ -14,10 +15,10 @@ duckdbfs::load_spatial()
14
 
15
  css <- HTML("<link rel='stylesheet' type='text/css' href='https://demos.creative-tim.com/material-dashboard/assets/css/material-dashboard.min.css?v=3.2.0'>")
16
 
17
- pmtiles <- "https://data.source.coop/cboettig/us-boundaries/mappinginequality.pmtiles"
18
 
19
  # Define the UI
20
  ui <- page_sidebar(
 
21
  tags$head(css),
22
  titlePanel("Demo App"),
23
  card(
@@ -27,7 +28,8 @@ ui <- page_sidebar(
27
  "Which county has the highest average social vulnerability?",
28
  width = "100%"),
29
  div(
30
- actionButton("user_msg", "", icon = icon("paper-plane"), class = "btn-primary btn-sm align-bottom"),
 
31
  class = "align-text-bottom"),
32
  col_widths = c(11, 1)),
33
  fill = FALSE
@@ -38,28 +40,31 @@ ui <- page_sidebar(
38
  plotOutput("chart1"),
39
  plotOutput("chart2"),
40
  ),
41
-
42
- col_widths = c(8, 4)
 
43
  ),
44
 
45
- gt_output("table"),
46
 
 
47
 
48
- card(fill = FALSE,
49
- layout_columns(
50
- br(),
51
  accordion(
52
  open = FALSE,
53
- accordion_panel("generated SQL Code",
 
 
54
  verbatimTextOutput("sql_code"),
55
  ),
56
- accordion_panel("Explanation",
 
 
57
  textOutput("explanation"),
58
  )
59
- ),
60
- br(),
61
- col_widths = c(2, 8, 2)
62
- )
63
  ),
64
 
65
  sidebar = sidebar(
@@ -68,13 +73,18 @@ ui <- page_sidebar(
68
  input_switch("svi", "Social Vulnerability", value = FALSE),
69
  input_switch("richness", "Biodiversity Richness", value = FALSE),
70
  input_switch("rsr", "Biodiversity Range Size Rarity", value = FALSE),
71
- # width = 350,
72
  ),
 
73
  theme = bs_theme(version = "5")
74
  )
75
 
76
- svi <- "https://data.source.coop/cboettig/social-vulnerability/svi2020_us_tract.parquet" |>
77
- open_dataset(tblname = "svi")
 
 
 
 
 
78
 
79
  con <- duckdbfs::cached_connection()
80
  schema <- DBI::dbGetQuery(con, "PRAGMA table_info(svi)")
@@ -84,9 +94,8 @@ You are a helpful agent who always replies strictly in JSON-formatted text.
84
  Your task is to translate the users question into a SQL query that will be run
85
  against the "svi" table in a duckdb database. The duckdb database has a
86
  spatial extension which understands PostGIS operations as well.
87
-
88
-
89
- Be careful to limit any return to no more than 50 rows.
90
 
91
  The table schema is <schema>
92
 
@@ -100,41 +109,60 @@ Format your answer as follows:
100
  }
101
  ', .open = "<", .close = ">")
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  # Define the server
104
  server <- function(input, output, session) {
105
 
106
- chat <- ellmer::chat_vllm(
107
- base_url = "https://llm.nrp-nautilus.io/",
108
- model = "llama3",
109
- api_key = Sys.getenv("NRP_API_KEY"),
110
- system_prompt = system_prompt
111
- )
112
-
113
  observeEvent(input$user_msg, {
114
  stream <- chat$chat(input$chat)
115
 
116
- chat_append("chat", stream)
117
- response <- jsonlite::fromJSON(stream)
118
 
119
- output$sql_code <- renderText({stringr::str_wrap(response$query, width = 60)})
 
 
120
  output$explanation <- renderText(response$explanation)
121
 
 
122
  df <- DBI::dbGetQuery(con, response$query)
123
 
 
124
  df <- df |> select(-any_of("Shape"))
125
  output$table <- render_gt(df, height = 300)
126
 
 
127
  })
128
 
129
  output$map <- renderMaplibre({
130
- m <- maplibre(center=c(-92.9, 41.3), zoom=3)
131
 
132
  if (input$redlines) {
133
  m <- m |>
134
  add_fill_layer(
135
  id = "redlines",
136
  source = list(type = "vector",
137
- url = paste0("pmtiles://", pmtiles)),
138
  source_layer = "mappinginequality",
139
  fill_color = list("get", "fill")
140
  )
@@ -163,18 +191,17 @@ server <- function(input, output, session) {
163
  if (input$svi) {
164
  m <- m |>
165
  add_fill_layer(
166
- id = "redlines",
167
  source = list(type = "vector",
168
  url = paste0("pmtiles://", "https://data.source.coop/cboettig/social-vulnerability/svi2020_us_tract.pmtiles")),
169
  source_layer = "SVI2000_US_tract",
 
170
  fill_opacity = 0.5,
171
  fill_color = interpolate(column = "RPL_THEMES",
172
  values = c(0, 1),
173
  stops = c("lightblue", "darkblue"),
174
  na_color = "lightgrey")
175
  )
176
-
177
-
178
  }
179
  m})
180
 
 
3
  library(markdown)
4
  library(shinychat)
5
  library(mapgl)
6
+ library(dplyr)
7
+ library(ggplot2)
8
  library(duckdbfs)
9
  library(fontawesome)
10
  library(bsicons)
 
15
 
16
  css <- HTML("<link rel='stylesheet' type='text/css' href='https://demos.creative-tim.com/material-dashboard/assets/css/material-dashboard.min.css?v=3.2.0'>")
17
 
 
18
 
19
  # Define the UI
20
  ui <- page_sidebar(
21
+ fillable = FALSE, # do not squeeze to vertical screen space
22
  tags$head(css),
23
  titlePanel("Demo App"),
24
  card(
 
28
  "Which county has the highest average social vulnerability?",
29
  width = "100%"),
30
  div(
31
+ actionButton("user_msg", "", icon = icon("paper-plane"),
32
+ class = "btn-primary btn-sm align-bottom"),
33
  class = "align-text-bottom"),
34
  col_widths = c(11, 1)),
35
  fill = FALSE
 
40
  plotOutput("chart1"),
41
  plotOutput("chart2"),
42
  ),
43
+ col_widths = c(8, 4),
44
+ row_heights = c("600px"),
45
+ max_height = "700px"
46
  ),
47
 
 
48
 
49
+ gt_output("table"),
50
 
51
+ card(fill = TRUE,
52
+ card_header(fa("robot")),
53
+
54
  accordion(
55
  open = FALSE,
56
+ accordion_panel(
57
+ title = "show sql",
58
+ icon = fa("terminal"),
59
  verbatimTextOutput("sql_code"),
60
  ),
61
+ accordion_panel(
62
+ title = "explain",
63
+ icon = fa("user", prefer_type="solid"),
64
  textOutput("explanation"),
65
  )
66
+ ),
67
+
 
 
68
  ),
69
 
70
  sidebar = sidebar(
 
73
  input_switch("svi", "Social Vulnerability", value = FALSE),
74
  input_switch("richness", "Biodiversity Richness", value = FALSE),
75
  input_switch("rsr", "Biodiversity Range Size Rarity", value = FALSE),
 
76
  ),
77
+
78
  theme = bs_theme(version = "5")
79
  )
80
 
81
+
82
+
83
+
84
+ repo <- ""
85
+ pmtiles <- ""
86
+ parquet <- "https://data.source.coop/cboettig/social-vulnerability/svi2020_us_tract.parquet"
87
+ svi <- open_dataset(parquet, tblname = "svi")
88
 
89
  con <- duckdbfs::cached_connection()
90
  schema <- DBI::dbGetQuery(con, "PRAGMA table_info(svi)")
 
94
  Your task is to translate the users question into a SQL query that will be run
95
  against the "svi" table in a duckdb database. The duckdb database has a
96
  spatial extension which understands PostGIS operations as well.
97
+ Include semantically meaningful columns like COUNTY and STATE name.
98
+
 
99
 
100
  The table schema is <schema>
101
 
 
109
  }
110
  ', .open = "<", .close = ">")
111
 
112
+ chat <- ellmer::chat_vllm(
113
+ base_url = "https://llm.nrp-nautilus.io/",
114
+ model = "llama3",
115
+ api_key = Sys.getenv("NRP_API_KEY"),
116
+ system_prompt = system_prompt,
117
+ api_args = list(temperature = 0)
118
+ )
119
+
120
+ # helper utilities
121
+ df <- tibble()
122
+ # faster/more scalable to pass maplibre the ids to refilter pmtiles,
123
+ # than to pass it the full geospatial/sf object
124
+ filter_column <- function(full_data, filtered_data, id_col = "FIPS") {
125
+ if (nrow(filtered_data) < 1) return(NULL)
126
+ values <- full_data |>
127
+ inner_join(filtered_data, copy = TRUE) |>
128
+ pull(id_col)
129
+ # maplibre syntax for the filter of PMTiles
130
+ list("in", list("get", id_col), list("literal", values))
131
+ }
132
+
133
  # Define the server
134
  server <- function(input, output, session) {
135
 
 
 
 
 
 
 
 
136
  observeEvent(input$user_msg, {
137
  stream <- chat$chat(input$chat)
138
 
139
+ # optional, remember previous discussion
140
+ #chat_append("chat", stream)
141
 
142
+ # Parse response
143
+ response <- jsonlite::fromJSON(stream)
144
+ output$sql_code <- renderText(stringr::str_wrap(response$query, width = 60))
145
  output$explanation <- renderText(response$explanation)
146
 
147
+ # Actually execute the SQL query generated:
148
  df <- DBI::dbGetQuery(con, response$query)
149
 
150
+ # don't display shape column in render
151
  df <- df |> select(-any_of("Shape"))
152
  output$table <- render_gt(df, height = 300)
153
 
154
+ # We need to somehow trigger this df to update the map.
155
  })
156
 
157
  output$map <- renderMaplibre({
158
+ m <- maplibre(center = c(-92.9, 41.3), zoom = 3, height = "400")
159
 
160
  if (input$redlines) {
161
  m <- m |>
162
  add_fill_layer(
163
  id = "redlines",
164
  source = list(type = "vector",
165
+ url = paste0("pmtiles://", "https://data.source.coop/cboettig/us-boundaries/mappinginequality.pmtiles")),
166
  source_layer = "mappinginequality",
167
  fill_color = list("get", "fill")
168
  )
 
191
  if (input$svi) {
192
  m <- m |>
193
  add_fill_layer(
194
+ id = "svi_layer",
195
  source = list(type = "vector",
196
  url = paste0("pmtiles://", "https://data.source.coop/cboettig/social-vulnerability/svi2020_us_tract.pmtiles")),
197
  source_layer = "SVI2000_US_tract",
198
+ filter = filter_column(svi, df, "FIPS"),
199
  fill_opacity = 0.5,
200
  fill_color = interpolate(column = "RPL_THEMES",
201
  values = c(0, 1),
202
  stops = c("lightblue", "darkblue"),
203
  na_color = "lightgrey")
204
  )
 
 
205
  }
206
  m})
207