@@ -270,471 +352,403 @@
Trends in nighttime lights within past 5 years
Code
- city_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "city_monthly.Rds" ))
-
- city_df %>%
- filter (date >= ymd ("2019-01-01" )) %>%
- ggplot () +
- geom_vline (xintercept = ymd ("2023-07-26" ),
- color = "red" ) +
- geom_col (aes (x = date,
- y = ntl_sum)) +
- theme_classic2 () +
- theme_custom +
- labs (x = NULL ,
- y = "Luminosity" ,
- title = "Trends in Nighttime Lights Across Top Cities" ) +
- facet_wrap (~ name,
- scales = "free_y" )
+ adm1_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm1_monthly.Rds" ))
+
+ adm1_df %>%
+ filter (date >= ymd ("2019-01-01" )) %>%
+ ggplot () +
+ geom_vline (xintercept = ymd ("2023-07-26" ),
+ color = "red" ) +
+ geom_col (aes (x = date,
+ y = ntl_sum)) +
+ geom_line (aes (x = date,
+ y = ntl_sum_3m_ma,
+ color = "3 Month \n Moving \n Average" ),
+ linewidth = 1 ) +
+ scale_color_manual (values = "darkorange" ) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = NULL ,
+ y = "Luminosity" ,
+ color = NULL ,
+ title = "Trends in Nighttime Lights Across Regions" ) +
+ facet_wrap (~ ADM1_FR,
+ scales = "free_y" )
+
+
+Code
+ city_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "city_monthly.Rds" ))
+
+ city_df %>%
+ filter (date >= ymd ("2019-01-01" )) %>%
+ ggplot () +
+ geom_vline (xintercept = ymd ("2023-07-26" ),
+ color = "red" ) +
+ geom_col (aes (x = date,
+ y = ntl_sum)) +
+ geom_line (aes (x = date,
+ y = ntl_sum_3m_ma,
+ color = "3 Month \n Moving \n Average" ),
+ linewidth = 1 ) +
+ scale_color_manual (values = "darkorange" ) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = NULL ,
+ color = NULL ,
+ y = "Luminosity" ,
+ title = "Trends in Nighttime Lights Across Top Cities" ) +
+ facet_wrap (~ name,
+ scales = "free_y" )
+
+
+
+
+
Maps of change in nighttime lights
+The below section shows the percent change in nighttime lights from August to December 2023 relative to August to December 2022. We show the percent change at the pixel level and at the ADM3 level.
Pixel level
Code
- r_load_month_pc <- function (month_i){
- r_b1 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_01" ,".tif" )))
- r_b2 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_02" ,".tif" )))
- r_b3 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_03" ,".tif" )))
- r_b4 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_04" ,".tif" )))
- r_b5 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_05" ,".tif" )))
- r_b6 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_06" ,".tif" )))
- r_e <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
- paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,month_i,".tif" )))
-
- r_b <- r_b1
- r_b[] <- (r_b1[] + r_b2[] + r_b3[] + r_b4[] + r_b5[] + r_b6[]) / 6
-
- r_e[] <- (r_e[] - r_b[]) / r_b[] * 100
- r_e[][r_b[] <= 0.5 ] <- NA
-
- r_e[][r_e[] >= 100 ] <- 100
- r_e[][r_e[] <= - 100 ] <- - 100
-
- r_e <- r_e %>% crop (adm0_sf) %>% mask (adm0_sf)
-
- return (r_e)
- }
-
- r_23_07 <- r_load_month_pc ("2023_07" )
- r_23_08 <- r_load_month_pc ("2023_08" )
- r_23_09 <- r_load_month_pc ("2023_09" )
- r_23_10 <- r_load_month_pc ("2023_10" )
- r_23_11 <- r_load_month_pc ("2023_11" )
- r_23_12 <- r_load_month_pc ("2023_12" )
- r_24_01 <- r_load_month_pc ("2024_01" )
- r_24_02 <- r_load_month_pc ("2024_02" )
- r_24_03 <- r_load_month_pc ("2024_03" )
-
-## Make map
- r_values <- unique (c (r_23_07[],
- r_23_08[],
- r_23_09[],
- r_23_10[],
- r_23_11[],
- r_23_12[],
- r_24_01[],
- r_24_02[],
- r_24_03[]))
-
- pal <- colorNumeric (c ("red" , "white" , "green" ), r_values,
- na.color = "transparent" )
-
-leaflet () %>%
- addProviderTiles (providers$ CartoDB.DarkMatter) %>%
- addRasterImage (r_23_07, colors = pal, opacity = 1 , group = "2023-07" ) %>%
- addRasterImage (r_23_08, colors = pal, opacity = 1 , group = "2023-08" ) %>%
- addRasterImage (r_23_09, colors = pal, opacity = 1 , group = "2023-09" ) %>%
- addRasterImage (r_23_10, colors = pal, opacity = 1 , group = "2023-10" ) %>%
- addRasterImage (r_23_11, colors = pal, opacity = 1 , group = "2023-11" ) %>%
- addRasterImage (r_23_12, colors = pal, opacity = 1 , group = "2023-12" ) %>%
- addRasterImage (r_24_01, colors = pal, opacity = 1 , group = "2024-01" ) %>%
- addRasterImage (r_24_02, colors = pal, opacity = 1 , group = "2024-02" ) %>%
- addRasterImage (r_24_03, colors = pal, opacity = 1 , group = "2024-03" ) %>%
- addLayersControl (
- baseGroups = c ("2023-07" ,
- "2023-08" ,
- "2023-09" ,
- "2023-10" ,
- "2023-11" ,
- "2023-12" ,
- "2024-01" ,
- "2024-02" ,
- "2024-03" ),
- options = layersControlOptions (collapsed= FALSE )
- ) %>%
- addLegend ("bottomright" , pal = pal, values = r_values,
- title = "% Change in NTL<br>relative to<br>Jan-June 2023" ,
- opacity = 1 ,
- labels = c ("< -100" , "-50" , "0" , "50" , "> 100" )
- )
+#### 2023
+ r_22_08 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2022_08" ,".tif" )))
+ r_22_09 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2022_09" ,".tif" )))
+ r_22_10 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2022_10" ,".tif" )))
+ r_22_11 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2022_11" ,".tif" )))
+ r_22_12 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2022_12" ,".tif" )))
+
+#### 2024
+ r_23_08 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_08" ,".tif" )))
+ r_23_09 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_09" ,".tif" )))
+ r_23_10 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_10" ,".tif" )))
+ r_23_11 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_11" ,".tif" )))
+ r_23_12 <- rast (file.path (ntl_dir, "individual_rasters" , "monthly" ,
+ paste0 ("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t" ,"2023_12" ,".tif" )))
+
+#### Percent Change
+ r_22 <- r_22_08
+ r_23 <- r_23_08
+
+ r_22[] <- (r_22_08[] + r_22_09[] + r_22_10[] + r_22_11[] + r_22_12[]) / 5
+ r_23[] <- (r_23_08[] + r_23_09[] + r_23_10[] + r_23_11[] + r_23_12[]) / 5
+
+ r_23_pc <- r_23
+ r_23_pc[] <- (r_23[] - r_22[]) / r_22[] * 100
+
+ r_23_pc[][r_22[] <= 0.5 ] <- NA
+ r_23_pc[][r_23_pc[] >= 100 ] <- 100
+ r_23_pc[][r_23_pc[] <= - 100 ] <- - 100
+
+ r_23_pc <- r_23_pc %>% crop (adm0_sf) %>% mask (adm0_sf)
+
+## Make map
+ r_values <- r_23_pc[] %>%
+ unlist () %>%
+ as.vector () %>%
+ unique ()
+
+ pal <- colorNumeric (c ("red" , "white" , "green" ), r_values,
+ na.color = "transparent" )
+
+leaflet () %>%
+ addProviderTiles (providers$ CartoDB.DarkMatter) %>%
+ addRasterImage (r_23_pc, colors = pal, opacity = 1 ) %>%
+ addLegend ("bottomright" , pal = pal, values = r_values,
+ title = "% Change in NTL<br>Aug-Dec 2023<br>Relative to<br>Aug-Dec 2022" ,
+ opacity = 1 ,
+ labels = c ("< -100" , "-50" , "0" , "50" , "> 100" )
+ )
ADM level
+
+Static Map
+
+
+Code
+## Polygon
+ adm3_sf <- read_sf (file.path (boundaries_dir,
+ "ner_adm_ignn_20230720_ab_shp" ,
+ "NER_admbnda_adm3_IGNN_20230720.shp" ))
+
+ adm3_sf <- adm3_sf %>%
+ select (- date)
+
+## Data
+ adm3_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm3_monthly.Rds" ))
+
+ adm3_sum_df <- adm3_df %>%
+ dplyr:: mutate (base_end = case_when (
+ date %in% c (ymd ("2022-08-01" ),
+ ymd ("2022-09-01" ),
+ ymd ("2022-10-01" ),
+ ymd ("2022-11-01" ),
+ ymd ("2022-12-01" )) ~ "baseline" ,
+ date %in% c (ymd ("2023-08-01" ),
+ ymd ("2023-09-01" ),
+ ymd ("2023-10-01" ),
+ ymd ("2023-11-01" ),
+ ymd ("2023-12-01" )) ~ "endline"
+ )) %>%
+ dplyr:: filter (! is.na (base_end)) %>%
+ group_by (ADM3_PCODE, base_end) %>%
+ dplyr:: summarise (ntl_sum = mean (ntl_sum)) %>%
+ ungroup () %>%
+ pivot_wider (id_cols = ADM3_PCODE,
+ names_from = base_end,
+ values_from = ntl_sum) %>%
+ dplyr:: mutate (pc = (endline - baseline) / baseline * 100 )
+
+ adm3_sum_df$ pc[adm3_sum_df$ pc >= 100 ] <- 100
+
+ adm3_data_sf <- adm3_sf %>%
+ left_join (adm3_sum_df, by = "ADM3_PCODE" )
+
+## Static Map
+ggplot () +
+ geom_sf (data = adm3_data_sf,
+ aes (fill = pc)) +
+ theme_void () +
+ scale_fill_gradient2 (low = "red" ,
+ high = "forestgreen" ) +
+ labs (fill = "% Change" ,
+ title = "Percent change in nighttime lights from Aug-Dec 2023 relative to Aug-Dec 2022" )
+
+
+
+
+
+
+
+Interactive Map
+Click an administrative division to see the percent change
Code
- adm3_sf <- read_sf (file.path (boundaries_dir,
- "ner_adm_ignn_20230720_ab_shp" ,
- "NER_admbnda_adm3_IGNN_20230720.shp" ))
-
- adm3_sf <- adm3_sf %>%
- select (- date)
-
- adm3_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm3_monthly.Rds" ))
-
- adm3_df <- adm3_df %>%
- mutate (baseline = (date %in% c (ymd ("2023-01-01" ),
- ymd ("2023-02-01" ),
- ymd ("2023-03-01" ),
- ymd ("2023-04-01" ),
- ymd ("2023-05-01" ),
- ymd ("2023-06-01" )))) %>%
- group_by (ADM3_PCODE) %>%
- mutate (ntl_sum_baseline = mean (ntl_sum[baseline %in% 1 ])) %>%
- ungroup () %>%
-
- mutate (ntl_pc = (ntl_sum - ntl_sum_baseline) / ntl_sum_baseline * 100 ,
- ntl_change = ntl_sum - ntl_sum_baseline) %>%
- select (date, ADM3_PCODE, ntl_pc, ntl_change, ntl_sum, ntl_sum_baseline) %>%
-
- filter (date >= ymd ("2023-01-01" ))
-
- adm3_df$ ntl_sum_baseline_dates <- adm3_df$ ntl_sum
- adm3_df$ ntl_sum_baseline_dates[adm3_df$ date >= ymd ("2023-07-01" )] <- NA
-
- make_ntl_date <- function (adm3_df, ym_i){
- adm3_df[[paste0 ("ntl_sum_" , ym_i)]] <- adm3_df$ ntl_sum
- adm3_df[[paste0 ("ntl_sum_" , ym_i)]][adm3_df$ date != ymd (paste0 (ym_i, "-01" ))] <- NA
- return (adm3_df)
- }
-
- adm3_df <- adm3_df %>%
- make_ntl_date ("2023_07" ) %>%
- make_ntl_date ("2023_08" ) %>%
- make_ntl_date ("2023_09" ) %>%
- make_ntl_date ("2023_10" ) %>%
- make_ntl_date ("2023_11" ) %>%
- make_ntl_date ("2023_12" ) %>%
- make_ntl_date ("2024_01" ) %>%
- make_ntl_date ("2024_02" ) %>%
- make_ntl_date ("2024_03" )
-
- adm3_df$ ntl_pc[adm3_df$ ntl_pc >= 100 ] <- 100
- adm3_df$ ntl_pc[adm3_df$ ntl_pc <= - 100 ] <- - 100
-
-## Add sparkline
- ntl_date_color <- "black"
-
- adm3_df_line <- adm3_df %>%
- filter (date >= ymd ("2023-01-01" )) %>%
- arrange (date) %>%
- split (.$ ADM3_PCODE) %>%
- map_df (~ {
- l_ntl_all <- sparkline (.x$ ntl_sum,
- type= 'bar' ,
- barColor= "orange" ,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = 'orange' ,
- highlightSpotColor = 'orange' )
- l_ntl_base <- sparkline (.x$ ntl_sum_baseline_dates,
- type= 'bar' ,
- barColor= "blue" ,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = 'blue' ,
- highlightSpotColor = 'blue' )
- l_ntl_2023_07 <- sparkline (.x$ ntl_sum_2023_07,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_08 <- sparkline (.x$ ntl_sum_2023_08,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_09 <- sparkline (.x$ ntl_sum_2023_09,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_10 <- sparkline (.x$ ntl_sum_2023_10,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_11 <- sparkline (.x$ ntl_sum_2023_11,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_12 <- sparkline (.x$ ntl_sum_2023_12,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_01 <- sparkline (.x$ ntl_sum_2024_01,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_02 <- sparkline (.x$ ntl_sum_2024_02,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_03 <- sparkline (.x$ ntl_sum_2024_03,
- type= 'bar' ,
- barColor= ntl_date_color,
- chartRangeMin = 0 ,
- chartRangeMax = 8 ,
- width = 100 ,
- height = 50 ,
- tooltipChartTitle = "Nighttime Lights" ,
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl <- spk_composite (l_ntl_all,
- l_ntl_base)
- data.frame (l_ntl = as.character (htmltools:: as.tags (l_ntl)))
- }, .id = 'ADM3_PCODE' )
-
-## Merge data with sf object
- adm3_sf <- adm3_sf %>%
- right_join (adm3_df, by = c ("ADM3_PCODE" )) %>%
- left_join (adm3_df_line, by = "ADM3_PCODE" )
-
- adm3_sf <- adm3_sf %>%
- mutate (popup = paste0 ("<h4>" , ADM3_FR, "</h4>" ,
- "<b>NTL " , substring (date, 1 , 7 ), ":</b> " , round (ntl_sum, 2 ), "<br>" ,
- "<b>NTL Baseline:</b> " , round (ntl_sum_baseline, 2 ), "<br>" ,
- "<b>Trends in NTL since Jan 2023</b><br><br>" ,
- "<span style='color: blue;'>Blue</span> = Jan - June 2023<br>" ,
- l_ntl))
-
-## Make map
- r_values <- unique (adm3_sf$ ntl_pc)
-
- pal <- colorNumeric (c ("red" , "white" , "green" ), r_values,
- na.color = "transparent" )
-
- line_color <- "black"
- fill_opacity <- 0.8
- opacity <- 0.5
-
- add_deps <- function (dtbl, name, pkg = name) {
- tagList (
- dtbl,
- htmlwidgets:: getDependency (name, pkg)
- )
- }
-
-leaflet () %>%
- addProviderTiles (providers$ OpenStreetMap.HOT) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-07-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-07" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-08-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-08" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-09-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-09" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-10-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-10" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-11-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-11" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2023-12-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2023-12" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2024-01-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2024-01" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2024-02-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2024-02" ) %>%
- addPolygons (data = adm3_sf[adm3_sf$ date == ymd ("2024-03-01" ),],
- fillColor = ~ pal (ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~ lapply (popup, HTML),
- group = "2024-03" ) %>%
- onRender ("function(el,x) {
- this.on('tooltipopen', function() {HTMLWidgets.staticRender();})
- }" ) %>%
- addLayersControl (
- baseGroups = c ("2023-07" ,
- "2023-08" ,
- "2023-09" ,
- "2023-10" ,
- "2023-11" ,
- "2023-12" ,
- "2024-01" ,
- "2024-02" ,
- "2024-03" ),
- options = layersControlOptions (collapsed= FALSE )
- ) %>%
- addLegend ("bottomright" , pal = pal, values = r_values,
- title = "% Change in NTL<br>relative to<br>Jan-June 2023" ,
- opacity = 1 ,
- labels = c ("< -100" , "-50" , "0" , "50" , "> 100" )
- ) %>%
- add_deps ("sparkline" ) %>%
- browsable ()
+## Interactive map
+ pal <- colorNumeric (c ("red" , "white" , "green" ), adm3_data_sf$ pc,
+ na.color = "transparent" )
+
+ adm3_data_sf <- adm3_data_sf %>%
+ mutate (popup = paste0 (ADM3_FR, "<br>% Change: " , round (pc, 2 ), "%" ))
+
+leaflet () %>%
+ addProviderTiles (providers$ CartoDB) %>%
+ addPolygons (data = adm3_data_sf,
+ fillColor = ~ pal (pc),
+ fillOpacity = 1 ,
+ color = "black" ,
+ weight = 1 ,
+ popup = ~ popup) %>%
+ addLegend ("bottomright" , pal = pal, values = r_values,
+ title = "% Change in NTL<br>Aug-Dec 2023<br>Relative to<br>Aug-Dec 2022" ,
+ opacity = 1 ,
+ labels = c ("< -100" , "-50" , "0" , "50" , "> 100" )
+ )
+
+
+Percent change in nighttime lights from 2022 to 2023
+Below we show the percent change in nighttime lights for each month in 2023 relative to the same month in 2022
+
+
+Code
+## Load data
+ adm0_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm0_monthly.Rds" ))
+ adm1_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm1_monthly.Rds" ))
+ city_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "city_monthly.Rds" ))
+
+## Add dummpy ADM1 variable so function will more easily work on both datasets
+ adm0_df$ ADM1_FR <- ""
+ city_df$ ADM1_FR <- city_df$ name
+
+## Function to prep data as percent changes
+ prep_month_pc <- function (adm_df){
+
+ adm_long_df <- adm_df %>%
+ filter (date >= ymd ("2022-01-01" ),
+ date <= ymd ("2023-12-01" )) %>%
+ select (ADM1_FR, date, ntl_sum) %>%
+ mutate (month = date %>% month,
+ year = date %>% year) %>%
+ pivot_wider (id_cols = c (ADM1_FR, month),
+ values_from = ntl_sum,
+ names_from = year) %>%
+ #filter(!is.na(`2024`)) %>%
+ mutate (pc = (` 2023 ` - ` 2022 ` ) / ` 2022 ` * 100 )
+
+ return (adm_long_df)
+ }
+
+ adm0_pc_df <- prep_month_pc (adm0_df)
+ adm1_pc_df <- prep_month_pc (adm1_df)
+ city_pc_df <- prep_month_pc (city_df)
+
+## Maps
+ adm0_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ geom_vline (xintercept = 7 , color = "red" ) +
+ theme_classic2 () +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across Niger from 2022 to 2023" )
+
+
+
+
+
+Code
+ adm1_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ geom_vline (xintercept = 7 , color = "red" ) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across regions from 2022 to 2023" ) +
+ facet_wrap (~ ADM1_FR,
+ scales = "free_y" )
+
+
+
+
+
+Code
+ city_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ geom_vline (xintercept = 7 , color = "red" ) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across regions from 2023 to 2024" ) +
+ facet_wrap (~ ADM1_FR,
+ scales = "free_y" )
+
+
+
+
+
+
Percent change in nighttime lights from 2023 to 2024
Below we show the percent change in nighttime lights for each month in 2024 relative to the same month in 2023.
Code
-## Load data
- adm0_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm0_monthly.Rds" ))
- adm1_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm1_monthly.Rds" ))
-
-## Add dummpy ADM1 variable so function will more easily work on both datasets
- adm0_df$ ADM1_FR <- ""
-
-## Function to prep data as percent changes
- prep_month_pc <- function (adm_df){
-
- adm_long_df <- adm_df %>%
- filter (date >= ymd ("2023-01-01" ),
- date <= ymd ("2024-06-01" )) %>%
- select (ADM1_FR, date, ntl_sum) %>%
- mutate (month = date %>% month,
- year = date %>% year) %>%
- pivot_wider (id_cols = c (ADM1_FR, month),
- values_from = ntl_sum,
- names_from = year) %>%
- filter (! is.na (` 2024 ` )) %>%
- mutate (pc = (` 2024 ` - ` 2023 ` ) / ` 2023 ` * 100 )
-
- return (adm_long_df)
- }
-
- adm0_pc_df <- prep_month_pc (adm0_df)
- adm1_pc_df <- prep_month_pc (adm1_df)
-
-## Maps
- adm0_pc_df %>%
- ggplot () +
- geom_col (aes (x = month,
- y = pc)) +
- theme_classic2 () +
- labs (x = "Month" ,
- y = "Percent change" ,
- title = "Percent change in nighttime lights across Niger from 2023 to 2024" )
+## Load data
+ adm0_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm0_monthly.Rds" ))
+ adm1_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "adm1_monthly.Rds" ))
+ city_df <- readRDS (file.path (ntl_dir, "aggregated_appended" , "city_monthly.Rds" ))
+
+## Add dummpy ADM1 variable so function will more easily work on both datasets
+ adm0_df$ ADM1_FR <- ""
+ city_df$ ADM1_FR <- city_df$ name
+
+## Function to prep data as percent changes
+ prep_month_pc <- function (adm_df){
+
+ adm_long_df <- adm_df %>%
+ filter (date >= ymd ("2023-01-01" ),
+ date <= ymd ("2024-06-01" )) %>%
+ select (ADM1_FR, date, ntl_sum) %>%
+ mutate (month = date %>% month,
+ year = date %>% year) %>%
+ pivot_wider (id_cols = c (ADM1_FR, month),
+ values_from = ntl_sum,
+ names_from = year) %>%
+ filter (! is.na (` 2024 ` )) %>%
+ mutate (pc = (` 2024 ` - ` 2023 ` ) / ` 2023 ` * 100 )
+
+ return (adm_long_df)
+ }
+
+ adm0_pc_df <- prep_month_pc (adm0_df)
+ adm1_pc_df <- prep_month_pc (adm1_df)
+ city_pc_df <- prep_month_pc (city_df)
+
+## Maps
+ adm0_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ theme_classic2 () +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across Niger from 2023 to 2024" )
-
+
+
+
+Code
+ adm1_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across regions from 2023 to 2024" ) +
+ facet_wrap (~ ADM1_FR,
+ scales = "free_y" )
+
+
+
Code
- adm1_pc_df %>%
- ggplot () +
- geom_col (aes (x = month,
- y = pc)) +
- theme_classic2 () +
- theme_custom +
- labs (x = "Month" ,
- y = "Percent change" ,
- title = "Percent change in nighttime lights across regions from 2023 to 2024" ) +
- facet_wrap (~ ADM1_FR,
- scales = "free_y" )
+ city_pc_df %>%
+ ggplot () +
+ geom_col (aes (x = month,
+ y = pc)) +
+ theme_classic2 () +
+ theme_custom +
+ labs (x = "Month" ,
+ y = "Percent change" ,
+ title = "Percent change in nighttime lights across regions from 2023 to 2024" ) +
+ facet_wrap (~ ADM1_FR,
+ scales = "free_y" )
-
+
diff --git a/notebooks/nighttime-lights/ntl_analysis.qmd b/notebooks/nighttime-lights/ntl_analysis.qmd
index 244dae7..9c0e4f5 100644
--- a/notebooks/nighttime-lights/ntl_analysis.qmd
+++ b/notebooks/nighttime-lights/ntl_analysis.qmd
@@ -14,6 +14,11 @@ Nighttime lights have become a commonly used resource to estimate changes in loc
We use nighttime lights data from the VIIRS Black Marble dataset. Raw nighttime lights data requires correction due to cloud cover and stray light, such as lunar light. The Black Marble dataset applies advanced algorithms to correct raw nighttime light values and calibrate data so that trends in lights over time can be meaningfully analyzed. From VIIRS Black Marble, we use data from January 2012 through present—where data is available at a 500-meter resolution.
+The following code downloads and processes data:
+[_main.R](https://github.com/datapartnership/niger-economic-monitoring/blob/main/notebooks/nighttime-lights/_main.R) Defines filepaths and loads packages
+[01_download_ntl.R](https://github.com/datapartnership/niger-economic-monitoring/blob/main/notebooks/nighttime-lights/01_download_ntl.R) Downloads NTL data
+[02_extract_data.R](https://github.com/datapartnership/niger-economic-monitoring/blob/main/notebooks/nighttime-lights/02_extract_data.R) Extracts/aggregates nighttime light data to different polygons
+
```{r}
#| include: false
@@ -27,14 +32,19 @@ library(terra)
library(sparkline)
library(htmltools)
library(htmlwidgets)
+library(WDI)
+library(janitor)
+
+# data_dir <- file.path("/Users",
+# "rmarty",
+# "Library",
+# "CloudStorage",
+# "OneDrive-SharedLibraries-WBG",
+# "Development Data Partnership - Niger Economic Monitor",
+# "Data")
-data_dir <- file.path("/Users",
- "rmarty",
- "Library",
- "CloudStorage",
- "OneDrive-SharedLibraries-WBG",
- "Development Data Partnership - Niger Economic Monitor",
- "Data")
+data_dir <- file.path("~/Dropbox/World Bank/Side Work/",
+ "Development Data Partnership - Niger Economic Monitor", "Data")
boundaries_dir <- file.path(data_dir, "Boundaries")
ntl_dir <- file.path(data_dir, "Nighttime Lights")
@@ -114,6 +124,88 @@ leaflet() %>%
)
```
+# Association of NTL with GDP
+
+Existing research demonstrates that nighttime lights correlates with GDP, serving as a proxy for economic activity (for example, see [here](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4108272/)). Below we show the association between nighttime lights and GDP for Niger at the annual and country level.
+
+```{r, warning=FALSE, message=FALSE}
+## NTL
+adm0_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm0_annually.Rds"))
+
+## GDP
+gdp_df <- WDI(country = "NER",
+ indicator = c("NY.GDP.MKTP.KD", "NY.GDP.PCAP.KD"),
+ start = 1992,
+ end = 2023,
+ extra = TRUE)
+
+gdp_long_df <- gdp_df %>%
+ dplyr::select(year, NY.GDP.MKTP.KD, NY.GDP.PCAP.KD) %>%
+ dplyr::rename("GDP" = "NY.GDP.MKTP.KD",
+ "GDP, Per Capita" = "NY.GDP.PCAP.KD") %>%
+ pivot_longer(cols = -year) %>%
+ dplyr::rename(date = year) %>%
+ clean_names()
+
+## Annual trend figures
+data_long_df <- bind_rows(
+ gdp_long_df %>%
+ dplyr::select(date, value, name),
+
+ adm0_df %>%
+ dplyr::mutate(value = ntl_sum,
+ name = "Nighttime Lights") %>%
+ dplyr::select(date, value, name)
+)
+
+data_long_df %>%
+ dplyr::filter(date >= 2012) %>%
+ ggplot(aes(x = date,
+ y = value)) +
+ geom_line() +
+ facet_wrap(~name,
+ scales = "free_y",
+ ncol = 1) +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "Year",
+ y = NULL)
+
+## Correlation Figures
+data_wide_df <- adm0_df %>%
+ left_join(gdp_long_df, by = "date")
+
+data_wide_df %>%
+ dplyr::filter(name == "GDP") %>%
+ ggplot(aes(x = value,
+ y = ntl_sum)) +
+ geom_point() +
+ geom_smooth(se = F,
+ method = "lm",
+ color = "darkorange") +
+ stat_cor() +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "GDP",
+ y = "Nighttime lughts",
+ title = "GDP vs Nighttime Lights")
+
+data_wide_df %>%
+ dplyr::filter(name == "GDP, Per Capita") %>%
+ ggplot(aes(x = value,
+ y = ntl_sum)) +
+ geom_point() +
+ geom_smooth(se = F,
+ method = "lm",
+ color = "darkorange") +
+ stat_cor() +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "GDP, Per Capita",
+ y = "Nighttime lughts",
+ title = "Per Capita GDP vs Nighttime Lights")
+
+```
# Trends in nighttime lights within past 5 years
@@ -129,10 +221,16 @@ adm0_df %>%
color = "red") +
geom_col(aes(x = date,
y = ntl_sum)) +
+ geom_line(aes(x = date,
+ y = ntl_sum_3m_ma,
+ color = "3 Month\nMoving\nAverage"),
+ linewidth = 1) +
+ scale_color_manual(values = "darkorange") +
theme_classic2() +
labs(x = NULL,
y = "Luminosity",
- title = "Trends in Nighttime Lights Across Niger")
+ title = "Trends in Nighttime Lights Across Niger",
+ color = NULL)
```
```{r}
@@ -145,10 +243,16 @@ adm1_df %>%
color = "red") +
geom_col(aes(x = date,
y = ntl_sum)) +
+ geom_line(aes(x = date,
+ y = ntl_sum_3m_ma,
+ color = "3 Month\nMoving\nAverage"),
+ linewidth = 1) +
+ scale_color_manual(values = "darkorange") +
theme_classic2() +
theme_custom +
labs(x = NULL,
y = "Luminosity",
+ color = NULL,
title = "Trends in Nighttime Lights Across Regions") +
facet_wrap(~ADM1_FR,
scales = "free_y")
@@ -164,9 +268,15 @@ city_df %>%
color = "red") +
geom_col(aes(x = date,
y = ntl_sum)) +
+ geom_line(aes(x = date,
+ y = ntl_sum_3m_ma,
+ color = "3 Month\nMoving\nAverage"),
+ linewidth = 1) +
+ scale_color_manual(values = "darkorange") +
theme_classic2() +
theme_custom +
labs(x = NULL,
+ color = NULL,
y = "Luminosity",
title = "Trends in Nighttime Lights Across Top Cities") +
facet_wrap(~name,
@@ -175,95 +285,76 @@ city_df %>%
# Maps of change in nighttime lights
+The below section shows the percent change in nighttime lights from August to December 2023 relative to August to December 2022. We show the percent change at the pixel level and at the ADM3 level.
+
## Pixel level
```{r}
-r_load_month_pc <- function(month_i){
- r_b1 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_01",".tif")))
- r_b2 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_02",".tif")))
- r_b3 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_03",".tif")))
- r_b4 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_04",".tif")))
- r_b5 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_05",".tif")))
- r_b6 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_06",".tif")))
- r_e <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
- paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t",month_i,".tif")))
-
- r_b <- r_b1
- r_b[] <- (r_b1[] + r_b2[] + r_b3[] + r_b4[] + r_b5[] + r_b6[]) / 6
-
- r_e[] <- (r_e[] - r_b[]) / r_b[] * 100
- r_e[][r_b[] <= 0.5] <- NA
-
- r_e[][r_e[] >= 100] <- 100
- r_e[][r_e[] <= -100] <- -100
-
- r_e <- r_e %>% crop(adm0_sf) %>% mask(adm0_sf)
-
- return(r_e)
-}
-
-r_23_07 <- r_load_month_pc("2023_07")
-r_23_08 <- r_load_month_pc("2023_08")
-r_23_09 <- r_load_month_pc("2023_09")
-r_23_10 <- r_load_month_pc("2023_10")
-r_23_11 <- r_load_month_pc("2023_11")
-r_23_12 <- r_load_month_pc("2023_12")
-r_24_01 <- r_load_month_pc("2024_01")
-r_24_02 <- r_load_month_pc("2024_02")
-r_24_03 <- r_load_month_pc("2024_03")
+#### 2023
+r_22_08 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2022_08",".tif")))
+r_22_09 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2022_09",".tif")))
+r_22_10 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2022_10",".tif")))
+r_22_11 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2022_11",".tif")))
+r_22_12 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2022_12",".tif")))
+
+#### 2024
+r_23_08 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_08",".tif")))
+r_23_09 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_09",".tif")))
+r_23_10 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_10",".tif")))
+r_23_11 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_11",".tif")))
+r_23_12 <- rast(file.path(ntl_dir, "individual_rasters", "monthly",
+ paste0("VNP46A3_NearNadir_Composite_Snow_Free_qflag_t","2023_12",".tif")))
+
+#### Percent Change
+r_22 <- r_22_08
+r_23 <- r_23_08
+
+r_22[] <- (r_22_08[] + r_22_09[] + r_22_10[] + r_22_11[] + r_22_12[]) / 5
+r_23[] <- (r_23_08[] + r_23_09[] + r_23_10[] + r_23_11[] + r_23_12[]) / 5
+
+r_23_pc <- r_23
+r_23_pc[] <- (r_23[] - r_22[]) / r_22[] * 100
+
+r_23_pc[][r_22[] <= 0.5] <- NA
+r_23_pc[][r_23_pc[] >= 100] <- 100
+r_23_pc[][r_23_pc[] <= -100] <- -100
+
+r_23_pc <- r_23_pc %>% crop(adm0_sf) %>% mask(adm0_sf)
## Make map
-r_values <- unique(c(r_23_07[],
- r_23_08[],
- r_23_09[],
- r_23_10[],
- r_23_11[],
- r_23_12[],
- r_24_01[],
- r_24_02[],
- r_24_03[]))
+r_values <- r_23_pc[] %>%
+ unlist() %>%
+ as.vector() %>%
+ unique()
pal <- colorNumeric(c("red", "white", "green"), r_values,
na.color = "transparent")
leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
- addRasterImage(r_23_07, colors = pal, opacity = 1, group = "2023-07") %>%
- addRasterImage(r_23_08, colors = pal, opacity = 1, group = "2023-08") %>%
- addRasterImage(r_23_09, colors = pal, opacity = 1, group = "2023-09") %>%
- addRasterImage(r_23_10, colors = pal, opacity = 1, group = "2023-10") %>%
- addRasterImage(r_23_11, colors = pal, opacity = 1, group = "2023-11") %>%
- addRasterImage(r_23_12, colors = pal, opacity = 1, group = "2023-12") %>%
- addRasterImage(r_24_01, colors = pal, opacity = 1, group = "2024-01") %>%
- addRasterImage(r_24_02, colors = pal, opacity = 1, group = "2024-02") %>%
- addRasterImage(r_24_03, colors = pal, opacity = 1, group = "2024-03") %>%
- addLayersControl(
- baseGroups = c("2023-07",
- "2023-08",
- "2023-09",
- "2023-10",
- "2023-11",
- "2023-12",
- "2024-01",
- "2024-02",
- "2024-03"),
- options = layersControlOptions(collapsed=FALSE)
- ) %>%
+ addRasterImage(r_23_pc, colors = pal, opacity = 1) %>%
addLegend("bottomright", pal = pal, values = r_values,
- title = "% Change in NTL
relative to
Jan-June 2023",
+ title = "% Change in NTL
Aug-Dec 2023
Relative to
Aug-Dec 2022",
opacity = 1,
labels = c("< -100", "-50", "0", "50", "> 100")
)
```
+
## ADM level
-```{r}
+### Static Map
+
+```{r, warning=FALSE, message=FALSE}
+## Polygon
adm3_sf <- read_sf(file.path(boundaries_dir,
"ner_adm_ignn_20230720_ab_shp",
"NER_admbnda_adm3_IGNN_20230720.shp"))
@@ -271,271 +362,147 @@ adm3_sf <- read_sf(file.path(boundaries_dir,
adm3_sf <- adm3_sf %>%
select(-date)
+## Data
adm3_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm3_monthly.Rds"))
-adm3_df <- adm3_df %>%
- mutate(baseline = (date %in% c(ymd("2023-01-01"),
- ymd("2023-02-01"),
- ymd("2023-03-01"),
- ymd("2023-04-01"),
- ymd("2023-05-01"),
- ymd("2023-06-01")))) %>%
- group_by(ADM3_PCODE) %>%
- mutate(ntl_sum_baseline = mean(ntl_sum[baseline %in% 1])) %>%
+adm3_sum_df <- adm3_df %>%
+ dplyr::mutate(base_end = case_when(
+ date %in% c(ymd("2022-08-01"),
+ ymd("2022-09-01"),
+ ymd("2022-10-01"),
+ ymd("2022-11-01"),
+ ymd("2022-12-01")) ~ "baseline",
+ date %in% c(ymd("2023-08-01"),
+ ymd("2023-09-01"),
+ ymd("2023-10-01"),
+ ymd("2023-11-01"),
+ ymd("2023-12-01")) ~ "endline"
+ )) %>%
+ dplyr::filter(!is.na(base_end)) %>%
+ group_by(ADM3_PCODE, base_end) %>%
+ dplyr::summarise(ntl_sum = mean(ntl_sum)) %>%
ungroup() %>%
-
- mutate(ntl_pc = (ntl_sum - ntl_sum_baseline) / ntl_sum_baseline * 100,
- ntl_change = ntl_sum - ntl_sum_baseline) %>%
- select(date, ADM3_PCODE, ntl_pc, ntl_change, ntl_sum, ntl_sum_baseline) %>%
-
- filter(date >= ymd("2023-01-01"))
-
-adm3_df$ntl_sum_baseline_dates <- adm3_df$ntl_sum
-adm3_df$ntl_sum_baseline_dates[adm3_df$date >= ymd("2023-07-01")] <- NA
+ pivot_wider(id_cols = ADM3_PCODE,
+ names_from = base_end,
+ values_from = ntl_sum) %>%
+ dplyr::mutate(pc = (endline - baseline) / baseline * 100)
+
+adm3_sum_df$pc[adm3_sum_df$pc >= 100] <- 100
+
+adm3_data_sf <- adm3_sf %>%
+ left_join(adm3_sum_df, by = "ADM3_PCODE")
+
+## Static Map
+ggplot() +
+ geom_sf(data = adm3_data_sf,
+ aes(fill = pc)) +
+ theme_void() +
+ scale_fill_gradient2(low = "red",
+ high = "forestgreen") +
+ labs(fill = "% Change",
+ title = "Percent change in nighttime lights from Aug-Dec 2023 relative to Aug-Dec 2022")
-make_ntl_date <- function(adm3_df, ym_i){
- adm3_df[[paste0("ntl_sum_", ym_i)]] <- adm3_df$ntl_sum
- adm3_df[[paste0("ntl_sum_", ym_i)]][adm3_df$date != ymd(paste0(ym_i, "-01"))] <- NA
- return(adm3_df)
-}
+```
-adm3_df <- adm3_df %>%
- make_ntl_date("2023_07") %>%
- make_ntl_date("2023_08") %>%
- make_ntl_date("2023_09") %>%
- make_ntl_date("2023_10") %>%
- make_ntl_date("2023_11") %>%
- make_ntl_date("2023_12") %>%
- make_ntl_date("2024_01") %>%
- make_ntl_date("2024_02") %>%
- make_ntl_date("2024_03")
-
-adm3_df$ntl_pc[adm3_df$ntl_pc >= 100] <- 100
-adm3_df$ntl_pc[adm3_df$ntl_pc <= -100] <- -100
-
-## Add sparkline
-ntl_date_color <- "black"
-
-adm3_df_line <- adm3_df %>%
- filter(date >= ymd("2023-01-01")) %>%
- arrange(date) %>%
- split(.$ADM3_PCODE) %>%
- map_df(~{
- l_ntl_all <- sparkline(.x$ntl_sum,
- type='bar',
- barColor="orange",
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = 'orange',
- highlightSpotColor = 'orange')
- l_ntl_base <- sparkline(.x$ntl_sum_baseline_dates,
- type='bar',
- barColor="blue",
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = 'blue',
- highlightSpotColor = 'blue')
- l_ntl_2023_07 <- sparkline(.x$ntl_sum_2023_07,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_08 <- sparkline(.x$ntl_sum_2023_08,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_09 <- sparkline(.x$ntl_sum_2023_09,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_10 <- sparkline(.x$ntl_sum_2023_10,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_11 <- sparkline(.x$ntl_sum_2023_11,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2023_12 <- sparkline(.x$ntl_sum_2023_12,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_01 <- sparkline(.x$ntl_sum_2024_01,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_02 <- sparkline(.x$ntl_sum_2024_02,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl_2024_03 <- sparkline(.x$ntl_sum_2024_03,
- type='bar',
- barColor=ntl_date_color,
- chartRangeMin = 0,
- chartRangeMax = 8,
- width = 100,
- height = 50,
- tooltipChartTitle = "Nighttime Lights",
- highlightLineColor = ntl_date_color,
- highlightSpotColor = ntl_date_color)
- l_ntl <- spk_composite(l_ntl_all,
- l_ntl_base)
- data.frame(l_ntl = as.character(htmltools::as.tags(l_ntl)))
- }, .id = 'ADM3_PCODE')
-
-## Merge data with sf object
-adm3_sf <- adm3_sf %>%
- right_join(adm3_df, by = c("ADM3_PCODE")) %>%
- left_join(adm3_df_line, by = "ADM3_PCODE")
+### Interactive Map
-adm3_sf <- adm3_sf %>%
- mutate(popup = paste0("
", ADM3_FR, " ",
- "
NTL ", substring(date, 1, 7), ": ", round(ntl_sum, 2), "
",
- "
NTL Baseline: ", round(ntl_sum_baseline, 2), "
",
- "
Trends in NTL since Jan 2023 ",
- "
Blue = Jan - June 2023
",
- l_ntl))
+Click an administrative division to see the percent change
-## Make map
-r_values <- unique(adm3_sf$ntl_pc)
-
-pal <- colorNumeric(c("red", "white", "green"), r_values,
+```{r, warning=FALSE, message=FALSE}
+## Interactive map
+pal <- colorNumeric(c("red", "white", "green"), adm3_data_sf$pc,
na.color = "transparent")
-line_color <- "black"
-fill_opacity <- 0.8
-opacity <- 0.5
-
-add_deps <- function(dtbl, name, pkg = name) {
- tagList(
- dtbl,
- htmlwidgets::getDependency(name, pkg)
- )
-}
+adm3_data_sf <- adm3_data_sf %>%
+ mutate(popup = paste0(ADM3_FR, "
% Change: ", round(pc, 2), "%"))
leaflet() %>%
- addProviderTiles(providers$OpenStreetMap.HOT) %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-07-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-07") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-08-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-08") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-09-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-09") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-10-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-10") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-11-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-11") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2023-12-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2023-12") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2024-01-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2024-01") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2024-02-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2024-02") %>%
- addPolygons(data = adm3_sf[adm3_sf$date == ymd("2024-03-01"),],
- fillColor = ~pal(ntl_pc),
- color = line_color, opacity = opacity, fillOpacity = fill_opacity,
- label = ~lapply(popup, HTML),
- group = "2024-03") %>%
- onRender("function(el,x) {
- this.on('tooltipopen', function() {HTMLWidgets.staticRender();})
- }") %>%
- addLayersControl(
- baseGroups = c("2023-07",
- "2023-08",
- "2023-09",
- "2023-10",
- "2023-11",
- "2023-12",
- "2024-01",
- "2024-02",
- "2024-03"),
- options = layersControlOptions(collapsed=FALSE)
- ) %>%
+ addProviderTiles(providers$CartoDB) %>%
+ addPolygons(data = adm3_data_sf,
+ fillColor = ~pal(pc),
+ fillOpacity = 1,
+ color = "black",
+ weight = 1,
+ popup = ~popup) %>%
addLegend("bottomright", pal = pal, values = r_values,
- title = "% Change in NTL
relative to
Jan-June 2023",
+ title = "% Change in NTL
Aug-Dec 2023
Relative to
Aug-Dec 2022",
opacity = 1,
labels = c("< -100", "-50", "0", "50", "> 100")
- ) %>%
- add_deps("sparkline") %>%
- browsable()
+ )
+```
+
+# Percent change in nighttime lights from 2022 to 2023
+Below we show the percent change in nighttime lights for each month in 2023 relative to the same month in 2022
+
+```{r}
+## Load data
+adm0_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm0_monthly.Rds"))
+adm1_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm1_monthly.Rds"))
+city_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "city_monthly.Rds"))
+
+## Add dummpy ADM1 variable so function will more easily work on both datasets
+adm0_df$ADM1_FR <- ""
+city_df$ADM1_FR <- city_df$name
+
+## Function to prep data as percent changes
+prep_month_pc <- function(adm_df){
+
+ adm_long_df <- adm_df %>%
+ filter(date >= ymd("2022-01-01"),
+ date <= ymd("2023-12-01")) %>%
+ select(ADM1_FR, date, ntl_sum) %>%
+ mutate(month = date %>% month,
+ year = date %>% year) %>%
+ pivot_wider(id_cols = c(ADM1_FR, month),
+ values_from = ntl_sum,
+ names_from = year) %>%
+ #filter(!is.na(`2024`)) %>%
+ mutate(pc = (`2023` - `2022`) / `2022` * 100)
+
+ return(adm_long_df)
+}
+
+adm0_pc_df <- prep_month_pc(adm0_df)
+adm1_pc_df <- prep_month_pc(adm1_df)
+city_pc_df <- prep_month_pc(city_df)
+
+## Maps
+adm0_pc_df %>%
+ ggplot() +
+ geom_col(aes(x = month,
+ y = pc)) +
+ geom_vline(xintercept = 7, color = "red") +
+ theme_classic2() +
+ labs(x = "Month",
+ y = "Percent change",
+ title = "Percent change in nighttime lights across Niger from 2022 to 2023")
+
+adm1_pc_df %>%
+ ggplot() +
+ geom_col(aes(x = month,
+ y = pc)) +
+ geom_vline(xintercept = 7, color = "red") +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "Month",
+ y = "Percent change",
+ title = "Percent change in nighttime lights across regions from 2022 to 2023") +
+ facet_wrap(~ADM1_FR,
+ scales = "free_y")
+
+city_pc_df %>%
+ ggplot() +
+ geom_col(aes(x = month,
+ y = pc)) +
+ geom_vline(xintercept = 7, color = "red") +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "Month",
+ y = "Percent change",
+ title = "Percent change in nighttime lights across regions from 2023 to 2024") +
+ facet_wrap(~ADM1_FR,
+ scales = "free_y")
```
@@ -547,9 +514,11 @@ Below we show the percent change in nighttime lights for each month in 2024 rela
## Load data
adm0_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm0_monthly.Rds"))
adm1_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "adm1_monthly.Rds"))
+city_df <- readRDS(file.path(ntl_dir, "aggregated_appended", "city_monthly.Rds"))
## Add dummpy ADM1 variable so function will more easily work on both datasets
adm0_df$ADM1_FR <- ""
+city_df$ADM1_FR <- city_df$name
## Function to prep data as percent changes
prep_month_pc <- function(adm_df){
@@ -571,6 +540,7 @@ prep_month_pc <- function(adm_df){
adm0_pc_df <- prep_month_pc(adm0_df)
adm1_pc_df <- prep_month_pc(adm1_df)
+city_pc_df <- prep_month_pc(city_df)
## Maps
adm0_pc_df %>%
@@ -593,8 +563,19 @@ adm1_pc_df %>%
title = "Percent change in nighttime lights across regions from 2023 to 2024") +
facet_wrap(~ADM1_FR,
scales = "free_y")
-```
+city_pc_df %>%
+ ggplot() +
+ geom_col(aes(x = month,
+ y = pc)) +
+ theme_classic2() +
+ theme_custom +
+ labs(x = "Month",
+ y = "Percent change",
+ title = "Percent change in nighttime lights across regions from 2023 to 2024") +
+ facet_wrap(~ADM1_FR,
+ scales = "free_y")
+```
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-1.png
new file mode 100644
index 0000000..13667f0
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-2.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-2.png
new file mode 100644
index 0000000..f74cf96
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-2.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-3.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-3.png
new file mode 100644
index 0000000..2faefc5
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-10-3.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-1.png
new file mode 100644
index 0000000..cb439ca
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-2.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-2.png
new file mode 100644
index 0000000..5f23808
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-2.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-3.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-3.png
new file mode 100644
index 0000000..e48ae60
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-11-3.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-1.png
index 1e50c50..0bb9def 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-1.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-2.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-2.png
new file mode 100644
index 0000000..c207fd4
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-2.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-3.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-3.png
new file mode 100644
index 0000000..461f069
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-3-3.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-4-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-4-1.png
index 3c4cb9c..0a343c3 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-4-1.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-4-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-5-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-5-1.png
index 4af34c3..62f966c 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-5-1.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-5-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-6-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-6-1.png
index 0d64d56..7ee40b5 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-6-1.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-6-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-1.png
index 0d64d56..36c7258 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-1.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-2.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-2.png
index d1f6568..5f23808 100644
Binary files a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-2.png and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-8-2.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-1.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-1.png
new file mode 100644
index 0000000..13667f0
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-1.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-2.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-2.png
new file mode 100644
index 0000000..f74cf96
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-2.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-3.png b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-3.png
new file mode 100644
index 0000000..2faefc5
Binary files /dev/null and b/notebooks/nighttime-lights/ntl_analysis_files/figure-html/unnamed-chunk-9-3.png differ
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-binding-2.2.1/leaflet.js b/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-binding-2.2.1/leaflet.js
new file mode 100644
index 0000000..c8e7e5e
--- /dev/null
+++ b/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-binding-2.2.1/leaflet.js
@@ -0,0 +1,2789 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i
this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength);
+ var colIndex = -1;
+
+ if (typeof col === "undefined") {
+ var rowData = {};
+ this.colnames.forEach(function (name, i) {
+ rowData[name] = _this3.columns[i][row % _this3.columns[i].length];
+ });
+ return rowData;
+ } else if (typeof col === "string") {
+ colIndex = this._colIndex(col);
+ } else if (typeof col === "number") {
+ colIndex = col;
+ }
+
+ if (colIndex < 0 || colIndex > this.columns.length) {
+ if (missingOK) return void 0;else throw new Error("Unknown column index: " + col);
+ }
+
+ return this.columns[colIndex][row % this.columns[colIndex].length];
+ }
+ }, {
+ key: "nrow",
+ value: function nrow() {
+ return this.effectiveLength;
+ }
+ }]);
+
+ return DataFrame;
+}();
+
+exports["default"] = DataFrame;
+
+
+},{"./util":17}],5:[function(require,module,exports){
+"use strict";
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+// In RMarkdown's self-contained mode, we don't have a way to carry around the
+// images that Leaflet needs but doesn't load into the page. Instead, we'll use
+// the unpkg CDN.
+if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") {
+ _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/";
+}
+
+
+},{"./global/leaflet":10}],6:[function(require,module,exports){
+"use strict";
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+// add texxtsize, textOnly, and style
+_leaflet2["default"].Tooltip.prototype.options.textsize = "10px";
+_leaflet2["default"].Tooltip.prototype.options.textOnly = false;
+_leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it.
+
+var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout;
+
+_leaflet2["default"].Tooltip.prototype._initLayout = function () {
+ initLayoutOriginal.call(this);
+ this._container.style.fontSize = this.options.textsize;
+
+ if (this.options.textOnly) {
+ _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only");
+ }
+
+ if (this.options.style) {
+ for (var property in this.options.style) {
+ this._container.style[property] = this.options.style[property];
+ }
+ }
+};
+
+
+},{"./global/leaflet":10}],7:[function(require,module,exports){
+"use strict";
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+var protocolRegex = /^\/\//;
+
+var upgrade_protocol = function upgrade_protocol(urlTemplate) {
+ if (protocolRegex.test(urlTemplate)) {
+ if (window.location.protocol === "file:") {
+ // if in a local file, support http
+ // http should auto upgrade if necessary
+ urlTemplate = "http:" + urlTemplate;
+ }
+ }
+
+ return urlTemplate;
+};
+
+var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize;
+
+_leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) {
+ urlTemplate = upgrade_protocol(urlTemplate);
+ originalLTileLayerInitialize.call(this, urlTemplate, options);
+};
+
+var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize;
+
+_leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) {
+ urlTemplate = upgrade_protocol(urlTemplate);
+ originalLTileLayerWMSInitialize.call(this, urlTemplate, options);
+};
+
+
+},{"./global/leaflet":10}],8:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = global.HTMLWidgets;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],9:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = global.jQuery;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],10:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = global.L;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],11:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = global.L.Proj;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],12:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = global.Shiny;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],13:[function(require,module,exports){
+"use strict";
+
+var _jquery = require("./global/jquery");
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+var _shiny = require("./global/shiny");
+
+var _shiny2 = _interopRequireDefault(_shiny);
+
+var _htmlwidgets = require("./global/htmlwidgets");
+
+var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets);
+
+var _util = require("./util");
+
+var _crs_utils = require("./crs_utils");
+
+var _controlStore = require("./control-store");
+
+var _controlStore2 = _interopRequireDefault(_controlStore);
+
+var _layerManager = require("./layer-manager");
+
+var _layerManager2 = _interopRequireDefault(_layerManager);
+
+var _methods = require("./methods");
+
+var _methods2 = _interopRequireDefault(_methods);
+
+require("./fixup-default-icon");
+
+require("./fixup-default-tooltip");
+
+require("./fixup-url-protocol");
+
+var _dataframe = require("./dataframe");
+
+var _dataframe2 = _interopRequireDefault(_dataframe);
+
+var _clusterLayerStore = require("./cluster-layer-store");
+
+var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+window.LeafletWidget = {};
+window.LeafletWidget.utils = {};
+
+var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]);
+
+window.LeafletWidget.DataFrame = _dataframe2["default"];
+window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"];
+window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input.
+
+function updateBounds(map) {
+ var id = map.getContainer().id;
+ var bounds = map.getBounds();
+
+ _shiny2["default"].onInputChange(id + "_bounds", {
+ north: bounds.getNorthEast().lat,
+ east: bounds.getNorthEast().lng,
+ south: bounds.getSouthWest().lat,
+ west: bounds.getSouthWest().lng
+ });
+
+ _shiny2["default"].onInputChange(id + "_center", {
+ lng: map.getCenter().lng,
+ lat: map.getCenter().lat
+ });
+
+ _shiny2["default"].onInputChange(id + "_zoom", map.getZoom());
+}
+
+function preventUnintendedZoomOnScroll(map) {
+ // Prevent unwanted scroll capturing. Similar in purpose to
+ // https://github.com/CliffCloud/Leaflet.Sleep but with a
+ // different set of heuristics.
+ // The basic idea is that when a mousewheel/DOMMouseScroll
+ // event is seen, we disable scroll wheel zooming until the
+ // user moves their mouse cursor or clicks on the map. This
+ // is slightly trickier than just listening for mousemove,
+ // because mousemove is fired when the page is scrolled,
+ // even if the user did not physically move the mouse. We
+ // handle this by examining the mousemove event's screenX
+ // and screenY properties; if they change, we know it's a
+ // "true" move.
+ // lastScreen can never be null, but its x and y can.
+ var lastScreen = {
+ x: null,
+ y: null
+ };
+ (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) {
+ // Disable zooming (until the mouse moves or click)
+ map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored.
+
+ lastScreen = {
+ x: e.originalEvent.screenX,
+ y: e.originalEvent.screenY
+ };
+ });
+ (0, _jquery2["default"])(document).on("mousemove", "*", function (e) {
+ // Did the mouse really move?
+ if (map.options.scrollWheelZoom) {
+ if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) {
+ // It really moved. Enable zooming.
+ map.scrollWheelZoom.enable();
+ lastScreen = {
+ x: null,
+ y: null
+ };
+ }
+ }
+ });
+ (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) {
+ // Clicking always enables zooming.
+ if (map.options.scrollWheelZoom) {
+ map.scrollWheelZoom.enable();
+ lastScreen = {
+ x: null,
+ y: null
+ };
+ }
+ });
+}
+
+_htmlwidgets2["default"].widget({
+ name: "leaflet",
+ type: "output",
+ factory: function factory(el, width, height) {
+ var map = null;
+ return {
+ // we need to store our map in our returned object.
+ getMap: function getMap() {
+ return map;
+ },
+ renderValue: function renderValue(data) {
+ // Create an appropriate CRS Object if specified
+ if (data && data.options && data.options.crs) {
+ data.options.crs = (0, _crs_utils.getCRS)(data.options.crs);
+ } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810
+
+
+ if (map) {
+ map.remove();
+
+ map = function () {
+ return;
+ }(); // undefine map
+
+ }
+
+ if (data.options.mapFactory && typeof data.options.mapFactory === "function") {
+ map = data.options.mapFactory(el, data.options);
+ } else {
+ map = _leaflet2["default"].map(el, data.options);
+ }
+
+ preventUnintendedZoomOnScroll(map); // Store some state in the map object
+
+ map.leafletr = {
+ // Has the map ever rendered successfully?
+ hasRendered: false,
+ // Data to be rendered when resize is called with area != 0
+ pendingRenderData: null
+ }; // Check if the map is rendered statically (no output binding)
+
+ if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) {
+ map.id = el.id; // Store the map on the element so we can find it later by ID
+
+ (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app
+
+ map.on("click", function (e) {
+ _shiny2["default"].onInputChange(map.id + "_click", {
+ lat: e.latlng.lat,
+ lng: e.latlng.lng,
+ ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed
+
+ });
+ });
+ var groupTimerId = null;
+ map.on("moveend", function (e) {
+ updateBounds(e.target);
+ }).on("layeradd layerremove", function (e) {
+ // If the layer that's coming or going is a group we created, tell
+ // the server.
+ if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) {
+ // But to avoid chattiness, coalesce events
+ if (groupTimerId) {
+ clearTimeout(groupTimerId);
+ groupTimerId = null;
+ }
+
+ groupTimerId = setTimeout(function () {
+ groupTimerId = null;
+
+ _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups());
+ }, 100);
+ }
+ });
+ }
+
+ this.doRenderValue(data, map);
+ },
+ doRenderValue: function doRenderValue(data, map) {
+ // Leaflet does not behave well when you set up a bunch of layers when
+ // the map is not visible (width/height == 0). Popups get misaligned
+ // relative to their owning markers, and the fitBounds calculations
+ // are off. Therefore we wait until the map is actually showing to
+ // render the value (we rely on the resize() callback being invoked
+ // at the appropriate time).
+ if (el.offsetWidth === 0 || el.offsetHeight === 0) {
+ map.leafletr.pendingRenderData = data;
+ return;
+ }
+
+ map.leafletr.pendingRenderData = null; // Merge data options into defaults
+
+ var options = _jquery2["default"].extend({
+ zoomToLimits: "always"
+ }, data.options);
+
+ if (!map.layerManager) {
+ map.controls = new _controlStore2["default"](map);
+ map.layerManager = new _layerManager2["default"](map);
+ } else {
+ map.controls.clear();
+ map.layerManager.clear();
+ }
+
+ var explicitView = false;
+
+ if (data.setView) {
+ explicitView = true;
+ map.setView.apply(map, data.setView);
+ }
+
+ if (data.fitBounds) {
+ explicitView = true;
+ methods.fitBounds.apply(map, data.fitBounds);
+ }
+
+ if (data.flyTo) {
+ if (!explicitView && !map.leafletr.hasRendered) {
+ // must be done to give a initial starting point
+ map.fitWorld();
+ }
+
+ explicitView = true;
+ map.flyTo.apply(map, data.flyTo);
+ }
+
+ if (data.flyToBounds) {
+ if (!explicitView && !map.leafletr.hasRendered) {
+ // must be done to give a initial starting point
+ map.fitWorld();
+ }
+
+ explicitView = true;
+ methods.flyToBounds.apply(map, data.flyToBounds);
+ }
+
+ if (data.options.center) {
+ explicitView = true;
+ } // Returns true if the zoomToLimits option says that the map should be
+ // zoomed to map elements.
+
+
+ function needsZoom() {
+ return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered;
+ }
+
+ if (!explicitView && needsZoom() && !map.getZoom()) {
+ if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) {
+ // Use the natural limits of what's being drawn on the map
+ // If the size of the bounding box is 0, leaflet gets all weird
+ var pad = 0.006;
+
+ if (data.limits.lat[0] === data.limits.lat[1]) {
+ data.limits.lat[0] = data.limits.lat[0] - pad;
+ data.limits.lat[1] = data.limits.lat[1] + pad;
+ }
+
+ if (data.limits.lng[0] === data.limits.lng[1]) {
+ data.limits.lng[0] = data.limits.lng[0] - pad;
+ data.limits.lng[1] = data.limits.lng[1] + pad;
+ }
+
+ map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]);
+ } else {
+ map.fitWorld();
+ }
+ }
+
+ for (var i = 0; data.calls && i < data.calls.length; i++) {
+ var call = data.calls[i];
+ if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method);
+ }
+
+ map.leafletr.hasRendered = true;
+
+ if (_htmlwidgets2["default"].shinyMode) {
+ setTimeout(function () {
+ updateBounds(map);
+ }, 1);
+ }
+ },
+ resize: function resize(width, height) {
+ if (map) {
+ map.invalidateSize();
+
+ if (map.leafletr.pendingRenderData) {
+ this.doRenderValue(map.leafletr.pendingRenderData, map);
+ }
+ }
+ }
+ };
+ }
+});
+
+if (_htmlwidgets2["default"].shinyMode) {
+ _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) {
+ var id = data.id;
+ var el = document.getElementById(id);
+ var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null;
+
+ if (!map) {
+ (0, _util.log)("Couldn't find map with id " + id);
+ return;
+ } // If the map has not rendered, stash the proposed `leafletProxy()` calls
+ // in `pendingRenderData.calls` to be run on display via `doRenderValue()`.
+ // This is necessary if the map has not been rendered.
+ // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded.
+
+
+ if (!map.leafletr.hasRendered) {
+ map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls);
+ return;
+ }
+
+ for (var i = 0; i < data.calls.length; i++) {
+ var call = data.calls[i];
+ var args = call.args;
+
+ for (var _i = 0; _i < call.evals.length; _i++) {
+ window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]);
+ }
+
+ if (call.dependencies) {
+ _shiny2["default"].renderDependencies(call.dependencies);
+ }
+
+ if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method);
+ }
+ });
+}
+
+
+},{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = undefined;
+
+var _jquery = require("./global/jquery");
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+var _util = require("./util");
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+var LayerManager = /*#__PURE__*/function () {
+ function LayerManager(map) {
+ _classCallCheck(this, LayerManager);
+
+ this._map = map; // BEGIN layer indices
+ // {: {: layer}}
+
+ this._byGroup = {}; // {: {: layer}}
+
+ this._byCategory = {}; // {: layer}
+
+ this._byLayerId = {}; // {: {
+ // "group": ,
+ // "layerId": ,
+ // "category": ,
+ // "container":
+ // }
+ // }
+
+ this._byStamp = {}; // {: {: [, , ...], ...}}
+
+ this._byCrosstalkGroup = {}; // END layer indices
+ // {: L.layerGroup}
+
+ this._categoryContainers = {}; // {: L.layerGroup}
+
+ this._groupContainers = {};
+ }
+
+ _createClass(LayerManager, [{
+ key: "addLayer",
+ value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) {
+ var _this = this;
+
+ // Was a group provided?
+ var hasId = typeof layerId === "string";
+ var grouped = typeof group === "string";
+ var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to.
+ // We may overwrite this let before using it (i.e. if a group is assigned).
+ // This one liner creates the _categoryContainers[category] entry if it
+ // doesn't already exist.
+
+ var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map);
+
+ var oldLayer = null;
+
+ if (hasId) {
+ // First, remove any layer with the same category and layerId
+ var prefixedLayerId = this._layerIdKey(category, layerId);
+
+ oldLayer = this._byLayerId[prefixedLayerId];
+
+ if (oldLayer) {
+ this._removeLayer(oldLayer);
+ } // Update layerId index
+
+
+ this._byLayerId[prefixedLayerId] = layer;
+ } // Update group index
+
+
+ if (grouped) {
+ this._byGroup[group] = this._byGroup[group] || {};
+ this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer
+ // group; instead, use the group's layer group.
+ // This one liner creates the _groupContainers[group] entry if it doesn't
+ // already exist.
+
+ container = this.getLayerGroup(group, true);
+ } // Update category index
+
+
+ this._byCategory[category] = this._byCategory[category] || {};
+ this._byCategory[category][stamp] = layer; // Update stamp index
+
+ var layerInfo = this._byStamp[stamp] = {
+ layer: layer,
+ group: group,
+ ctGroup: ctGroup,
+ ctKey: ctKey,
+ layerId: layerId,
+ category: category,
+ container: container,
+ hidden: false
+ }; // Update crosstalk group index
+
+ if (ctGroup) {
+ if (layer.setStyle) {
+ // Need to save this info so we know what to set opacity to later
+ layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5;
+ layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2;
+ }
+
+ var ctg = this._byCrosstalkGroup[ctGroup];
+
+ if (!ctg) {
+ ctg = this._byCrosstalkGroup[ctGroup] = {};
+ var crosstalk = global.crosstalk;
+
+ var handleFilter = function handleFilter(e) {
+ if (!e.value) {
+ var groupKeys = Object.keys(ctg);
+
+ for (var i = 0; i < groupKeys.length; i++) {
+ var key = groupKeys[i];
+ var _layerInfo = _this._byStamp[ctg[key]];
+
+ _this._setVisibility(_layerInfo, true);
+ }
+ } else {
+ var selectedKeys = {};
+
+ for (var _i = 0; _i < e.value.length; _i++) {
+ selectedKeys[e.value[_i]] = true;
+ }
+
+ var _groupKeys = Object.keys(ctg);
+
+ for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) {
+ var _key = _groupKeys[_i2];
+ var _layerInfo2 = _this._byStamp[ctg[_key]];
+
+ _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]);
+ }
+ }
+ };
+
+ var filterHandle = new crosstalk.FilterHandle(ctGroup);
+ filterHandle.on("change", handleFilter);
+
+ var handleSelection = function handleSelection(e) {
+ if (!e.value || !e.value.length) {
+ var groupKeys = Object.keys(ctg);
+
+ for (var i = 0; i < groupKeys.length; i++) {
+ var key = groupKeys[i];
+ var _layerInfo3 = _this._byStamp[ctg[key]];
+
+ _this._setOpacity(_layerInfo3, 1.0);
+ }
+ } else {
+ var selectedKeys = {};
+
+ for (var _i3 = 0; _i3 < e.value.length; _i3++) {
+ selectedKeys[e.value[_i3]] = true;
+ }
+
+ var _groupKeys2 = Object.keys(ctg);
+
+ for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) {
+ var _key2 = _groupKeys2[_i4];
+ var _layerInfo4 = _this._byStamp[ctg[_key2]];
+
+ _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2);
+ }
+ }
+ };
+
+ var selHandle = new crosstalk.SelectionHandle(ctGroup);
+ selHandle.on("change", handleSelection);
+ setTimeout(function () {
+ handleFilter({
+ value: filterHandle.filteredKeys
+ });
+ handleSelection({
+ value: selHandle.value
+ });
+ }, 100);
+ }
+
+ if (!ctg[ctKey]) ctg[ctKey] = [];
+ ctg[ctKey].push(stamp);
+ } // Add to container
+
+
+ if (!layerInfo.hidden) container.addLayer(layer);
+ return oldLayer;
+ }
+ }, {
+ key: "brush",
+ value: function brush(bounds, extraInfo) {
+ var _this2 = this;
+
+ /* eslint-disable no-console */
+ // For each Crosstalk group...
+ Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) {
+ var ctg = _this2._byCrosstalkGroup[ctGroupName];
+ var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple
+ // layers)...
+
+ Object.keys(ctg).forEach(function (ctKey) {
+ // ...and for each layer...
+ ctg[ctKey].forEach(function (stamp) {
+ var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point...
+
+ if (layerInfo.layer.getLatLng) {
+ // ... and it's inside the selection bounds...
+ // TODO: Use pixel containment, not lat/lng containment
+ if (bounds.contains(layerInfo.layer.getLatLng())) {
+ // ...add the key to the selection.
+ selection.push(ctKey);
+ }
+ }
+ });
+ });
+ new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo);
+ });
+ }
+ }, {
+ key: "unbrush",
+ value: function unbrush(extraInfo) {
+ Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) {
+ new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo);
+ });
+ }
+ }, {
+ key: "_setVisibility",
+ value: function _setVisibility(layerInfo, visible) {
+ if (layerInfo.hidden ^ visible) {
+ return;
+ } else if (visible) {
+ layerInfo.container.addLayer(layerInfo.layer);
+ layerInfo.hidden = false;
+ } else {
+ layerInfo.container.removeLayer(layerInfo.layer);
+ layerInfo.hidden = true;
+ }
+ }
+ }, {
+ key: "_setOpacity",
+ value: function _setOpacity(layerInfo, opacity) {
+ if (layerInfo.layer.setOpacity) {
+ layerInfo.layer.setOpacity(opacity);
+ } else if (layerInfo.layer.setStyle) {
+ layerInfo.layer.setStyle({
+ opacity: opacity * layerInfo.layer.options.origOpacity,
+ fillOpacity: opacity * layerInfo.layer.options.origFillOpacity
+ });
+ }
+ }
+ }, {
+ key: "getLayer",
+ value: function getLayer(category, layerId) {
+ return this._byLayerId[this._layerIdKey(category, layerId)];
+ }
+ }, {
+ key: "removeLayer",
+ value: function removeLayer(category, layerIds) {
+ var _this3 = this;
+
+ // Find layer info
+ _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) {
+ var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)];
+
+ if (layer) {
+ _this3._removeLayer(layer);
+ }
+ });
+ }
+ }, {
+ key: "clearLayers",
+ value: function clearLayers(category) {
+ var _this4 = this;
+
+ // Find all layers in _byCategory[category]
+ var catTable = this._byCategory[category];
+
+ if (!catTable) {
+ return false;
+ } // Remove all layers. Make copy of keys to avoid mutating the collection
+ // behind the iterator you're accessing.
+
+
+ var stamps = [];
+
+ _jquery2["default"].each(catTable, function (k, v) {
+ stamps.push(k);
+ });
+
+ _jquery2["default"].each(stamps, function (i, stamp) {
+ _this4._removeLayer(stamp);
+ });
+ }
+ }, {
+ key: "getLayerGroup",
+ value: function getLayerGroup(group, ensureExists) {
+ var g = this._groupContainers[group];
+
+ if (ensureExists && !g) {
+ this._byGroup[group] = this._byGroup[group] || {};
+ g = this._groupContainers[group] = _leaflet2["default"].featureGroup();
+ g.groupname = group;
+ g.addTo(this._map);
+ }
+
+ return g;
+ }
+ }, {
+ key: "getGroupNameFromLayerGroup",
+ value: function getGroupNameFromLayerGroup(layerGroup) {
+ return layerGroup.groupname;
+ }
+ }, {
+ key: "getVisibleGroups",
+ value: function getVisibleGroups() {
+ var _this5 = this;
+
+ var result = [];
+
+ _jquery2["default"].each(this._groupContainers, function (k, v) {
+ if (_this5._map.hasLayer(v)) {
+ result.push(k);
+ }
+ });
+
+ return result;
+ }
+ }, {
+ key: "getAllGroupNames",
+ value: function getAllGroupNames() {
+ var result = [];
+
+ _jquery2["default"].each(this._groupContainers, function (k, v) {
+ result.push(k);
+ });
+
+ return result;
+ }
+ }, {
+ key: "clearGroup",
+ value: function clearGroup(group) {
+ var _this6 = this;
+
+ // Find all layers in _byGroup[group]
+ var groupTable = this._byGroup[group];
+
+ if (!groupTable) {
+ return false;
+ } // Remove all layers. Make copy of keys to avoid mutating the collection
+ // behind the iterator you're accessing.
+
+
+ var stamps = [];
+
+ _jquery2["default"].each(groupTable, function (k, v) {
+ stamps.push(k);
+ });
+
+ _jquery2["default"].each(stamps, function (i, stamp) {
+ _this6._removeLayer(stamp);
+ });
+ }
+ }, {
+ key: "clear",
+ value: function clear() {
+ function clearLayerGroup(key, layerGroup) {
+ layerGroup.clearLayers();
+ } // Clear all indices and layerGroups
+
+
+ this._byGroup = {};
+ this._byCategory = {};
+ this._byLayerId = {};
+ this._byStamp = {};
+ this._byCrosstalkGroup = {};
+
+ _jquery2["default"].each(this._categoryContainers, clearLayerGroup);
+
+ this._categoryContainers = {};
+
+ _jquery2["default"].each(this._groupContainers, clearLayerGroup);
+
+ this._groupContainers = {};
+ }
+ }, {
+ key: "_removeLayer",
+ value: function _removeLayer(layer) {
+ var stamp;
+
+ if (typeof layer === "string") {
+ stamp = layer;
+ } else {
+ stamp = _leaflet2["default"].Util.stamp(layer);
+ }
+
+ var layerInfo = this._byStamp[stamp];
+
+ if (!layerInfo) {
+ return false;
+ }
+
+ layerInfo.container.removeLayer(stamp);
+
+ if (typeof layerInfo.group === "string") {
+ delete this._byGroup[layerInfo.group][stamp];
+ }
+
+ if (typeof layerInfo.layerId === "string") {
+ delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)];
+ }
+
+ delete this._byCategory[layerInfo.category][stamp];
+ delete this._byStamp[stamp];
+
+ if (layerInfo.ctGroup) {
+ var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup];
+ var layersForKey = ctGroup[layerInfo.ctKey];
+ var idx = layersForKey ? layersForKey.indexOf(stamp) : -1;
+
+ if (idx >= 0) {
+ if (layersForKey.length === 1) {
+ delete ctGroup[layerInfo.ctKey];
+ } else {
+ layersForKey.splice(idx, 1);
+ }
+ }
+ }
+ }
+ }, {
+ key: "_layerIdKey",
+ value: function _layerIdKey(category, layerId) {
+ return category + "\n" + layerId;
+ }
+ }]);
+
+ return LayerManager;
+}();
+
+exports["default"] = LayerManager;
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){
+(function (global){(function (){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _jquery = require("./global/jquery");
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+var _leaflet = require("./global/leaflet");
+
+var _leaflet2 = _interopRequireDefault(_leaflet);
+
+var _shiny = require("./global/shiny");
+
+var _shiny2 = _interopRequireDefault(_shiny);
+
+var _htmlwidgets = require("./global/htmlwidgets");
+
+var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets);
+
+var _util = require("./util");
+
+var _crs_utils = require("./crs_utils");
+
+var _dataframe = require("./dataframe");
+
+var _dataframe2 = _interopRequireDefault(_dataframe);
+
+var _clusterLayerStore = require("./cluster-layer-store");
+
+var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore);
+
+var _mipmapper = require("./mipmapper");
+
+var _mipmapper2 = _interopRequireDefault(_mipmapper);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+var methods = {};
+exports["default"] = methods;
+
+function mouseHandler(mapId, layerId, group, eventName, extraInfo) {
+ return function (e) {
+ if (!_htmlwidgets2["default"].shinyMode) return;
+ var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng;
+
+ if (latLng) {
+ // retrieve only lat, lon values to remove prototype
+ // and extra parameters added by 3rd party modules
+ // these objects are for json serialization, not javascript
+ var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape
+
+
+ latLng = {
+ lat: latLngVal.lat,
+ lng: latLngVal.lng
+ };
+ }
+
+ var eventInfo = _jquery2["default"].extend({
+ id: layerId,
+ ".nonce": Math.random() // force reactivity
+
+ }, group !== null ? {
+ group: group
+ } : null, latLng, extraInfo);
+
+ _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo);
+ };
+}
+
+methods.mouseHandler = mouseHandler;
+
+methods.clearGroup = function (group) {
+ var _this = this;
+
+ _jquery2["default"].each((0, _util.asArray)(group), function (i, v) {
+ _this.layerManager.clearGroup(v);
+ });
+};
+
+methods.setView = function (center, zoom, options) {
+ this.setView(center, zoom, options);
+};
+
+methods.fitBounds = function (lat1, lng1, lat2, lng2, options) {
+ this.fitBounds([[lat1, lng1], [lat2, lng2]], options);
+};
+
+methods.flyTo = function (center, zoom, options) {
+ this.flyTo(center, zoom, options);
+};
+
+methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) {
+ this.flyToBounds([[lat1, lng1], [lat2, lng2]], options);
+};
+
+methods.setMaxBounds = function (lat1, lng1, lat2, lng2) {
+ this.setMaxBounds([[lat1, lng1], [lat2, lng2]]);
+};
+
+methods.addPopups = function (lat, lng, popup, layerId, group, options) {
+ var _this2 = this;
+
+ var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options);
+
+ var _loop = function _loop(i) {
+ if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) {
+ (function () {
+ var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup"));
+
+ var thisId = df.get(i, "layerId");
+ var thisGroup = df.get(i, "group");
+ this.layerManager.addLayer(popup, "popup", thisId, thisGroup);
+ }).call(_this2);
+ }
+ };
+
+ for (var i = 0; i < df.nrow(); i++) {
+ _loop(i);
+ }
+};
+
+methods.removePopup = function (layerId) {
+ this.layerManager.removeLayer("popup", layerId);
+};
+
+methods.clearPopups = function () {
+ this.layerManager.clearLayers("popup");
+};
+
+methods.addTiles = function (urlTemplate, layerId, group, options) {
+ this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group);
+};
+
+methods.removeTiles = function (layerId) {
+ this.layerManager.removeLayer("tile", layerId);
+};
+
+methods.clearTiles = function () {
+ this.layerManager.clearLayers("tile");
+};
+
+methods.addWMSTiles = function (baseUrl, layerId, group, options) {
+ if (options && options.crs) {
+ options.crs = (0, _crs_utils.getCRS)(options.crs);
+ }
+
+ this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group);
+}; // Given:
+// {data: ["a", "b", "c"], index: [0, 1, 0, 2]}
+// returns:
+// ["a", "b", "a", "c"]
+
+
+function unpackStrings(iconset) {
+ if (!iconset) {
+ return iconset;
+ }
+
+ if (typeof iconset.index === "undefined") {
+ return iconset;
+ }
+
+ iconset.data = (0, _util.asArray)(iconset.data);
+ iconset.index = (0, _util.asArray)(iconset.index);
+ return _jquery2["default"].map(iconset.index, function (e, i) {
+ return iconset.data[e];
+ });
+}
+
+function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) {
+ (function () {
+ var _this3 = this;
+
+ var clusterGroup = this.layerManager.getLayer("cluster", clusterId),
+ cluster = clusterOptions !== null;
+
+ if (cluster && !clusterGroup) {
+ clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions);
+
+ if (clusterOptions.freezeAtZoom) {
+ var freezeAtZoom = clusterOptions.freezeAtZoom;
+ delete clusterOptions.freezeAtZoom;
+ clusterGroup.freezeAtZoom(freezeAtZoom);
+ }
+
+ clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup);
+ }
+
+ var extraInfo = cluster ? {
+ clusterId: clusterId
+ } : {};
+
+ var _loop2 = function _loop2(i) {
+ if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) {
+ (function () {
+ var marker = markerFunc(df, i);
+ var thisId = df.get(i, "layerId");
+ var thisGroup = cluster ? null : df.get(i, "group");
+
+ if (cluster) {
+ clusterGroup.clusterLayerStore.add(marker, thisId);
+ } else {
+ this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true));
+ }
+
+ var popup = df.get(i, "popup");
+ var popupOptions = df.get(i, "popupOptions");
+
+ if (popup !== null) {
+ if (popupOptions !== null) {
+ marker.bindPopup(popup, popupOptions);
+ } else {
+ marker.bindPopup(popup);
+ }
+ }
+
+ var label = df.get(i, "label");
+ var labelOptions = df.get(i, "labelOptions");
+
+ if (label !== null) {
+ if (labelOptions !== null) {
+ if (labelOptions.permanent) {
+ marker.bindTooltip(label, labelOptions).openTooltip();
+ } else {
+ marker.bindTooltip(label, labelOptions);
+ }
+ } else {
+ marker.bindTooltip(label);
+ }
+ }
+
+ marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this);
+ marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this);
+ marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this);
+ marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this);
+ }).call(_this3);
+ }
+ };
+
+ for (var i = 0; i < df.nrow(); i++) {
+ _loop2(i);
+ }
+
+ if (cluster) {
+ this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group);
+ }
+ }).call(map);
+}
+
+methods.addGenericMarkers = addMarkers;
+
+methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) {
+ var icondf;
+ var getIcon;
+
+ if (icon) {
+ // Unpack icons
+ icon.iconUrl = unpackStrings(icon.iconUrl);
+ icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl);
+ icon.shadowUrl = unpackStrings(icon.shadowUrl);
+ icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all
+ // present on the icon object.
+
+ icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe.
+
+ getIcon = function getIcon(i) {
+ var opts = icondf.get(i);
+
+ if (!opts.iconUrl) {
+ return new _leaflet2["default"].Icon.Default();
+ } // Composite options (like points or sizes) are passed from R with each
+ // individual component as its own option. We need to combine them now
+ // into their composite form.
+
+
+ if (opts.iconWidth) {
+ opts.iconSize = [opts.iconWidth, opts.iconHeight];
+ }
+
+ if (opts.shadowWidth) {
+ opts.shadowSize = [opts.shadowWidth, opts.shadowHeight];
+ }
+
+ if (opts.iconAnchorX) {
+ opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY];
+ }
+
+ if (opts.shadowAnchorX) {
+ opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY];
+ }
+
+ if (opts.popupAnchorX) {
+ opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY];
+ }
+
+ return new _leaflet2["default"].Icon(opts);
+ };
+ }
+
+ if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) {
+ var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {});
+ if (icon) icondf.effectiveLength = df.nrow();
+ addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) {
+ var options = df.get(i);
+ if (icon) options.icon = getIcon(i);
+ return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options);
+ });
+ }
+};
+
+methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) {
+ var icondf;
+ var getIcon;
+
+ if (icon) {
+ // This cbinds the icon URLs and any other icon options; they're all
+ // present on the icon object.
+ icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe.
+
+ getIcon = function getIcon(i) {
+ var opts = icondf.get(i);
+
+ if (!opts) {
+ return new _leaflet2["default"].AwesomeMarkers.icon();
+ }
+
+ if (opts.squareMarker) {
+ opts.className = "awesome-marker awesome-marker-square";
+ }
+
+ return new _leaflet2["default"].AwesomeMarkers.icon(opts);
+ };
+ }
+
+ if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) {
+ var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {});
+ if (icon) icondf.effectiveLength = df.nrow();
+ addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) {
+ var options = df.get(i);
+ if (icon) options.icon = getIcon(i);
+ return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options);
+ });
+ }
+};
+
+function addLayers(map, category, df, layerFunc) {
+ var _loop3 = function _loop3(i) {
+ (function () {
+ var layer = layerFunc(df, i);
+
+ if (!_jquery2["default"].isEmptyObject(layer)) {
+ var thisId = df.get(i, "layerId");
+ var thisGroup = df.get(i, "group");
+ this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true));
+
+ if (layer.bindPopup) {
+ var popup = df.get(i, "popup");
+ var popupOptions = df.get(i, "popupOptions");
+
+ if (popup !== null) {
+ if (popupOptions !== null) {
+ layer.bindPopup(popup, popupOptions);
+ } else {
+ layer.bindPopup(popup);
+ }
+ }
+ }
+
+ if (layer.bindTooltip) {
+ var label = df.get(i, "label");
+ var labelOptions = df.get(i, "labelOptions");
+
+ if (label !== null) {
+ if (labelOptions !== null) {
+ layer.bindTooltip(label, labelOptions);
+ } else {
+ layer.bindTooltip(label);
+ }
+ }
+ }
+
+ layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this);
+ layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this);
+ layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this);
+ var highlightStyle = df.get(i, "highlightOptions");
+
+ if (!_jquery2["default"].isEmptyObject(highlightStyle)) {
+ var defaultStyle = {};
+
+ _jquery2["default"].each(highlightStyle, function (k, v) {
+ if (k != "bringToFront" && k != "sendToBack") {
+ if (df.get(i, k)) {
+ defaultStyle[k] = df.get(i, k);
+ }
+ }
+ });
+
+ layer.on("mouseover", function (e) {
+ this.setStyle(highlightStyle);
+
+ if (highlightStyle.bringToFront) {
+ this.bringToFront();
+ }
+ });
+ layer.on("mouseout", function (e) {
+ this.setStyle(defaultStyle);
+
+ if (highlightStyle.sendToBack) {
+ this.bringToBack();
+ }
+ });
+ }
+ }
+ }).call(map);
+ };
+
+ for (var i = 0; i < df.nrow(); i++) {
+ _loop3(i);
+ }
+}
+
+methods.addGenericLayers = addLayers;
+
+methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) {
+ if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) {
+ var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {});
+ addLayers(this, "shape", df, function (df, i) {
+ if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) {
+ return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i));
+ } else {
+ return null;
+ }
+ });
+ }
+};
+
+methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) {
+ if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) {
+ var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options);
+ addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) {
+ return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i));
+ });
+ }
+};
+/*
+ * @param lat Array of arrays of latitude coordinates for polylines
+ * @param lng Array of arrays of longitude coordinates for polylines
+ */
+
+
+methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) {
+ if (polygons.length > 0) {
+ var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options);
+ addLayers(this, "shape", df, function (df, i) {
+ var shapes = df.get(i, "shapes");
+ shapes = shapes.map(function (shape) {
+ return _htmlwidgets2["default"].dataframeToD3(shape[0]);
+ });
+
+ if (shapes.length > 1) {
+ return _leaflet2["default"].polyline(shapes, df.get(i));
+ } else {
+ return _leaflet2["default"].polyline(shapes[0], df.get(i));
+ }
+ });
+ }
+};
+
+methods.removeMarker = function (layerId) {
+ this.layerManager.removeLayer("marker", layerId);
+};
+
+methods.clearMarkers = function () {
+ this.layerManager.clearLayers("marker");
+};
+
+methods.removeMarkerCluster = function (layerId) {
+ this.layerManager.removeLayer("cluster", layerId);
+};
+
+methods.removeMarkerFromCluster = function (layerId, clusterId) {
+ var cluster = this.layerManager.getLayer("cluster", clusterId);
+ if (!cluster) return;
+ cluster.clusterLayerStore.remove(layerId);
+};
+
+methods.clearMarkerClusters = function () {
+ this.layerManager.clearLayers("cluster");
+};
+
+methods.removeShape = function (layerId) {
+ this.layerManager.removeLayer("shape", layerId);
+};
+
+methods.clearShapes = function () {
+ this.layerManager.clearLayers("shape");
+};
+
+methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) {
+ var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options);
+ addLayers(this, "shape", df, function (df, i) {
+ if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) {
+ return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i));
+ } else {
+ return null;
+ }
+ });
+};
+/*
+ * @param lat Array of arrays of latitude coordinates for polygons
+ * @param lng Array of arrays of longitude coordinates for polygons
+ */
+
+
+methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) {
+ if (polygons.length > 0) {
+ var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options);
+ addLayers(this, "shape", df, function (df, i) {
+ // This code used to use L.multiPolygon, but that caused
+ // double-click on a multipolygon to fail to zoom in on the
+ // map. Surprisingly, putting all the rings in a single
+ // polygon seems to still work; complicated multipolygons
+ // are still rendered correctly.
+ var shapes = df.get(i, "shapes").map(function (polygon) {
+ return polygon.map(_htmlwidgets2["default"].dataframeToD3);
+ }).reduce(function (acc, val) {
+ return acc.concat(val);
+ }, []);
+ return _leaflet2["default"].polygon(shapes, df.get(i));
+ });
+ }
+};
+
+methods.addGeoJSON = function (data, layerId, group, style) {
+ // This time, self is actually needed because the callbacks below need
+ // to access both the inner and outer senses of "this"
+ var self = this;
+
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+
+ var globalStyle = _jquery2["default"].extend({}, style, data.style || {});
+
+ var gjlayer = _leaflet2["default"].geoJson(data, {
+ style: function style(feature) {
+ if (feature.style || feature.properties.style) {
+ return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style);
+ } else {
+ return globalStyle;
+ }
+ },
+ onEachFeature: function onEachFeature(feature, layer) {
+ var extraInfo = {
+ featureId: feature.id,
+ properties: feature.properties
+ };
+ var popup = feature.properties ? feature.properties.popup : null;
+ if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup);
+ layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this);
+ layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this);
+ layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this);
+ }
+ });
+
+ this.layerManager.addLayer(gjlayer, "geojson", layerId, group);
+};
+
+methods.removeGeoJSON = function (layerId) {
+ this.layerManager.removeLayer("geojson", layerId);
+};
+
+methods.clearGeoJSON = function () {
+ this.layerManager.clearLayers("geojson");
+};
+
+methods.addTopoJSON = function (data, layerId, group, style) {
+ // This time, self is actually needed because the callbacks below need
+ // to access both the inner and outer senses of "this"
+ var self = this;
+
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+
+ var globalStyle = _jquery2["default"].extend({}, style, data.style || {});
+
+ var gjlayer = _leaflet2["default"].geoJson(null, {
+ style: function style(feature) {
+ if (feature.style || feature.properties.style) {
+ return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style);
+ } else {
+ return globalStyle;
+ }
+ },
+ onEachFeature: function onEachFeature(feature, layer) {
+ var extraInfo = {
+ featureId: feature.id,
+ properties: feature.properties
+ };
+ var popup = feature.properties.popup;
+ if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup);
+ layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this);
+ layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this);
+ layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this);
+ }
+ });
+
+ global.omnivore.topojson.parse(data, null, gjlayer);
+ this.layerManager.addLayer(gjlayer, "topojson", layerId, group);
+};
+
+methods.removeTopoJSON = function (layerId) {
+ this.layerManager.removeLayer("topojson", layerId);
+};
+
+methods.clearTopoJSON = function () {
+ this.layerManager.clearLayers("topojson");
+};
+
+methods.addControl = function (html, position, layerId, classes) {
+ function onAdd(map) {
+ var div = _leaflet2["default"].DomUtil.create("div", classes);
+
+ if (typeof layerId !== "undefined" && layerId !== null) {
+ div.setAttribute("id", layerId);
+ }
+
+ this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to
+ // not be, when a static leaflet widget is included as part of the shiny
+ // UI directly (not through leafletOutput or uiOutput). In this case we
+ // don't do the normal Shiny stuff as that will all happen when Shiny
+ // itself loads and binds the entire doc.
+
+ if (window.Shiny && _shiny2["default"].initializeInputs) {
+ _shiny2["default"].renderHtml(html, this._div);
+
+ _shiny2["default"].initializeInputs(this._div);
+
+ _shiny2["default"].bindAll(this._div);
+ } else {
+ this._div.innerHTML = html;
+ }
+
+ return this._div;
+ }
+
+ function onRemove(map) {
+ if (window.Shiny && _shiny2["default"].unbindAll) {
+ _shiny2["default"].unbindAll(this._div);
+ }
+ }
+
+ var Control = _leaflet2["default"].Control.extend({
+ options: {
+ position: position
+ },
+ onAdd: onAdd,
+ onRemove: onRemove
+ });
+
+ this.controls.add(new Control(), layerId, html);
+};
+
+methods.addCustomControl = function (control, layerId) {
+ this.controls.add(control, layerId);
+};
+
+methods.removeControl = function (layerId) {
+ this.controls.remove(layerId);
+};
+
+methods.getControl = function (layerId) {
+ this.controls.get(layerId);
+};
+
+methods.clearControls = function () {
+ this.controls.clear();
+};
+
+methods.addLegend = function (options) {
+ var legend = _leaflet2["default"].control({
+ position: options.position
+ });
+
+ var gradSpan;
+
+ legend.onAdd = function (map) {
+ var div = _leaflet2["default"].DomUtil.create("div", options.className),
+ colors = options.colors,
+ labels = options.labels,
+ legendHTML = "";
+
+ if (options.type === "numeric") {
+ // # Formatting constants.
+ var singleBinHeight = 20; // The distance between tick marks, in px
+
+ var vMargin = 8; // If 1st tick mark starts at top of gradient, how
+ // many extra px are needed for the top half of the
+ // 1st label? (ditto for last tick mark/label)
+
+ var tickWidth = 4; // How wide should tick marks be, in px?
+
+ var labelPadding = 6; // How much distance to reserve for tick mark?
+ // (Must be >= tickWidth)
+ // # Derived formatting parameters.
+ // What's the height of a single bin, in percentage (of gradient height)?
+ // It might not just be 1/(n-1), if the gradient extends past the tick
+ // marks (which can be the case for pretty cut points).
+
+ var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient?
+
+ var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top
+ // of the gradient?
+
+ var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1;
+ gradSpan = (0, _jquery2["default"])(" ").css({
+ "background": "linear-gradient(" + colors + ")",
+ "opacity": options.opacity,
+ "height": totalHeight + "px",
+ "width": "18px",
+ "display": "block",
+ "margin-top": vMargin + "px"
+ });
+ var leftDiv = (0, _jquery2["default"])("
").css("float", "left"),
+ rightDiv = (0, _jquery2["default"])("
").css("float", "left");
+ leftDiv.append(gradSpan);
+ (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])(" ")); // Have to attach the div to the body at this early point, so that the
+ // svg text getComputedTextLength() actually works, below.
+
+ document.body.appendChild(div);
+ var ns = "http://www.w3.org/2000/svg";
+ var svg = document.createElementNS(ns, "svg");
+ rightDiv.append(svg);
+ var g = document.createElementNS(ns, "g");
+ (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")");
+ svg.appendChild(g); // max label width needed to set width of svg, and right-justify text
+
+ var maxLblWidth = 0; // Create tick marks and labels
+
+ _jquery2["default"].each(labels, function (i, label) {
+ var y = tickOffset + i * singleBinHeight + 0.5;
+ var thisLabel = document.createElementNS(ns, "text");
+ (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex");
+ g.appendChild(thisLabel);
+ maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength());
+ var thisTick = document.createElementNS(ns, "line");
+ (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1);
+ g.appendChild(thisTick);
+ }); // Now that we know the max label width, we can right-justify
+
+
+ (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for
+
+ (0, _jquery2["default"])(svg).css({
+ width: maxLblWidth + labelPadding + "px",
+ height: totalHeight + vMargin * 2 + "px"
+ });
+
+ if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) {
+ (0, _jquery2["default"])(div).append(" " + options.na_label + "
");
+ }
+ } else {
+ if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) {
+ colors.push(options.na_color);
+ labels.push(options.na_label);
+ }
+
+ for (var i = 0; i < colors.length; i++) {
+ legendHTML += " " + labels[i] + " ";
+ }
+
+ div.innerHTML = legendHTML;
+ }
+
+ if (options.title) (0, _jquery2["default"])(div).prepend("" + options.title + "
");
+ return div;
+ };
+
+ if (options.group) {
+ // Auto generate a layerID if not provided
+ if (!options.layerId) {
+ options.layerId = _leaflet2["default"].Util.stamp(legend);
+ }
+
+ var map = this;
+ map.on("overlayadd", function (e) {
+ if (e.name === options.group) {
+ map.controls.add(legend, options.layerId);
+ }
+ });
+ map.on("overlayremove", function (e) {
+ if (e.name === options.group) {
+ map.controls.remove(options.layerId);
+ }
+ });
+ map.on("groupadd", function (e) {
+ if (e.name === options.group) {
+ map.controls.add(legend, options.layerId);
+ }
+ });
+ map.on("groupremove", function (e) {
+ if (e.name === options.group) {
+ map.controls.remove(options.layerId);
+ }
+ });
+ }
+
+ this.controls.add(legend, options.layerId);
+};
+
+methods.addLayersControl = function (baseGroups, overlayGroups, options) {
+ var _this4 = this;
+
+ // Only allow one layers control at a time
+ methods.removeLayersControl.call(this);
+ var firstLayer = true;
+ var base = {};
+
+ _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) {
+ var layer = _this4.layerManager.getLayerGroup(g, true);
+
+ if (layer) {
+ base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one
+
+ if (_this4.hasLayer(layer)) {
+ if (firstLayer) {
+ firstLayer = false;
+ } else {
+ _this4.removeLayer(layer);
+ }
+ }
+ }
+ });
+
+ var overlay = {};
+
+ _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) {
+ var layer = _this4.layerManager.getLayerGroup(g, true);
+
+ if (layer) {
+ overlay[g] = layer;
+ }
+ });
+
+ this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options);
+ this.addControl(this.currentLayersControl);
+};
+
+methods.removeLayersControl = function () {
+ if (this.currentLayersControl) {
+ this.removeControl(this.currentLayersControl);
+ this.currentLayersControl = null;
+ }
+};
+
+methods.addScaleBar = function (options) {
+ // Only allow one scale bar at a time
+ methods.removeScaleBar.call(this);
+
+ var scaleBar = _leaflet2["default"].control.scale(options).addTo(this);
+
+ this.currentScaleBar = scaleBar;
+};
+
+methods.removeScaleBar = function () {
+ if (this.currentScaleBar) {
+ this.currentScaleBar.remove();
+ this.currentScaleBar = null;
+ }
+};
+
+methods.hideGroup = function (group) {
+ var _this5 = this;
+
+ _jquery2["default"].each((0, _util.asArray)(group), function (i, g) {
+ var layer = _this5.layerManager.getLayerGroup(g, true);
+
+ if (layer) {
+ _this5.removeLayer(layer);
+ }
+ });
+};
+
+methods.showGroup = function (group) {
+ var _this6 = this;
+
+ _jquery2["default"].each((0, _util.asArray)(group), function (i, g) {
+ var layer = _this6.layerManager.getLayerGroup(g, true);
+
+ if (layer) {
+ _this6.addLayer(layer);
+ }
+ });
+};
+
+function setupShowHideGroupsOnZoom(map) {
+ if (map.leafletr._hasInitializedShowHideGroups) {
+ return;
+ }
+
+ map.leafletr._hasInitializedShowHideGroups = true;
+
+ function setVisibility(layer, visible, group) {
+ if (visible !== map.hasLayer(layer)) {
+ if (visible) {
+ map.addLayer(layer);
+ map.fire("groupadd", {
+ "name": group,
+ "layer": layer
+ });
+ } else {
+ map.removeLayer(layer);
+ map.fire("groupremove", {
+ "name": group,
+ "layer": layer
+ });
+ }
+ }
+ }
+
+ function showHideGroupsOnZoom() {
+ if (!map.layerManager) return;
+ var zoom = map.getZoom();
+ map.layerManager.getAllGroupNames().forEach(function (group) {
+ var layer = map.layerManager.getLayerGroup(group, false);
+
+ if (layer && typeof layer.zoomLevels !== "undefined") {
+ setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group);
+ }
+ });
+ }
+
+ map.showHideGroupsOnZoom = showHideGroupsOnZoom;
+ map.on("zoomend", showHideGroupsOnZoom);
+}
+
+methods.setGroupOptions = function (group, options) {
+ var _this7 = this;
+
+ _jquery2["default"].each((0, _util.asArray)(group), function (i, g) {
+ var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels
+
+
+ if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) {
+ layer.zoomLevels = (0, _util.asArray)(options.zoomLevels);
+ }
+ });
+
+ setupShowHideGroupsOnZoom(this);
+ this.showHideGroupsOnZoom();
+};
+
+methods.addRasterImage = function (uri, bounds, opacity, attribution, layerId, group) {
+ // uri is a data URI containing an image. We want to paint this image as a
+ // layer at (top-left) bounds[0] to (bottom-right) bounds[1].
+ // We can't simply use ImageOverlay, as it uses bilinear scaling which looks
+ // awful as you zoom in (and sometimes shifts positions or disappears).
+ // Instead, we'll use a TileLayer.Canvas to draw pieces of the image.
+ // First, some helper functions.
+ // degree2tile converts latitude, longitude, and zoom to x and y tile
+ // numbers. The tile numbers returned can be non-integral, as there's no
+ // reason to expect that the lat/lng inputs are exactly on the border of two
+ // tiles.
+ //
+ // We'll use this to convert the bounds we got from the server, into coords
+ // in tile-space at a given zoom level. Note that once we do the conversion,
+ // we don't to do any more trigonometry to convert between pixel coordinates
+ // and tile coordinates; the source image pixel coords, destination canvas
+ // pixel coords, and tile coords all can be scaled linearly.
+ function degree2tile(lat, lng, zoom) {
+ // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
+ var latRad = lat * Math.PI / 180;
+ var n = Math.pow(2, zoom);
+ var x = (lng + 180) / 360 * n;
+ var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n;
+ return {
+ x: x,
+ y: y
+ };
+ } // Given a range [from,to) and either one or two numbers, returns true if
+ // there is any overlap between [x,x1) and the range--or if x1 is omitted,
+ // then returns true if x is within [from,to).
+
+
+ function overlap(from, to, x,
+ /* optional */
+ x1) {
+ if (arguments.length == 3) x1 = x;
+ return x < to && x1 >= from;
+ }
+
+ function getCanvasSmoothingProperty(ctx) {
+ var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"];
+
+ for (var i = 0; i < candidates.length; i++) {
+ if (typeof ctx[candidates[i]] !== "undefined") {
+ return candidates[i];
+ }
+ }
+
+ return null;
+ } // Our general strategy is to:
+ // 1. Load the data URI in an Image() object, so we can get its pixel
+ // dimensions and the underlying image data. (We could have done this
+ // by not encoding as PNG at all but just send an array of RGBA values
+ // from the server, but that would inflate the JSON too much.)
+ // 2. Create a hidden canvas that we use just to extract the image data
+ // from the Image (using Context2D.getImageData()).
+ // 3. Create a TileLayer.Canvas and add it to the map.
+ // We want to synchronously create and attach the TileLayer.Canvas (so an
+ // immediate call to clearRasters() will be respected, for example), but
+ // Image loads its data asynchronously. Fortunately we can resolve this
+ // by putting TileLayer.Canvas into async mode, which will let us create
+ // and attach the layer but have it wait until the image is loaded before
+ // it actually draws anything.
+ // These are the variables that we will populate once the image is loaded.
+
+
+ var imgData = null; // 1d row-major array, four [0-255] integers per pixel
+
+ var imgDataMipMapper = null;
+ var w = null; // image width in pixels
+
+ var h = null; // image height in pixels
+ // We'll use this array to store callbacks that need to be invoked once
+ // imgData, w, and h have been resolved.
+
+ var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data
+ // is available.
+
+ function getImageData(callback) {
+ if (imgData != null) {
+ // Must not invoke the callback immediately; it's too confusing and
+ // fragile to have a function invoke the callback *either* immediately
+ // or in the future. Better to be consistent here.
+ setTimeout(function () {
+ callback(imgData, w, h, imgDataMipMapper);
+ }, 0);
+ } else {
+ imgDataCallbacks.push(callback);
+ }
+ }
+
+ var img = new Image();
+
+ img.onload = function () {
+ // Save size
+ w = img.width;
+ h = img.height; // Create a dummy canvas to extract the image data
+
+ var imgDataCanvas = document.createElement("canvas");
+ imgDataCanvas.width = w;
+ imgDataCanvas.height = h;
+ imgDataCanvas.style.display = "none";
+ document.body.appendChild(imgDataCanvas);
+ var imgDataCtx = imgDataCanvas.getContext("2d");
+ imgDataCtx.drawImage(img, 0, 0); // Save the image data.
+
+ imgData = imgDataCtx.getImageData(0, 0, w, h).data;
+ imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd.
+
+ document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting.
+
+ for (var i = 0; i < imgDataCallbacks.length; i++) {
+ imgDataCallbacks[i](imgData, w, h, imgDataMipMapper);
+ }
+
+ imgDataCallbacks = [];
+ };
+
+ img.src = uri;
+
+ var canvasTiles = _leaflet2["default"].gridLayer({
+ opacity: opacity,
+ attribution: attribution,
+ detectRetina: true,
+ async: true
+ }); // NOTE: The done() function MUST NOT be invoked until after the current
+ // tick; done() looks in Leaflet's tile cache for the current tile, and
+ // since it's still being constructed, it won't be found.
+
+
+ canvasTiles.createTile = function (tilePoint, done) {
+ var zoom = tilePoint.z;
+
+ var canvas = _leaflet2["default"].DomUtil.create("canvas");
+
+ var error; // setup tile width and height according to the options
+
+ var size = this.getTileSize();
+ canvas.width = size.x;
+ canvas.height = size.y;
+ getImageData(function (imgData, w, h, mipmapper) {
+ try {
+ // The Context2D we'll being drawing onto. It's always 256x256.
+ var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into
+ // x/y tile coordinates. This is essentially doing a spherical mercator
+ // projection, then multiplying by 2^zoom.
+
+ var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom);
+ var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates.
+
+ var extent = {
+ x: bottomRight.x - topLeft.x,
+ y: bottomRight.y - topLeft.y
+ }; // Short circuit if tile is totally disjoint from image.
+
+ if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return;
+ if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit.
+ // If the linear resolution (in either direction) of the image is less than 256px
+ // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in
+ // scaling.
+
+ var imgRes = {
+ x: w / extent.x,
+ y: h / extent.y
+ }; // We can do the actual drawing in one of three ways:
+ // - Call drawImage(). This is easy and fast, and results in smooth
+ // interpolation (bilinear?). This is what we want when we are
+ // reducing the image from its native size.
+ // - Call drawImage() with imageSmoothingEnabled=false. This is easy
+ // and fast and gives us nearest-neighbor interpolation, which is what
+ // we want when enlarging the image. However, it's unsupported on many
+ // browsers (including QtWebkit).
+ // - Do a manual nearest-neighbor interpolation. This is what we'll fall
+ // back to when enlarging, and imageSmoothingEnabled isn't supported.
+ // In theory it's slower, but still pretty fast on my machine, and the
+ // results look the same AFAICT.
+ // Is imageSmoothingEnabled supported? If so, we can let canvas do
+ // nearest-neighbor interpolation for us.
+
+ var smoothingProperty = getCanvasSmoothingProperty(ctx);
+
+ if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) {
+ // Use built-in scaling
+ // Turn off anti-aliasing if necessary
+ if (smoothingProperty) {
+ ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256;
+ } // Don't necessarily draw with the full-size image; if we're
+ // downscaling, use the mipmapper to get a pre-downscaled image
+ // (see comments on Mipmapper class for why this matters).
+
+
+ mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) {
+ // It's possible that the image will go off the edge of the canvas--
+ // that's OK, the canvas should clip appropriately.
+ ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert
+ // to rel pixel coords
+ (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just
+ // convert from size in tile coords straight to pixels
+ extent.x * 256, extent.y * 256);
+ });
+ } else {
+ // Use manual nearest-neighbor interpolation
+ // Calculate the source image pixel coordinates that correspond with
+ // the top-left and bottom-right of this tile. (If the source image
+ // only partially overlaps the tile, we use max/min to limit the
+ // sourceStart/End to only reflect the overlapping portion.)
+ var sourceStart = {
+ x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)),
+ y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y))
+ };
+ var sourceEnd = {
+ x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)),
+ y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y))
+ }; // The size, in dest pixels, that each source pixel should occupy.
+ // This might be greater or less than 1 (e.g. if x and y resolution
+ // are very different).
+
+ var pixelSize = {
+ x: 256 / imgRes.x,
+ y: 256 / imgRes.y
+ }; // For each pixel in the source image that overlaps the tile...
+
+ for (var row = sourceStart.y; row < sourceEnd.y; row++) {
+ for (var col = sourceStart.x; col < sourceEnd.x; col++) {
+ // ...extract the pixel data...
+ var i = (row * w + col) * 4;
+ var r = imgData[i];
+ var g = imgData[i + 1];
+ var b = imgData[i + 2];
+ var a = imgData[i + 3];
+ ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image
+ // where it should be drawn...
+
+ var pixelPos = {
+ x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256,
+ y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256
+ }; // ...and draw a rectangle there.
+
+ ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from
+ // causing overlap between this rect and its neighbors. The
+ // minuend is the location of the next pixel, while the
+ // subtrahend is the position of the current pixel (to turn an
+ // absolute coordinate to a width/height). Yes, I had to look
+ // up minuend and subtrahend.
+ Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y));
+ }
+ }
+ }
+ } catch (e) {
+ error = e;
+ } finally {
+ done(error, canvas);
+ }
+ });
+ return canvas;
+ };
+
+ this.layerManager.addLayer(canvasTiles, "image", layerId, group);
+};
+
+methods.removeImage = function (layerId) {
+ this.layerManager.removeLayer("image", layerId);
+};
+
+methods.clearImages = function () {
+ this.layerManager.clearLayers("image");
+};
+
+methods.addMeasure = function (options) {
+ // if a measureControl already exists, then remove it and
+ // replace with a new one
+ methods.removeMeasure.call(this);
+ this.measureControl = _leaflet2["default"].control.measure(options);
+ this.addControl(this.measureControl);
+};
+
+methods.removeMeasure = function () {
+ if (this.measureControl) {
+ this.removeControl(this.measureControl);
+ this.measureControl = null;
+ }
+};
+
+methods.addSelect = function (ctGroup) {
+ var _this8 = this;
+
+ methods.removeSelect.call(this);
+ this._selectButton = _leaflet2["default"].easyButton({
+ states: [{
+ stateName: "select-inactive",
+ icon: "ion-qr-scanner",
+ title: "Make a selection",
+ onClick: function onClick(btn, map) {
+ btn.state("select-active");
+ _this8._locationFilter = new _leaflet2["default"].LocationFilter2();
+
+ if (ctGroup) {
+ var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup);
+ selectionHandle.on("change", function (e) {
+ if (e.sender !== selectionHandle) {
+ if (_this8._locationFilter) {
+ _this8._locationFilter.disable();
+
+ btn.state("select-inactive");
+ }
+ }
+ });
+
+ var handler = function handler(e) {
+ _this8.layerManager.brush(_this8._locationFilter.getBounds(), {
+ sender: selectionHandle
+ });
+ };
+
+ _this8._locationFilter.on("enabled", handler);
+
+ _this8._locationFilter.on("change", handler);
+
+ _this8._locationFilter.on("disabled", function () {
+ selectionHandle.close();
+ _this8._locationFilter = null;
+ });
+ }
+
+ _this8._locationFilter.addTo(map);
+ }
+ }, {
+ stateName: "select-active",
+ icon: "ion-close-round",
+ title: "Dismiss selection",
+ onClick: function onClick(btn, map) {
+ btn.state("select-inactive");
+
+ _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections
+
+
+ _this8.layerManager.unbrush();
+ }
+ }]
+ });
+
+ this._selectButton.addTo(this);
+};
+
+methods.removeSelect = function () {
+ if (this._locationFilter) {
+ this._locationFilter.disable();
+ }
+
+ if (this._selectButton) {
+ this.removeControl(this._selectButton);
+ this._selectButton = null;
+ }
+};
+
+methods.createMapPane = function (name, zIndex) {
+ this.createPane(name);
+ this.getPane(name).style.zIndex = zIndex;
+};
+
+
+}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+// This class simulates a mipmap, which shrinks images by powers of two. This
+// stepwise reduction results in "pixel-perfect downscaling" (where every
+// pixel of the original image has some contribution to the downscaled image)
+// as opposed to a single-step downscaling which will discard a lot of data
+// (and with sparse images at small scales can give very surprising results).
+var Mipmapper = /*#__PURE__*/function () {
+ function Mipmapper(img) {
+ _classCallCheck(this, Mipmapper);
+
+ this._layers = [img];
+ } // The various functions on this class take a callback function BUT MAY OR MAY
+ // NOT actually behave asynchronously.
+
+
+ _createClass(Mipmapper, [{
+ key: "getBySize",
+ value: function getBySize(desiredWidth, desiredHeight, callback) {
+ var _this = this;
+
+ var i = 0;
+ var lastImg = this._layers[0];
+
+ var testNext = function testNext() {
+ _this.getByIndex(i, function (img) {
+ // If current image is invalid (i.e. too small to be rendered) or
+ // it's smaller than what we wanted, return the last known good image.
+ if (!img || img.width < desiredWidth || img.height < desiredHeight) {
+ callback(lastImg);
+ return;
+ } else {
+ lastImg = img;
+ i++;
+ testNext();
+ return;
+ }
+ });
+ };
+
+ testNext();
+ }
+ }, {
+ key: "getByIndex",
+ value: function getByIndex(i, callback) {
+ var _this2 = this;
+
+ if (this._layers[i]) {
+ callback(this._layers[i]);
+ return;
+ }
+
+ this.getByIndex(i - 1, function (prevImg) {
+ if (!prevImg) {
+ // prevImg could not be calculated (too small, possibly)
+ callback(null);
+ return;
+ }
+
+ if (prevImg.width < 2 || prevImg.height < 2) {
+ // Can't reduce this image any further
+ callback(null);
+ return;
+ } // If reduce ever becomes truly asynchronous, we should stuff a promise or
+ // something into this._layers[i] before calling this.reduce(), to prevent
+ // redundant reduce operations from happening.
+
+
+ _this2.reduce(prevImg, function (reducedImg) {
+ _this2._layers[i] = reducedImg;
+ callback(reducedImg);
+ return;
+ });
+ });
+ }
+ }, {
+ key: "reduce",
+ value: function reduce(img, callback) {
+ var imgDataCanvas = document.createElement("canvas");
+ imgDataCanvas.width = Math.ceil(img.width / 2);
+ imgDataCanvas.height = Math.ceil(img.height / 2);
+ imgDataCanvas.style.display = "none";
+ document.body.appendChild(imgDataCanvas);
+
+ try {
+ var imgDataCtx = imgDataCanvas.getContext("2d");
+ imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2);
+ callback(imgDataCanvas);
+ } finally {
+ document.body.removeChild(imgDataCanvas);
+ }
+ }
+ }]);
+
+ return Mipmapper;
+}();
+
+exports["default"] = Mipmapper;
+
+
+},{}],17:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.log = log;
+exports.recycle = recycle;
+exports.asArray = asArray;
+
+function log(message) {
+ /* eslint-disable no-console */
+ if (console && console.log) console.log(message);
+ /* eslint-enable no-console */
+}
+
+function recycle(values, length, inPlace) {
+ if (length === 0 && !inPlace) return [];
+
+ if (!(values instanceof Array)) {
+ if (inPlace) {
+ throw new Error("Can't do in-place recycling of a non-Array value");
+ }
+
+ values = [values];
+ }
+
+ if (typeof length === "undefined") length = values.length;
+ var dest = inPlace ? values : [];
+ var origLength = values.length;
+
+ while (dest.length < length) {
+ dest.push(values[dest.length % origLength]);
+ }
+
+ if (dest.length > length) {
+ dest.splice(length, dest.length - length);
+ }
+
+ return dest;
+}
+
+function asArray(value) {
+ if (value instanceof Array) return value;else return [value];
+}
+
+
+},{}]},{},[13]);
diff --git a/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-providers-plugin-2.2.1/leaflet-providers-plugin.js b/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-providers-plugin-2.2.1/leaflet-providers-plugin.js
new file mode 100644
index 0000000..82cd630
--- /dev/null
+++ b/notebooks/nighttime-lights/ntl_analysis_files/libs/leaflet-providers-plugin-2.2.1/leaflet-providers-plugin.js
@@ -0,0 +1,3 @@
+LeafletWidget.methods.addProviderTiles = function(provider, layerId, group, options) {
+ this.layerManager.addLayer(L.tileLayer.provider(provider, options), "tile", layerId, group);
+};