diff --git a/data/engwales.dbf b/data/engwales.dbf new file mode 100644 index 00000000..21ed5ec0 Binary files /dev/null and b/data/engwales.dbf differ diff --git a/data/engwales.prj b/data/engwales.prj new file mode 100644 index 00000000..fec0ee28 --- /dev/null +++ b/data/engwales.prj @@ -0,0 +1 @@ +PROJCS["British_National_Grid",GEOGCS["GCS_OSGB_1936",DATUM["D_OSGB_1936",SPHEROID["Airy_1830",6377563.396,299.3249646]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",400000.0],PARAMETER["False_Northing",-100000.0],PARAMETER["Central_Meridian",-2.0],PARAMETER["Scale_Factor",0.9996012717],PARAMETER["Latitude_Of_Origin",49.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/data/engwales.shp b/data/engwales.shp new file mode 100644 index 00000000..5585a88f Binary files /dev/null and b/data/engwales.shp differ diff --git a/data/engwales.shx b/data/engwales.shx new file mode 100644 index 00000000..38129305 Binary files /dev/null and b/data/engwales.shx differ diff --git a/data/latest_df.for.plotting.incidence.ltlas.RData b/data/latest_df.for.plotting.incidence.ltlas.RData index 70eb553e..816481a8 100644 Binary files a/data/latest_df.for.plotting.incidence.ltlas.RData and b/data/latest_df.for.plotting.incidence.ltlas.RData differ diff --git a/data/latest_projected.cases.ltlas.RData b/data/latest_projected.cases.ltlas.RData index 4a7dd6f7..5dcd3066 100644 Binary files a/data/latest_projected.cases.ltlas.RData and b/data/latest_projected.cases.ltlas.RData differ diff --git a/data/population_by_region.csv b/data/population_by_region.csv index c4324e84..b8a1b19b 100644 --- a/data/population_by_region.csv +++ b/data/population_by_region.csv @@ -427,4 +427,5 @@ Mid Ulster,Local Government District,148528 "Newry, Mourne and Down",Local Government District,181368 Aylesbury Vale,Not sure,199448 South Bucks,Not sure,70043 -Wycombe,Not sure,174641 \ No newline at end of file +Wycombe,Not sure,174641 +Chiltern,Not sure,95927 diff --git a/global.R b/global.R index 17ec2393..59811b8c 100644 --- a/global.R +++ b/global.R @@ -12,6 +12,9 @@ library(data.table) # for cases by age library(stringr) # for cases by age library(stringi) # for cases by age library(viridis) +library(leaflet) # for maps +library(sf) # for maps +library(adegenet) # for colouring maps # library(httr) # for accessing latest data; needed this when updates were done within the app but don't need it whilst running "prepping_the_data.R" manually # options(shiny.trace = TRUE) # options(shiny.trace = FALSE) @@ -21,6 +24,9 @@ bs <- 24 # base font size for plots source("prep/daily_tracker_setup.R") # load data and plotting scripts for daily tracker tab source("prep/cases_by_age_setup.R") # get plotting script for cases by age tab +engwalesmap <- st_read("data/engwales.shp") # load shapefile for maps +mapcounter <- 1 + # load synthetic control analysis for (i in 1:8) { tmp <- read.csv(glue("data/sc_dataset_scenario_{i}.csv")) diff --git a/markdown/about.Rmd b/markdown/about.Rmd index 9ae13e58..5c2c5fa1 100644 --- a/markdown/about.Rmd +++ b/markdown/about.Rmd @@ -19,6 +19,8 @@ The tabs "March-June Pillar 1 tracker" and "March-June Pillar 1 synthetic contro The "Daily tracker" tab which is updated daily builds upon our published analysis, using a shorter waiting time between infection and swab date to reflect shorter times to Pillar 2 testing. +The "Map tracker" tab presents the same results as the "Daily tracker" but visualised using maps. We thank Will Probert for providing shapefiles and code towards this. + The "Cases by age" tab presents an analysis developed by Robert Hinch and Michelle Kendall and is also updated daily. The source code is available on [GitHub](https://github.com/BDI-pathogens/LocalCovidTracker). @@ -26,7 +28,7 @@ Please let us know of any issues or requests via the [Issues](https://github.com
-[1] Michelle Kendall, Luke Milsom, Lucie Abeler-Dörner, Chris Wymant, Luca Ferretti, Mark Briers, Chris Holmes, David Bonsall, Johannes Abeler, Christophe Fraser. Epidemiological changes on the Isle of Wight after the launch of the NHS Test and Trace programme: a preliminary analysis, The Lancet Digital Health, published online October 14, 2020. +[1] Michelle Kendall, Luke Milsom, Lucie Abeler-Dörner, Chris Wymant, Luca Ferretti, Mark Briers, Chris Holmes, David Bonsall, Johannes Abeler, Christophe Fraser. Epidemiological changes on the Isle of Wight after the launch of the NHS Test and Trace programme: a preliminary analysis, The Lancet Digital Health, Volume 2, Issue 12, E658-E666, December 01, 2020. diff --git a/markdown/about.md b/markdown/about.md index 9ae13e58..5c2c5fa1 100644 --- a/markdown/about.md +++ b/markdown/about.md @@ -19,6 +19,8 @@ The tabs "March-June Pillar 1 tracker" and "March-June Pillar 1 synthetic contro The "Daily tracker" tab which is updated daily builds upon our published analysis, using a shorter waiting time between infection and swab date to reflect shorter times to Pillar 2 testing. +The "Map tracker" tab presents the same results as the "Daily tracker" but visualised using maps. We thank Will Probert for providing shapefiles and code towards this. + The "Cases by age" tab presents an analysis developed by Robert Hinch and Michelle Kendall and is also updated daily. The source code is available on [GitHub](https://github.com/BDI-pathogens/LocalCovidTracker). @@ -26,7 +28,7 @@ Please let us know of any issues or requests via the [Issues](https://github.com
-[1] Michelle Kendall, Luke Milsom, Lucie Abeler-Dörner, Chris Wymant, Luca Ferretti, Mark Briers, Chris Holmes, David Bonsall, Johannes Abeler, Christophe Fraser. Epidemiological changes on the Isle of Wight after the launch of the NHS Test and Trace programme: a preliminary analysis, The Lancet Digital Health, published online October 14, 2020. +[1] Michelle Kendall, Luke Milsom, Lucie Abeler-Dörner, Chris Wymant, Luca Ferretti, Mark Briers, Chris Holmes, David Bonsall, Johannes Abeler, Christophe Fraser. Epidemiological changes on the Isle of Wight after the launch of the NHS Test and Trace programme: a preliminary analysis, The Lancet Digital Health, Volume 2, Issue 12, E658-E666, December 01, 2020. diff --git a/server.R b/server.R index 85f3b895..9186c630 100644 --- a/server.R +++ b/server.R @@ -552,6 +552,252 @@ server <- function(input, output, session) { ) }) + ### MAPS + + getDateMaps <- reactive({ + as.Date(input$date.slider.maps, format="%d %b %y") + }) + + output$mapDate <- renderUI({ + nice.date <- format(getDateMaps(), format="%d %B %Y") + HTML(glue("

{nice.date}

")) + }) + + output$NowcastMap <- renderLeaflet({ + this.date.nowcast <- projected.cases.ltlas %>% filter(Dates == last.date - 31 - R.trim) + this.date.nowcast$lad19cd <- this.date.nowcast$AreaCode + this.date.nowcast$fill <- num2col(this.date.nowcast$scaled_per_capita, col.pal=colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d')), x.max=60) + + # combine shape and epi data + # using "right_join" removes the places for which we don't have data (Scotland etc.) + engwales <- right_join(engwalesmap, subset(this.date.nowcast, select = c("scaled_per_capita","lad19cd","fill")), by = "lad19cd") + + engwales <- engwales %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + NowcastHoverInfo <- sprintf( + "%s
Projected %g new
infections per 100,000
in the near future", + engwales$lad19nm, round(engwales$scaled_per_capita,1) + ) %>% lapply(htmltools::HTML) + + leaflet(engwales) %>% + addPolygons(layerId = paste0("nowcast.",mapcounter,".",1:337), + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = NowcastHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + addLegend("topright", + colors = colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d'))(7), + labels = c(seq(0,50,by=10),"60+"), + title = "per 100,000") + + }) + + output$InfectionsMap <- renderLeaflet({ + this.date.inf <- df.for.plotting.incidence.ltlas %>% filter(Dates == last.date - 31 - R.trim) + this.date.inf$lad19cd <- this.date.inf$AreaCode + this.date.inf$fill <- num2col(this.date.inf$scaled_per_capita, col.pal=colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d')), x.max=60) + + # combine shape and epi data + # using "right_join" removes the places for which we don't have data (Scotland etc.) + engwales <- right_join(engwalesmap, subset(this.date.inf, select = c("scaled_per_capita","lad19cd", "fill")), by = "lad19cd") + + engwales <- engwales %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + InfHoverInfo <- sprintf( + "%s
%g new infections per 100,000", + engwales$lad19nm, round(engwales$scaled_per_capita,1) + ) %>% lapply(htmltools::HTML) + + leaflet(engwales) %>% + addPolygons(layerId = paste0("infections.",mapcounter,".",1:337), + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = InfHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + addLegend("topright", + colors = colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d'))(7), + labels = c(seq(0,50,by=10),"60+"), + title = "per 100,000") + }) + + output$RMap <- renderLeaflet({ + this.date.R <- df.for.plotting.R.ltlas %>% filter(Dates == last.date - 31 - R.trim) + this.date.R$lad19cd <- this.date.R$AreaCode + this.date.R$fill <- num2col(this.date.R$R, col.pal=colorRampPalette(c('#313695','#4575b4','#74add1','#abd9e9','#e0f3f8','#ffffbf','#fee090','#fdae61','#f46d43','#d73027','#a50026')), x.min=0, x.max=2) + + # combine shape and epi data + # using "right_join" removes the places for which we don't have data (Scotland etc.) + engwales <- right_join(engwalesmap, subset(this.date.R, select = c("R", "lad19cd", "fill")), by = "lad19cd") + + engwales <- engwales %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + RHoverInfo <- sprintf( + "%s
R = %g", + engwales$lad19nm, round(engwales$R,2) + ) %>% lapply(htmltools::HTML) + + leaflet(engwales) %>% + addPolygons(layerId = paste0("R.",mapcounter,".",1:337), + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = RHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + addLegend("topright", + colors = colorRampPalette(c('#313695','#4575b4','#74add1','#abd9e9','#e0f3f8','#ffffbf','#fee090','#fdae61','#f46d43','#d73027','#a50026'))(5), + labels = c("0.0","0.5","1.0","1.5","2.0+"), + title = "R") + + }) + + observeEvent(input$date.slider.maps, { + this.date <- getDateMaps() + + this.date.nowcast <- projected.cases.ltlas %>% filter(Dates == this.date) + this.date.nowcast$lad19cd <- this.date.nowcast$AreaCode + this.date.nowcast$fill <- num2col(this.date.nowcast$scaled_per_capita, col.pal=colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d')), x.max=60) + + this.date.inf <- df.for.plotting.incidence.ltlas %>% filter(Dates == this.date) + this.date.inf$lad19cd <- this.date.inf$AreaCode + this.date.inf$fill <- num2col(this.date.inf$scaled_per_capita, col.pal=colorRampPalette(c('#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d')), x.max=60) + + this.date.R <- df.for.plotting.R.ltlas %>% filter(Dates == this.date) + this.date.R$lad19cd <- this.date.R$AreaCode + this.date.R$fill <- num2col(this.date.R$R, col.pal=colorRampPalette(c('#313695','#4575b4','#74add1','#abd9e9','#e0f3f8','#ffffbf','#fee090','#fdae61','#f46d43','#d73027','#a50026')), x.min=0, x.max=2) + + # combine shape and epi data + # using "right_join" removes the places for which we don't have data (Scotland etc.) + this.date.nowcast <- right_join(engwalesmap, subset(this.date.nowcast, select = c("scaled_per_capita","lad19cd","fill")), by = "lad19cd") + this.date.nowcast <- this.date.nowcast %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + this.date.inf <- right_join(engwalesmap, subset(this.date.inf, select = c("scaled_per_capita","lad19cd", "fill")), by = "lad19cd") + this.date.inf <- this.date.inf %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + this.date.R <- right_join(engwalesmap, subset(this.date.R, select = c("R", "lad19cd", "fill")), by = "lad19cd") + this.date.R <- this.date.R %>% st_transform('+proj=longlat +datum=WGS84') # convert the shapefile coordinates into longitudes and latitudes, ready for the leaflet package + + NowcastHoverInfo <- sprintf( + "%s
Projected %g new
infections per 100,000
in the near future", + this.date.nowcast$lad19nm, round(this.date.nowcast$scaled_per_capita,1) + ) %>% lapply(htmltools::HTML) + + InfHoverInfo <- sprintf( + "%s
%g new infections per 100,000", + this.date.inf$lad19nm, round(this.date.inf$scaled_per_capita,1) + ) %>% lapply(htmltools::HTML) + + RHoverInfo <- sprintf( + "%s
R = %g", + this.date.R$lad19nm, round(this.date.R$R,2) + ) %>% lapply(htmltools::HTML) + + leafletProxy("NowcastMap", data=this.date.nowcast) %>% + addPolygons(layerId = paste0("nowcast.",mapcounter+1,".",1:337), # assigning layerIds is supposed to mean that the previous ones get deleted (otherwise there's a big slow-down as more dates are viewed) + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = NowcastHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + removeShape(layerId = paste0("nowcast.",mapcounter,".",1:337)) # remove the _underneath_ layer so that we don't get jumpiness or slowdown + + leafletProxy("InfectionsMap", data=this.date.inf) %>% + addPolygons(layerId = paste0("infections.",mapcounter+1,".",1:337), + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = InfHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + removeShape(layerId = paste0("infections.",mapcounter,".",1:337)) + + leafletProxy("RMap", data=this.date.R) %>% + addPolygons(layerId = paste0("R.",mapcounter+1,".",1:337), + fillColor = ~fill, + weight = 1, + opacity = 1, + color = "white", + dashArray = "3", + fillOpacity = 1, + highlight = highlightOptions( + weight = 5, + color = "#666", + dashArray = "", + fillOpacity = 0.7, + bringToFront = TRUE), + label = RHoverInfo, + labelOptions = labelOptions( + style = list("font-weight" = "normal", padding = "3px 8px"), + textsize = "15px", + direction = "auto")) %>% + removeShape(layerId = paste0("R.",mapcounter,".",1:337)) + + mapcounter <- mapcounter + 1 + }) + + # output$MapTitle <- renderUI({ + # h3(format(getDateMaps(), "%d %B %Y")) + # }) ### Cases by age @@ -985,6 +1231,13 @@ server <- function(input, output, session) { HTML(glue("
Last updated {last.datestamp}
using data up to {last.date.of.data}.
")) }) + + output$updatedInfoMaps <- renderUI({ + last.datestamp <- getLastDatestamp() + last.date.of.data <- getLastDateOfData() + HTML(glue("
Last updated {last.datestamp}
using data up to {last.date.of.data}.
")) + }) + output$updatedInfoAges <- renderUI({ last.datestamp <- getLastDatestamp() last.date.of.data <- getLastDateOfData() diff --git a/ui.R b/ui.R index 90d9fd47..9d0b8db3 100644 --- a/ui.R +++ b/ui.R @@ -3,6 +3,10 @@ ui <- fluidPage( "label { font-size: 16px; }" ), tags$head(includeHTML(("google-analytics.html"))), # google analytics token + tags$head( + tags$style(HTML(".leaflet-container { background: #FFFFFF; }")) + ), # make map backgrounds white + tags$head(tags$style(type='text/css', ".slider-animate-button { font-size: 26pt !important; }")), # make 'play' button nicer on slider theme = shinytheme("yeti"), # change the theme here; use "themeSelector()" below for an easy way to pick #shinythemes::themeSelector(), # use this to dynamically select a theme @@ -241,6 +245,63 @@ ui <- fluidPage( ) # end sidebarLayout ), # end "daily tracker" tabPanel + tabPanel( + "Map tracker", + + h5("Select a date of interest or press 'play' (right) to watch the progression of the epidemic so far:"), + + sliderTextInput( + inputId = "date.slider.maps", + label = NULL, + choices = format(seq(from=as.Date("2020-03-01"), to = last.date - R.trim - 1, by=1), "%d %b %y"), + selected = format(last.date - 31 - R.trim, "%d %b %y"), + animate = animationOptions( + interval = 1000, + loop = FALSE, + playButton = NULL, + pauseButton = NULL + ), + grid = TRUE, + width = "100%" + ), + + div(style = "padding: 0px 0px; margin-top:-2em", + fluidRow( + column( + width=4, + h3("Nowcast"), + withSpinner(leafletOutput("NowcastMap", height="60vh"), type = 7) + ), + column( + width=4, + h3("New infections"), + withSpinner(leafletOutput("InfectionsMap", height="60vh"), type = 7) + ), + column( + width=4, + h3("Instantaneous reproduction number R"), + withSpinner(leafletOutput("RMap", height="60vh"), type = 7) + ) + ) + ), + + + + uiOutput("mapDate"), + + h5("For details of the data presented here please see the 'Daily tracker' tab."), + + + # "last updated" info + fluidRow( + column( + width=12, + align="right", + uiOutput("updatedInfoMaps") + ) + ) + + ), # end "map tracker" tabPanel tabPanel( "Cases by age",