├── .gitignore ├── .here ├── ABSData ├── 2016 Census GCP Postal Areas for VIC │ └── 2016Census_G01_VIC_POA.csv └── Boundaries │ ├── 2016_asgs_non-abs_metadata.md │ ├── POA_2016_AUST.cpg │ ├── POA_2016_AUST.dbf │ ├── POA_2016_AUST.prj │ ├── POA_2016_AUST.shp │ └── POA_2016_AUST.shx ├── APIKeys └── APIKeys.Rmd ├── Choropleth ├── mmc_surrounds.R └── mmc_surrounds.Rmd ├── README.Rmd ├── README.html ├── README.md ├── RehabCatchment ├── Mapdeck.Rmd ├── README.Rmd ├── makefile ├── map1.png ├── map1_mv.png ├── map2.png ├── map2_mv.png ├── map3.png ├── map3_mv.png ├── map4.png ├── map4_mv.png ├── map5.png ├── map5_mv.png └── rvspy │ ├── README.Rmd │ ├── README.md │ └── makefile ├── RehabCatchmentAdvanced ├── Googleway_Mapdeck.Rmd ├── Googleway_Mapdeck.html ├── directions_all_rehab_mapdeck_hexagons.png ├── directions_all_sample_mapdeck.png ├── directions_rehab1_mapdeck.png ├── directions_rehab1_mapdeck_hexagons.png ├── directions_rehab1_sample_mapdeck.png ├── directions_rehab1_sample_mapdeck2.png ├── directions_rehab1_sample_mapdeck_hexagons.png ├── directions_rehab2_sample_mapdeck.png ├── directions_rehab3_sample_mapdeck.png ├── duration_all_rehab.png ├── map_20_polylines.png └── nearest_rehab.png ├── article ├── Makefile ├── README.md ├── auto-ref.bib ├── choropleth.png ├── directions_all_rehab_mapdeck_hexagons.png ├── distance_mmc.png ├── duration_all_rehab.png ├── frontiers-in-physics.csl ├── frontiersHLTH.cls ├── frontiers_suppmat.cls ├── frontiersinHLTH&FPHY.bst ├── frontiersinHLTHFPHY.bst ├── geospatial-stroke.Rmd ├── geospatial-stroke.tex ├── logo1.eps ├── logo2.eps ├── logos.eps ├── map1_mv.png ├── map2_mv.png ├── map3_mv.png ├── map4_mv.png ├── map5_mv.png ├── morgue.md ├── nearest_rehab.png ├── references.bib ├── scrape-gdrive-into-rmd.R └── supplementary_geospatial_stroke.tex ├── data └── googleway │ ├── RehabLocations_googleway.rds │ ├── basicDemographicsRehab.rds │ ├── directions │ ├── directions1.rds │ ├── directions1_sample.rds │ ├── directions2.rds │ ├── directions2_sample.rds │ ├── directions3.rds │ └── directions3_sample.rds │ ├── directions_queries │ ├── df_random.rds │ ├── df_sample.rds │ └── df_sample2.rds │ ├── distances │ └── distance_matrix.rds │ └── randomaddresses │ ├── df_random.rds │ └── randomaddresses.rds └── python ├── .gitignore ├── environment.yml ├── notebooks ├── data │ └── postcode_strokes.csv ├── example1.html ├── example1.ipynb ├── example2.html ├── example2.ipynb └── webmap │ └── mmc.html ├── readme.md ├── requirements.txt └── screenshots ├── 01-open-cmd-prompt.png ├── 02-change-directories.png ├── 03-create-env.png ├── 04-activate-env-install-kernelspec.png └── 05-cd-notebooks-jupyter-lab.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.Rproj 2 | .DS_Store 3 | .Rhistory 4 | /data/googleway/directions_queries/rehab* 5 | /data/googleway/directions_queries/Archive.zip 6 | .Rproj.user 7 | /article/*.aux 8 | /article/*.bbl 9 | /article/*.blg 10 | /article/*.log 11 | /article/*.out 12 | /article/*.pdf 13 | /article/*.synctex.gz 14 | -------------------------------------------------------------------------------- /.here: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/.here -------------------------------------------------------------------------------- /ABSData/Boundaries/2016_asgs_non-abs_metadata.md: -------------------------------------------------------------------------------- 1 | # METADATA FOR DIGITAL BOUNDARY FILES 2 | 3 | Australian Statistical Geography Standard (ASGS) Volume 3 - Non-ABS Structures (cat no. 1270.0.55.003) 4 | 5 | **Data Currency**: 12 July 2016 6 | **Presentation Format**: Digital boundaries 7 | **Custodian**: Australian Bureau of Statistics (ABS) 8 | 9 | ## DESCRIPTION 10 | **Abstract**: 11 | The Australian Statistical Geography Standard (ASGS) brings together in one framework all of the regions which the ABS and many others organisations use to collect, release and analyse geographically classified statistics. The ASGS ensures that these statistics are comparable and geospatially integrated and provides users with an coherent set of standard regions so that they can access, visualise, analyse and understand statistics. The 2016 ASGS will be used for the 2016 Census of Population and Housing and progressively introduced into other ABS data collections. The ABS encourages the use of the ASGS by other organisations to improve the comparability and usefulness of statistics generally, and in analysis and visualisation of statistical and other data. 12 | 13 | This product, **Australian Statistical Geography Standard (ASGS) Volume 3 - Non-ABS Structures** (cat no. 1270.0.55.003), is the third in a series of five volumes that describe the structures that make up the ASGS. Its purpose is to outline the conceptual basis for the design of the Non-ABS Structures. This product contains several elements including the manual, region names and codes and the digital boundaries current for the ASGS Edition 2016 (date of effect 1 July 2016). 14 | 15 | The digital boundaries for Volume 3 of the ASGS are the region types for supported non-ABS structures. These region types are: 16 | * Local Government Area (LGA) 17 | * Postal Area (POA) 18 | * State Suburb (SSC) 19 | * Commonwealth Electoral Division (CED) 20 | * State Electoral Division (SED) 21 | * Natural Resource Management Region (NRMR) 22 | * Australian Drainage Division (ADD) 23 | 24 | **File Nomenclature**: 25 | File names have the format `[type]_[YYYY]_[COVERAGE]` where: 26 | `[type]` represents the type of boundaries in each file 27 | * LGA = Local Government Areas 28 | * POA = Postal Areas 29 | * SSC = State Suburbs 30 | * CED = Commonwealth Electoral Divisions 31 | * SEC = State Electoral Divisions 32 | * NRMR = Natural Resource Management Region 33 | * ADD = Australian Drainage Division 34 | 35 | `[YYYY]` represents the Australian Statistical Geography Standard (ASGS) Edition by year. `2016` is the current edition. 36 | `[COVERAGE]` indicates the geographic area covered by the data as defined in the ASGS manual. The value will be `AUS` for the non-ABS structures. 37 | 38 | **State and Territory Codes and Names** 39 | Within the files, the States and Territories are identified by unique one digit codes. 40 | | Code | State/Territory | 41 | |------|-----------------| 42 | | 1 | New South Wales | 43 | | 2 | Victoria | 44 | | 3 | Queensland | 45 | | 4 | South Australia | 46 | | 5 | Western Australia | 47 | | 6 | Tasmania | 48 | | 7 | Northern Territory | 49 | | 8 | Australian Capital Territory | 50 | | 9 | Other Territories | 51 | 52 | **Australia** 53 | The code for Australia is shown as `036` where it appears as the parent geography of data released on a State and Territory level, or where coverage is for the whole of Australia. 54 | 55 | This allows alignment with both the UN Statistical Division's *"Standard country or area codes for statistical use (M49)"* ( https://unstats.un.org/unsd/methodology/m49/ ) and ISO 3166-1 *"Codes for the representation of names of countries and their subdivisions"* alpha-3 codes ( https://www.iso.org/iso-3166-country-codes.html ) 56 | 57 | ### File Attributes: 58 | For each file type the field name, data type, and length is shown. 59 | 60 | __Note__ - While metadata for each spatial unit in the ASGS is shown, any given file will only contain the referenced spatial unit, and the parent spatial units above it in the ASGS hierarchy. 61 | 62 | **MapInfo (.mid/.mif & TAB) and Geopackage (.gpkg)** 63 | | Count | Field | Data Type | Length | Name | 64 | |-------|---------------------|-----------|--------|---------------------------------| 65 | | 1 | LGA_CODE_2016 | Character | 8 | 2016 Local Government Area Code | 66 | | 2 | LGA_NAME_2016 | Character | 50 | 2016 Local Government Area Name | 67 | | 3 | POA_CODE_2016 | Character | 7 | 2016 Postal Area Code | 68 | | 4 | POA_CODE_2016 | Character | 40 | 2016 Postal Area Name | 69 | | 5 | SSC_CODE_2016 | Character | 8 | 2016 State Suburb Code | 70 | | 6 | SSC_NAME_2016 | Character | 45 | 2016 State Suburb Name | 71 | | 7 | CONF_VALUE | Character | 12 | SSC Confidence Value | 72 | | 8 | CED_CODE_2016 | Character | 6 | 2016 Commonwealth Electoral Division Code | 73 | | 9 | CED_NAME_2016 | Character | 40 | 2016 Commonwealth Electoral Division Name | 74 | | 10 | SED_CODE_2016 | Character | 8 | 2016 State Electoral Division Code | 75 | | 11 | SED_NAME_2016 | Character | 50 | 2016 State Electoral Division Name | 76 | | 12 | NRMR_CODE_2016 | Character | 7 | 2016 Natural Resource Management Region Code | 77 | | 13 | NRMR_NAME_2016 | Character | 40 | 2016 Natural Resource Management Region Name | 78 | | 14 | ADD_CODE_2016 | Character | 3 | 2016 State/Territory Code | 79 | | 15 | ADD_NAME_2016 | Character | 40 | 2016 State/Territory Name | 80 | | 16 | AREA_ALBERS_SQKM | Float | - | Area (Albers) in sq/km | 81 | 82 | **ESRI Shape Files (.shp)** 83 | | Count | Field | Data Type | Length | Name | 84 | |-------|------------|-----------|--------|---------------------------------| 85 | | 1 | LGA_CODE | Character | 8 | 2016 Local Government Area Code | 86 | | 2 | LGA_NAME | Character | 50 | 2016 Local Government Area Name | 87 | | 3 | POA_CODE | Character | 7 | 2016 Postal Area Code | 88 | | 4 | POA_CODE | Character | 40 | 2016 Postal Area Name | 89 | | 5 | SSC_CODE | Character | 8 | 2016 State Suburb Code | 90 | | 6 | SSC_NAME | Character | 45 | 2016 State Suburb Name | 91 | | 7 | CONF_VALUE | Character | 12 | SSC Confidence Value | 92 | | 8 | CED_CODE | Character | 6 | 2016 Commonwealth Electoral Division Code | 93 | | 9 | CED_NAME | Character | 40 | 2016 Commonwealth Electoral Division Name | 94 | | 10 | SED_CODE | Character | 8 | 2016 State Electoral Division Code | 95 | | 11 | SED_NAME | Character | 50 | 2016 State Electoral Division Name | 96 | | 12 | NRMR_CODE | Character | 7 | 2016 Natural Resource Management Region Code | 97 | | 13 | NRMR_NAME | Character | 40 | 2016 Natural Resource Management Region Name | 98 | | 14 | ADD_CODE_ | Character | 3 | 2016 State/Territory Code | 99 | | 15 | ADD_NAME_ | Character | 40 | 2016 State/Territory Name | 100 | | 16 | AREA_SQKM | Float | - | Area (Albers) in sq/km | 101 | 102 | **Note**: CONF_VALUE field provides an indicator of how accurately the SSC represents the suburb/locality based on the percentage of common population. The values that are applied to each SSC are: 103 | * 94% and above common population - very good 104 | * 88 to less than 94% common population - good 105 | * 75 to less than 88% common population - acceptable 106 | * 50 to less than 75% common population - poor 107 | * Less than 50% common population - very poor 108 | 109 | ### XML METADATA FILE 110 | The compressed download files include geospatial metadata data for each region type in Extensible Markup Language (XML) format. The geospatial metadata conforms to International Organisation for Standardization (ISO) geospatial metadata standard, `ISO 19115:2003`, and the associated XML implementation schema specified by `ISO 19139:2012`. 111 | 112 | *DATA CURRENCY* 113 | **Date of Effect**: 12 July 2016 114 | 115 | *DATASET STATUS* 116 | **Progress**: Completed dataset 117 | **Maintenance and Update Frequency**: 118 | No further updates for these boundaries planned. There will be a progressive release of the other regions that make up the ASGS until late 2018 (ASGS Volumes 4 and 5). The ASGS will be revised in 2021. 119 | 120 | *ACCESS* 121 | **Stored Data Format**: 122 | Digital as separate files for each level of the Main Structure and GCCSA of the ASGS 2016. 123 | 124 | **Available Format**: 125 | The digital boundary files are in MapInfo TAB format (.TAB), MapInfo Interchange Format (.MID .MIF), Geopackage (.gpkg) and ESRI Shapefile (.shp) format. 126 | 127 | **Spatial Representation Type**: Vector 128 | 129 | **Access Constraints**: 130 | Copyright Commonwealth of Australia administered by the ABS. Unless otherwise noted, content is licensed under a Creative Commons Attribution 2.5 Australia licence. 131 | 132 | **Datum**: Geocentric Datum of Australia 1994 (GDA94) 133 | 134 | **Projection**: Geographical (i.e. Latitudes and Longitudes) 135 | 136 | **Geographic Extent**: Geographic Australia. 137 | 138 | The Australian Statistical Geography Standard (ASGS) uses the Geographic definition of Australia, as set out in section 2B of the Acts Interpretation Act 1901, which currently defines Australia or the Commonwealth as meaning: 139 | 140 | *"...the Commonwealth of Australia and, when used in a geographical sense, includes Norfolk Island, the Territory of Christmas Island and the Territory of Cocos (Keeling) Islands, but does not include any other external Territory."* 141 | 142 | Included in this definition of Geographic Australia are the: 143 | * States of New South Wales, Victoria, Queensland, South Australia, Western Australia and Tasmania 144 | * Northern Territory 145 | * Australian Capital Territory (ACT) 146 | * Territory of Cocos (Keeling) Islands 147 | * Territory of Christmas Island 148 | * Jervis Bay Territory 149 | * Territory of Norfolk Island 150 | 151 | **Extent - Geographic Bounding Box**: 152 | 153 | * North Bounding Latitude: -8 154 | * South Bounding Latitude: -45 155 | * West Bounding Latitude: 96 156 | * East Bounding Latitude: 169 157 | 158 | *DATA QUALITY* 159 | **Lineage**: 160 | Mesh Block boundaries were created using various sources including the PSMA digital datasets and ABS boundaries, zoning information from state planning agencies and imagery. 161 | 162 | **Positional Accuracy**: 163 | Positional accuracy is an assessment of the closeness of the location of the spatial objects in relation to their true positions on the earth's surface. The positional accuracy includes both a horizontal accuracy assessment and a vertical accuracy assessment. Positional accuracy for ABS boundaries is dependent on the accuracy of the features they have been aligned to. ABS boundaries are aligned to a number of layers supplied by the PSMA with an accuracy of `+/-50 mm`. PSMA layers and their positional accuracy are as follows: 164 | > ***Transport and Topography*** 165 | > `+/- 2 meters` in urban areas and `+/- 10 meters` in rural and remote areas 166 | > ***CadLite*** 167 | > `+/- 2 meters` in urban areas and `+/- 10 meters` in rural and remote areas 168 | > ***Administrative Boundaries*** 169 | > Derived from the cadastre data from each Australian State and Territory jurisdiction. 170 | > ***Greenspace and Hydrology*** 171 | > 90% of well-defined features are within `1mm` (at plot scale) of their true position, eg `1:500` equates to `+/- 0.5metre` and `1:25,000` equates to `+/- 25 metres`. 172 | > 173 | > Relative spatial accuracy of these themes reflects that of the jurisdictional source data. The accuracy is `+/- 2 metres` in urban areas and `+/- 10 metres` in rural and remote areas. 174 | 175 | No "shift" of data as a means of "cartographic enhancement" to facilitate presentation has been employed for any real world feature. 176 | 177 | **Attribute Accuracy**: 178 | All codes and labels for all levels within the ASGS non-ABS Structures are fully validated as accurate at time of release. 179 | 180 | **Logical Consistency**: 181 | Regions are closed polygons. Attribute records without spatial objects have been included in the data for administrative purposes. 182 | 183 | **Completeness**: 184 | All levels of the non-ABS structures supported within the 2016 ASGS are represented. 185 | 186 | *CONTACT INFORMATION* 187 | **Contact Organisation**: Australian Bureau of Statistics 188 | For further information email or contact the National Information and Referral Service (NIRS) on `1300 135 070`. -------------------------------------------------------------------------------- /ABSData/Boundaries/POA_2016_AUST.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /ABSData/Boundaries/POA_2016_AUST.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/ABSData/Boundaries/POA_2016_AUST.dbf -------------------------------------------------------------------------------- /ABSData/Boundaries/POA_2016_AUST.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_GDA_1994",DATUM["D_GDA_1994",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433],AUTHORITY["EPSG",4283]] -------------------------------------------------------------------------------- /ABSData/Boundaries/POA_2016_AUST.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/ABSData/Boundaries/POA_2016_AUST.shp -------------------------------------------------------------------------------- /ABSData/Boundaries/POA_2016_AUST.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/ABSData/Boundaries/POA_2016_AUST.shx -------------------------------------------------------------------------------- /APIKeys/APIKeys.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "APIKeys" 3 | output: html_document 4 | --- 5 | 6 | 7 | 8 | ## API Keys 9 | 10 | 11 | Online servies which offer an interface to their applications will sometimes require you to use an API key, 12 | or application programming interface key. This key should be unique for each user, developer or 13 | application making use of the service as it is a way for it to monitor and, where applicable, charge for use. 14 | 15 | Two major mapping platforms that require an API key are Google Maps and Mapbox. There are examples of the use of both in 16 | the documents in this repository. At the time of writing both allow unrestricted use of the mapping API. 17 | However, Google has limits on the other services it offers such as geocoding and direction services, and now requires 18 | that payment options are configured. 19 | 20 | Documents in this repository requiring keys have a code chunk near the start where the keys 21 | can be inserted. 22 | 23 | Keys can be obtained from the following locations: 24 | 25 | 1. [Google API key](https://developers.google.com/maps/documentation/javascript/get-api-key) 26 | 27 | 1. [MapBox access token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/) 28 | -------------------------------------------------------------------------------- /Choropleth/mmc_surrounds.R: -------------------------------------------------------------------------------- 1 | ## Choropleth for vicinity of MMC. Predicted stroke incidence by post code 2 | 3 | ## Illustrate the combination of geospatial geometries and census age data. 4 | ## coding up a simple incidence model from the NEMESIS paper: 5 | ## 6 | ## 7 | ## @article{thrift2000stroke, 8 | ## title={Stroke Incidence on the East Coast of Australia The North East Melbourne Stroke Incidence Study (NEMESIS)}, 9 | ## author={Thrift, Amanda G and Dewey, Helen M and Macdonell, Richard AL and McNeil, John J and Donnan, Geoffrey A}, 10 | ## journal={Stroke}, 11 | ## volume={31}, 12 | ## number={9}, 13 | ## pages={2087--2092}, 14 | ## year={2000}, 15 | ## publisher={Am Heart Assoc} 16 | ##} 17 | ## 18 | ## 19 | ## 20 | ## Combined Male/Female incidence per 100 000. 21 | ## 22 | ## Age Incidnce 23 | ## 0-14 0 24 | ## 15-24 5 25 | ## 25-34 30 26 | ## 35-44 44 27 | ## 45-54 111 28 | ## 55-64 299 29 | ## 65-74 747 30 | ## 75-84 1928 31 | ## 85+ 3976 32 | 33 | ## 2016 census data 34 | # https://datapacks.censusdata.abs.gov.au/datapacks/ 35 | # Dempgraphic data by postcode is available, with age information in 36 | # "ABSData/2016 Census GCP Postal Areas for VIC/2016Census_G01_VIC_POA.csv" 37 | 38 | ## Boundary data available from the same site 39 | 40 | ## ---- RPackageCheck ---- 41 | ip <- installed.packages () [, 1] # names of installed packages 42 | requiredpackages <- c("tidyverse", "sf", "here", 43 | "units", "tmaptools", "tmap", "knitr") 44 | if (!all(requiredpackages %in% ip)) { 45 | msg <- paste("This script requires the following packages: ", paste(requiredpackages, collapse=", ")) 46 | message(msg) 47 | message("Attempting to install them") 48 | options(repos=c(CRAN="https://cloud.r-project.org")) 49 | missingCRAN <- setdiff(requiredpackages, ip) 50 | if (length(missingCRAN) > 0) { 51 | message(paste("Missing packages are", missingCRAN)) 52 | install.packages(missingCRAN) 53 | } 54 | } 55 | ## ---- Libraries ---- 56 | library(tidyverse) 57 | library(sf) 58 | library(units) 59 | library(tmaptools) 60 | 61 | ## ---- CensusData ---- 62 | postcodeboundariesAUS <- sf::read_sf( 63 | here::here("ABSData", 64 | "Boundaries", 65 | "POA_2016_AUST.shp")) 66 | 67 | basicDemographicsVIC <- readr::read_csv( 68 | here::here("ABSData", 69 | "2016 Census GCP Postal Areas for VIC", 70 | "2016Census_G01_VIC_POA.csv")) 71 | 72 | ## ---- MonashMedicalCentre ---- 73 | ## Location of hopsital providing acute stroke services 74 | ## address: 246 Clayton Rd, Clayton VIC, 3168 75 | MMCLocation <- tmaptools::geocode_OSM("Monash Medical Centre, Clayton, Victoria, Australia", as.sf=TRUE) 76 | MMCLocation 77 | 78 | ## ---- JoinCensusAndBoundaries ---- 79 | ## Join the demographics and shape tables, retaining victoria only 80 | ## use postcode boundaries as the reference data frame so that coordinate 81 | ## reference system is retained. 82 | basicDemographicsVIC <- right_join(postcodeboundariesAUS, basicDemographicsVIC, 83 | by=c("POA_CODE" = "POA_CODE_2016")) 84 | 85 | ## ---- StrokeIncidence ---- 86 | 87 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 88 | Age_0_24_yr_P = Age_0_4_yr_P + Age_5_14_yr_P + 89 | Age_15_19_yr_P + Age_20_24_yr_P) 90 | basicDemographicsVIC <- mutate(basicDemographicsVIC, stroke_count_estimate = ( 91 | Age_0_24_yr_P * 5 + 92 | Age_25_34_yr_P * 30 + 93 | Age_35_44_yr_P * 44 + 94 | Age_45_54_yr_P * 111 + 95 | Age_55_64_yr_P * 299 + 96 | Age_65_74_yr_P * 747 + 97 | Age_75_84_yr_P * 1928 + 98 | Age_85ov_P * 3976) / 100000) 99 | 100 | ## ---- SpatialComputations ---- 101 | ## Add some geospatial measures to the data frame 102 | ## Reproduce the existing area column as a demo. 103 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 104 | PostcodeArea=units::set_units(st_area(geometry), km^2)) 105 | 106 | ## Distance to MMC 107 | basicDemographicsVIC <- sf::st_transform( basicDemographicsVIC, crs = sf::st_crs( MMCLocation ) ) 108 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 109 | DistanceToMMC=units::set_units(st_distance(geometry,MMCLocation)[,1], km)) 110 | 111 | ## comments re auto-great-circle, straight line assumption, 112 | ## distance to polygon or polygon-centroid ... 113 | plot(basicDemographicsVIC["DistanceToMMC"]) 114 | 115 | ## ---- FilteringPostcodes ---- 116 | ## Make a small dataset for MMC surrounds. 117 | basicDemographicsMMC <- filter(basicDemographicsVIC, DistanceToMMC < set_units(20, km)) 118 | 119 | ## ---- PostcodesTable ---- 120 | ## tables to paste into latex 121 | tt <- knitr::kable(select(head(basicDemographicsMMC), POA_NAME, Tot_P_P, stroke_count_estimate, DistanceToMMC), format="latex") 122 | writeLines(tt, "mmcdemograhics") 123 | ## ---- InteractiveDisplay ---- 124 | library(tmap) 125 | tmap_mode("view") 126 | 127 | MMCLocation <- mutate(MMCLocation, ID="Monash Medical Centre") 128 | basicDemographicsMMC <- mutate(basicDemographicsMMC, Over65 = Age_65_74_yr_P + Age_75_84_yr_P + Age_85ov_P) 129 | tm_shape(basicDemographicsMMC, name="Annual stroke counts") + 130 | tm_polygons("stroke_count_estimate", id="POA_NAME", popup.vars=c("Cases"="stroke_count_estimate"), alpha=0.6) + 131 | tm_shape(MMCLocation) + tm_markers() + 132 | tm_basemap("OpenStreetMap") 133 | 134 | 135 | ## ---- Dummy ---- 136 | 137 | ## Display with Mapdeck 138 | #library(mapdeck) 139 | #set_token(read.dcf("~/Documents/.googleAPI", fields = "MAPBOX")) 140 | 141 | #mapdeck( 142 | # location = c(145.2, -37.9) 143 | # , zoom = 9 144 | # ) %>% 145 | # add_polygon( 146 | # data = basicDemographicsMMC 147 | # , fill_colour = "stroke_count_estimate" 148 | # , elevation = "stroke_count_estimate" 149 | # , tooltip = "stroke_count_estimate" 150 | # ) 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /Choropleth/mmc_surrounds.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Choropleth - stroke by postcode" 3 | output: 4 | html_document: 5 | toc: true 6 | toc_float: true 7 | number_sections: false 8 | theme: flatly 9 | md_document: 10 | variant: markdown_github 11 | number_sections: true 12 | --- 13 | 14 | ```{r setup, include=FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | warning = TRUE, 18 | message = TRUE, 19 | width = 120, 20 | comment = "#>", 21 | fig.retina = 2, 22 | fig.path = "README-", 23 | fig.width = 10 24 | ) 25 | knitr::read_chunk("mmc_surrounds.R") 26 | ``` 27 | 28 | Choropleths are a staple of geospatial visualization. The idea of this 29 | example is to introduced those basics in stroke context. 30 | 31 | The example loads the data, estimates annual stroke incidence based on data 32 | from the NEMISIS study, performs a join, calculates some 33 | geospatial measures, filters postcodes based on distance to the hospital and displays the results. 34 | 35 | 36 | ## 0. Package loading 37 | 38 | ```{r RPackageCheck, echo=FALSE} 39 | 40 | ``` 41 | ```{r Libraries, message=FALSE} 42 | 43 | ``` 44 | 45 | ## 1. Loading census and boundary data 46 | 47 | ```{r CensusData, message=FALSE, warning=FALSE} 48 | 49 | ``` 50 | 51 | ## 2. Combine demographics and spatial data 52 | 53 | ```{r JoinCensusAndBoundaries} 54 | 55 | ``` 56 | 57 | ## 3. Geocode hospital location 58 | 59 | ```{r MonashMedicalCentre} 60 | 61 | ``` 62 | 63 | ## 4. Compute per-postcode stroke incidence 64 | 65 | ```{r StrokeIncidence} 66 | 67 | ``` 68 | 69 | 70 | ## 5. Compute distance to hospital from postcode 71 | ```{r SpatialComputations} 72 | 73 | ``` 74 | 75 | ## 6. Discard remote postcodes 76 | ```{r FilteringPostcodes} 77 | 78 | ``` 79 | 80 | ## 7. Interactive display of the result 81 | ```{r InteractiveDisplay} 82 | 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Geospatial Stroke" 3 | output: md_document 4 | --- 5 | 6 | ```{r setup, include=FALSE} 7 | knitr::opts_chunk$set(echo = TRUE) 8 | ``` 9 | 10 | 11 | # GeospatialStroke 12 | 13 | Repository of contributions for Frontiers in Neurology-Stroke journal, focussing on geospatial analysis and transport. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GeospatialStroke 2 | ================ 3 | 4 | Source code for [_A review of software tools, data and services for 5 | geospatial analysis of stroke 6 | services_](https://www.frontiersin.org/articles/10.3389/fneur.2019.00743/full) 7 | in Frontiers in Neurology-Stroke. 8 | 9 | Interactive versions of the examples available at 10 | [https://richardbeare.github.io/GeospatialStroke] 11 | 12 | 13 | -------------------------------------------------------------------------------- /RehabCatchment/Mapdeck.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mapdeck visuals for catchment areas for rehabilitation centres" 3 | output: 4 | rmarkdown::html_vignette: 5 | self_contained: no 6 | 7 | md_document: 8 | variant: markdown_github 9 | --- 10 | 11 | ```{r opts, echo = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | warning = TRUE, 15 | message = TRUE, 16 | width = 120, 17 | comment = "#>", 18 | fig.retina = 2, 19 | fig.path = "README-", 20 | fig.width = 10 21 | ) 22 | ``` 23 | 24 | ```{r libraries} 25 | library(mapdeck) 26 | ``` 27 | 28 | A mapbox token is required for the `mapdeck` package, which must be set with the 29 | `set_token()` function, either by 30 | 31 | ```{r, eval = FALSE} 32 | set_token("") 33 | ``` 34 | 35 | or by making a file `~/.Renviron` containing the line 36 | 37 | ```{bash, eval = FALSE} 38 | MAPBOX_TOKEN="" 39 | ``` 40 | 41 | and then running in R: 42 | 43 | ```{r mapbox-token-echo, echo = FALSE} 44 | set_token(Sys.getenv("MAPBOX_TOKEN")) 45 | ``` 46 | 47 | ```{r mapbox-token, echo = FALSE} 48 | if (requireNamespace ("mapdeck")) 49 | set_token(Sys.getenv("MAPBOX_TOKEN")) 50 | ``` 51 | 52 | 53 | ## Check geocoding 54 | 55 | ```{r mapdeck1, eval = FALSE} 56 | mapdeck() %>% 57 | add_scatterplot (RehabLocations, radius = 200) 58 | ``` 59 | ![](map1.png) 60 | 61 | ## ---- PlotSampleLocations ---- 62 | 63 | ```{r mapdeck2, eval = FALSE} 64 | mapdeck() %>% 65 | add_scatterplot (randomaddresses, 66 | radius = 2) 67 | ``` 68 | ![](map2.png) 69 | 70 | 71 | ```{r mapdeck-postcode-polygons, eval = FALSE} 72 | mapdeck() %>% 73 | add_polygon (basicDemographicsRehab, 74 | stroke_colour = "#000000", 75 | stroke_width = 100, 76 | #fill_colour = "Postcode", 77 | fill_colour = "#22cccc", 78 | fill_opacity = 250) 79 | ``` 80 | ![](map3.png) 81 | 82 | 83 | ## ---- CatchmentBasins ---- 84 | 85 | ```{r disconnected-plot-mapdeck, eval = FALSE} 86 | mapdeck() %>% 87 | add_scatterplot(data = fromCoords, lon = "x", lat = "y", 88 | fill_colour = "DestNumber", 89 | radius = 5, 90 | palette = "plasma") 91 | ``` 92 | ![](map4.png) 93 | 94 | 95 | ## ---- CatchmentBasin Polygons ---- 96 | 97 | ```{r catchment-polygon-plot, eval = FALSE} 98 | mapdeck() %>% 99 | add_polygon (data = v, 100 | fill_colour = "DestNumber", 101 | fill_opacity = 150, 102 | palette = "plasma") %>% 103 | add_scatterplot (data = RehabLocations, 104 | radius = 200) 105 | ``` 106 | ![](map5.png) -------------------------------------------------------------------------------- /RehabCatchment/README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Catchment areas for rehabilitation centres" 3 | output: 4 | html_document: 5 | toc: true 6 | toc_float: true 7 | number_sections: false 8 | theme: flatly 9 | md_document: 10 | variant: markdown_github 11 | number_sections: true 12 | --- 13 | 14 | ```{r opts, echo = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | warning = TRUE, 18 | message = TRUE, 19 | width = 120, 20 | comment = "#>", 21 | fig.retina = 2, 22 | fig.path = "choroplethfig-", 23 | fig.width = 10 24 | ) 25 | ``` 26 | 27 | ## 0. Package loading 28 | 29 | 30 | ```{r package check, echo = FALSE} 31 | ip <- installed.packages () [, 1] # names of installed packages 32 | requiredpackages <- c("tidyverse", "sf", "here", "osmextract", "units", "mapview", "tmaptools", "PSMA", "dodgr", "devtools", "knitr") 33 | if (!all(requiredpackages %in% ip)) { 34 | if (! ("PSMA" %in% ip) ) { 35 | msg <- "PSMA needs to be installed using devtools as follows: \n devtools::install_github(\"HughParsonage/PSMA\")" 36 | warning(msg) 37 | } 38 | msg <- paste("This script requires the following packages: ", paste(requiredpackages, collapse=", ")) 39 | stop (msg) 40 | } 41 | ``` 42 | 43 | ```{r initial-load, message = FALSE} 44 | library(tidyverse) 45 | library(sf) 46 | library(units) 47 | library(tmaptools) 48 | library (mapview) 49 | ``` 50 | 51 | ## 1. Loading census and boundary data 52 | 53 | Load postcode boundaries and demographic data from the 2016 census. 54 | ```{r postcodeboundaries} 55 | postcodeboundariesAUS <- 56 | file.path(here::here(), "ABSData", "Boundaries", "POA_2016_AUST.shp") %>% 57 | sf::read_sf () 58 | 59 | basicDemographicsVIC <- file.path(here::here(), "ABSData", 60 | "2016 Census GCP Postal Areas for VIC", 61 | "2016Census_G01_VIC_POA.csv") %>% 62 | readr::read_csv() 63 | ``` 64 | 65 | Clean up the demographics table so that it only contains columns of interest, 66 | which in this case are the postcodes and age related columns. 67 | The columns about education status are being removed for clarity. 68 | 69 | ```{r initial-clean} 70 | basicDemographicsVIC <- select(basicDemographicsVIC, POA_CODE_2016, 71 | starts_with("Age_"), 72 | -starts_with("Age_psns_")) 73 | ``` 74 | 75 | ## 2. Geocoding hospital locations 76 | 77 | Geocoding transforms a text address into a latitude/longitude coordinate. In this example 78 | we are using the OpenStreetMap Nominatim service, that can be queried without an API key. 79 | 80 | ```{r rehab-addresses} 81 | rehab_addresses <- c(DandenongHospital = "Dandenong Hospital, Dandenong VIC 3175, Australia", 82 | CaseyHospital = "62-70 Kangan Dr, Berwick VIC 3806, Australia", 83 | KingstonHospital = "400 Warrigal Rd, Cheltenham VIC 3192, Australia") 84 | RehabLocations <- tmaptools::geocode_OSM(rehab_addresses, as.sf=TRUE) 85 | ``` 86 | 87 | These `RehabLocations` then need to be transformed to the same coordinate 88 | reference system as the `basicDemographicsVIC`. 89 | ```{r rehab-addresses-transform} 90 | RehabLocations <- select(RehabLocations, -bbox) 91 | RehabLocations <- sf::st_transform(RehabLocations, 92 | sf::st_crs(postcodeboundariesAUS)) 93 | ``` 94 | These locations can then be viewed with `mapview` in one line: 95 | ```{r mapview1-fakey, eval = TRUE} 96 | m <- mapview(RehabLocations, map.type="OpenStreetMap.HOT", color='red', col.regions='red', cex=10) 97 | mapshot(m, "map1.html") 98 | ``` 99 | ![](map1_mv.png) 100 | 101 | [Interactive version of this map](map1.html) 102 | 103 | ## 3. Combine demographics and spatial data 104 | 105 | Join the demographics and shape tables of postcode boundaries, retaining 106 | Victoria only. Use postcode boundaries as the reference data frame so that 107 | `sf` data frame structure is retained. The `right_join` uses postcodes in 108 | the right hand argument (basicDemographicsVIC) to determine which rows to 109 | keep in the output. 110 | ```{r join-demo-postcodes} 111 | basicDemographicsVIC <- right_join(postcodeboundariesAUS, 112 | basicDemographicsVIC, 113 | by=c("POA_CODE" = "POA_CODE_2016")) 114 | ``` 115 | 116 | ## 4. Compute distance to each service centre from each postcode 117 | 118 | There are `r nrow (basicDemographicsVIC)` postcodes which we now want to reduce 119 | to only those within a zone around the rehab locations. In this example we use a 120 | 10km straight-line distance as a simple approach to producing a set of postcodes 121 | of interest. Distances are calculated to centroids of each postcode polygon. 122 | (Running this code produces a warning that `st_centroid` does not give correct 123 | results for longitude/latitude data, but results are nevertheless good enough 124 | for our purposes here.) 125 | 126 | ```{r postcode-dists} 127 | dist_to_loc <- function (geometry, location){ 128 | units::set_units(st_distance(st_centroid (geometry), location)[,1], km) 129 | } 130 | dist_range <- units::set_units(10, km) 131 | 132 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 133 | DirectDistanceToDandenong = dist_to_loc(geometry,RehabLocations["DandenongHospital", ]), 134 | DirectDistanceToCasey = dist_to_loc(geometry,RehabLocations["CaseyHospital", ]), 135 | DirectDistanceToKingston = dist_to_loc(geometry,RehabLocations["KingstonHospital", ]), 136 | DirectDistanceToNearest = pmin(DirectDistanceToDandenong, 137 | DirectDistanceToCasey, 138 | DirectDistanceToKingston) 139 | ) 140 | 141 | basicDemographicsRehab <- filter(basicDemographicsVIC, 142 | DirectDistanceToNearest < dist_range) %>% 143 | mutate(Postcode = as.numeric(POA_CODE16)) %>% 144 | select(-starts_with("POA_")) 145 | ``` 146 | That reduces the data down to `r nrow (basicDemographicsRehab)` nearby 147 | postcodes, with the last 2 lines converting all prior postcode columns (of which 148 | there were several all beginning with "POA") to a single numeric column named 149 | "Postcode". 150 | ```{r basicDemog-mapview, eval = TRUE} 151 | m <- mapview (basicDemographicsRehab, map.type="OpenStreetMap.HOT", alpha.regions=0.5) 152 | mapshot(m, "map3.html") 153 | ``` 154 | ![](map3_mv.png) 155 | [Interactive version of this map](map3.html) 156 | 157 | ## 5. Randomly sample addresses in postcodes 158 | 159 | Case loads for rehabilitation centres will be estimated based on a set of random addresses. The addresses 160 | are generated by sampling a geocoded database, PSMA, to produce a specified number 161 | of unique addresses per postcode. The number of addresses selected will depend on the 162 | subsequent processing steps, with numbers being reduced if queries to web services are involved. 163 | 164 | ```{r addressesPerPostcode} 165 | addressesPerPostcode <- 1000 166 | ``` 167 | 168 | We define a function, `samplePCode`, to sample a single postcode and apply 169 | it to every postcode using the `map` function. 170 | Sampling syntax is due to the use of data.table inside PSMA. The last 171 | `st_as_sf()` command converts the points labelled "LONGITUDE" and "LATITUDE" 172 | into `sf::POINT` objects. The results are combined in a single table. 173 | 174 | ```{r random-addresses} 175 | library(PSMA) 176 | samplePCode <- function(pcode, number) { 177 | d <- fetch_postcodes(pcode) 178 | return(d[, .SD[sample(.N, min(number, .N))], by=.(POSTCODE)]) 179 | } 180 | 181 | randomaddresses <- map(basicDemographicsRehab$Postcode, 182 | samplePCode, 183 | number=addressesPerPostcode) %>% 184 | bind_rows() %>% 185 | sf::st_as_sf(coords = c("LONGITUDE", "LATITUDE"), 186 | crs=st_crs(basicDemographicsRehab), 187 | agr = "constant") 188 | head(randomaddresses) 189 | ``` 190 | 191 | ## 6. Display sample addresses and postcodes 192 | 193 | Note that there are `r nrow (randomaddresses)` random addresses. Plotting this 194 | many points can be quite slow using `mapview`, so if you want to view the 195 | results, you might need to be patient. (Much faster plotting can be achieved 196 | with an API key via `mapdeck`.) 197 | ```{r mapview2, eval = TRUE} 198 | m <- mapview(randomaddresses, map.type="OpenStreetMap.HOT", cex = 1, color = "blue") 199 | if (!file.exists("map2.html")) { 200 | mapshot(m, "map2.html") 201 | } 202 | ``` 203 | ![](map2_mv.png) 204 | 205 | [Interactive version of this map](map2.html) 206 | 207 | ## 7. Create a street network database 208 | 209 | 210 | Road distance and travel time from each address to each hospital 211 | can be computed using a database of the street network within the bounding polygon 212 | defined by `basicDemographicsRehab`. Street network data can be obtained from 213 | OpenStreetMap using the `dodgr` package, which calls the `osmdata` package to do the downloading. 214 | 215 | Use of a carefully selected polygon of interest will, in many cases, dramatically reduce the 216 | download volume compared to a simple rectangular bounding box. 217 | Here we use the polygon defined by our nearby postcodes, which first need to 218 | be re-projected onto the CRS of OpenStreetMap data. `st_union` merges all of the polygons to form the single 219 | enclosing polygon, and the final command extracts the coordinates in a form required for the 220 | dodgr query. 221 | ```{r postcode-bounding-polygon, eval = TRUE} 222 | bounding_polygon <- sf::st_transform(basicDemographicsRehab, 223 | sf::st_crs(4326)) %>% 224 | sf::st_union () #%>% 225 | #sf::st_coordinates () 226 | #bounding_polygon <- bounding_polygon [, 1:2] 227 | ``` 228 | We now need to create the street network enclosed within the polygon. It is possible to 229 | directly retrieve small street networks with `dodgr_streetnet`, but larger networks tend 230 | to result in intermittent errors. The simplest way to create larger street networks 231 | is to first retrieve regional OpenStreemap databases and query those, as follows (downloading 232 | and converting to a geopackage file to avoid problems with large data sizes): 233 | ```{r} 234 | osmdb <- here::here("data", "australia-latest.osm.pbf") 235 | osmgpkg <- here::here("data", "australia-latest.gpkg") 236 | if (!file.exists(osmdb)) { 237 | origtimeout <- options(timeout=600) 238 | download.file("https://download.geofabrik.de/australia-oceania/australia-latest.osm.pbf", destfile = osmdb) 239 | p <- osmextract::oe_vectortranslate(file_path=osmdb ) 240 | options(timeout=origtimeout) 241 | } 242 | ``` 243 | 244 | Geopackages are sqlite databases that can be queried as follows: 245 | 246 | ```{r} 247 | # list the tables 248 | aus_tables <- sf::read_sf(osmgpkg, query="SELECT * from gpkg_contents") 249 | print(aus_tables) 250 | 251 | # wkt is a spatialfilter defining which lines we read. 252 | wkt <- st_as_text(st_geometry(bounding_polygon)) 253 | dandenong_streets <- read_sf(osmgpkg, wkt_filter = wkt, query="select * from lines where highway!='NULL'") 254 | ``` 255 | 256 | 257 | We can now download the street network enclosed within the polygon. Note that 258 | this is still a rather large network - over 40MB of data representing over 259 | 60,000 street sections - that might take a minute or two to process. It is 260 | therefore easier to save the result to disc for quicker re-use. 261 | 262 | 263 | ## 8. Estimation of travel time 264 | 265 | Travel time is estimated using distance along the stret network. The 266 | `dodgr` package needs to decompose the `sf`-formatted street network, which 267 | consists of long, connected road segments, into individual edges. This is done 268 | with the `weight_streetnet()` function, which modifies the distance of each edge 269 | to reflect typical travel conditions for a nominated mode of transport. 270 | 271 | ```{r weight-streetnet, message = FALSE} 272 | library (dodgr) 273 | net <- weight_streetnet (dandenong_streets, wt_profile = "motorcar") 274 | format (nrow (net), big.mark = ",") 275 | ``` 276 | This command has decomposed the 277 | `r format (nrow (dandenong_streets), big.mark = ",")` streets into 278 | `r format (nrow (net), big.mark = ",")` distinct segments. The resultant network 279 | has a `d_weighted` column which preferentially weights the distances for the 280 | nominated mode of tranport. Those parts of the network which are unsuitable for 281 | vehicular transport have values of `.Machine$double.xmax = ` `r 282 | .Machine$double.xmax`. Because we want to align our random points to the 283 | *routable* component of the network, these need to be removed. 284 | ```{r} 285 | net <- net [which (net$d_weighted < .Machine$double.xmax), ] 286 | nrow (net) 287 | ``` 288 | This reduces the number of edges in the network to 289 | `r format (nrow(net), big.mark = ",")`. We can now use the `net` object to 290 | calculate the distances, along with simple numeric coordinates of our routing 291 | points, projected on to the same CRS as OpenStreetMap (OSM), which is 4326: 292 | ```{r get-routing-coordinates} 293 | fromCoords <- st_coordinates (st_transform (randomaddresses, crs = 4326)) 294 | toCoords <- st_coordinates (st_transform (RehabLocations, crs = 4326)) 295 | ``` 296 | Although not necessary, distance calculation is quicker if we map these `from` 297 | and `to` points precisely on to the network itself. OSM assigns unique 298 | identifiers to every single object, and so our routing coordinates can be 299 | converted to OSM identifiers of the nearest street nodes. The nodes themselves 300 | are obtained with the `dodgr_vertices()` function. 301 | ```{r convert-routing-coordinates} 302 | nodes <- dodgr_vertices (net) 303 | fromIDX <- match_pts_to_graph (nodes, fromCoords, connected = TRUE) 304 | from <- unique (nodes$id [fromIDX]) 305 | to <- nodes$id [match_pts_to_graph (nodes, toCoords, connected = TRUE)] 306 | 307 | ## Store graph node for each address 308 | randomaddresses <- mutate(randomaddresses, NodeIDX=fromIDX, GraphNodeID=nodes$id[fromIDX]) 309 | ``` 310 | The matrices of `from` and `to` coordinates have now been converted to simple 311 | vectors of OSM identifiers. Calculating the pair-wise distances between all of 312 | those coordinates is as simple as, 313 | ```{r dodgr-dists, eval = FALSE} 314 | d <- dodgr_dists (net, from = from, to = to) 315 | ``` 316 | ```{r dodgr-dists-with-time, echo = FALSE} 317 | st <- system.time ( 318 | d <- dodgr_dists (net, from = from, to = to) 319 | ) 320 | ``` 321 | 322 | And that takes only around `r formatC (st [3], format = "f", digits = 1)` 323 | seconds to calculate distances between (3 rehab centres times 20,000 random 324 | addresses = ) 60,000 pairs of points. Travel times may then be presumed directly 325 | proportional to those distances. 326 | 327 | 328 | ## 9. Address-based catchment basins 329 | 330 | First assign each point to its nearest hospital according to the street network 331 | distances returned from `dodgr_dists`. Note that points on the outer periphery 332 | of the network may not necessarily be connected to the main part of the network, 333 | as we'll see below. The following code assigns each source address to the 334 | nearest destination. 335 | ```{r alllocate-points} 336 | DestNames <- c(rownames(RehabLocations), "Disconnected") 337 | DestNumber <- as.numeric (apply(d, MARGIN=1, which.min)) 338 | DestNumber [is.na (DestNumber)] <- 4 # the disconnected points 339 | BestDestination <- DestNames[DestNumber] 340 | table (BestDestination) 341 | ``` 342 | And there are `r length (which (DestNumber == 4))` points that are not 343 | connected. The allocation of points, including these disconnected ones, can be 344 | inspected on a map with the following code, start by setting up a `data.frame` 345 | of `fromCoords`. 346 | ```{r fromCoords} 347 | fromCoords <- nodes [match (from, nodes$id), ] 348 | fromCoords$DestNumber <- DestNumber 349 | fromCoords$Destination <- BestDestination 350 | ``` 351 | The results can be viewed with `mapview`, first requiring these points to be 352 | converted to `sf` form, where `coords = 2:3` simply specifies the longitude and 353 | latitude columns, and the `select` command filters the data down to just the 354 | geometrical points and the `DestNumber`, so the latter will be automatically 355 | used to colour the `mapview` points. 356 | ```{r disconnected-plot-mapview, eval = TRUE} 357 | fromCoords_sf <- st_as_sf (fromCoords, coords = 2:3, crs = 4326) %>% 358 | select (c (DestNumber, geometry)) 359 | m <- mapview (fromCoords_sf, map.type="OpenStreetMap.HOT", cex=1, color=DestNumber) 360 | if (!file.exists("map4.html")) { 361 | mapshot(m, "map4.html") 362 | } 363 | ``` 364 | ![](map4_mv.png) 365 | 366 | [Interactive version of this map](map4.html) 367 | 368 | This map (in its interactive form) clearly reveals that the 369 | `r length (which (BestDestination == "Disconnected"))` destinations that are 370 | disconnected from the street network all lie in the periphery, and can be simply 371 | discarded. 372 | 373 | ## 10. Polygon catchment basins 374 | 375 | As a final step, we'll convert those clusters of points into enclosing polygons, 376 | using a Voronoi tesselation. `sf::st_voronoi` doesn't return the polygons in the 377 | same order as the original points, requiring a manual re-sorting in order to use 378 | this to match voronoi polygons to points for each catchment. 379 | ```{r voronoi} 380 | g <- st_multipoint(as.matrix(fromCoords[,c("x", "y")])) 381 | v <- st_voronoi(x=g) # results in geometry collection objects 382 | v <- st_collection_extract(v) # converts to polygons 383 | fromCoords_sf <- st_as_sf(fromCoords, coords=c("x", "y")) 384 | vorder <- unlist(st_intersects(fromCoords_sf, v)) 385 | v <- v[vorder] # polygons in same order as points 386 | v <- st_sf (DestNumber = fromCoords$DestNumber, 387 | Destination = fromCoords$Destination, 388 | geometry = v, 389 | crs = 4326) 390 | ``` 391 | We then combine the Voronoi polygons associated with each rehabilitation centre to 392 | produce larger polgons defining the each catchment region. 393 | 394 | ```{r catchment-polygons, message = FALSE, eval = TRUE} 395 | bounding_polygon <- sf::st_transform(basicDemographicsRehab, 396 | sf::st_crs(4326)) %>% 397 | sf::st_union () 398 | v <- lapply (1:3, function (i) { 399 | v [v$DestNumber == i, ] %>% 400 | st_intersection (bounding_polygon) %>% 401 | st_union() }) 402 | v <- st_sf (DestNumber = 1:3, 403 | Destination = DestNames [1:3], 404 | geometry = do.call (c, v)) 405 | ``` 406 | 407 | Then plot with `mapview`, with easy addition of rehab centres: 408 | ```{r catchment-polygon-plot-mapview, eval = TRUE} 409 | m <- mapview (v, map.type="OpenStreetMap.HOT", col.regions=v$DestNumber, alpha.regions=0.4) %>% 410 | leafem::addFeatures (data = RehabLocations,color='blue', col.regions='blue', cex=10, opacity=1, fillOpacity=1) 411 | mapshot(m, "map5.html") 412 | ``` 413 | ![](map5_mv.png) 414 | 415 | [Interactive version of this map](map5.html) 416 | 417 | ## 11. Estimate caseload per centre 418 | 419 | Finally, we use a per postcode breakdown of proportion of addresses going to 420 | each centre, so that we can compute the number of cases going to each centre. 421 | 422 | In step 8 above we recorded the node id of each address. We now join the 423 | destination to the random address information based on the node id, allowing 424 | us to produce per postcode summaries, and thus per rehabilitation centre 425 | estimates. 426 | 427 | ```{r postcodes-fake, eval = FALSE} 428 | randomaddresses <- left_join(randomaddresses, fromCoords, by=c("GraphNodeID"="id")) 429 | postcodes <- st_set_geometry(randomaddresses, NULL) %>% group_by(POSTCODE, Destination) %>% summarise(n=length(DestNumber)) 430 | head (postcodes) 431 | ``` 432 | ```{r postcodes, echo = FALSE} 433 | randomaddresses <- left_join(randomaddresses, fromCoords, by=c("GraphNodeID"="id")) 434 | postcodes <- st_set_geometry(randomaddresses, NULL) %>% group_by(POSTCODE, Destination) %>% summarise(n=length(DestNumber)) 435 | ``` 436 | 437 | ```{r postcodes-kable, echo=FALSE} 438 | knitr::kable (head (postcodes)) 439 | ``` 440 | 441 | 442 | This table provides the breakdown for each postcode of cases going to each rehab 443 | centre. We simply need to allocate all of these to each centre with the 444 | following code, which converts the final estimated total cases to each centre 445 | into relative proportions. 446 | ```{r postcode-groups-allocation, eval = FALSE} 447 | postcodes %>% 448 | filter (Destination != "Disconnected") %>% 449 | group_by (Destination) %>% 450 | summarise (total = sum (n)) %>% 451 | mutate (percent = 100 * total / sum (total)) 452 | ``` 453 | ```{r postcode-groups-allocation-kable, eval = TRUE, echo = FALSE} 454 | postcodes %>% 455 | filter (Destination != "Disconnected") %>% 456 | group_by (Destination) %>% 457 | summarise (total = sum (n)) %>% 458 | mutate (percent = 100 * total / sum (total)) %>% 459 | knitr::kable (digits = 2) 460 | ``` 461 | 462 | 463 | Those results reflect random samples from each postcode, and so do not reflect 464 | possible demograhic differences in stroke rates between postcodes. That can be 465 | derived using the following table of stroke incidence per 100,000: 466 | 467 | Age | Incidence 468 | -- | -- 469 | 0-14 | 0 470 | 15-24 | 5 471 | 25-34 | 30 472 | 35-44 | 44 473 | 45-54 | 111 474 | 55-64 | 299 475 | 65-74 | 747 476 | 75-84 | 1928 477 | 85+ | 3976 478 | 479 | We have the demographic profile of each postcode in `basicDemographicsRehab`, 480 | for which we now need to regroup some of the columns (0-4 + 5-14, and 15-19 + 481 | 20-24). This then gives the total population for that postcode for each 482 | demographic group, from which we can work out the expected stroke incidence. 483 | The following code also removes previous demographic columns (the `select` 484 | line). 485 | ```{r} 486 | basicDemographicsRehab <- basicDemographicsRehab %>% 487 | select(-starts_with("POA_")) 488 | ``` 489 | 490 | ```{r mutate-basicDemographicsRehab} 491 | s <- 1 / 100000 # rate per 100,000 492 | basicDemographicsRehab <- basicDemographicsRehab %>% 493 | mutate (stroke_cases = s * ((Age_15_19_yr_P + Age_20_24_yr_P) * 5 + 494 | Age_25_34_yr_P * 30 + 495 | Age_35_44_yr_P * 44 + 496 | Age_45_54_yr_P * 111 + 497 | Age_55_64_yr_P * 299 + 498 | Age_65_74_yr_P * 747 + 499 | Age_75_84_yr_P * 1928 + 500 | Age_85ov_P * 3976)) %>% 501 | select (-c (contains ("_yr_"), contains ("85ov"))) 502 | ``` 503 | 504 | The per postcode estimate of stroke cases is then joined to our simulation 505 | data. 506 | ```{r join-postcodes-to-demographics, eval = FALSE} 507 | basicDemographicsRehab <- rename (basicDemographicsRehab, POSTCODE = Postcode) 508 | postcodes <- left_join (postcodes, basicDemographicsRehab, by = "POSTCODE") %>% 509 | select (POSTCODE, DestNumber, Destination, stroke_cases) 510 | postcodes 511 | ``` 512 | ```{r join-postcodes-to-demographics-kable, echo = FALSE} 513 | basicDemographicsRehab <- rename (basicDemographicsRehab, POSTCODE = Postcode) 514 | postcodes <- left_join (postcodes, basicDemographicsRehab, by = "POSTCODE") %>% 515 | select (POSTCODE, DestNumber, Destination, stroke_cases) 516 | knitr::kable (head (postcodes, n = 10), digits = 2) 517 | ``` 518 | 519 | The number of random addresses with valid destinations is then included in our postcodes data set. 520 | ```{r SamplesPerPostcode} 521 | postcodesamples <- filter(postcodes, Destination != "Disconnected") %>% 522 | group_by(POSTCODE) %>% 523 | summarise(totalsamples=sum(n)) 524 | postcodes <- left_join(postcodes, postcodesamples, by="POSTCODE") 525 | ``` 526 | 527 | Finally the proportion of cases from a postcode attending a rehabilitation 528 | center can be computed by dividing the number of random addresses attending a center 529 | by the total number of random addresses (usually 1000). The number of cases from a 530 | postcode attending a center is therefore the estimated stroke case count for the postcode 531 | multiplied by that proportion. The total loading can be computed by adding the contributions 532 | from all postcodes. 533 | 534 | ```{r postcode-groups-allocation2, eval = FALSE} 535 | postcodes %>% 536 | filter (Destination != "Disconnected") %>% 537 | group_by (Destination) %>% 538 | summarise (total = sum (stroke_cases * n/totalsamples)) %>% 539 | mutate (percent = 100 * total / sum (total)) 540 | ``` 541 | ```{r postcode-groups-allocation-kable2, eval = TRUE, echo = FALSE} 542 | postcodes %>% 543 | filter (Destination != "Disconnected") %>% 544 | group_by (Destination) %>% 545 | summarise (total = sum (stroke_cases * n/totalsamples)) %>% 546 | mutate (percent = 100 * total / sum (total)) %>% 547 | knitr::kable (digits = c (2, 0)) 548 | ``` 549 | -------------------------------------------------------------------------------- /RehabCatchment/makefile: -------------------------------------------------------------------------------- 1 | LFILE = README 2 | 3 | all: knith open 4 | 5 | knith: $(LFILE).Rmd 6 | echo "rmarkdown::render('$(LFILE).Rmd',output_file='$(LFILE).html')" | R --no-save -q 7 | 8 | knitr: $(LFILE).Rmd 9 | echo "rmarkdown::render('$(LFILE).Rmd',rmarkdown::md_document(variant='gfm'))" | R --no-save -q 10 | 11 | open: $(LFILE).html 12 | xdg-open $(LFILE).html & 13 | 14 | clean: 15 | rm -rf *.html *.png README_cache 16 | -------------------------------------------------------------------------------- /RehabCatchment/map1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map1.png -------------------------------------------------------------------------------- /RehabCatchment/map1_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map1_mv.png -------------------------------------------------------------------------------- /RehabCatchment/map2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map2.png -------------------------------------------------------------------------------- /RehabCatchment/map2_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map2_mv.png -------------------------------------------------------------------------------- /RehabCatchment/map3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map3.png -------------------------------------------------------------------------------- /RehabCatchment/map3_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map3_mv.png -------------------------------------------------------------------------------- /RehabCatchment/map4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map4.png -------------------------------------------------------------------------------- /RehabCatchment/map4_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map4_mv.png -------------------------------------------------------------------------------- /RehabCatchment/map5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map5.png -------------------------------------------------------------------------------- /RehabCatchment/map5_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchment/map5_mv.png -------------------------------------------------------------------------------- /RehabCatchment/rvspy/README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Catchment areas for rehabilitation centres: R versus Python" 3 | output: 4 | rmarkdown::html_vignette: 5 | self_contained: no 6 | 7 | md_document: 8 | variant: markdown_github 9 | --- 10 | 11 | ```{r opts, echo = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | warning = TRUE, 15 | message = TRUE, 16 | width = 120, 17 | comment = "#>", 18 | fig.retina = 2, 19 | fig.path = "README-", 20 | fig.width = 10 21 | ) 22 | ``` 23 | 24 | The R and python analyses differed in 2 primary ways: 25 | 26 | 1. The R routing used a weighted network, so cars were preferentially routed 27 | along the weighted version, yet resultant distances calculated from the 28 | unweighted version. The python routing used a non-weighted graph, with the 29 | network instead reduced to only those ways usable by cars. 30 | 2. The R analyses sampled actual postcode addresses with the `PSMA` package, 31 | while the python analyses simply sampled random points within each postcode. 32 | 33 | The effects of these 2 differences are compared here (in R code). This code is 34 | merely a stripped-down version of code from the main document. 35 | 36 | 37 | ```{r initial-load, message = FALSE} 38 | library(tidyverse) 39 | library(sf) 40 | library(units) 41 | library(tmaptools) 42 | ``` 43 | 44 | # Data pre-processing 45 | 46 | Load and clean Census Data 47 | ```{r load-basic-data} 48 | postcodeboundariesAUS <- 49 | file.path(here::here(), "ABSData", "Boundaries/POA_2016_AUST.shp") %>% 50 | sf::read_sf () 51 | 52 | basicDemographicsVIC <- file.path(here::here(), "ABSData", 53 | "2016 Census GCP Postal Areas for VIC", 54 | "2016Census_G01_VIC_POA.csv") %>% 55 | readr::read_csv() 56 | basicDemographicsVIC <- select(basicDemographicsVIC, POA_CODE_2016, starts_with("Age_"), -starts_with("Age_psns_")) 57 | ``` 58 | 59 | JoinCensusAndBoundaries 60 | ```{r join-demo-postcodes} 61 | basicDemographicsVIC <- right_join(postcodeboundariesAUS, 62 | basicDemographicsVIC, 63 | by=c("POA_CODE" = "POA_CODE_2016")) 64 | ``` 65 | 66 | Geocode and transform RehabNetwork 67 | ```{r rehab-addresses} 68 | rehab_addresses <- c(DandenongHospital = "Dandenong Hospital, Dandenong VIC 3175, Australia", 69 | CaseyHospital = "62-70 Kangan Dr, Berwick VIC 3806, Australia", 70 | KingstonHospital = "The Kingston Centre, Heatherton VIC 3202, Australia") 71 | RehabLocations <- tmaptools::geocode_OSM(rehab_addresses, as.sf=TRUE) 72 | RehabLocations <- sf::st_transform(RehabLocations, 73 | sf::st_crs(basicDemographicsVIC)) 74 | ``` 75 | 76 | Postcodes surrounding rehab locations 77 | 78 | ```{r postcode-dists} 79 | dist_to_loc <- function (geometry, location){ 80 | units::set_units(st_distance(geometry, location)[,1], km) 81 | } 82 | dist_range <- units::set_units(10, km) 83 | 84 | basicDemographicsVIC_old <- basicDemographicsVIC 85 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 86 | DirectDistanceToDandenong = dist_to_loc(geometry,RehabLocations["DandenongHospital", ]), 87 | DirectDistanceToCasey = dist_to_loc(geometry,RehabLocations["CaseyHospital", ]), 88 | DirectDistanceToKingston = dist_to_loc(geometry,RehabLocations["KingstonHospital", ]), 89 | DirectDistanceToNearest = pmin(DirectDistanceToDandenong, 90 | DirectDistanceToCasey, 91 | DirectDistanceToKingston) 92 | ) 93 | basicDemographicsRehab <- filter(basicDemographicsVIC, 94 | DirectDistanceToNearest < dist_range) %>% 95 | mutate(Postcode = as.numeric(POA_CODE16)) %>% 96 | select(-starts_with("POA_")) 97 | ``` 98 | 99 | ## Data sampling 100 | 101 | The major difference between the R and python code is the sampling method. The R 102 | code sampled actual random addresses from postcodes, whereas the python code - 103 | simply because of the unavailability of the amazing `PSMA` R package - could not 104 | do this, and so sample random points from within the postcode polygons. These 105 | two approaches are replicated here in R code, the first referred to as 106 | `randomaddresses`, the second as `randomPoints`. 107 | 108 | The `addressesPerPostcode` value below is modified by the estimated stroke rate 109 | per postcode calculated in the python code. 110 | 111 | ```{r addressesPerPostcode} 112 | addressesPerPostcode <- 1000 113 | ``` 114 | The python code has fewer postcodes than the R code, with numbers determined 115 | manually here by comparing the corresponding maps. The reduced version 116 | equivalent to the python code is: 117 | ```{r} 118 | #mapview::mapview (basicDemographicsRehab_py) 119 | removes <- c (40, 56, 57, 53, 43, 10, 7, 8, 29, 11, 1, 3) 120 | index <- seq (nrow (basicDemographicsRehab)) 121 | basicDemographicsRehab_py <- basicDemographicsRehab [!index %in% removes, ] 122 | ``` 123 | 124 | Random addresses: 125 | ```{r random-addresses} 126 | library(PSMA) 127 | samplePCode <- function(pcode, number) { 128 | d <- fetch_postcodes(pcode) 129 | return(d[, .SD[sample(.N, min(number, .N))], by=.(POSTCODE)]) 130 | } 131 | 132 | randomaddresses <- map(basicDemographicsRehab$Postcode, 133 | samplePCode, 134 | number=addressesPerPostcode) %>% 135 | bind_rows() %>% 136 | sf::st_as_sf(coords = c("LONGITUDE", "LATITUDE"), 137 | crs=st_crs(basicDemographicsRehab), 138 | agr = "constant") 139 | randomaddresses_py <- map(basicDemographicsRehab_py$Postcode, 140 | samplePCode, 141 | number=addressesPerPostcode) %>% 142 | bind_rows() %>% 143 | sf::st_as_sf(coords = c("LONGITUDE", "LATITUDE"), 144 | crs=st_crs(basicDemographicsRehab), 145 | agr = "constant") 146 | ``` 147 | 148 | Random points: 149 | ```{r random-points} 150 | randomPoints <- apply (basicDemographicsRehab, 1, function (i) { 151 | x <- st_sample (i$geometry, 152 | size = addressesPerPostcode) 153 | st_sf (POSTCODE = i$Postcode, 154 | geometry = x) 155 | }) 156 | randomPoints <- do.call (rbind, randomPoints) 157 | st_crs (randomPoints) <- 4326 158 | randomPoints_py <- apply (basicDemographicsRehab_py, 1, function (i) { 159 | x <- st_sample (i$geometry, 160 | size = addressesPerPostcode) 161 | st_sf (POSTCODE = i$Postcode, 162 | geometry = x) 163 | }) 164 | randomPoints_py <- do.call (rbind, randomPoints_py) 165 | st_crs (randomPoints_py) <- 4326 166 | ``` 167 | 168 | 169 | Code to examine the distributions. The two are not shown here, to avoid junking 170 | up the repo with unnecessary files, but there really is a striking difference - 171 | the postcodes are much more concentrated where people actually live, and so much 172 | greater overall spatial heterogeneity, while the random points have relatively 173 | many more points in less populated areas. 174 | ```{r mapdeck, eval = FALSE} 175 | library (mapdeck) 176 | set_token(Sys.getenv("MAPBOX_TOKEN")) 177 | mapdeck(location = c(145.2, -38), zoom = 14) %>% 178 | add_scatterplot (randomaddresses, radius = 2) 179 | mapdeck(location = c(145.2, -38), zoom = 14) %>% 180 | add_scatterplot (randomPoints, radius = 2) 181 | ``` 182 | 183 | 184 | ## Street Network 185 | 186 | ```{r postcode-bounding-polygon, eval = TRUE} 187 | bounding_polygon <- sf::st_transform(basicDemographicsRehab, 188 | sf::st_crs(4326)) %>% 189 | sf::st_union () %>% 190 | sf::st_coordinates () 191 | bounding_polygon <- bounding_polygon [, 1:2] 192 | ``` 193 | ```{r get-streetnet, eval = FALSE} 194 | library(dodgr) 195 | system.time ( 196 | dandenong_streets <- dodgr_streetnet (bounding_polygon, expand = 0, quiet = FALSE) 197 | ) 198 | saveRDS (dandenong_streets, file = "../dandenong-streets.Rds") 199 | ``` 200 | ```{r reload-streetnet-demo, eval = TRUE, message = FALSE} 201 | dandenong_streets <- readRDS ("../dandenong-streets.Rds") 202 | library (dodgr) 203 | net <- weight_streetnet (dandenong_streets, wt_profile = "motorcar") 204 | net <- net [which (net$d_weighted < .Machine$double.xmax), ] 205 | ``` 206 | An unweighted network analogous to that used in the python analyses can then be 207 | created simply by 208 | ```{r unweight-net} 209 | net_unwt <- net 210 | net_unwt$d_weighted <- net_unwt$d 211 | ``` 212 | A final bit of pre-processing to speed up the following code: 213 | ```{r dodgr_vertices} 214 | nodes <- dodgr_vertices (net) # same for both net and net_unwt 215 | ``` 216 | 217 | ## direct sample of street network points within postcode boundary 218 | 219 | Following the python code, simply sample a fixed number of random 220 | points from the street network within the entire postcode boundary, as well as 221 | simply from within the boundary itself. 222 | ```{r trulyRandomPoints} 223 | npts <- 10000 224 | pts_in_net <- as.matrix (nodes [sample (nrow (nodes), size = npts), 225 | c ("x", "y")]) %>% 226 | as.data.frame () %>% 227 | st_as_sf (coords = c (1, 2)) %>% 228 | st_sf (crs = st_crs (basicDemographicsRehab)) 229 | 230 | assign_postcodes <- function (pts, basicDemographicsRehab) 231 | { 232 | pts_in_postcodes <- st_contains (basicDemographicsRehab, pts) 233 | postcodes <- rep (NA, length (pts)) 234 | for (i in seq (pts_in_postcodes)) 235 | postcodes [pts_in_postcodes [[i]] ] <- basicDemographicsRehab$Postcode [i] 236 | st_sf (POSTCODE = postcodes, 237 | geometry = pts$geometry) 238 | } 239 | 240 | pts_in_net <- assign_postcodes (pts_in_net, basicDemographicsRehab) 241 | 242 | # Then points randomly sample from within the bounding polygon of all postcodes 243 | bp <- st_union (basicDemographicsRehab) 244 | pts_in_poly <- st_sf (geometry = st_sample (bp, size = npts)) 245 | pts_in_poly <- assign_postcodes (pts_in_poly, basicDemographicsRehab) 246 | ``` 247 | 248 | 249 | That suffices to now examine the differences in estimated cases per centre. 250 | 251 | ## CasesPerCentre 252 | 253 | ```{r cases-per-centre} 254 | cases_per_centre <- function (randomxy, net, nodes, RehabLocations, stroke_rate) 255 | { 256 | fromCoords <- st_coordinates (st_transform (randomxy, crs = 4326)) 257 | fromIDX <- match_pts_to_graph (nodes, fromCoords, connected = TRUE) 258 | from <- nodes$id [fromIDX] 259 | toCoords <- st_coordinates (st_transform (RehabLocations, crs = 4326)) 260 | to <- nodes$id [match_pts_to_graph (nodes, toCoords, connected = TRUE)] 261 | d <- dodgr_dists (net, from = from, to = to) 262 | 263 | DestNames <- c(rownames(RehabLocations), "Disconnected") 264 | DestNumber <- as.numeric (apply(d, MARGIN=1, which.min)) 265 | DestNumber [is.na (DestNumber)] <- 4 # the disconnected points 266 | BestDestination <- DestNames[DestNumber] 267 | postcodes <- data.frame (POSTCODE = randomxy$POSTCODE, 268 | DestNumber = DestNumber, 269 | Destination = BestDestination, 270 | stringsAsFactors = FALSE) %>% 271 | group_by (POSTCODE, DestNumber, Destination) %>% 272 | summarise (n = length (DestNumber)) 273 | index <- match (postcodes$POSTCODE, stroke_rate$POSTCODE) 274 | postcodes$load <- stroke_rate$strokes [index] 275 | 276 | postcodes %>% 277 | filter (Destination != "Disconnected") %>% 278 | group_by (Destination) %>% 279 | summarise (total = sum (load)) %>% 280 | mutate (percent = 100 * total / sum (total)) 281 | } 282 | ``` 283 | Then run that function for the eight possible combinations of differences, first 284 | loading the stroke rate estimates from the python code to use to load the final 285 | postcode-based estimates. 286 | ```{r, cases-per-centre-output} 287 | stroke_rate <- read.csv ("../../python/notebooks/data/postcode_strokes.csv", 288 | stringsAsFactors = FALSE) 289 | stroke_rate$POSTCODE <- substr (stroke_rate$POA_CODE, 4, 7) 290 | library (knitr) # just for neat table output 291 | kable (cases_per_centre (randomaddresses, net, nodes, RehabLocations, stroke_rate)) 292 | kable (cases_per_centre (randomaddresses, net_unwt, nodes, RehabLocations, stroke_rate)) 293 | kable (cases_per_centre (randomPoints, net, nodes, RehabLocations, stroke_rate)) 294 | kable (cases_per_centre (randomPoints, net_unwt, nodes, RehabLocations, stroke_rate)) 295 | 296 | # The `_py` addresses from the reduced set of postcodes 297 | kable (cases_per_centre (randomaddresses_py, net, nodes, RehabLocations, stroke_rate)) 298 | kable (cases_per_centre (randomaddresses_py, net_unwt, nodes, RehabLocations, stroke_rate)) 299 | kable (cases_per_centre (randomPoints_py, net, nodes, RehabLocations, stroke_rate)) 300 | kable (cases_per_centre (randomPoints_py, net_unwt, nodes, RehabLocations, stroke_rate)) 301 | 302 | # And finally the "trulyRandomAddresses" simply sample from within the enclosing 303 | # polygon of all postcodes 304 | kable (cases_per_centre (pts_in_net, net, nodes, RehabLocations, stroke_rate)) 305 | kable (cases_per_centre (pts_in_net, net_unwt, nodes, RehabLocations, stroke_rate)) 306 | kable (cases_per_centre (pts_in_poly, net, nodes, RehabLocations, stroke_rate)) 307 | kable (cases_per_centre (pts_in_poly, net_unwt, nodes, RehabLocations, stroke_rate)) 308 | ``` 309 | 310 | And that only makes a very small difference, in spite of the huge apparent 311 | difference in distributions of random points, and still does not reproduce the 312 | values generated in the python code. 313 | 314 | ```{r error-estimates, message = FALSE} 315 | get1 <- function (net, nodes, RehabLocations, BasicDemographicsRehab, 316 | stroke_rate, npts = 1000) 317 | { 318 | pts_in_net <- as.matrix (nodes [sample (nrow (nodes), size = npts), 319 | c ("x", "y")]) %>% 320 | as.data.frame () %>% 321 | st_as_sf (coords = c (1, 2)) %>% 322 | st_sf (crs = st_crs (basicDemographicsRehab)) 323 | pts_in_net <- assign_postcodes (pts_in_net, basicDemographicsRehab) 324 | cases_per_centre (pts_in_net, net_unwt, nodes, RehabLocations, 325 | stroke_rate)$percent 326 | } 327 | results <- lapply (1:10, function (i) 328 | get1 (net, nodes, RehabLocations, BasicDemographicsRehab, 329 | stroke_rate, npts = 1000)) 330 | results <- do.call (rbind, results) 331 | res <- rbind (apply (results, 2, mean), 332 | apply (results, 2, sd)) 333 | colnames (res) <- rownames (RehabLocations) 334 | rownames (res) <- c ("mean", "sd") 335 | res 336 | ``` 337 | 338 | -------------------------------------------------------------------------------- /RehabCatchment/rvspy/README.md: -------------------------------------------------------------------------------- 1 | The R and python analyses differed in 2 primary ways: 2 | 3 | 1. The R routing used a weighted network, so cars were preferentially 4 | routed along the weighted version, yet resultant distances 5 | calculated from the unweighted version. The python routing used a 6 | non-weighted graph, with the network instead reduced to only those 7 | ways usable by cars. 8 | 2. The R analyses sampled actual postcode addresses with the `PSMA` 9 | package, while the python analyses simply sampled random points 10 | within each postcode. 11 | 12 | The effects of these 2 differences are compared here (in R code). This 13 | code is merely a stripped-down version of code from the main document. 14 | 15 | ``` r 16 | library(tidyverse) 17 | library(sf) 18 | library(units) 19 | library(tmaptools) 20 | #> Warning in fun(libname, pkgname): rgeos: versions of GEOS runtime 3.7.1-CAPI-1.11.1 21 | #> and GEOS at installation 3.7.0-CAPI-1.11.0differ 22 | ``` 23 | 24 | # Data pre-processing 25 | 26 | Load and clean Census Data 27 | 28 | ``` r 29 | postcodeboundariesAUS <- 30 | file.path(here::here(), "ABSData", "Boundaries/POA_2016_AUST.shp") %>% 31 | sf::read_sf () 32 | 33 | basicDemographicsVIC <- file.path(here::here(), "ABSData", 34 | "2016 Census GCP Postal Areas for VIC", 35 | "2016Census_G01_VIC_POA.csv") %>% 36 | readr::read_csv() 37 | #> Parsed with column specification: 38 | #> cols( 39 | #> .default = col_double(), 40 | #> POA_CODE_2016 = col_character() 41 | #> ) 42 | #> See spec(...) for full column specifications. 43 | basicDemographicsVIC <- select(basicDemographicsVIC, POA_CODE_2016, starts_with("Age_"), -starts_with("Age_psns_")) 44 | ``` 45 | 46 | JoinCensusAndBoundaries 47 | 48 | ``` r 49 | basicDemographicsVIC <- right_join(postcodeboundariesAUS, 50 | basicDemographicsVIC, 51 | by=c("POA_CODE" = "POA_CODE_2016")) 52 | ``` 53 | 54 | Geocode and transform 55 | RehabNetwork 56 | 57 | ``` r 58 | rehab_addresses <- c(DandenongHospital = "Dandenong Hospital, Dandenong VIC 3175, Australia", 59 | CaseyHospital = "62-70 Kangan Dr, Berwick VIC 3806, Australia", 60 | KingstonHospital = "The Kingston Centre, Heatherton VIC 3202, Australia") 61 | RehabLocations <- tmaptools::geocode_OSM(rehab_addresses, as.sf=TRUE) 62 | RehabLocations <- sf::st_transform(RehabLocations, 63 | sf::st_crs(basicDemographicsVIC)) 64 | ``` 65 | 66 | Postcodes surrounding rehab locations 67 | 68 | ``` r 69 | dist_to_loc <- function (geometry, location){ 70 | units::set_units(st_distance(geometry, location)[,1], km) 71 | } 72 | dist_range <- units::set_units(10, km) 73 | 74 | basicDemographicsVIC_old <- basicDemographicsVIC 75 | basicDemographicsVIC <- mutate(basicDemographicsVIC, 76 | DirectDistanceToDandenong = dist_to_loc(geometry,RehabLocations["DandenongHospital", ]), 77 | DirectDistanceToCasey = dist_to_loc(geometry,RehabLocations["CaseyHospital", ]), 78 | DirectDistanceToKingston = dist_to_loc(geometry,RehabLocations["KingstonHospital", ]), 79 | DirectDistanceToNearest = pmin(DirectDistanceToDandenong, 80 | DirectDistanceToCasey, 81 | DirectDistanceToKingston) 82 | ) 83 | basicDemographicsRehab <- filter(basicDemographicsVIC, 84 | DirectDistanceToNearest < dist_range) %>% 85 | mutate(Postcode = as.numeric(POA_CODE16)) %>% 86 | select(-starts_with("POA_")) 87 | ``` 88 | 89 | ## Data sampling 90 | 91 | The major difference between the R and python code is the sampling 92 | method. The R code sampled actual random addresses from postcodes, 93 | whereas the python code - simply because of the unavailability of the 94 | amazing `PSMA` R package - could not do this, and so sample random 95 | points from within the postcode polygons. These two approaches are 96 | replicated here in R code, the first referred to as `randomaddresses`, 97 | the second as `randomPoints`. 98 | 99 | The `addressesPerPostcode` value below is modified by the estimated 100 | stroke rate per postcode calculated in the python code. 101 | 102 | ``` r 103 | addressesPerPostcode <- 1000 104 | ``` 105 | 106 | The python code has fewer postcodes than the R code, with numbers 107 | determined manually here by comparing the corresponding maps. The 108 | reduced version equivalent to the python code is: 109 | 110 | ``` r 111 | #mapview::mapview (basicDemographicsRehab_py) 112 | removes <- c (40, 56, 57, 53, 43, 10, 7, 8, 29, 11, 1, 3) 113 | index <- seq (nrow (basicDemographicsRehab)) 114 | basicDemographicsRehab_py <- basicDemographicsRehab [!index %in% removes, ] 115 | ``` 116 | 117 | Random addresses: 118 | 119 | ``` r 120 | library(PSMA) 121 | samplePCode <- function(pcode, number) { 122 | d <- fetch_postcodes(pcode) 123 | return(d[, .SD[sample(.N, min(number, .N))], by=.(POSTCODE)]) 124 | } 125 | 126 | randomaddresses <- map(basicDemographicsRehab$Postcode, 127 | samplePCode, 128 | number=addressesPerPostcode) %>% 129 | bind_rows() %>% 130 | sf::st_as_sf(coords = c("LONGITUDE", "LATITUDE"), 131 | crs=st_crs(basicDemographicsRehab), 132 | agr = "constant") 133 | randomaddresses_py <- map(basicDemographicsRehab_py$Postcode, 134 | samplePCode, 135 | number=addressesPerPostcode) %>% 136 | bind_rows() %>% 137 | sf::st_as_sf(coords = c("LONGITUDE", "LATITUDE"), 138 | crs=st_crs(basicDemographicsRehab), 139 | agr = "constant") 140 | ``` 141 | 142 | Random points: 143 | 144 | ``` r 145 | randomPoints <- apply (basicDemographicsRehab, 1, function (i) { 146 | x <- st_sample (i$geometry, 147 | size = addressesPerPostcode) 148 | st_sf (POSTCODE = i$Postcode, 149 | geometry = x) 150 | }) 151 | randomPoints <- do.call (rbind, randomPoints) 152 | st_crs (randomPoints) <- 4326 153 | randomPoints_py <- apply (basicDemographicsRehab_py, 1, function (i) { 154 | x <- st_sample (i$geometry, 155 | size = addressesPerPostcode) 156 | st_sf (POSTCODE = i$Postcode, 157 | geometry = x) 158 | }) 159 | randomPoints_py <- do.call (rbind, randomPoints_py) 160 | st_crs (randomPoints_py) <- 4326 161 | ``` 162 | 163 | Code to examine the distributions. The two are not shown here, to avoid 164 | junking up the repo with unnecessary files, but there really is a 165 | striking difference - the postcodes are much more concentrated where 166 | people actually live, and so much greater overall spatial heterogeneity, 167 | while the random points have relatively many more points in less 168 | populated areas. 169 | 170 | ``` r 171 | library (mapdeck) 172 | set_token(Sys.getenv("MAPBOX_TOKEN")) 173 | mapdeck(location = c(145.2, -38), zoom = 14) %>% 174 | add_scatterplot (randomaddresses, radius = 2) 175 | mapdeck(location = c(145.2, -38), zoom = 14) %>% 176 | add_scatterplot (randomPoints, radius = 2) 177 | ``` 178 | 179 | ## Street Network 180 | 181 | ``` r 182 | bounding_polygon <- sf::st_transform(basicDemographicsRehab, 183 | sf::st_crs(4326)) %>% 184 | sf::st_union () %>% 185 | sf::st_coordinates () 186 | bounding_polygon <- bounding_polygon [, 1:2] 187 | ``` 188 | 189 | ``` r 190 | library(dodgr) 191 | system.time ( 192 | dandenong_streets <- dodgr_streetnet (bounding_polygon, expand = 0, quiet = FALSE) 193 | ) 194 | saveRDS (dandenong_streets, file = "../dandenong-streets.Rds") 195 | ``` 196 | 197 | ``` r 198 | dandenong_streets <- readRDS ("../dandenong-streets.Rds") 199 | library (dodgr) 200 | net <- weight_streetnet (dandenong_streets, wt_profile = "motorcar") 201 | net <- net [which (net$d_weighted < .Machine$double.xmax), ] 202 | ``` 203 | 204 | An unweighted network analogous to that used in the python analyses can 205 | then be created simply by 206 | 207 | ``` r 208 | net_unwt <- net 209 | net_unwt$d_weighted <- net_unwt$d 210 | ``` 211 | 212 | A final bit of pre-processing to speed up the following code: 213 | 214 | ``` r 215 | nodes <- dodgr_vertices (net) # same for both net and net_unwt 216 | ``` 217 | 218 | ## direct sample of street network points within postcode boundary 219 | 220 | Following the python code, simply sample a fixed number of random points 221 | from the street network within the entire postcode boundary, as well as 222 | simply from within the boundary itself. 223 | 224 | ``` r 225 | npts <- 10000 226 | pts_in_net <- as.matrix (nodes [sample (nrow (nodes), size = npts), 227 | c ("x", "y")]) %>% 228 | as.data.frame () %>% 229 | st_as_sf (coords = c (1, 2)) %>% 230 | st_sf (crs = st_crs (basicDemographicsRehab)) 231 | 232 | assign_postcodes <- function (pts, basicDemographicsRehab) 233 | { 234 | pts_in_postcodes <- st_contains (basicDemographicsRehab, pts) 235 | postcodes <- rep (NA, length (pts)) 236 | for (i in seq (pts_in_postcodes)) 237 | postcodes [pts_in_postcodes [[i]] ] <- basicDemographicsRehab$Postcode [i] 238 | st_sf (POSTCODE = postcodes, 239 | geometry = pts$geometry) 240 | } 241 | 242 | pts_in_net <- assign_postcodes (pts_in_net, basicDemographicsRehab) 243 | #> although coordinates are longitude/latitude, st_contains assumes that they are planar 244 | 245 | # Then points randomly sample from within the bounding polygon of all postcodes 246 | bp <- st_union (basicDemographicsRehab) 247 | pts_in_poly <- st_sf (geometry = st_sample (bp, size = npts)) 248 | #> although coordinates are longitude/latitude, st_intersects assumes that they are planar 249 | pts_in_poly <- assign_postcodes (pts_in_poly, basicDemographicsRehab) 250 | #> although coordinates are longitude/latitude, st_contains assumes that they are planar 251 | ``` 252 | 253 | That suffices to now examine the differences in estimated cases per 254 | centre. 255 | 256 | ## CasesPerCentre 257 | 258 | ``` r 259 | cases_per_centre <- function (randomxy, net, nodes, RehabLocations, stroke_rate) 260 | { 261 | fromCoords <- st_coordinates (st_transform (randomxy, crs = 4326)) 262 | fromIDX <- match_pts_to_graph (nodes, fromCoords, connected = TRUE) 263 | from <- nodes$id [fromIDX] 264 | toCoords <- st_coordinates (st_transform (RehabLocations, crs = 4326)) 265 | to <- nodes$id [match_pts_to_graph (nodes, toCoords, connected = TRUE)] 266 | d <- dodgr_dists (net, from = from, to = to) 267 | 268 | DestNames <- c(rownames(RehabLocations), "Disconnected") 269 | DestNumber <- as.numeric (apply(d, MARGIN=1, which.min)) 270 | DestNumber [is.na (DestNumber)] <- 4 # the disconnected points 271 | BestDestination <- DestNames[DestNumber] 272 | postcodes <- data.frame (POSTCODE = randomxy$POSTCODE, 273 | DestNumber = DestNumber, 274 | Destination = BestDestination, 275 | stringsAsFactors = FALSE) %>% 276 | group_by (POSTCODE, DestNumber, Destination) %>% 277 | summarise (n = length (DestNumber)) 278 | index <- match (postcodes$POSTCODE, stroke_rate$POSTCODE) 279 | postcodes$load <- stroke_rate$strokes [index] 280 | 281 | postcodes %>% 282 | filter (Destination != "Disconnected") %>% 283 | group_by (Destination) %>% 284 | summarise (total = sum (load)) %>% 285 | mutate (percent = 100 * total / sum (total)) 286 | } 287 | ``` 288 | 289 | Then run that function for the eight possible combinations of 290 | differences, first loading the stroke rate estimates from the python 291 | code to use to load the final postcode-based 292 | estimates. 293 | 294 | ``` r 295 | stroke_rate <- read.csv ("../../python/notebooks/data/postcode_strokes.csv", 296 | stringsAsFactors = FALSE) 297 | stroke_rate$POSTCODE <- substr (stroke_rate$POA_CODE, 4, 7) 298 | library (knitr) # just for neat table output 299 | kable (cases_per_centre (randomaddresses, net, nodes, RehabLocations, stroke_rate)) 300 | ``` 301 | 302 | | Destination | total | percent | 303 | | :---------------- | --------: | -------: | 304 | | CaseyHospital | 705.3581 | 14.25748 | 305 | | DandenongHospital | 2001.2102 | 40.45069 | 306 | | KingstonHospital | 2240.7153 | 45.29183 | 307 | 308 | ``` r 309 | kable (cases_per_centre (randomaddresses, net_unwt, nodes, RehabLocations, stroke_rate)) 310 | ``` 311 | 312 | | Destination | total | percent | 313 | | :---------------- | --------: | -------: | 314 | | CaseyHospital | 705.3581 | 14.89834 | 315 | | DandenongHospital | 1850.5974 | 39.08771 | 316 | | KingstonHospital | 2178.5182 | 46.01395 | 317 | 318 | ``` r 319 | kable (cases_per_centre (randomPoints, net, nodes, RehabLocations, stroke_rate)) 320 | ``` 321 | 322 | | Destination | total | percent | 323 | | :---------------- | --------: | -------: | 324 | | CaseyHospital | 809.1363 | 15.54715 | 325 | | DandenongHospital | 2001.2102 | 38.45227 | 326 | | KingstonHospital | 2394.0547 | 46.00058 | 327 | 328 | ``` r 329 | kable (cases_per_centre (randomPoints, net_unwt, nodes, RehabLocations, stroke_rate)) 330 | ``` 331 | 332 | | Destination | total | percent | 333 | | :---------------- | --------: | -------: | 334 | | CaseyHospital | 809.1363 | 15.79906 | 335 | | DandenongHospital | 1850.5974 | 36.13446 | 336 | | KingstonHospital | 2461.6856 | 48.06647 | 337 | 338 | ``` r 339 | 340 | # The `_py` addresses from the reduced set of postcodes 341 | kable (cases_per_centre (randomaddresses_py, net, nodes, RehabLocations, stroke_rate)) 342 | ``` 343 | 344 | | Destination | total | percent | 345 | | :---------------- | --------: | -------: | 346 | | CaseyHospital | 479.1516 | 13.56808 | 347 | | DandenongHospital | 1354.5159 | 38.35568 | 348 | | KingstonHospital | 1697.7935 | 48.07623 | 349 | 350 | ``` r 351 | kable (cases_per_centre (randomaddresses_py, net_unwt, nodes, RehabLocations, stroke_rate)) 352 | ``` 353 | 354 | | Destination | total | percent | 355 | | :---------------- | --------: | -------: | 356 | | CaseyHospital | 479.1516 | 14.80624 | 357 | | DandenongHospital | 1121.3991 | 34.65229 | 358 | | KingstonHospital | 1635.5964 | 50.54147 | 359 | 360 | ``` r 361 | kable (cases_per_centre (randomPoints_py, net, nodes, RehabLocations, stroke_rate)) 362 | ``` 363 | 364 | | Destination | total | percent | 365 | | :---------------- | --------: | -------: | 366 | | CaseyHospital | 479.1516 | 13.00346 | 367 | | DandenongHospital | 1354.5159 | 36.75955 | 368 | | KingstonHospital | 1851.1329 | 50.23699 | 369 | 370 | ``` r 371 | kable (cases_per_centre (randomPoints_py, net_unwt, nodes, RehabLocations, stroke_rate)) 372 | ``` 373 | 374 | | Destination | total | percent | 375 | | :---------------- | --------: | -------: | 376 | | CaseyHospital | 479.1516 | 13.81623 | 377 | | DandenongHospital | 1223.2709 | 35.27274 | 378 | | KingstonHospital | 1765.6121 | 50.91103 | 379 | 380 | ``` r 381 | 382 | # And finally the "trulyRandomAddresses" simply sample from within the enclosing 383 | # polygon of all postcodes 384 | kable (cases_per_centre (pts_in_net, net, nodes, RehabLocations, stroke_rate)) 385 | ``` 386 | 387 | | Destination | total | percent | 388 | | :---------------- | --------: | -------: | 389 | | CaseyHospital | 809.1363 | 15.54715 | 390 | | DandenongHospital | 2001.2102 | 38.45227 | 391 | | KingstonHospital | 2394.0547 | 46.00058 | 392 | 393 | ``` r 394 | kable (cases_per_centre (pts_in_net, net_unwt, nodes, RehabLocations, stroke_rate)) 395 | ``` 396 | 397 | | Destination | total | percent | 398 | | :---------------- | --------: | -------: | 399 | | CaseyHospital | 809.1363 | 16.08019 | 400 | | DandenongHospital | 1981.8425 | 39.38571 | 401 | | KingstonHospital | 2240.9030 | 44.53409 | 402 | 403 | ``` r 404 | kable (cases_per_centre (pts_in_poly, net, nodes, RehabLocations, stroke_rate)) 405 | ``` 406 | 407 | | Destination | total | percent | 408 | | :---------------- | --------: | -------: | 409 | | CaseyHospital | 809.1363 | 15.82109 | 410 | | DandenongHospital | 1911.0995 | 37.36783 | 411 | | KingstonHospital | 2394.0547 | 46.81108 | 412 | 413 | ``` r 414 | kable (cases_per_centre (pts_in_poly, net_unwt, nodes, RehabLocations, stroke_rate)) 415 | ``` 416 | 417 | | Destination | total | percent | 418 | | :---------------- | --------: | -------: | 419 | | CaseyHospital | 809.1363 | 16.28608 | 420 | | DandenongHospital | 1850.5974 | 37.24834 | 421 | | KingstonHospital | 2308.5340 | 46.46557 | 422 | 423 | And that only makes a very small difference, in spite of the huge 424 | apparent difference in distributions of random points, and still does 425 | not reproduce the values generated in the python code. 426 | 427 | ``` r 428 | get1 <- function (net, nodes, RehabLocations, BasicDemographicsRehab, 429 | stroke_rate, npts = 1000) 430 | { 431 | pts_in_net <- as.matrix (nodes [sample (nrow (nodes), size = npts), 432 | c ("x", "y")]) %>% 433 | as.data.frame () %>% 434 | st_as_sf (coords = c (1, 2)) %>% 435 | st_sf (crs = st_crs (basicDemographicsRehab)) 436 | pts_in_net <- assign_postcodes (pts_in_net, basicDemographicsRehab) 437 | cases_per_centre (pts_in_net, net_unwt, nodes, RehabLocations, 438 | stroke_rate)$percent 439 | } 440 | results <- lapply (1:10, function (i) 441 | get1 (net, nodes, RehabLocations, BasicDemographicsRehab, 442 | stroke_rate, npts = 1000)) 443 | results <- do.call (rbind, results) 444 | res <- rbind (apply (results, 2, mean), 445 | apply (results, 2, sd)) 446 | colnames (res) <- rownames (RehabLocations) 447 | rownames (res) <- c ("mean", "sd") 448 | res 449 | #> DandenongHospital CaseyHospital KingstonHospital 450 | #> mean 15.86253 37.62824 46.509231 451 | #> sd 0.92361 1.09542 1.262073 452 | ``` 453 | -------------------------------------------------------------------------------- /RehabCatchment/rvspy/makefile: -------------------------------------------------------------------------------- 1 | LFILE = README 2 | 3 | all: knith open 4 | 5 | knith: $(LFILE).Rmd 6 | echo "rmarkdown::render('$(LFILE).Rmd',output_file='$(LFILE).html')" | R --no-save -q 7 | 8 | knitr: $(LFILE).Rmd 9 | echo "rmarkdown::render('$(LFILE).Rmd',rmarkdown::md_document(variant='gfm'))" | R --no-save -q 10 | 11 | open: $(LFILE).html 12 | xdg-open $(LFILE).html & 13 | 14 | clean: 15 | rm -rf *.html *.png README_cache 16 | -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/Googleway_Mapdeck.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Googleway, Mapdeck and API keys" 3 | output: 4 | html_document: 5 | toc: true 6 | toc_float: true 7 | number_sections: false 8 | theme: flatly 9 | md_document: 10 | variant: markdown_github 11 | number_sections: true 12 | --- 13 | 14 | ## Introduction 15 | 16 | This document reproduces the rehabilitation center catchment basin example using services that require API keys. It is therefore 17 | a more advanced example that requires some setup by the user, specifically creating and installing access tokens and API keys, as discussed 18 | below. 19 | 20 | The document has been created using the author's keys and access tokens to produce 21 | screenshots and fetch some data and all code is included. However no keys are 22 | distributed with the document. Users will therefore need to obtain their own keys in order to experiment with 23 | the interactive options provided by `mapdeck` and the various Google services. 24 | 25 | ```{r setup, include=FALSE} 26 | knitr::opts_chunk$set(echo = TRUE) 27 | 28 | library(mapdeck) 29 | library(googleway) 30 | library(here) 31 | library(dplyr) 32 | ``` 33 | 34 | 35 | The `googleway` library provides access to Google Maps API, both for creating maps, and using the mapping services such as geocoding, direction and route finding, and searching for places. 36 | 37 | The `mapdeck` library allows you to plot large amounts of data on a Mapbox map through the `Deck.gl` javascript library developed by Uber. 38 | 39 | To use any of Google Map's services you will need an [API key](https://developers.google.com/maps/documentation/javascript/get-api-key), and to plot a Mapbox map you will need an [access token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/) 40 | 41 | ```{r visualisationGoogleway, echo = FALSE, eval = FALSE, include = FALSE} 42 | 43 | set_key(read.dcf("~/Documents/.googleAPI", fields = "GOOGLE_MAP_KEY"), api = "map") 44 | set_key(read.dcf("~/Documents/.googleAPI", fields = "GOOGLE_API_KEY")) 45 | set_token( read.dcf("~/Documents/.googleAPI", fields = "MAPBOX" ) ) 46 | ``` 47 | 48 | Replace "GOOGLE_MAP_KEY", "GOOGLE_API_KEY", "MAPBOX_TOKEN" with the respective tokens. The tokens are long collections of random characters and numbers and the should be 49 | surrounded by quotes. 50 | ```{r eval = FALSE} 51 | 52 | set_key( "GOOGLE_MAP_KEY", api = "map") ## for Google Maps 53 | set_key( "GOOGLE_API_KEY") ## for other Google services 54 | set_token( "MAPBOX_TOKEN" ) 55 | 56 | ``` 57 | 58 | In this section we are calculating the times and distances to the rehab centres from random addresses using Google's API (using the `googleway` library), and plotting the maps using the `mapdeck` library. 59 | 60 | The random address list, demographic data, and calculations used to determine case load per rehab centre are taken from section 11 of the Rehab Catchment Basement section. 61 | 62 | ## Geocoding 63 | 64 | The geocoding API will take as input an address, or search term, and return various pieces of information. For example, here we are querying the API for the three rehab centre addresses. 65 | 66 | ```{r, eval = FALSE} 67 | rehab_addresses <- c( 68 | DandenongHospital = "Dandenong Hospital, Dandenong VIC 3175, Australia", 69 | CaseyHospital = "62-70 Kangan Dr, Berwick VIC 3806, Australia", 70 | KingstonHospital = "The Kingston Centre, Heatherton VIC 3202, Australia" 71 | ) 72 | 73 | RehabLocations <- lapply(rehab_addresses, googleway::google_geocode) 74 | ``` 75 | 76 | ```{r eval = FALSE, echo = FALSE} 77 | saveRDS(RehabLocations, file = "../data/googleway/RehabLocations_googleway.rds") 78 | ``` 79 | 80 | ```{r, eval = TRUE, echo = FALSE} 81 | RehabLocations <- readRDS("../data/googleway/RehabLocations_googleway.rds") 82 | ``` 83 | 84 | From the results we can extract various pieces of information, such as the formatted address, the type of place, and the coordinates. 85 | 86 | ```{r} 87 | lapply( RehabLocations, googleway::geocode_address ) 88 | lapply( RehabLocations, googleway::geocode_address_components ) 89 | lapply( RehabLocations, googleway::geocode_coordinates ) 90 | 91 | ``` 92 | 93 | 94 | ## Estimate of travel time 95 | 96 | 97 | We have 56,000 random addresses, and 3 rehab centres. This makes `r format( 56000 * 3, big.mark = ",")` direction queries. 98 | 99 | Google charges 0.005 USD per query, but gives you $200 credit each month. 100 | 101 | Therefore, we get `r format(200 / 0.005, big.mark= ",")` queries credited per month. 102 | 103 | To stay within limits, and have enough spare, I'm going to sample 50 of those random addresses per postcode, and query the API for the directions from those addresses to each rehab centres. 104 | 105 | 106 | The first step is to format the results of the geocoding into an easily usable structure 107 | 108 | ```{r} 109 | 110 | ## formatting the geocoded rehab centres into a data.frame 111 | lst <- lapply( RehabLocations, function(x) { 112 | coords <- googleway::geocode_coordinates(x) 113 | data.frame( 114 | formatted_address = googleway::geocode_address( x ), 115 | lat = coords[["lat"]], 116 | lon = coords[["lng"]] 117 | ) 118 | }) 119 | 120 | df_rehab <- do.call(rbind, lst) 121 | rm(lst) 122 | 123 | knitr::kable( df_rehab ) 124 | 125 | ``` 126 | 127 | 128 | To find the directions we need to query one route at a time. We can do this in an `lapply`. 129 | 130 | As this takes a while we are saving the results of each query to file at each iteration. Each result is in JSON, so we are saving the raw JSON. 131 | 132 | ```{r, echo = FALSE } 133 | 134 | randomaddresses <- readRDS(here::here("data/googleway/randomaddresses/randomaddresses.rds")) 135 | 136 | ``` 137 | 138 | 139 | ```{r, eval = FALSE, echo = TRUE, include = TRUE} 140 | 141 | # ## TODO - get randomaddresses from RehabCatchment 142 | # ## need coordinates for googleway 143 | df_random <- as.data.frame( randomaddresses ) 144 | df_random[, c("lon", "lat")] <- sf::st_coordinates( randomaddresses ) 145 | 146 | set.seed(12345) 147 | df_sample <- df_random %>% 148 | dplyr::group_by(POSTCODE) %>% 149 | dplyr::sample_n(size = 50) 150 | 151 | df_sample$sample_row <- 1:nrow(df_sample) 152 | 153 | saveRDS(df_sample, here::here("data/googleway/directions_queries/df_sample2.rds")) 154 | 155 | res1 <- lapply(1:nrow(df_sample), function(x) { 156 | 157 | js <- googleway::google_directions( 158 | origin = as.numeric( df_sample[x, c("lat", "lon")] ) 159 | , destination = as.numeric( df_rehab[1, c("lat","lon")] ) 160 | , simplify = F 161 | ) 162 | js <- paste0(js, collapse = "") 163 | js <- gsub(" ","",js) 164 | fn <- here::here(paste0("data/googleway/directions_queries/rehab1_sample/",x,"_results.json")) 165 | f <- file(fn) 166 | writeLines(js, f) 167 | close(f) 168 | return( js ) 169 | }) 170 | 171 | res2 <- lapply(1:nrow(df_sample), function(x) { 172 | js <- googleway::google_directions( 173 | origin = as.numeric( df_sample[x, c("lat", "lon")] ) 174 | , destination = as.numeric( df_rehab[2, c("lat","lon")] ) 175 | , simplify = F 176 | ) 177 | js <- paste0(js, collapse = "") 178 | js <- gsub(" ","",js) 179 | fn <- here::here(paste0("data/googleway/directions_queries/rehab2_sample/",x,"_results.json") 180 | f <- file(fn) 181 | writeLines(js, f) 182 | close(f) 183 | return( js ) 184 | }) 185 | 186 | res3 <- lapply(1:nrow(df_sample), function(x) { 187 | js <- googleway::google_directions( 188 | origin = as.numeric( df_sample[x, c("lat", "lon")] ) 189 | , destination = as.numeric( df_rehab[3, c("lat","lon")] ) 190 | , simplify = F 191 | ) 192 | js <- paste0(js, collapse = "") 193 | js <- gsub(" ","",js) 194 | fn <- here::here(paste0("data/googleway/directions_queries/rehab3_sample/",x,"_results.json")) 195 | f <- file(fn) 196 | writeLines(js, f) 197 | close(f) 198 | return( js ) 199 | }) 200 | 201 | ``` 202 | 203 | ```{r, echo = FALSE} 204 | df_sample <- readRDS(here::here("data/googleway/directions_queries/df_sample2.rds")) 205 | ``` 206 | 207 | 208 | Now we have all the results of the directions query saved we can read them back into the R session. 209 | 210 | ```{r, echo = TRUE, include = FALSE, eval = FALSE} 211 | 212 | get_directions <- function(rehab) { 213 | path <- here::here("data/googleway/directions_queries") 214 | lst_files <- list.files(path = paste0(path, rehab) ) 215 | files <- paste0(path, rehab, lst_files) 216 | 217 | lst <- lapply( files, function(x) { 218 | js <- jsonlite::fromJSON( x ) 219 | bname <- basename( x ) 220 | sample_row <- gsub("[^0-9]","",bname) 221 | list(sample_row = sample_row, result = js) 222 | }) 223 | 224 | return( lst ) 225 | } 226 | 227 | directions1 <- get_directions( "/rehab1_sample/") 228 | directions2 <- get_directions( "/rehab2_sample/") 229 | directions3 <- get_directions( "/rehab3_sample/") 230 | ``` 231 | 232 | ```{r echo = FALSE, include = FALSE, eval = FALSE} 233 | 234 | saveRDS(directions1, file = here::here("data/googleway/directions/directions1_sample.rds")) 235 | saveRDS(directions2, file = here::here("data/googleway/directions/directions2_sample.rds")) 236 | saveRDS(directions3, file = here::here("data/googleway/directions/directions3_sample.rds")) 237 | 238 | ``` 239 | 240 | ```{r, echo = FALSE} 241 | directions1 <- readRDS(here::here("data/googleway/directions/directions1_sample.rds")) 242 | directions2 <- readRDS(here::here("data/googleway/directions/directions2_sample.rds")) 243 | directions3 <- readRDS(here::here("data/googleway/directions/directions3_sample.rds")) 244 | ``` 245 | 246 | 247 | The route geometry returned from the API comes in the form of an [encoded polyline](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). 248 | 249 | ```{r} 250 | substr( googleway::direction_polyline( directions1[[1]][["result"]] ), 1, 100 ) 251 | ``` 252 | 253 | We can convert this back into coordinates using the `googlePolylines` library, for example 254 | 255 | ```{r} 256 | pl <- googleway::direction_polyline( directions1[[1]][["result"]] ) 257 | 258 | head( googlePolylines::decode( pl )[[1]] ) 259 | ``` 260 | 261 | However, `googleway` and `mapdeck` both support plotting these polylines directly, so we can avoid this step and just use the polylines. We also get the distance and time on the routes in the API query results. 262 | 263 | ```{r} 264 | 265 | is_valid <- function( direction ) { 266 | direction[["result"]][["status"]] == "OK" 267 | } 268 | 269 | get_distance <- function(x) { 270 | ifelse( 271 | is_valid( x ) 272 | , googleway::direction_legs(x[["result"]])[["distance"]][["value"]] 273 | , NA_real_ 274 | ) 275 | } 276 | get_duration <- function(x) { 277 | ifelse( 278 | is_valid( x ) 279 | , googleway::direction_legs(x[["result"]])[["duration"]][["value"]] 280 | , NA_real_ 281 | ) 282 | } 283 | 284 | get_polyline <- function(x) { 285 | ifelse( 286 | is_valid( x ) 287 | , googleway::direction_polyline( x[["result"]] ) 288 | , NA_character_ 289 | ) 290 | } 291 | 292 | format_directions <- function( d, df_sample ) { 293 | secs <- sapply( d, get_duration ) 294 | dist <- sapply( d, get_distance ) 295 | sample_row <- sapply( d, function(x) x[["sample_row"]] ) 296 | street <- df_sample[ sample_row, ]$STREET_NAME 297 | postcode <- df_sample[ sample_row, ]$POSTCODE 298 | polylines <- sapply( d, get_polyline ) 299 | data.frame( 300 | id = sample_row 301 | , street = street 302 | , POSTCODE = postcode ## capitalised because we join on it later 303 | , polyline = polylines 304 | , distance_m = dist 305 | , duration_s = secs 306 | , duration_m = round( secs / 60, 2 ) 307 | , stringsAsFactors = FALSE 308 | ) 309 | } 310 | 311 | df_directions1 <- format_directions( directions1, df_sample ) 312 | df_directions2 <- format_directions( directions2, df_sample ) 313 | df_directions3 <- format_directions( directions3, df_sample ) 314 | 315 | ``` 316 | 317 | The `is_valid()` function is used to test the result of the API query. Sometimes the API will return an "ACCESS_DENIED" or "OVER_QUERY_LIMIT" response if it wasn't able to return any data. 318 | 319 | Now we have three data.frames, each containing the directions, distance, times and route for 2,800 random addresses to the rehab centres. 320 | 321 | ```{r, eval = FALSE} 322 | 323 | mapdeck( 324 | style = mapdeck_style("light") 325 | , location = c(145, -37.9) 326 | , zoom = 10 327 | ) %>% 328 | add_scatterplot( 329 | data = df_rehab[1, ] 330 | , lon = "lon", lat = "lat" 331 | , radius = 500 332 | , update_view = F 333 | ) %>% 334 | add_path( 335 | data = df_directions1[ !is.na( df_directions1$polyline ), ] 336 | , polyline = "polyline" 337 | , stroke_colour = "duration_m" 338 | , stroke_opacity = 100 339 | , stroke_width = 35 340 | , legend = T 341 | , legend_options = list( title = "Duration (minutes) ") 342 | , update_view = F 343 | , palette = "plasma" 344 | ) 345 | 346 | ``` 347 | 348 | ![](./directions_rehab1_sample_mapdeck2.png) 349 | 350 | 351 | ```{r, eval = FALSE} 352 | 353 | mapdeck( 354 | style = mapdeck_style("light") 355 | , location = c(145, -37.9) 356 | , zoom = 10 357 | ) %>% 358 | add_scatterplot( 359 | data = df_rehab[2, ] 360 | , lon = "lon", lat = "lat" 361 | , radius = 500 362 | , update_view = F 363 | ) %>% 364 | add_path( 365 | data = df_directions2[ !is.na( df_directions2$polyline ), ] 366 | , polyline = "polyline" 367 | , stroke_colour = "duration_m" 368 | , stroke_opacity = 100 369 | , stroke_width = 35 370 | , legend = T 371 | , legend_options = list( title = "Duration (minutes) ") 372 | , update_view = F 373 | , palette = "plasma" 374 | ) 375 | 376 | ``` 377 | 378 | ![](./directions_rehab2_sample_mapdeck.png) 379 | 380 | ```{r, eval = FALSE} 381 | 382 | mapdeck( 383 | style = mapdeck_style("light") 384 | , location = c(145, -37.9) 385 | , zoom = 10 386 | ) %>% 387 | add_scatterplot( 388 | data = df_rehab[3, ] 389 | , lon = "lon", lat = "lat" 390 | , radius = 500 391 | , update_view = F 392 | ) %>% 393 | add_path( 394 | data = df_directions3[ !is.na( df_directions3$polyline ), ] 395 | , polyline = "polyline" 396 | , stroke_colour = "duration_m" 397 | , stroke_opacity = 100 398 | , stroke_width = 35 399 | , legend = T 400 | , legend_options = list( title = "Duration (minutes) ") 401 | , update_view = F 402 | , palette = "plasma" 403 | ) 404 | 405 | ``` 406 | 407 | ![](./directions_rehab3_sample_mapdeck.png) 408 | 409 | 410 | ```{r, eval = FALSE} 411 | 412 | mapdeck( 413 | style = mapdeck_style("light") 414 | , location = c(145, -37.9) 415 | , zoom = 10 416 | ) %>% 417 | add_scatterplot( 418 | data = df_rehab 419 | , lon = "lon", lat = "lat" 420 | , radius = 500 421 | , update_view = F 422 | ) %>% 423 | add_path( 424 | data = df_directions1[ !is.na( df_directions1$polyline ), ] 425 | , polyline = "polyline" 426 | , stroke_colour = "duration_m" 427 | , stroke_opacity = 100 428 | , stroke_width = 35 429 | , legend = T 430 | , legend_options = list( title = "Duration to Dandenong (mins) ") 431 | , update_view = F 432 | , palette = "plasma" 433 | , layer_id = "rehab1" 434 | ) %>% 435 | add_path( 436 | data = df_directions2[ !is.na( df_directions2$polyline ), ] 437 | , polyline = "polyline" 438 | , stroke_colour = "duration_m" 439 | , stroke_opacity = 100 440 | , stroke_width = 35 441 | , legend = T 442 | , legend_options = list( title = "Duration to Casey (mins) ") 443 | , update_view = F 444 | , palette = "plasma" 445 | , layer_id = "rehab2" 446 | ) %>% 447 | add_path( 448 | data = df_directions3[ !is.na( df_directions3$polyline ), ] 449 | , polyline = "polyline" 450 | , stroke_colour = "duration_m" 451 | , stroke_opacity = 100 452 | , stroke_width = 35 453 | , legend = T 454 | , legend_options = list( title = "Duration to Kingston (mins) ") 455 | , update_view = F 456 | , palette = "plasma" 457 | , layer_id = "rehab3e" 458 | ) 459 | 460 | ``` 461 | 462 | ![](./directions_all_sample_mapdeck.png) 463 | 464 | We can extract the step-by-step guide, including times and locations, to get the 'time to destination' for each step. 465 | 466 | ```{r} 467 | ttd <- googleway::direction_steps( directions1[[1]][["result"]] ) 468 | total_time <- get_duration( directions1[[1]] ) 469 | 470 | ## calculate time remaining at every step 471 | ttd_duration <- ttd$duration 472 | ttd_duration$total_time <- cumsum( ttd_duration$value ) 473 | ttd_duration$time_remaining <- total_time - ttd_duration$total_time 474 | 475 | ## get the coordinates for every step 476 | ttd_duration <- cbind( ttd$start_location, ttd_duration ) 477 | ttd_duration 478 | ``` 479 | 480 | Doing this for each of the 8,400 directions gives us the time to the rehab centres for many points on the route, not just the origin. 481 | 482 | ```{r} 483 | time_to_destination <- function( direction ) { 484 | ttd <- googleway::direction_steps( direction[["result"]] ) 485 | total_time <- get_duration( direction ) 486 | 487 | ## calculate time remaining at every step 488 | ttd_duration <- ttd$duration 489 | ttd_duration$total_time <- cumsum( ttd_duration$value ) 490 | ttd_duration$time_remaining <- total_time - ttd_duration$total_time 491 | 492 | ## get the coordinates for every step 493 | ttd_duration <- cbind( ttd$start_location, ttd_duration ) 494 | ttd_duration$sample_row <- direction[["sample_row"]] 495 | 496 | if( inherits( ttd_duration, "data.frame" ) > 0 ) { 497 | ttd_duration$sample_row_pt <- 1:nrow( ttd_duration ) 498 | } 499 | ttd_duration 500 | } 501 | 502 | lst_times <- lapply( directions1, time_to_destination ) 503 | df_times1 <- do.call(rbind, lst_times) 504 | df_times1$rehab <- row.names(df_rehab[1, ]) 505 | 506 | lst_times <- lapply( directions2, time_to_destination ) 507 | df_times2 <- do.call(rbind, lst_times) 508 | df_times2$rehab <- row.names(df_rehab[2, ]) 509 | 510 | lst_times <- lapply( directions3, time_to_destination ) 511 | df_times3 <- do.call(rbind, lst_times) 512 | df_times3$rehab <- row.names(df_rehab[3, ]) 513 | ``` 514 | 515 | This now gives us `r format( nrow( df_times1 ) * 3, big.mark=",")` random addresses to each of the three rehab centres. 516 | 517 | This plot shows the average times to `r googleway::geocode_address( RehabLocations[[1]] )` for `r nrow( df_times1 )` addresses. The height and colour of hexagons represent the average time to the rehab centre. 518 | 519 | ```{r, eval = FALSE} 520 | mapdeck() %>% 521 | add_hexagon( 522 | data = df_times1 523 | , lon = "lng", lat = "lat" 524 | , elevation = "time_remaining" 525 | , elevation_function = "average" 526 | , colour = "time_remaining" 527 | , colour_function = "average" 528 | , elevation_scale = 10 529 | , radius = 250 530 | , colour_range = colourvalues::colour_values(1:6, palette = "plasma") 531 | , layer_id = "times1" 532 | ) 533 | 534 | ``` 535 | ![](./directions_rehab1_sample_mapdeck_hexagons.png) 536 | 537 | ```{r, eval = FALSE, echo = FALSE, include = FALSE} 538 | 539 | # df_times1$rehab <- row.names( df_rehab[1, ] ) 540 | # df_times2$rehab <- row.names( df_rehab[2, ] ) 541 | # df_times3$rehab <- row.names( df_rehab[3, ] ) 542 | # 543 | # df_times <- rbind( df_times1, df_times2, df_times3 ) 544 | # 545 | # df_min_times <- df_times %>% 546 | # group_by(lat, lng) %>% 547 | # summarise( min_time = min( time_remaining ) ) 548 | # 549 | # mapdeck() %>% 550 | # add_hexagon( 551 | # data = df_min_times 552 | # , lon = "lng", lat = "lat" 553 | # , elevation = "min_time" 554 | # , elevation_function = "average" 555 | # , colour = "min_time" 556 | # , colour_function = "average" 557 | # , elevation_scale = 10 558 | # , radius = 250 559 | # , colour_range = colourvalues::colour_values(1:6, palette = "plasma") 560 | # , layer_id = "times1" 561 | # ) 562 | 563 | ``` 564 | 565 | 566 | 567 | ## Distances 568 | 569 | While the Directions API gives us a lot of information, it only handles one direction at a time. If you want more (quantity and frequency), but less detailed results, you can query the distance API. 570 | 571 | In this example we are querying the distances for the first ten random addresses. 572 | 573 | ```{r, eval = FALSE} 574 | 575 | distance_matrix <- googleway::google_distance( 576 | origin = df_sample[1:10, c("lat", "lon")] 577 | , destination = googleway::geocode_address( RehabLocations[[1]] ) 578 | ) 579 | ``` 580 | 581 | ```{r eval = FALSE, echo = FALSE} 582 | saveRDS(distance_matrix, here::here("data/googleway/distances/distance_matrix.rds")) 583 | ``` 584 | 585 | ```{r echo = FALSE} 586 | distance_matrix <- readRDS(here::here("data/googleway/distances/distance_matrix.rds")) 587 | ``` 588 | 589 | ```{r} 590 | distance_matrix 591 | ``` 592 | 593 | 594 | Both the distance and directions API allow you to specify the `departure_time`, so it's possible to query the time & distance for a given time of day. 595 | 596 | 597 | ## Address-based catchment basins 598 | 599 | From Google's API results we have the driving time to each of the rehab centres for the random addresses. So we can get the nearest rehab centre for each address and view them with mapdeck 600 | 601 | 602 | ```{r} 603 | 604 | df_directions1$rehab <- row.names(df_rehab)[1] 605 | df_directions2$rehab <- row.names(df_rehab)[2] 606 | df_directions3$rehab <- row.names(df_rehab)[3] 607 | 608 | df_directions1$rehab_address <- df_rehab[1, "formatted_address"] 609 | df_directions2$rehab_address <- df_rehab[2, "formatted_address"] 610 | df_directions3$rehab_address <- df_rehab[3, "formatted_address"] 611 | 612 | df_directions <- rbind( 613 | df_directions1 614 | , df_directions2 615 | , df_directions3 616 | ) 617 | 618 | df_nearest <- df_directions %>% 619 | dplyr::group_by(id) %>% 620 | dplyr::arrange(duration_m) %>% 621 | dplyr::slice(1) %>% 622 | dplyr::ungroup() 623 | 624 | ``` 625 | 626 | Here we see which rehab centre each address goes to 627 | 628 | ```{r, eval = FALSE} 629 | 630 | mapdeck( 631 | style = mapdeck_style("light") 632 | , location = c(145.1, -37.9) 633 | , zoom = 10 634 | ) %>% 635 | add_scatterplot( 636 | data = df_rehab 637 | , lon = "lon", lat = "lat" 638 | , radius = 600 639 | ) %>% 640 | add_path( 641 | data = df_nearest 642 | , polyline = "polyline" 643 | , stroke_colour = "rehab" 644 | , legend = T 645 | , legend_options = list(css = "max-width: 300px") 646 | , update_view = F 647 | ) 648 | 649 | ``` 650 | ![](./nearest_rehab.png) 651 | 652 | This map shows the travel times to the nearest rehab centre 653 | 654 | ```{r eval = FALSE} 655 | 656 | mapdeck( 657 | style = mapdeck_style("light") 658 | , location = c(145.1, -37.9) 659 | , zoom = 10 660 | ) %>% 661 | add_scatterplot( 662 | data = df_rehab 663 | , lon = "lon", lat = "lat" 664 | , radius = 600 665 | ) %>% 666 | add_path( 667 | data = df_nearest 668 | , polyline = "polyline" 669 | , stroke_colour = "duration_m" 670 | , legend = T 671 | , legend_options = list(css = "max-width: 300px") 672 | , update_view = F 673 | ) 674 | 675 | 676 | ``` 677 | 678 | ![](./duration_all_rehab.png) 679 | 680 | We can represent the times to the nearest rehab centre as hexagons, where the height and colour represents the travel time. 681 | 682 | ```{r, eval = FALSE} 683 | 684 | df_times <- rbind( 685 | df_times1 686 | , df_times2 687 | , df_times3 688 | ) 689 | 690 | df_nearest_times <- df_times %>% 691 | dplyr::group_by(sample_row, sample_row_pt) %>% 692 | dplyr::arrange(time_remaining) %>% 693 | dplyr::slice(1) %>% 694 | dplyr::ungroup() 695 | 696 | head( df_nearest_times ) 697 | 698 | mapdeck() %>% 699 | add_hexagon( 700 | data = df_nearest_times 701 | , lon = "lng", lat = "lat" 702 | , elevation = "time_remaining" 703 | , elevation_function = "average" 704 | , colour = "time_remaining" 705 | , colour_function = "average" 706 | , elevation_scale = 10 707 | , radius = 250 708 | , colour_range = colourvalues::colour_values(1:6, palette = "plasma") 709 | , layer_id = "times1" 710 | ) 711 | 712 | ``` 713 | 714 | ![](./directions_all_rehab_mapdeck_hexagons.png) 715 | 716 | ## Estimate caseload per centre 717 | 718 | 719 | ```{r} 720 | postcodes <- df_nearest %>% 721 | dplyr::group_by(POSTCODE, rehab) %>% 722 | dplyr::summarise( n = n() ) 723 | 724 | knitr::kable( head( postcodes ) ) 725 | ``` 726 | 727 | ```{r} 728 | 729 | postcodes %>% 730 | dplyr::group_by(rehab) %>% 731 | dplyr::summarise( total = sum(n)) %>% 732 | dplyr::mutate( percent = 100 * total / sum( total )) %>% 733 | knitr::kable( digits = 2) 734 | 735 | ``` 736 | 737 | 738 | 739 | ```{r incidenceRate, echo = FALSE} 740 | 741 | basicDemographicsRehab <- readRDS(here::here("data/googleway/basicDemographicsRehab.rds")) 742 | 743 | df_incidence <- data.frame( 744 | age = c("0-4","15-24","25-34","35-44","45-54","55-64","65-74","75-84","85+") 745 | , incidence = c(0,5,30,44,111,299,747,1928,3976) 746 | ) 747 | 748 | ``` 749 | 750 | ```{r} 751 | 752 | knitr::kable( df_incidence ) 753 | 754 | ``` 755 | 756 | ```{r} 757 | s <- 1 / 100000 # rate per 100,000 758 | basicDemographicsRehab <- basicDemographicsRehab %>% 759 | mutate (stroke_cases = s * ((Age_15_19_yr_P + Age_20_24_yr_P) * 5 + 760 | Age_25_34_yr_P * 30 + 761 | Age_35_44_yr_P * 44 + 762 | Age_45_54_yr_P * 111 + 763 | Age_55_64_yr_P * 299 + 764 | Age_65_74_yr_P * 747 + 765 | Age_75_84_yr_P * 1928 + 766 | Age_85ov_P * 3976)) %>% 767 | select (-c (contains ("_yr_"), contains ("85ov"))) 768 | 769 | ``` 770 | 771 | 772 | ```{r} 773 | 774 | basicDemographicsRehab <- rename (basicDemographicsRehab, POSTCODE = Postcode) 775 | 776 | postcodes <- left_join (postcodes, basicDemographicsRehab, by = "POSTCODE") 777 | 778 | 779 | postcodesamples <- postcodes %>% 780 | dplyr::group_by( POSTCODE ) %>% 781 | summarise( totalsamples = sum( n ) ) 782 | 783 | postcodes <- left_join( postcodes, postcodesamples, by = "POSTCODE") 784 | 785 | 786 | postcodes %>% 787 | group_by (rehab) %>% 788 | summarise (total = sum (stroke_cases * n/totalsamples, na.rm = TRUE )) %>% 789 | mutate (percent = 100 * total / sum (total)) %>% 790 | knitr::kable (digits = c (2, 0)) 791 | 792 | ``` 793 | -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_all_rehab_mapdeck_hexagons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_all_rehab_mapdeck_hexagons.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_all_sample_mapdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_all_sample_mapdeck.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab1_mapdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab1_mapdeck.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab1_mapdeck_hexagons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab1_mapdeck_hexagons.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck2.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck_hexagons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab1_sample_mapdeck_hexagons.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab2_sample_mapdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab2_sample_mapdeck.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/directions_rehab3_sample_mapdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/directions_rehab3_sample_mapdeck.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/duration_all_rehab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/duration_all_rehab.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/map_20_polylines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/map_20_polylines.png -------------------------------------------------------------------------------- /RehabCatchmentAdvanced/nearest_rehab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/RehabCatchmentAdvanced/nearest_rehab.png -------------------------------------------------------------------------------- /article/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .DEFAULT_GOAL := all 3 | 4 | NAME = geospatial-stroke 5 | SUPP = supplementary_geospatial_stroke 6 | 7 | PNGS=$(notdir $(wildcard *.png)) 8 | JPGS=$(notdir $(wildcard *.jpg)) 9 | DIAS=$(notdir $(wildcard *.dia)) 10 | FIGS=$(notdir $(wildcard *.fig)) 11 | EPSS=$(notdir $(wildcard *.eps)) 12 | TIFS=$(notdir $(wildcard *.tif)) 13 | 14 | ${NAME}.pdf: ${NAME}.tex references.bib ${PNGS} 15 | latexmk -bibtex -pdf -pdflatex="pdflatex -interaction=nonstopmode" -use-make ${NAME}.tex 16 | 17 | ${SUPP}.pdf: ${SUPP}.tex 18 | latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode" -use-make ${SUPP}.tex 19 | 20 | all: ${SUPP}.pdf ${NAME}.pdf 21 | 22 | clean: 23 | latexmk -CA 24 | 25 | -------------------------------------------------------------------------------- /article/README.md: -------------------------------------------------------------------------------- 1 | # GeoSpatialStroke 2 | 3 | ## What is in this articles folder? 4 | 5 | `scrape-gdrive-into-rmd.R` 6 | 7 | This contains scripts that describe how I created "geospatial-stroke.Rmd" 8 | 9 | `geospatial-stroke.Rmd` 10 | 11 | * This is the `.Rmd` file which is used to create the tex file we will collaborate on. 12 | * I used the `.Rmd` file to create some of the citations thanks to the `knitcitations` package. 13 | * The comments from the google drive doc are recorded here with letters a-l, recorded as [a], with the corresponding format for [a] below it. Feel free to delete the [a]-[l] comments, but I just wanted to include them to start. 14 | 15 | `references.bib` 16 | 17 | The final references, partly generated inside the `.Rmd` file 18 | 19 | `morgue.md` 20 | 21 | This is a file that contains code/text that we want to remove from the paper, but don't want to part with right now -there might be some good things in there. 22 | 23 | `geospatial-stroke.tex` 24 | 25 | * the `TeX` auto-generated by `geospatial.Rmd`. 26 | 27 | `geospatial-stroke.pdf` 28 | 29 | * the PDF generated by `geospatial.Rmd` 30 | 31 | `auto-ref.bib` 32 | 33 | * references automatically generated by `knitcitations` inside `geospatial-stroke.Rmd` 34 | 35 | ## So what do we edit? 36 | 37 | My understanding is that are will edit the `geospatial-stroke.tex` file - the rest of the files here are mostly generated by the `.Rmd`. Feel free to move them somewhere else, perhaps a `raw` folder. 38 | 39 | ## NOTE 40 | 41 | * Some of the references were generated by [zbib.org](https://zbib.org/) - sorry if they look weird! Feel free to edit. 42 | -------------------------------------------------------------------------------- /article/auto-ref.bib: -------------------------------------------------------------------------------- 1 | @Misc{2008, 2 | doi = {10.1111/ijs.2008.3.issue-s1}, 3 | url = {https://doi.org/10.1111/ijs.2008.3.issue-s1}, 4 | year = {2008}, 5 | month = {sep}, 6 | publisher = {{SAGE} Publications}, 7 | volume = {3}, 8 | } 9 | 10 | @Article{Adeoye_2014, 11 | doi = {10.1161/strokeaha.114.006293}, 12 | url = {https://doi.org/10.1161/strokeaha.114.006293}, 13 | year = {2014}, 14 | month = {oct}, 15 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 16 | volume = {45}, 17 | number = {10}, 18 | pages = {3019--3024}, 19 | author = {Opeolu Adeoye and Karen C. Albright and Brendan G. Carr and Catherine Wolff and Micheal T. Mullen and Todd Abruzzo and Andrew Ringer and Pooja Khatri and Charles Branas and Dawn Kleindorfer}, 20 | title = {Geographic Access to Acute Stroke Care in the United States}, 21 | journal = {Stroke}, 22 | } 23 | 24 | @Article{Milne_2017, 25 | doi = {10.1161/strokeaha.116.015321}, 26 | url = {https://doi.org/10.1161/strokeaha.116.015321}, 27 | year = {2017}, 28 | month = {mar}, 29 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 30 | volume = {48}, 31 | number = {3}, 32 | pages = {791--794}, 33 | author = {Matthew S.W. Milne and Jessalyn K. Holodinsky and Michael D. Hill and Anders Nygren and Chao Qiu and Mayank Goyal and Noreen Kamal}, 34 | title = {Drip `n Ship Versus Mothership for Endovascular Treatment}, 35 | journal = {Stroke}, 36 | } 37 | 38 | @Article{Pebesma_2018, 39 | author = {Edzer Pebesma}, 40 | title = {{Simple Features for R: Standardized Support for Spatial 41 | Vector Data}}, 42 | year = {2018}, 43 | journal = {{The R Journal}}, 44 | url = {https://journal.r-project.org/archive/2018/RJ-2018-009/index.html}, 45 | } 46 | 47 | @Article{Phan_2017, 48 | doi = {10.1161/strokeaha.116.015323}, 49 | url = {https://doi.org/10.1161/strokeaha.116.015323}, 50 | year = {2017}, 51 | month = {may}, 52 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 53 | volume = {48}, 54 | number = {5}, 55 | pages = {1353--1361}, 56 | author = {Thanh G. Phan and Richard Beare and Jian Chen and Benjamin Clissold and John Ly and Shaloo Singhal and Henry Ma and Velandai Srikanth}, 57 | title = {Googling Service Boundaries for Endovascular Clot Retrieval Hub Hospitals in a Metropolitan Setting}, 58 | journal = {Stroke}, 59 | } 60 | 61 | @Manual{R_Core_Team_2018, 62 | title = {R: A Language and Environment for Statistical Computing}, 63 | author = {{R Core Team}}, 64 | organization = {R Foundation for Statistical Computing}, 65 | address = {Vienna, Austria}, 66 | year = {2018}, 67 | url = {https://www.R-project.org/}, 68 | } 69 | -------------------------------------------------------------------------------- /article/choropleth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/choropleth.png -------------------------------------------------------------------------------- /article/directions_all_rehab_mapdeck_hexagons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/directions_all_rehab_mapdeck_hexagons.png -------------------------------------------------------------------------------- /article/distance_mmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/distance_mmc.png -------------------------------------------------------------------------------- /article/duration_all_rehab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/duration_all_rehab.png -------------------------------------------------------------------------------- /article/frontiers-in-physics.csl: -------------------------------------------------------------------------------- 1 | 2 | 141 | -------------------------------------------------------------------------------- /article/geospatial-stroke.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "A review of software tools, data and services for geospatial analysis of stroke services" 3 | author: "Nicholas Tierney, Mark Padgham, Michael Sumner, Geoff Boeing, Richard Beare" 4 | bibliography: references.bib 5 | output: 6 | pdf_document: 7 | keep_tex: true 8 | --- 9 | 10 | ```{r libs, include = FALSE} 11 | library(knitcitations) 12 | cleanbib() 13 | options("citation_format" = "pandoc") 14 | ``` 15 | 16 | 17 | # Introduction 18 | 19 | In this article we review a family of computational techniques and services, collectively termed geospatial analysis tools, that can be applied to a range of questions relevant to stroke services. Geospatial analysis tools allow manipulation and modelling of geospatial data. These tools, data, and modelling techniques have a long track record in the quantitative geography, city and regional planning, and civil engineering research literatures. Geospatial data, in the context of stroke research, includes the location of patients and treatment centres, routes through the road network linking patients to treatment centres, geographic and administrative region boundaries (e.g. post codes, government areas, national boundaries) and disease incidence and demographic information associated with such regions. 20 | 21 | Recent advances in acute stroke therapy, in the form of endovascular clot retrieval and clot busting drugs, are extremely effective when treatment can be delivered within a relatively short time window following stroke. There are many factors affecting the time delay between stroke onset and treatment, one of which is transport of the patient to a treatment center. Geospatial approaches have been used to analyse the delivery of emergency clot retrieval services `r citep("10.1161/STROKEAHA.116.015323")` [a] and to evaluate "Drip and Ship" approaches in specific locales `r citep("10.1161/STROKEAHA.116.015321")` [b] and population level access to services `r citep("10.1161/STROKEAHA.114.006293")` [c]. 22 | 23 | 24 | Geospatial tools can be used to analyse and visualise geospatial data, such as patient collection location as well as perform a range of simulations at varying levels of detail. For example, in Phan et al `r citep("10.1161/STROKEAHA.116.015323")` [d], travel times between a set of randomly generated addresses and a set of possible destinations were estimated using queries to several Google Application Programming Interfaces (API[e] (Richard: "Is this referring to the Google Distance *Matrix* API?")), allowing various configuration of the treatment network to be tested. Combination of the resulting catchment areas with demographic data allowed loadings to be estimated. 25 | 26 | 27 | The studies cited above were constructed using a series of standard geospatial analysis components. In this article we will introduce these components and provide examples of how they can be used to answer health related questions. Examples are implemented using open source software, specifically R and python, and source code provided so that readers can reproduce and modify them `r citep(citation())` [@sanner1999python] [f]. Geospatial analysis tools have traditionally been the domain of specialist commercial software and vendors, however this is no longer the case, with a range of open source options available to researchers. These tools are extremely flexible, but typically involve relatively steep learning curves. We hope that his article will provide stroke researchers with a useful introduction to the possibilities offered by these tools. 28 | 29 | 30 | The two examples we present are a choropleth and a service catchment basin estimation. A choropleth is a map display in which regions are coloured by a measure of the region. Choropleths are the workhorse of geographical visualization. We use demographic and boundary data from the Australian Bureau of Statistics and incidence data from the NEMISIS `r citep("0.1111/j.1747-4949.2008.00204.x.")` [g] study to estimate stroke cases per postcode and display the result on an interactive map. 31 | The service catchment basin estimation involves a Monte-Carlo simulation of patients attending a rehabilitation service of 3 hospitals. The catchment basin of each hospital is the region that has lower travel time to that hospital than any other. Catchment basins can be combined with incidence data to estimate load on rehabilitation centres. The data can be used to explore scenarios, such as the removal or addition of service centres. 32 | 33 | # Background 34 | 35 | ## Geospatial frameworks 36 | 37 | The fundamental unit of geospatial data is a point location. In practice, most forms of analysis relevant to this discussion will involve two-dimensional locations, typically represented as a latitude/longitude pair. More complex data, such as national boundaries or administrative boundaries consist of sets of points connected together in defined orders, typically to produce a closed shape. Other structures, such as road networks, are also constructed using sets of points and include other types of information, such as speed limits, travel direction etc. A geospatial framework provides mechanisms for representing, loading, and saving geospatial data and performing fundamental mathematical operations. For example, the simple features (sf) `r citep(citation("sf"))` [h] package, on which our R examples are based, provides structures to represent all manner of shapes and associate them with non spatial quantities, perform transforms between coordinate systems, display shapes, compute geometric quantities like areas and distances and perform operations like intersections and unions. The equivalent python framework is the geopandas package that provides a geospatial extension to standard dataframes. 38 | 39 | 40 | ## Sources of regional data 41 | 42 | The examples below use postcode boundary data available from the Australian Bureau of Statistics. It is common for boundaries used in reporting of regional statistics to be available in standard file formats from the reporting bodies or central authorities along with the reported statistics. The regional demographics measures, often derived from national census data, also represent an important source of information for researchers, including age, sex, income, ethnicity etc. For example, in the US, key data sources on sociodemographics and the built environment include the Census Bureau’s decennial census [@us_census_bureau_decennial] [i] (a complete enumeration at fine spatial scales but coarse, decadal temporal scales), American Community Survey [@us_census_bureau_acs] [j] (a survey with annual temporal scales, but often fairly large standard errors at small spatial scales due to the sample size), and TIGER/Line shapefiles [@us_census_tiger_line] [k] of tract, municipal, and urbanized area boundaries. Additional regional data are frequently available from municipal, state, county, or metropolitan governmental agencies. 43 | 44 | Demographic data for countries in the European Union are provided by Eurostat [@eurostat]. This includes time series data from several years to decades on economics, demography, infrastructure, health, traffic, and more of the EU [@Lahti2017). Geographic data for the EU is available through the Geographic Information System of the COmmission (GISCO), part of Eurostat. 45 | Similar levels of demographic data are available from France through INSEE [@insee], Germany through Destatis [@destatis] and, Switzerland through [@swiss-bfs]. For geospatial data we recommend the following for Europe [@diva-gis; @eea-ref-grid], and for Germany, [@germany-gis], and for Switzerland the swissBOUNDARIES3D page [@swiss-3d]. 46 | 47 | 48 | ## Geocoding and reverse geocoding 49 | 50 | Location information, such as a patient home address, is often available as a street address, rather than a coordinate (a latitude/longitude pair). However operations, such as plotting addresses on a map, require a coordinate. Geocoding is the process of converting an address to a coordinate. Reverse geocoding converts a coordinate to an address. A coordinate is useful in many other types of computation, as we shall see in the examples below. 51 | 52 | 53 | There are two common approaches to geocoding and reverse geocoding. The most ubiquitous is via web services such as Google Maps. Other services, such as OpenStreetMap’s nominatim web service, opencage (https://opencagedata.com/), provide similar capabilities and all can be queried in a automated way from R and python [@opencage]. The other approach is via a local database of geocoded addresses. One example, for Australia, is the PSMA (formerly Public Sector Mapping Agencies) address database available in an R queryable form. A local database allows many high speed queries, but is often less flexible in terms of query structure than the web services. Web services are discussed in more detail below. 54 | 55 | ## Distance and travel estimation 56 | 57 | A key part of a number of studies cited above is the estimation of travel time between patient and treatment center. The popularity of personal navigation systems in smartphones has driven the development of extremely sophisticated tools to estimate the fastest route between points. One of the best known, Google Maps (footnote - the two APIs involvfed are the directions api and distance api), uses a combination of information about the road network, historic travel time data derived from smartphone users and live information from smartphones. The travel time estimates are thus sensitive to time of day, weather conditions and possibly traffic accidents. Google, and other web services for travel time estimation, can be queried in a similar fashion to the geocoding services. It is also possible to create a local database to represent the road network, allowing more rapid querying, but losing some of the benefits of traffic models. 58 | 59 | ## Visualization 60 | 61 | Two forms of visualization are used in the following examples - static and interactive. Static maps are required for printed reports and typically present a carefully selected view. Interactive maps allow exploration of a data set, via zooming and toggling of overlays. Interactive maps often use web services to provide the background map "tiles", over which data is superimposed. Different interactive web services specialise in different types of display. Some tools produce static and interactive displays in very similar ways. 62 | 63 | ## Introduction to web services 64 | 65 | Web services providing various forms of geospatial capabilities are a crucial component of the geospatial analysis tools now available to researchers. Web services deliver what used to be complex and specialised information products to the general public. Geocoding and travel time estimation two common examples that have already been discussed. Other capabilities include delivery of tiled maps (such as the Google Map display), street network and building footprint data (such as from OpenStreetMap), and census data on sociodemographic or built environment characteristics (such as from the US Census Bureau’s web site). 66 | 67 | 68 | ### Application programming interfaces (API) 69 | 70 | Web services are accompanied via an API. The API allows software tools, such as R or python, to make requests to the web service and retrieve results. Thus, if we consider the Google Map example, not only can a user access a map query for an address via a web browser, but a program can submit the same request. Furthermore, a program can submit a series of automated requests. For example, given a list of addresses, it is relatively simple to generate an R or python procedure to geocode all of them via a web service. 71 | 72 | 73 | The ability to use APIs in automated methods also leaves them open to abuse. In addition, many APIs are commercial products and thus charge for use, although the use is often free for small volumes. 74 | 75 | 76 | The combination of these factors tends to mean that many APIs require somewhat complex setup, typically via signup and creation of keys. Terms of use may evolve over time, with charging being introduced, possibly leading to a need to enter credit card details. 77 | 78 | 79 | We have endeavoured to create examples that do not require keys, simplifying getting started. However, some extensions have been included that do require keys. These are described in supplementary material. 80 | 81 | 82 | ### OpenStreetMap (OSM) 83 | 84 | OSM (https://www.openstreetmap.org/) is a service collecting and distributing crowdsourced geospatial data. Many useful OSM services are available without API keys, and it is thus the platform of choice for examples in this paper. OSM is also unusual in that allows access to geospatial structures, such as road networks, rather than images generated from those structures. This capability is used to estimate travel time. 85 | 86 | 87 | ### Access to the examples 88 | 89 | The examples are available in their source code form from (github). "Live" versions are available at (githubpages) and can be viewed in conjunction with the methods section. The description focuses on the R versions of the examples. Code is visible in the shaded boxes, while output of the code, such as maps, are displayed immediately after the code. Python versions are provided and implement equivalent steps. Details on downloading and running the examples are available in supplementary material and at the web site. 90 | 91 | # Methods 92 | 93 | ## Example 1: Choropleth to visualize estimated stroke numbers 94 | 95 | ### Overview: 96 | 97 | We demonstrate accessing and using different data sources. The first is Australian Bureau of Statistics census data provided at the postcode level for population information, stratified by age, as well as postcode boundary information. The second data source is incidence data from the North East Melbourne Stroke Incidence Study (NEMESIS). This is combined with the first dataset to estimate per-postcode stroke incidence. We demonstrate geocoding by finding the location of a hospital delivering acute stroke services, and then display postcodes within 15km, colouring each postcode by estimated stroke incidence. 98 | 99 | 100 | The steps involved are: 101 | 102 | 1. Loading census and boundary data: Data from the 2016 Australian National Census is available from the Australian Bureau of Statistics (https://datapacks.censusdata.abs.gov.au/datapacks/) , and copies are included with examples. The two parts of the data are the national postcode boundaries (loaded with the sf::read_sf command) and the demographics, by postcode, for the state of Victoria, loaded with the readr::read_csv command. 103 | 104 | 2. Geocoding hospital location: The coordinates of the hospital of interest, Monash Medical Centre, are determined by geocoding the hospital address, using the tmaptools::geocode_OSM command. This command using OpenStreetMap to provide the geocoding service. 105 | 106 | 3. Combine demographics and spatial data: An important feature of the simple features and geopandas frameworks is the ability to combine spatial data, such as postcode boundaries, with associated statistical summaries (stroke count, demographics etc). This step uses the right_join function to attach the demographic data to the set of postcodes. The right_join performs two tasks - attaching the demographics data and discarding the postcodes for which we don’t have demographics data (i.e. those from other states of Australia). 107 | 108 | 4. Compute per-postcode stroke incidence: A column representing stroke incidence per postcode is added to the demographics table. The computation uses incidence data published by the NEMISIS [@thrift_stroke_2000] [l] study to provide rates per 100000 for various age ranges. The demographics data also includes population by age range, allowing computation of stroke incidence as a weighted sum of population columns. Names such as Age_55_64_yr_P refer to the name of a column in the demographics table. 109 | 110 | 5. Compute distance from postcode to hospital: We create a column containing the distance from each postcode to the hospital of interest using the sf::st_distance function, which automatically accounts for complexities, such as the curvature of the earth. We also set the units of quantities to km. We then use the distance in a simple, static, choropleth to verify the operation. Cool colours, corresponding to small distances are in the expected location. 111 | 112 | 6. Discard remote postcodes: Postcodes further than 20km from the hospital are discarded by filtering the data based on the newly calculated distance column. 113 | 114 | 7. Interactive display of the result: Finally, an interactive map is created using the tmap package. The postcode boundaries are coloured according to our estimated stroke count and overlaid on a zoomable map provided by OpenStreetMap. Any column in our dataset can be visualized in a similar way. A number of useful interactive features are available in this style of display, including popup displaying the postcode when hovering the mouse over a region and more detailed information available when clicking on a region. 115 | 116 | 117 | ### Example 2: Service regions for stroke rehabilitation 118 | 119 | In the second example we demonstrate the idea of estimating catchment basins for a set of three service centres. The idea can be easily extended to more service centres. A catchment basin, or catchment area for a service centre is the region that is closer to that service centre than any other. The definition of "closer" is critical in this calculation, with travel time through the road network being a useful measure for many practical purposes. The approach used in this example involves the sampling of random addresses within a region of interest around the service centres, estimation of travel time from each address to each service centre, assignment of addresses to the closest service centre, combination of addresses based on service centre to form catchment areas. The catchment areas can then be used to estimate loadings on service centres. 120 | 121 | The first four steps, 1) Loading census and boundary data, 2) Geocoding service centre location and 3) Combining demographics and spatial data are the same as the previous example, with addresses of multiple service centres being geocoded. The additional steps are: 122 | 123 | 124 | 1. Compute distance to each service centre from each postcode: A study area is generated by computing the distance to each service centre frome each postcode and retaining only postcodes within 10km of a service centre 125 | 126 | 2. Sample postcodes: A set of random addresses is created for each postcode by randomly sampling a database of addresses. The number of addresses sampled depends on the sampling approach and the subsequent computations, but if local methods are used it is feasible to use large numbers of samples. In this case we use 1000 per postcode. Lower numbers would be appropriate if subsequent computations required charged web services. 127 | 128 | 3. Display sample addresses and postcodes: Display the samples in a map form to verify that the distribution matches expected population distribution - i.e that there are lower densities in rural areas, and that the study area is appropriate. 129 | 130 | 4. Create a street network database: In this example we are employing a local approach to travel time estimation. The first step is to fetch a road network database from OpenStreetmap and convert to a network form for analysis. There are a number of tricks discussed in the online document that reduce the size of the download by exploiting knowledge of the study area. 131 | 132 | 5. Estimation of travel time: travel time from each address to each service centre is then computed using the dodgr::dodgr_dists function, which is optimised to rapidly compute large sets of pairwise distances. 133 | 134 | 6. Address-based catchment basins: Each address is assigned to a service centre by identifying the centre with the shortest travel time. A view using a scatter plot of points coloured by destination is then created to verify the result. 135 | 136 | 7. Polygon catchment basins: We convert the pointwise classification to a polygon representation using a Voronoi tessellation approach. The Voronoi tessellation of a set of points is a set of polygon catchment basins, one basin for each point. However the definition of "closer" for the Voronoi basins is based on Euclidean distance rather than road network distance or travel time. The Voronoi polygons are from addresses assigned to the same service centre are then merged to create the polygon representation of the service centre catchment, which can be displayed. 137 | 138 | 8. Estimate caseload per centre: The catchment areas can be used in conjunction with the per postcode demographics to make estimates. We use our per postcode stroke estimate procedure from the previous example as a basis for determining the number of rehabilitation cases (a simplification for illustration purposes). The sampled addresses are the basis for this computation, with the proportion of sampled addresses from a postcode assigned to a service centre corresponding to the proportion of cases from that postcode attending the centre. 139 | 140 | # Discussion 141 | 142 | These examples illustrate fundamental geospatial computational components in R and python. This includes geocoding with databases and web services, interactive and static visualization. It also includes geometric computation of areas and distances, and geospatial computations of travel time. 143 | 144 | 145 | The examples in this article illustrate the use of a range of components that underpin geospatial analysis. By providing an accessible introduction to these areas, clinicians and researchers can create code to answer clinically relevant questions on a topics such as service delivery and service demand. Importantly, these factor in key features of transport and travel time. 146 | 147 | # Supplementary Material 148 | 149 | ## API Keys 150 | 151 | Online services which offer an interface to their applications will sometimes require use of an API key, or application programming interface key. This key should be unique for each user, developer or application making use of the service as it is a way for the provider to monitor and, where applicable, charge for use. 152 | 153 | Two major mapping platforms that require an API key are Google Maps and Mapbox. At the time of writing both allow unrestricted use of the mapping API. However, Google has limits on the other services it offers such as geocoding and direction services. 154 | 155 | 156 | Setting up API Keys for examples 157 | 158 | ```{r write-bib, include = FALSE} 159 | write.bibtex(file = "auto-ref.bib") 160 | ``` 161 | 162 | [a]citation: 163 | http://stroke.ahajournals.org/content/early/2017/03/29/STROKEAHA.116.015323 164 | [b]citation: 165 | https://doi.org/10.1161/STROKEAHA.116.015321 166 | [c]doi:10.1161/STROKEAHA.114.006293 167 | [d]citation: 168 | http://stroke.ahajournals.org/content/early/2017/03/29/STROKEAHA.116.015323 169 | [e]Is this referring to the Google Distance *Matrix* API? 170 | [f]cite R and Python. 171 | [g]doi: 10.1111/j.1747-4949.2008.00204.x. 172 | [h]cite 173 | [i]citation: https://www.census.gov/history/www/programs/demographic/decennial_census.html 174 | [j]citation: https://www.census.gov/programs-surveys/acs/ 175 | [k]citation: https://www.census.gov/geo/maps-data/data/tiger-line.html 176 | [l]citation: 177 | ``` 178 | @article{thrift2000stroke, 179 | title={Stroke Incidence on the East Coast of Australia The North East Melbourne Stroke Incidence Study (NEMESIS)}, 180 | author={Thrift, Amanda G and Dewey, Helen M and Macdonell, Richard AL and McNeil, John J and Donnan, Geoffrey A}, 181 | journal={Stroke}, 182 | volume={31}, 183 | number={9}, 184 | pages={2087--2092}, 185 | year={2000}, 186 | publisher={Am Heart Assoc}} 187 | ``` 188 | -------------------------------------------------------------------------------- /article/logo1.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/logo1.eps -------------------------------------------------------------------------------- /article/logo2.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/logo2.eps -------------------------------------------------------------------------------- /article/logos.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/logos.eps -------------------------------------------------------------------------------- /article/map1_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/map1_mv.png -------------------------------------------------------------------------------- /article/map2_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/map2_mv.png -------------------------------------------------------------------------------- /article/map3_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/map3_mv.png -------------------------------------------------------------------------------- /article/map4_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/map4_mv.png -------------------------------------------------------------------------------- /article/map5_mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/map5_mv.png -------------------------------------------------------------------------------- /article/morgue.md: -------------------------------------------------------------------------------- 1 | # Morgue section of the paper 2 | 3 | The "Morgue" section of a paper is used as a place to gather (see [wiki article on this idea](https://en.wikipedia.org/wiki/Morgue_file)). 4 | 5 | 6 | ## Examples 7 | 8 | Need the right clinical connections for these. Will discuss with Thanh. 9 | 10 | 11 | The examples will provide a framework for introducing the concepts and packages. We’ll avoid a detailed discussion of theory. 12 | 13 | 14 | Monash Health - Hospital details: 15 | 16 | Monash Medical Centre (acute) : 246 Clayton Rd, Clayton VIC, 3168 17 | 18 | Addresses that OSM finds OK 19 | Dandenong Hospital (acute and rehab) : Dandenong Hospital, Dandenong VIC 3175, Australia 20 | Casey Hospital (acute and rehab) : 62-70 Kangan Dr, Berwick VIC 3806 21 | Kingston Hospital (rehab) : The Kingston Centre, Heatherton VIC 3202, Australia 22 | 23 | 24 | ### Visualisation of geospatial data 25 | 26 | * Choropleths 27 | * points on maps 28 | * interactivity. 29 | * Maps as data. 30 | * Complex visualisation for training (map deck). 31 | * Travel time isochrones. 32 | 33 | 34 | [This link might be of interest for isochrones](https://stories.thinkingmachin.es/airport-iso/) 35 | David Cooley - sketching out the visualisation part as you see it. 36 | 37 | 38 | ### A worked example 39 | 40 | ### Geocoding. What is it, why is it. 41 | 42 | Suppose a study has collected patient addresses. Summarising the addresses via a map is useful and may be accomplished in a number of ways. Individual points on the map, coloured by various characteristics, is useful if there is a relatively small number of addresses. This requires geocoding of addresses - i.e. translating the street address to a latitude/longitude. Larger numbers of addresses may be more usefully examined by regional summaries, such as postcodes, or by heat maps. Heatmaps also need geocoding. 43 | 44 | 45 | Careful to avoid too much visualisation info here. 46 | 47 | 48 | Reverse geocoding - application to simulation studies. 49 | [Also note the PSMA package by Hugh Parsonage](https://github.com/HughParsonage/PSMA) - it needs a good README, but [this issue](https://github.com/HughParsonage/PSMA/issues/2) outlines its use. PSMA core dataset needs to be discussed in the sources of data section. 50 | Geocoding - I use dismo::geocode for a very flexible interface to the Google API, but many others exist now so list the good ones .... Dodgr is king for calculating distances on networks, and integrates with network transport data from OSM. The best geocoding tool for Australia is PSMA, as it is super super fast due to no internet/API situation. 51 | 52 | 53 | ## Choropleth 54 | 55 | The choropleth is a classical geospatial visualization technique in which regions are coloured by a statistic or classification (see figure …). They are widely used to display political and demographic data (see figure …). This example demonstrates choropleths to display predicted stroke cases in the vicinity of out health network. Three hospitals are used to display services for acute stroke: 1) Monash medical centre, Clayton; 2) Dandenong; and 3) Casey. There are five steps to create a choropleth: 56 | 57 | 1. Retrieve census data : 58 | 1. Using Australian 2016 census, obtain: 59 | 1. Demographics 60 | 2. Shape files for post code boundaries (note: explain what a shape file is) 61 | 1. Generate relevant post codes 62 | 1. E.g., bounding box around monash medical center 63 | 1. Use incidence data to estimate strokes per post code 64 | 1. (Have this set up from previous study) 65 | 2. (note: incidence of what? Stroke data? Where is this from?) 66 | 3. (note: what sort of model is used to estimate incidence? This can get complex) 67 | 1. Tidy data so each variable forms a column 68 | 1. Discuss the idea of nested columns with simple features? 69 | 1. Display with ggmap 70 | 1. (Note: before displaying with ggmap, the data needs to be in tidy form, so that the columns/variables can be mapped to graphical aesthetics.) 71 | 2. Note: What columns/variables do we have? 72 | 1. Alternative display with osmplotr (maps as data idea) - colour roads by stat. 73 | 1. Note: how does this change the data structure requirements? 74 | 75 | 76 | #### Retrieving census data 77 | 78 | Illustrate loading and using census data, both spatial and demographic. 79 | 80 | 81 | #### Generate relevant post code 82 | 83 | Base on post codes in vicinity of a hospital. 84 | 85 | 86 | #### Estimate stroke incidence around post code 87 | 88 | Point out that we could colour by hospital attendance data. 89 | 90 | 91 | #### Tidy data 92 | 93 | 94 | #### Visualise 95 | 96 | * Using ggmap 97 | * Using osmplotr 98 | * Using leaflet 99 | * Using mapdeck 100 | * Key point: demonstrate that once the data structure is complete, the visualisation is not as much work 101 | 102 | 103 | #### Discuss 104 | 105 | * Highlight that same procedure can be used with real hospital data. 106 | 107 | ### Extending choropleths (Geocoding) 108 | 109 | Choropleths are useful for summarising large scale data, but what if we want more detail. For example, display of location of new cases may be useful for planning outpatient rehab services. Rehab services are provided by Dandenong, Casey and Kingston. We now modify the previous example to include individual information, which we generate randomly. 110 | 111 | 1. Generate random addresses within each postcode (using database). 112 | 2. Generate coordinates from each address (database and google) 113 | 3. Display points 114 | 4. Colour by something stroke related (random), shape by sex. 115 | 116 | 117 | Note: 118 | 119 | * Are these plots choropleths or are they points on a map? 120 | * Is this more of a modelling / simulation type situation? 121 | 122 | ## Distances 123 | 124 | Note: 125 | * There should be a section on "Calculating Distances", which describes the process, and some of the features that need to be accounted for. 126 | * Each of these "atomic" pieces of geospatial data analysis can then be combined in illustrative examples 127 | 128 | 129 | ## Routing and travel times 130 | 131 | The APIs from Google etc, and what they allow. Tools for querying them. 132 | 133 | 134 | Ad hoc routing and travel time calculations: dijkstra and a* algorithms, weighted by distance (graph edge length) or by free-flow travel time (a function of edge length and inferred/imputed speed limit) or by congested travel time (when congestion/slowdown information is available, either from traffic simulation or real-time data). 135 | 136 | 137 | * [Dodgr package by Mark Padgham](https://github.com/ATFutures/dodgr) could help with calculation of distances using graphs 138 | 139 | ## Sources of curated data 140 | 141 | This should cover specifics on how to load curated data, the nature of the data, how shapes are represented. Some ideas on data types: 142 | 143 | 144 | * Australian census 145 | * Shapefiles 146 | * Demographics 147 | * Eechidna package 148 | * Tidycensus package (USA) 149 | * ABS geographic data structures [ASGS package by Hugh Parsonage](https://github.com/HughParsonage/ASGS) 150 | * OpenStreetMap (easily loaded, analyzed, visualized with Python/OSMnx!) 151 | * [osmdata package](https://github.com/ropensci/osmdata) 152 | * Application: [Rwalkable package](https://github.com/ropenscilabs/rwalkable) (finds points of interest near a location) 153 | 154 | 1-In this article, we review and illustrate the use of these tools to show the transportation of patients in our hospital network (Monash Health) as the patients transit from acute to rehabilitation hospitals. 155 | 156 | 157 | 2-movement of doctors in hospital-responding to code stroke. Can mapbox and mapdeck help? 158 | 159 | 160 | 3-movement of family visiting patients in hospital-can mapbox and mapdeck help? 161 | 162 | 163 | ### Computation on geospatial data 164 | 165 | Note: 166 | * This section states that geospatial info needs to be pre-processed 167 | * It needs to explain why this pre-processing needs to happen 168 | 169 | 170 | Geospatial information typically needs to be transformed/preprocessed/combined multiple sources before being used in statistical analysis. May involve assigning samples to regions, sampling within regions, smoothing, interpolation (krigging etc), gridding, tesselation, combining data from different scales. Geocoding. Travel times, road distances. 171 | 172 | 173 | **Mark P and Michael Sumner to throw some ideas in here. What are the key computations, representations needed. Where are they in R-package land?** 174 | 175 | 176 | Assigning samples / Aggregation - point-in-polygon tests as engine to aggregate values-at-points into larger regions, requires known-coordinates for point and polygon data (ability to transform), and efficiencies for tree-search, i.e. we don’t test if a point is in a polygon if it falls outside the polygon’s bounding box. The simple features package sf is the state of the art, relying on compiled code to calculate p-in-p and bbox filtering. For given situations (an acceptable overall precision, or unchanged regions that are used for more summaries at later times) it can be easier and more efficient to use a rasterized version of polygons and so fasterize/raster is is the right tool. Our tools sometimes 1) identify polygon 2) index values 3) acculate summaries all in one, but sf has included improvements on being able to keep these as separate steps, and if the rasterize version can be used it’s easy to manage in data frames because the relationship is trivial. 177 | 178 | 179 | Sampling within regions - sp::spsample established sophisticated sampling techniques for within regions, along lines, with various rules. The spatial package had some early work in this, and spatstat also is very sophisticated but doesn’t include knowledge of map projections or ability to transform coordinates, and has less capacity for complex sets of regions as sf does. 180 | 181 | 182 | Smoothing, interpolation, kriging are closely related in intent but use quite different methods. The gstat package is the strongest here, but has not yet been updated to sf-era. Raster simplifies some of the capabilities here, spatstat includes some tools, and there are many new packages with some support. The simplest kind of gridding is to count points within raster cells, or to aggregate point values within raster cells - and raster is still the best at this, and can be scaled up on this task extremely well with dplyr. 183 | 184 | 185 | Tesselation involves breaking up regions into smaller, simpler pieces and keeping them grouped as per their source region. This might be used to compartmentalize a region into subregions, to speed up overlay algorithms or to provide greater structure for regions within larger regions, perhaps for visualization. RTriangle (available via sfdct) is the only real contender here for speed and flexibility, and has a non-commercial-use license so inaccessible to some users. 186 | 187 | 188 | # Thoughts / ideas to come back to 189 | 190 | 191 | Geoff Boeing 192 | 193 | * Do we want to scope this more narrowly? Some subset of all medical researchers? Certain types/uses of geospatial information? 194 | -------------------------------------------------------------------------------- /article/nearest_rehab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/article/nearest_rehab.png -------------------------------------------------------------------------------- /article/references.bib: -------------------------------------------------------------------------------- 1 | 2 | @Article{azarpazhooh2008patterns, 3 | title={Patterns of stroke recurrence according to subtype of first stroke event: the North East Melbourne Stroke Incidence Study (NEMESIS)}, 4 | author={Mahmoud Azarpazhooh and Marcus B Nicol and Geoffrey A Donnan and Helen M Dewey and Jonathan W Sturm and Richard AL Macdonell and Dora C Pearce and Amanda G Thrift}, 5 | journal={International Journal of Stroke}, 6 | volume={3}, 7 | number={3}, 8 | pages={158--164}, 9 | year={2008}, 10 | publisher={SAGE Publications Sage UK: London, England} 11 | } 12 | 13 | @Article{Adeoye_2014, 14 | doi = {10.1161/strokeaha.114.006293}, 15 | url = {https://doi.org/10.1161/strokeaha.114.006293}, 16 | year = {2014}, 17 | month = {oct}, 18 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 19 | volume = {45}, 20 | number = {10}, 21 | pages = {3019--3024}, 22 | author = {Opeolu Adeoye and Karen C. Albright and Brendan G. Carr and Catherine Wolff and Micheal T. Mullen and Todd Abruzzo and Andrew Ringer and Pooja Khatri and Charles Branas and Dawn Kleindorfer}, 23 | title = {Geographic Access to Acute Stroke Care in the United States}, 24 | journal = {Stroke} 25 | } 26 | 27 | @article{boeing_osmnx_2017, 28 | title = {{OSMnx}: {New} {Methods} for {Acquiring}, {Constructing}, {Analyzing}, and {Visualizing} {Complex} {Street} {Networks}}, 29 | volume = {65}, 30 | doi = {10.1016/j.compenvurbsys.2017.05.004}, 31 | pages = {126-139}, 32 | journal = {Computers, Environment and Urban Systems}, 33 | author = {Boeing, Geoff}, 34 | year = {2017} 35 | } 36 | 37 | @article{boeing_street_2019, 38 | title = {Street {Network} {Models} and {Measures} for {Every} {U}.{S}. {City}, {County}, {Urbanized} {Area}, {Census} {Tract}, and {Zillow}-{Defined} {Neighborhood}}, 39 | volume = {3}, 40 | doi = {10.3390/urbansci3010028}, 41 | number = {1}, 42 | journal = {Urban Science}, 43 | author = {Boeing, Geoff}, 44 | year = {2019}, 45 | pages = {28} 46 | } 47 | 48 | @Article{Milne_2017, 49 | doi = {10.1161/strokeaha.116.015321}, 50 | url = {https://doi.org/10.1161/strokeaha.116.015321}, 51 | year = {2017}, 52 | month = {mar}, 53 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 54 | volume = {48}, 55 | number = {3}, 56 | pages = {791--794}, 57 | author = {Matthew S.W. Milne and Jessalyn K. Holodinsky and Michael D. Hill and Anders Nygren and Chao Qiu and Mayank Goyal and Noreen Kamal}, 58 | title = {Drip `n Ship Versus Mothership for Endovascular Treatment}, 59 | journal = {Stroke} 60 | } 61 | @article{10.1001/jamaneurol.2018.2424, 62 | author = {Holodinsky, Jessalyn K. and Williamson, Tyler S. and Demchuk, Andrew M. and Zhao, Henry and Zhu, Luke and Francis, Michael J. and Goyal, Mayank and Hill, Michael D. and Kamal, Noreen}, 63 | title = "{Modeling Stroke Patient Transport for All Patients With Suspected Large-Vessel OcclusionModeling Stroke Patient Transport for All Suspected Large-Vessel Occlusion PatientsModeling Stroke Patient Transport for All Suspected Large-Vessel Occlusion Patients}", 64 | journal = {JAMA Neurology}, 65 | volume = {75}, 66 | number = {12}, 67 | pages = {1477-1486}, 68 | year = {2018}, 69 | month = {12}, 70 | issn = {2168-6149}, 71 | doi = {10.1001/jamaneurol.2018.2424}, 72 | url = {https://dx.doi.org/10.1001/jamaneurol.2018.2424}, 73 | eprint = {https://jamanetwork.com/journals/jamaneurology/articlepdf/2698434/jamaneurology\_holodinsky\_2018\_oi\_180058.pdf}, 74 | } 75 | 76 | 77 | 78 | 79 | @article{Padgham_2019, 80 | journal={Transport Findings}, 81 | doi={10.32866/6945}, 82 | publisher={Network Design Lab}, 83 | title={dodgr: An R package for network flow aggregation}, 84 | author={Padgham, Mark}, 85 | year={2019}, 86 | url={https://transportfindings.org/article/6945-dodgr-an-r-package-for-network-flow-aggregation} 87 | } 88 | 89 | @Article{Pebesma_2018, 90 | author = {Edzer Pebesma}, 91 | title = {{Simple Features for R: Standardized Support for Spatial 92 | Vector Data}}, 93 | year = {2018}, 94 | journal = {{The R Journal}}, 95 | url = {https://journal.r-project.org/archive/2018/RJ-2018-009/index.html} 96 | } 97 | 98 | @Article{Phan_2017, 99 | doi = {10.1161/strokeaha.116.015323}, 100 | url = {https://doi.org/10.1161/strokeaha.116.015323}, 101 | year = {2017}, 102 | month = {may}, 103 | publisher = {Ovid Technologies (Wolters Kluwer Health)}, 104 | volume = {48}, 105 | number = {5}, 106 | pages = {1353--1361}, 107 | author = {Thanh G. Phan and Richard Beare and Jian Chen and Benjamin Clissold and John Ly and Shaloo Singhal and Henry Ma and Velandai Srikanth}, 108 | title = {Googling Service Boundaries for Endovascular Clot Retrieval Hub Hospitals in a Metropolitan Setting}, 109 | journal = {Stroke} 110 | } 111 | 112 | @Manual{R_Core_Team_2018, 113 | title = {R: A Language and Environment for Statistical Computing}, 114 | author = {{R Core Team}}, 115 | organization = {R Foundation for Statistical Computing}, 116 | address = {Vienna, Austria}, 117 | year = {2018}, 118 | url = {https://www.R-project.org/} 119 | } 120 | 121 | @article{sanner1999python, 122 | title={Python: a programming language for software integration and development}, 123 | author={Michel F Sanner and others}, 124 | journal={J Mol Graph Model}, 125 | volume={17}, 126 | number={1}, 127 | pages={57--61}, 128 | year={1999} 129 | } 130 | 131 | 132 | @misc{us_census_bureau_decennial, 133 | title = {Decennial census - history - u. {S}. {Census} bureau}, 134 | url = {https://www.census.gov/history/www/programs/demographic/decennial_census.html}, 135 | abstract = {The U.S. Constitution requires that a census of the population be taken every 10 years.}, 136 | urldate = {2019-02-19TZ}, 137 | year = {2019}, 138 | author = {US Census Bureau, Census History Staff} 139 | } 140 | 141 | @misc{us_census_bureau_acs, 142 | title = {American community survey({Acs})}, 143 | url = {https://www.census.gov/programs-surveys/acs}, 144 | abstract = {The American Community Survey is the premier source for information about America's changing population, housing and workforce.}, 145 | urldate = {2019-02-19TZ}, 146 | year = {2019}, 147 | author = {Bureau, US Census} 148 | } 149 | 150 | @misc{us_census_tiger_line, 151 | title = {Tiger/line® shapefiles and tiger/line® files}, 152 | url = {https://www.census.gov/geo/maps-data/data/tiger-line.html}, 153 | abstract = {Access to the TIGER/Line® Shapefiles and TIGER/Line® File products for use in a GIS.}, 154 | urldate = {2019-02-19TZ}, 155 | year = {2019}, 156 | author = {Geography, US Census Bureau} 157 | } 158 | 159 | 160 | @misc{eurostat, 161 | title = {{GISCO} A {Geographic} information system of the {Commission}}, 162 | url = {https://ec.europa.eu/eurostat/web/products-catalogues/-/KS-04-14-908}, 163 | abstract = {This leaflet presents information on GISCO a permanent service of Eurostat whose goal is to promote and stimulate the use of geographic information within the European Statistical System and the European Commission.}, 164 | urldate = {2019-02-10TZ}, 165 | author = {{Eurostat}}, 166 | doi = {10.2785/58916}, 167 | year = {2015}, 168 | note = {publisher: European Union} 169 | } 170 | 171 | @Article{Lahti2017, 172 | title = "Retrieval and analysis of Eurostat open data with the eurostat 173 | package", 174 | author = {Leo Lahti and Janne Huovari and Markus Kainus and Przemys{\l}aw Biecek}, 175 | journal = "The R journal", 176 | volume = 9, 177 | number = 1, 178 | pages = "385--392", 179 | year = 2017 180 | } 181 | 182 | @Manual{opencage, 183 | title = {opencage: Interface to the OpenCage API}, 184 | author = {Maëlle Salmon}, 185 | year = {2018}, 186 | note = {R package version 0.1.4}, 187 | url = {https://CRAN.R-project.org/package=opencage} 188 | } 189 | 190 | 191 | @Article{thrift_stroke_2000, 192 | title = {Stroke incidence on the east coast of australia: the north east melbourne stroke incidence study({Nemesis})}, 193 | volume = {31}, 194 | issn = {1524-4628}, 195 | number = {9}, 196 | journal = {Stroke}, 197 | author={Amanda G Thrift and Helen M Dewey and Richard AL Macdonell and John J McNeil and Geoffrey A Donnan}, 198 | month = sep, 199 | year = {2000}, 200 | pmid = {10978034}, 201 | pages = {2087--2092} 202 | } 203 | 204 | 205 | @misc{insee, 206 | title = {Résultats de la recherche {\textbar} {Insee}}, 207 | url = {https://www.insee.fr/en/statistiques}, 208 | abstract = {Recherchez vos statistiques par thème, niveau géographique, catégorie ou collection}, 209 | year = {2019}, 210 | urldate = {2019-02-19TZ} 211 | } 212 | 213 | @misc{destatis, 214 | title = {Homepage-federal statistical office({Destatis})}, 215 | url = {https://www-genesis.destatis.de/genesis/online/logon?language=en}, 216 | abstract = {Homepage of the Federal Statistical Office of Germany}, 217 | year = {2019}, 218 | urldate = {2019-02-19TZ} 219 | } 220 | 221 | @misc{swiss-bfs, 222 | title = {Federal statistical office}, 223 | url = {https://www.bfs.admin.ch/bfs/en/home.html}, 224 | abstract = {The Swiss Federal Statistical Office is the national competence centre for official statistics in Switzerland.}, 225 | urldate = {2019-02-19TZ}, 226 | year = {2019}, 227 | author = {Office, Federal Statistical} 228 | } 229 | 230 | 231 | @misc{diva-gis, 232 | title = {Download data by country {\textbar} {DIVA}-{GIS}}, 233 | url = {http://www.diva-gis.org/gdata}, 234 | year = {2019}, 235 | urldate = {2019-02-22TZ} 236 | } 237 | 238 | @misc{eea-ref-grid, 239 | type = {Data}, 240 | title = {{EEA} reference grid}, 241 | copyright = {EEA standard re-use policy: unless otherwise indicated, re-use of content on the EEA website for commercial or non-commercial purposes is permitted free of charge, provided that the source is acknowledged (https://www.eea.europa.eu/legal/copyright). Copyright holder: European Environment Agency (EEA).}, 242 | url = {https://www.eea.europa.eu/data-and-maps/data/eea-reference-grids-2}, 243 | abstract = {The grid is based on the recommendation at the 1st European Workshop on Reference Grids in 2003 and later INSPIRE geographical grid systems. For each country three vector polygon grid shape files, 1, 10 and 100 km, are available. The grids cover at least country borders - plus 15km buffer - and, where applicable, marine Exclusive Economic Zones v7.0 - plus 15km buffer - (www.vliz.be/vmdcdata/marbound). 244 | 245 | Note that the extent of the grid into the marine area does not reflect the extent of the territorial waters.}, 246 | urldate = {2019-02-22TZ}, 247 | year = {2019}, 248 | journal = {European Environment Agency} 249 | } 250 | 251 | @misc{germany-gis, 252 | title = {Dienstleistungszentrum}, 253 | url = {https://www.geodatenzentrum.de/geodaten/gdz_rahmen.gdz_div?gdz_spr=eng&gdz_akt_zeile=5&gdz_anz_zeile=1&gdz_unt_zeile=0&gdz_user_id=0}, 254 | abstract = {Bundesamt für Kartographie und Geodäsie}, 255 | year = {2019}, 256 | urldate = {2019-02-22TZ} 257 | } 258 | 259 | @misc{swiss-3d, 260 | title = {Swisstopo online shop}, 261 | url = {https://shop.swisstopo.admin.ch/en/products/landscape/boundaries3D}, 262 | year = {2019}, 263 | urldate = {2019-02-22TZ} 264 | } 265 | 266 | 267 | TPA references 268 | 269 | @article{tnionda1995tissue, 270 | title={Tissue plasminogen activator for acute ischemic stroke. The National Institute of Neurological Disorders and Stroke rt-PA stroke study group}, 271 | author={TNIoNDa, Stroke}, 272 | journal={The New England Journal of Medicine}, 273 | volume={333}, 274 | number={24}, 275 | pages={1581--1587}, 276 | year={1995} 277 | } 278 | 279 | ECR reference 280 | @article{berkhemer2015randomized, 281 | title={A randomized trial of intraarterial treatment for acute ischemic stroke}, 282 | author={Berkhemer, Olvert A and Fransen, Puck SS and Beumer, Debbie and van den Berg, Lucie A and Lingsma, Hester F and Yoo, Albert J and Schonewille, Wouter J and Vos, Jan Albert and Nederkoorn, Paul J and Wermer, Marieke JH and others}, 283 | journal={New England Journal of Medicine}, 284 | volume={372}, 285 | number={1}, 286 | pages={11--20}, 287 | year={2015}, 288 | publisher={Mass Medical Soc} 289 | } 290 | 291 | @article{goyal2016endovascular, 292 | title={Endovascular thrombectomy after large-vessel ischaemic stroke: a meta-analysis of individual patient data from five randomised trials}, 293 | author={Goyal, Mayank and Menon, Bijoy K and Van Zwam, Wim H and Dippel, Diederik WJ and Mitchell, Peter J and Demchuk, Andrew M and D{\'a}valos, Antoni and Majoie, Charles BLM and Van Der Lugt, Aad and De Miquel, Maria A and others}, 294 | journal={The Lancet}, 295 | volume={387}, 296 | number={10029}, 297 | pages={1723--1731}, 298 | year={2016}, 299 | publisher={Elsevier} 300 | } 301 | @article{goyal2015randomized, 302 | title={Randomized assessment of rapid endovascular treatment of ischemic stroke}, 303 | author={Goyal, Mayank and Demchuk, Andrew M and Menon, Bijoy K and Eesa, Muneer and Rempel, Jeremy L and Thornton, John and Roy, Daniel and Jovin, Tudor G and Willinsky, Robert A and Sapkota, Biggya L and others}, 304 | journal={New England Journal of Medicine}, 305 | volume={372}, 306 | number={11}, 307 | pages={1019--1030}, 308 | year={2015}, 309 | publisher={Mass Medical Soc} 310 | } 311 | @article{jovin2015thrombectomy, 312 | title={Thrombectomy within 8 hours after symptom onset in ischemic stroke}, 313 | author={Jovin, Tudor G and Chamorro, Angel and Cobo, Erik and de Miquel, Mar{\'\i}a A and Molina, Carlos A and Rovira, Alex and San Rom{\'a}n, Luis and Serena, Joaqu{\'\i}n and Abilleira, Sonia and Rib{\'o}, Marc and others}, 314 | journal={New England Journal of Medicine}, 315 | volume={372}, 316 | number={24}, 317 | pages={2296--2306}, 318 | year={2015}, 319 | publisher={Mass Medical Soc} 320 | } 321 | @article{campbell2015endovascular, 322 | title={Endovascular therapy for ischemic stroke with perfusion-imaging selection}, 323 | author={Campbell, Bruce CV and Mitchell, Peter J and Kleinig, Timothy J and Dewey, Helen M and Churilov, Leonid and Yassi, Nawaf and Yan, Bernard and Dowling, Richard J and Parsons, Mark W and Oxley, Thomas J and others}, 324 | journal={New England Journal of Medicine}, 325 | volume={372}, 326 | number={11}, 327 | pages={1009--1018}, 328 | year={2015}, 329 | publisher={Mass Medical Soc} 330 | } 331 | @article{saver2015stent, 332 | title={Stent-retriever thrombectomy after intravenous t-PA vs. t-PA alone in stroke}, 333 | author={Saver, Jeffrey L and Goyal, Mayank and Bonafe, Alain and Diener, Hans-Christoph and Levy, Elad I and Pereira, Vitor M and Albers, Gregory W and Cognard, Christophe and Cohen, David J and Hacke, Werner and others}, 334 | journal={New England Journal of Medicine}, 335 | volume={372}, 336 | number={24}, 337 | pages={2285--2295}, 338 | year={2015}, 339 | publisher={Mass Medical Soc} 340 | } 341 | 342 | @article{hacke2018new, 343 | title={A new DAWN for imaging-based selection in the treatment of acute stroke}, 344 | author={Hacke, Werner}, 345 | journal={N Engl J Med}, 346 | volume={378}, 347 | number={1}, 348 | doi={10.1056/NEJMe1713367}, 349 | pages={81--83}, 350 | year={2018} 351 | } 352 | 353 | @article{nogueira2018thrombectomy, 354 | title={Thrombectomy 6 to 24 hours after stroke with a mismatch between deficit and infarct}, 355 | author={Nogueira, Raul G and Jadhav, Ashutosh P and Haussen, Diogo C and Bonafe, Alain and Budzik, Ronald F and Bhuva, Parita and Yavagal, Dileep R and Ribo, Marc and Cognard, Christophe and Hanel, Ricardo A and others}, 356 | journal={New England Journal of Medicine}, 357 | volume={378}, 358 | number={1}, 359 | pages={11--21}, 360 | year={2018}, 361 | doi={10.1056/NEJMoa1706442}, 362 | publisher={Mass Medical Soc} 363 | } 364 | @article{froehler2017interhospital, 365 | title={Interhospital transfer before thrombectomy is associated with delayed treatment and worse outcome in the STRATIS registry (Systematic Evaluation of Patients Treated With Neurothrombectomy Devices for Acute Ischemic Stroke)}, 366 | author={Froehler, Michael T and Saver, Jeffrey L and Zaidat, Osama O and Jahan, Reza and Aziz-Sultan, Mohammad Ali and Klucznik, Richard P and Haussen, Diogo C and Hellinger Jr, Frank R and Yavagal, Dileep R and Yao, Tom L and others}, 367 | journal={Circulation}, 368 | volume={136}, 369 | number={24}, 370 | pages={2311--2321}, 371 | year={2017}, 372 | doi={10.1161/CIRCULATIONAHA.117.028920}, 373 | publisher={Am Heart Assoc} 374 | } 375 | 376 | @ARTICLE{10.3389/fneur.2019.00150, 377 | AUTHOR={Allen, Michael and Pearn, Kerry and Villeneuve, Emma and James, Martin and Stein, Ken}, 378 | TITLE={Planning and Providing Acute Stroke Care in England: The Effect of Planning Footprint Size}, 379 | JOURNAL={Frontiers in Neurology}, 380 | VOLUME={10}, 381 | PAGES={150}, 382 | YEAR={2019}, 383 | URL={https://www.frontiersin.org/article/10.3389/fneur.2019.00150}, 384 | DOI={10.3389/fneur.2019.00150}, 385 | ISSN={1664-2295} 386 | } 387 | 388 | @ARTICLE{10.3389/fneur.2019.00159, 389 | AUTHOR={Mathur, Shrey and Walter, Silke and Grunwald, Iris Q. and Helwig, Stefan A. and Lesmeister, Martin and Fassbender, Klaus}, 390 | TITLE={Improving Prehospital Stroke Services in Rural and Underserved Settings With Mobile Stroke Units}, 391 | JOURNAL={Frontiers in Neurology}, 392 | VOLUME={10}, 393 | PAGES={159}, 394 | YEAR={2019}, 395 | URL={https://www.frontiersin.org/article/10.3389/fneur.2019.00159}, 396 | DOI={10.3389/fneur.2019.00159}, 397 | ISSN={1664-2295} 398 | } 399 | 400 | @ARTICLE{10.3389/fneur.2019.00130, 401 | AUTHOR={Vidale, Simone and Arnaboldi, Marco and Frangi, Lara and Longoni, Marco and Monza, Gianmario and Agostoni, Elio}, 402 | TITLE={The Large ARtery Intracranial Occlusion Stroke Scale: A New Tool With High Accuracy in Predicting Large Vessel Occlusion}, 403 | JOURNAL={Frontiers in Neurology}, 404 | VOLUME={10}, 405 | PAGES={130}, 406 | YEAR={2019}, 407 | URL={https://www.frontiersin.org/article/10.3389/fneur.2019.00130}, 408 | DOI={10.3389/fneur.2019.00130}, 409 | ISSN={1664-2295} 410 | } 411 | 412 | @Article{10.3389/fneur.2019.00331, 413 | author = {Thanh G. Phan and Richard Beare and Mark Parsons and Henry Zhao and Stephen M. Davis and Geoffrey Donnan and Velandai Srikanth and Henry Ma}, 414 | title = {Googling boundaries for operating Mobile Stroke Unit for Stroke Codes}, 415 | journal = {Frontiers in Neurology}, 416 | year = {2019}, 417 | OPTkey = {}, 418 | OPTvolume = {}, 419 | OPTnumber = {}, 420 | OPTpages = {}, 421 | OPTmonth = {}, 422 | OPTnote = {}, 423 | DOI={10.3389/fneur.2019.00331}, 424 | OPTannote = {} 425 | } 426 | 427 | @ARTICLE{10.3389/fcvm.2017.00089, 428 | AUTHOR={Cole, Justin and Beare, Richard and Phan, Thanh G. and Srikanth, Velandai and MacIsaac, Andrew and Tan, Christianne and Tong, David and Yee, Susan and Ho, Jesslyn and Layland, Jamie}, 429 | TITLE={Staff Recall Travel Time for ST Elevation Myocardial Infarction Impacted by Traffic Congestion and Distance: A Digitally Integrated Map Software Study}, 430 | JOURNAL={Frontiers in Cardiovascular Medicine}, 431 | VOLUME={4}, 432 | PAGES={89}, 433 | YEAR={2018}, 434 | URL={https://www.frontiersin.org/article/10.3389/fcvm.2017.00089}, 435 | DOI={10.3389/fcvm.2017.00089}, 436 | ISSN={2297-055X} 437 | } 438 | 439 | @article{cerin2017associations, 440 | title={Associations of neighborhood environment with brain imaging outcomes in the Australian Imaging, Biomarkers and Lifestyle cohort}, 441 | author={Cerin, Ester and Rainey-Smith, Stephanie R and Ames, David and Lautenschlager, Nicola T and Macaulay, S Lance and Fowler, Christopher and Robertson, Joanne S and Rowe, Christopher C and Maruff, Paul and Martins, Ralph N and others}, 442 | journal={Alzheimer's \& Dementia}, 443 | volume={13}, 444 | number={4}, 445 | pages={388--398}, 446 | year={2017}, 447 | doi={10.1016/j.jalz.2016.06.2364}, 448 | publisher={Elsevier} 449 | } 450 | 451 | @article{smith2017increase, 452 | title={Increase in endovascular therapy in Get With The Guidelines-Stroke after the publication of pivotal trials}, 453 | author={Smith, Eric E and Saver, Jeffrey L and Cox, Margueritte and Liang, Li and Matsouaka, Roland and Xian, Ying and Bhatt, Deepak L and Fonarow, Gregg C and Schwamm, Lee H}, 454 | journal={Circulation}, 455 | volume={136}, 456 | number={24}, 457 | pages={2303--2310}, 458 | year={2017}, 459 | doi={10.1161/CIRCULATIONAHA.117.031097}, 460 | publisher={Am Heart Assoc} 461 | } 462 | 463 | @article{ma2019thrombolysis, 464 | title={Thrombolysis guided by perfusion imaging up to 9 hours after onset of stroke}, 465 | author={Ma, Henry and Campbell, Bruce CV and Parsons, Mark W and Churilov, Leonid and Levi, Christopher R and Hsu, Chung and Kleinig, Timothy J and Wijeratne, Tissa and Curtze, Sami and Dewey, Helen M and others}, 466 | journal={New England Journal of Medicine}, 467 | volume={380}, 468 | number={19}, 469 | pages={1795--1803}, 470 | year={2019}, 471 | publisher={Mass Medical Soc} 472 | } 473 | 474 | @article{holodinsky2018modeling, 475 | title={Modeling stroke patient transport for all patients with suspected large-vessel occlusion}, 476 | author={Holodinsky, Jessalyn K and Williamson, Tyler S and Demchuk, Andrew M and Zhao, Henry and Zhu, Luke and Francis, Michael J and Goyal, Mayank and Hill, Michael D and Kamal, Noreen}, 477 | journal={JAMA neurology}, 478 | volume={75}, 479 | number={12}, 480 | pages={1477--1486}, 481 | year={2018}, 482 | publisher={American Medical Association} 483 | } 484 | 485 | @article{albers2018thrombectomy, 486 | title={Thrombectomy for stroke at 6 to 16 hours with selection by perfusion imaging}, 487 | author={Albers, Gregory W and Marks, Michael P and Kemp, Stephanie and Christensen, Soren and Tsai, Jenny P and Ortega-Gutierrez, Santiago and McTaggart, Ryan A and Torbey, Michel T and Kim-Tenser, May and Leslie-Mazwi, Thabele and others}, 488 | journal={New England Journal of Medicine}, 489 | volume={378}, 490 | number={8}, 491 | pages={708--718}, 492 | year={2018}, 493 | publisher={Mass Medical Soc} 494 | } 495 | 496 | @article{man2018comparison, 497 | title={Comparison of Acute Ischemic Stroke Care and Outcomes Between Comprehensive Stroke Centers and Primary Stroke Centers in the United States}, 498 | author={Man, Shumei and Zhao, Xin and Uchino, Ken and Hussain, M Shazam and Smith, Eric E and Bhatt, Deepak L and Xian, Ying and Schwamm, Lee H and Shah, Shreyansh and Khan, Yosef and others}, 499 | journal={Circulation: Cardiovascular Quality and Outcomes}, 500 | volume={11}, 501 | number={6}, 502 | pages={e004512}, 503 | year={2018}, 504 | publisher={Am Heart Assoc} 505 | } 506 | 507 | @Article{10.3389/fneur.2019.00692, 508 | author = {Tajaddini, A. and Phan, T. G. and Beare R. and Ma H. and Srikanth, V. Currie, G. and Vu, H.L.}, 509 | title = {Application of strategic transport model and Google Maps to develop better Stroke Service}, 510 | journal = {Frontiers in Neurology}, 511 | year = {2019}, 512 | OPTkey = {}, 513 | OPTvolume = {}, 514 | OPTnumber = {}, 515 | OPTpages = {}, 516 | OPTmonth = {}, 517 | OPTnote = {}, 518 | DOI={10.3389/fneur.2019.00692}, 519 | OPTannote = {} 520 | } 521 | -------------------------------------------------------------------------------- /article/scrape-gdrive-into-rmd.R: -------------------------------------------------------------------------------- 1 | # install latest dev googledrive 2 | # pak::pkg_install("tidyverse/googledrive") 3 | 4 | # authorize google drive 5 | library(googledrive) 6 | drive_auth() 7 | 8 | # Download the googledrive into docx 9 | my_drive_id <- as_id("https://docs.google.com/document/d/1odvZbrnBuWLw-oxFyDhaaL5TdG2P23Oxr93iwp_XZ-0/edit#") 10 | 11 | drive_download(my_drive_id, 12 | type = "text/plain", 13 | path = "article/geospatial-stroke", 14 | verbose = TRUE) 15 | 16 | file.rename(from = "article/geospatial-stroke.txt", 17 | to = "article/geospatial-stroke.Rmd") 18 | 19 | 20 | -------------------------------------------------------------------------------- /article/supplementary_geospatial_stroke.tex: -------------------------------------------------------------------------------- 1 | 2 | \documentclass[utf8]{frontiers_suppmat} % for all articles 3 | \usepackage{url,hyperref,lineno,microtype} 4 | \usepackage[onehalfspacing]{setspace} 5 | \usepackage{verbatimbox} 6 | 7 | 8 | 9 | % Leave a blank line between paragraphs instead of using \\ 10 | 11 | \begin{document} 12 | \onecolumn 13 | \firstpage{1} 14 | 15 | \title[Supplementary Material]{{\helveticaitalic{Supplementary Material}}} 16 | 17 | 18 | \maketitle 19 | 20 | 21 | \section{Supplementary Material} 22 | 23 | 24 | \section{Introduction to examples} 25 | 26 | Supplementary material relating to this article is provided in the 27 | form of R and python source code and data that can be used to re-create the 28 | examples discussed in the text, as well as interactive versions of the 29 | outputs of those examples. The source material is available from \href{https://github.com/richardbeare/GeospatialStroke/archive/master.zip}{Github.com} and the live version is available 30 | \href{https://richardbeare.github.io/GeospatialStroke/index.html}{here}. 31 | 32 | 33 | The article discusses two examples: 34 | \begin{itemize} 35 | \item Construction of a choropleth or thematic map. 36 | \item Estimation of catchment basins and case loadings for rehabilitation centres. 37 | \end{itemize} 38 | 39 | The package available for download (above) includes several 40 | implementations of these examples, as follows: 41 | 42 | \begin{itemize} 43 | \item {\em R} implementations of both that do not require installation of API keys: \href{https://richardbeare.github.io/GeospatialStroke/Choropleth/mmc_surrounds.html}{Choropleth}, \href{https://richardbeare.github.io/GeospatialStroke/RehabCatchment/README.html}{Catchment basins}. 44 | \item {\em Python} implementations of both that do not require installation of API keys. \href{https://richardbeare.github.io/GeospatialStroke/python/notebooks/example1.html}{Choropleth}, \href{https://richardbeare.github.io/GeospatialStroke/python/notebooks/example2.html}{Catchment basins}. 45 | \item An alternative {\em R} implementation of the second example that utilises API keys to access Google services and Mapbox visualization services: \href{https://richardbeare.github.io/GeospatialStroke/RehabCatchmentAdvances/Googleway_Mapdeck.html}{Catchment basins with API keys}. 46 | \end{itemize} 47 | 48 | The live versions of the examples requiring API keys do not include interactive visualizations. Examples must be 49 | recreated by the user, with their own keys, in order to use the visualization tools. 50 | 51 | \section{Software setup} 52 | \subsection{{\em R} setup} 53 | 54 | The R statistical environment can be obtained for Windows, Macintosh 55 | and Linux from 56 | \href{http://cran.r-project.org/mirrors.html}{www.r-project.org}. 57 | 58 | {\em Rstudio}, a modern graphical development environment for {\em R}, 59 | is also recommended and may be obtained from 60 | \href{https://www.rstudio.com/products/rstudio/download/}{www.rstudio.com}. The 61 | following instructions assume an installation of {\em Rstudio}. 62 | 63 | The examples employ a number of {\em packages} to provide required 64 | functionality. These may be installed from inside {\em R} or {\em 65 | Rstudio} by issuing the following commands: 66 | 67 | \begin{verbatim} 68 | install.packages(c("tidyverse", "sf", "here", "units", "tmaptools", 69 | "tmap", "knitr", "mapdeck", "googleway", 70 | "mapview", "devtools", "dodgr", "viridisLite")) 71 | 72 | devtools::install_github("HughParsonage/PSMA") 73 | \end{verbatim} 74 | 75 | The interactive examples can then be created locally by opening the 76 | {\em R markdown} files, that have a {\em .Rmd} suffix, and click on 77 | the {\em knit} button in {\em Rstudio}. 78 | 79 | \subsection{{\em Python} setup} 80 | 81 | The \href{https://docs.conda.io/en/latest/miniconda.html}{miniconda} 82 | tools are recommended for management of python installations. The 83 | commands below should be executed in the terminal (Mac/Linux) or the 84 | command prompt (Windows). The steps are: 85 | 86 | \begin{enumerate} 87 | \item Install \href{https://docs.conda.io/en/latest/miniconda.html}{miniconda} 88 | \item Change directory to the python folder: 89 | \begin{verbatim} 90 | cd GeospatialStroke/Python 91 | \end{verbatim} 92 | \item Create virtual environment: 93 | \begin{verbatim} 94 | conda config --prepend channels conda-forge 95 | conda create -n GEO --strict-channel-priority --yes python=3 --file requirements.txt 96 | \end{verbatim} 97 | 98 | This command needs to be executed from within the python folder containing the {\em requirements.txt} file. 99 | 100 | \item Activate virtual environment and install notebook kernel: 101 | \begin{verbatim} 102 | conda activate GEO 103 | python -m ipykernel install --user --name GEO --display-name "Python (GEO)" 104 | \end{verbatim} 105 | 106 | \item Change directories in the terminal or command prompt to the location of the 107 | notebook folder and launch Jupyter to run the notebooks: 108 | \begin{verbatim} 109 | jupyter lab 110 | \end{verbatim} 111 | \end{enumerate} 112 | 113 | A {\em Jupyter notebook} server will run in your browser - select 114 | either {\em example1.pynb} or {\em example2.pynb} 115 | to open the examples. 116 | 117 | In future sessions on the following commands are needed to start the notebook: 118 | \begin{verbatim} 119 | conda activate GEO 120 | jupyter lab 121 | \end{verbatim} 122 | 123 | \subsection{API Keys and tokens}\label{api-keys} 124 | 125 | Online services which offer an interface to their applications will 126 | sometimes require use of an API key, or application programming 127 | interface key. This key should be unique for each user, developer or 128 | application making use of the service as it is a way for the provider to 129 | monitor and, where applicable, charge for use. 130 | 131 | Two major mapping platforms that require an API key are Google Maps 132 | and Mapbox, both of which are used in the second version of the 133 | catchment basin example. At the time of writing both allow 134 | unrestricted use of the mapping API. However, Google has limits on the 135 | other services it offers such as geocoding and direction services. 136 | 137 | Both Google and Mapbox require users create an account. 138 | 139 | The required Google API keys may be obtained by following instructions 140 | \href{https://developers.google.com/maps/documentation/embed/get-api-key}{provided 141 | by Google}. 142 | 143 | The required Mapbox token may be obtained by following instructions 144 | \href{https://www.mapbox.com/account/access-tokens}{provided by 145 | Mapbox}. 146 | 147 | \section{Supplementary Tables} 148 | 149 | \begin{table}[h] 150 | \small 151 | \begin{verbnobox}[\fontsize{8pt}{8pt}\selectfont] 152 | Simple feature collection with 1 feature and 7 fields 153 | geometry type: POINT 154 | dimension: XY 155 | bbox: xmin: 145.1207 ymin: -37.92093 xmax: 145.1207 ymax: -37.92093 156 | epsg (SRID): 4326 157 | proj4string: +proj=longlat +datum=WGS84 +no_defs 158 | query lat lon lat_min lat_max 159 | 1 Monash Medical Centre, Clayton, Victoria, Australia -37.92093 145.1207 -37.92098 -37.92088 160 | lon_min lon_max geometry 161 | 1 145.1207 145.1208 POINT (145.1207 -37.92093) 162 | \end{verbnobox} 163 | \normalsize 164 | \caption{Geocoding results for emergency hospital (Monash Medical Center).\label{tab:GeocodeMMC}} 165 | \end{table} 166 | 167 | \begin{table}[h] 168 | \begin{verbnobox}[\fontsize{8pt}{8pt}\selectfont] 169 | Simple feature collection with 6 features and 4 fields 170 | geometry type: MULTIPOLYGON 171 | dimension: XY 172 | bbox: xmin: 144.9055 ymin: -37.85553 xmax: 144.9914 ymax: -37.79821 173 | epsg (SRID): 4326 174 | proj4string: +proj=longlat +datum=WGS84 +no_defs 175 | # A tibble: 6 x 5 176 | POA_NAME Tot_P_P stroke_count_est… DistanceToMMC geometry 177 | [km] 178 | 1 3000 37975 24.7 15.77496 (((144.9576 -37.79972, 144.9588 -37 179 | 2 3002 4964 16.8 15.87279 (((144.9732 -37.80792, 144.9826 -37 180 | 3 3003 5515 5.14 18.86105 (((144.9165 -37.79821, 144.9257 -37 181 | 4 3004 9307 28.1 14.13294 (((144.985 -37.84569, 144.9842 -37 182 | 5 3005 525 0.578 18.11235 (((144.9479 -37.82339, 144.948 -37. 183 | 6 3006 18808 20.5 16.57805 (((144.956 -37.82305, 144.9579 -37. 184 | \end{verbnobox} 185 | \caption{Subset of simple features (sf) table containing both demographic and postcode boundary information for postcodes within 20km of the emergency service center. Colums displayed are postcode name, total population, estimate number of stroke cases, distance to emergency center and the postcode geometry. The estimate of stroke cases was based on a combination of population age bands (not illustrated) and incidence data from the NEMISIS study. The distance column was computed between the geometry column of this table ant he geometry column of the geocoded hospital locaiton using the {\em sf::st\_distance} function.\label{tab:MMC20}} 186 | \end{table} 187 | 188 | \begin{table}[h] 189 | \begin{verbnobox}[\fontsize{8pt}{8pt}\selectfont] 190 | Simple feature collection with 3 features and 7 fields 191 | geometry type: POINT 192 | dimension: XY 193 | bbox: xmin: 145.0797 ymin: -38.04446 xmax: 145.3457 ymax: -37.95604 194 | epsg (SRID): 4283 195 | proj4string: +proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs 196 | query lat lon lat_min lat_max 197 | DandenongHospital Dandenong Hospital, Dandenong VIC 3175, Australia -37.97611 145.2178 -37.97728 -37.97545 198 | CaseyHospital 62-70 Kangan Dr, Berwick VIC 3806, Australia -38.04446 145.3457 -38.04539 -38.04446 199 | KingstonHospital The Kingston Centre, Heatherton VIC 3202, Australia -37.95604 145.0797 -37.95830 -37.95344 200 | lon_min lon_max geometry 201 | DandenongHospital 145.2162 145.2198 POINT (145.2178 -37.97611) 202 | CaseyHospital 145.3456 145.3457 POINT (145.3457 -38.04446) 203 | KingstonHospital 145.0768 145.0810 POINT (145.0797 -37.95604) 204 | \end{verbnobox} 205 | \caption{Geocoded locations for the 3 rehabilitation centers.\label{tab:georehab}} 206 | \end{table} 207 | 208 | \begin{table}[h] 209 | \begin{verbnobox}[\fontsize{8pt}{8pt}\selectfont] 210 | Simple feature collection with 6 features and 13 fields 211 | Attribute-geometry relationship: 13 constant, 0 aggregate, 0 identity 212 | geometry type: POINT 213 | dimension: XY 214 | bbox: xmin: 145.0398 ymin: -37.89162 xmax: 145.0865 ymax: -37.86661 215 | epsg (SRID): 4283 216 | proj4string: +proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs 217 | POSTCODE ADDRESS_DETAIL_INTRNL_ID STREET_LOCALITY_INTRNL_ID BUILDING_NAME LOT_NUMBER FLAT_NUMBER 218 | 1 3145 11867025 590471 126 219 | 2 3145 10017734 530005 10 220 | 3 3145 10204296 526751 NA 221 | 4 3145 11223826 528755 NA 222 | 5 3145 9964136 473522 5 223 | 6 3145 9950516 421545 NA 224 | NUMBER_FIRST STREET_NAME STREET_TYPE_CODE lat_int lat_rem lon_int lon_rem geometry 225 | 1 1341 DANDENONG ROAD -37 -8876953 145 805995 POINT (145.0806 -37.8877) 226 | 2 13 LLOYDS AVENUE -37 -8857273 145 445100 POINT (145.0445 -37.88573) 227 | 3 94 TOORONGA ROAD -37 -8666070 145 398309 POINT (145.0398 -37.86661) 228 | 4 368 WAVERLEY ROAD -37 -8775029 145 594729 POINT (145.0595 -37.8775) 229 | 5 18 CAPON STREET -37 -8872378 145 865195 POINT (145.0865 -37.88724) 230 | 6 6 CARRUM STREET -37 -8916150 145 863720 POINT (145.0864 -37.89162) 231 | \end{verbnobox} 232 | \caption{Randomly sampled addresses from the PSMA data base.\label{tab:headpsma}} 233 | \end{table} 234 | 235 | \end{document} 236 | -------------------------------------------------------------------------------- /data/googleway/RehabLocations_googleway.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/RehabLocations_googleway.rds -------------------------------------------------------------------------------- /data/googleway/basicDemographicsRehab.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/basicDemographicsRehab.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions1.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions1.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions1_sample.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions1_sample.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions2.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions2.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions2_sample.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions2_sample.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions3.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions3.rds -------------------------------------------------------------------------------- /data/googleway/directions/directions3_sample.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions/directions3_sample.rds -------------------------------------------------------------------------------- /data/googleway/directions_queries/df_random.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions_queries/df_random.rds -------------------------------------------------------------------------------- /data/googleway/directions_queries/df_sample.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions_queries/df_sample.rds -------------------------------------------------------------------------------- /data/googleway/directions_queries/df_sample2.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/directions_queries/df_sample2.rds -------------------------------------------------------------------------------- /data/googleway/distances/distance_matrix.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/distances/distance_matrix.rds -------------------------------------------------------------------------------- /data/googleway/randomaddresses/df_random.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/randomaddresses/df_random.rds -------------------------------------------------------------------------------- /data/googleway/randomaddresses/randomaddresses.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/data/googleway/randomaddresses/randomaddresses.rds -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | cache/* 3 | notebooks/.ipynb_checkpoints/* 4 | notebooks/cache/* -------------------------------------------------------------------------------- /python/environment.yml: -------------------------------------------------------------------------------- 1 | name: GEO 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - altair=3.0.1=py37_0 7 | - asn1crypto=0.24.0=py37_1003 8 | - attrs=19.1.0=py_0 9 | - backcall=0.1.0=py_0 10 | - blas=1.0=mkl 11 | - bleach=3.1.0=py_0 12 | - boost-cpp=1.68.0=h6a4c333_1000 13 | - branca=0.3.1=py_0 14 | - bzip2=1.0.6=hfa6e2cd_1002 15 | - ca-certificates=2019.3.9=hecc5488_0 16 | - certifi=2019.3.9=py37_0 17 | - cffi=1.12.3=py37hb32ad35_0 18 | - cfitsio=3.430=hfa6e2cd_1002 19 | - chardet=3.0.4=py37_1003 20 | - click=7.0=py_0 21 | - click-plugins=1.1.1=py_0 22 | - cligj=0.5.0=py_0 23 | - colorama=0.4.1=py_0 24 | - cryptography=2.7=py37hb32ad35_0 25 | - curl=7.65.1=h4496350_0 26 | - cycler=0.10.0=py_1 27 | - decorator=4.4.0=py_0 28 | - defusedxml=0.5.0=py_1 29 | - descartes=1.1.0=py_3 30 | - entrypoints=0.3=py37_1000 31 | - expat=2.2.5=he025d50_1002 32 | - fiona=1.8.6=py37hce0be61_3 33 | - folium=0.9.1=py_0 34 | - freetype=2.10.0=h5db478b_0 35 | - freexl=1.0.5=hd288d7e_1002 36 | - gdal=2.4.1=py37hdf5ee75_3 37 | - geographiclib=1.49=py_0 38 | - geopandas=0.5.0=py_2 39 | - geopy=1.20.0=py_0 40 | - geos=3.7.1=he025d50_1000 41 | - geotiff=1.5.1=h8f84788_2 42 | - gettext=0.19.8.1=hb01d8f6_1002 43 | - glib=2.58.3=hc0c2ac7_1001 44 | - hdf4=4.2.13=hf8e6fe8_1002 45 | - hdf5=1.10.4=nompi_hcc15c50_1106 46 | - icc_rt=2019.0.0=h0cc432a_1 47 | - icu=58.1=vc14_0 48 | - idna=2.8=py37_1000 49 | - intel-openmp=2019.4=245 50 | - ipykernel=5.1.1=py37h39e3cac_0 51 | - ipython=7.5.0=py37h39e3cac_0 52 | - ipython_genutils=0.2.0=py_1 53 | - jedi=0.13.3=py37_0 54 | - jinja2=2.10.1=py_0 55 | - jpeg=9c=hfa6e2cd_1001 56 | - jsonschema=3.0.1=py37_0 57 | - jupyter_client=5.2.4=py_3 58 | - jupyter_core=4.4.0=py_0 59 | - jupyterlab=0.35.6=py37_0 60 | - jupyterlab_server=0.2.0=py_0 61 | - kealib=1.4.10=heacb130_1003 62 | - kiwisolver=1.1.0=py37he980bc4_0 63 | - krb5=1.16.3=hdd46e55_1001 64 | - libblas=3.8.0=8_mkl 65 | - libcblas=3.8.0=8_mkl 66 | - libcurl=7.65.1=h4496350_0 67 | - libffi=3.2.1=h6538335_1006 68 | - libgdal=2.4.1=h7dc65ee_3 69 | - libiconv=1.15=hfa6e2cd_1005 70 | - libkml=1.3.0=h4fd0f3b_1009 71 | - liblapack=3.8.0=8_mkl 72 | - libnetcdf=4.6.2=h396784b_1001 73 | - libpng=1.6.37=h7602738_0 74 | - libpq=11.3=hb0bdaea_0 75 | - libsodium=1.0.16=h2fa13f4_1001 76 | - libspatialindex=1.8.5=he025d50_4 77 | - libspatialite=4.3.0a=h221747d_1028 78 | - libssh2=1.8.2=h642c060_2 79 | - libtiff=4.0.10=h6512ee2_1003 80 | - libxml2=2.9.9=h9ce36c8_0 81 | - lz4-c=1.8.3=he025d50_1001 82 | - m2w64-expat=2.1.1=2 83 | - m2w64-gcc-libgfortran=5.3.0=6 84 | - m2w64-gcc-libs=5.3.0=7 85 | - m2w64-gcc-libs-core=5.3.0=7 86 | - m2w64-gettext=0.19.7=2 87 | - m2w64-gmp=6.1.0=2 88 | - m2w64-libiconv=1.14=6 89 | - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 90 | - m2w64-xz=5.2.2=2 91 | - mapclassify=2.0.1=py_0 92 | - markupsafe=1.1.1=py37hfa6e2cd_0 93 | - matplotlib=3.1.0=py37_1 94 | - matplotlib-base=3.1.0=py37h2852a4a_1 95 | - mistune=0.8.4=py37hfa6e2cd_1000 96 | - mkl=2019.4=245 97 | - msys2-conda-epoch=20160418=1 98 | - munch=2.3.2=py_0 99 | - nbconvert=5.5.0=py_0 100 | - nbformat=4.4.0=py_1 101 | - networkx=2.3=py_0 102 | - notebook=5.7.8=py37_1 103 | - numpy=1.16.3=py37h873a0b8_0 104 | - openjpeg=2.3.1=ha922770_0 105 | - openssl=1.1.1b=hfa6e2cd_2 106 | - osmnx=0.10=py_0 107 | - pandas=0.24.2=py37he350917_0 108 | - pandoc=2.7.2=0 109 | - pandocfilters=1.4.2=py_1 110 | - parso=0.4.0=py_0 111 | - pcre=8.41=h6538335_1003 112 | - pickleshare=0.7.5=py37_1000 113 | - pip=19.1.1=py37_0 114 | - poppler=0.67.0=heddaa77_6 115 | - poppler-data=0.4.9=1 116 | - postgresql=11.3=h06f7779_0 117 | - proj4=6.1.0=hc2d0af5_2 118 | - prometheus_client=0.6.0=py_0 119 | - prompt_toolkit=2.0.9=py_0 120 | - pycparser=2.19=py37_1 121 | - pygments=2.4.2=py_0 122 | - pyopenssl=19.0.0=py37_0 123 | - pyparsing=2.4.0=py_0 124 | - pyproj=2.2.0=py37h3f740ce_0 125 | - pyqt=5.9.2=py37h6538335_0 126 | - pyrsistent=0.15.2=py37hfa6e2cd_0 127 | - pysocks=1.7.0=py37_0 128 | - python=3.7.3=hb12ca83_0 129 | - python-dateutil=2.8.0=py_0 130 | - pytz=2019.1=py_0 131 | - pywinpty=0.5.5=py37_1000 132 | - pyzmq=18.0.1=py37he7828b0_1 133 | - qt=5.9.7=hc6833c9_1 134 | - requests=2.22.0=py37_0 135 | - rtree=0.8.3=py37h21ff451_1002 136 | - scipy=1.2.1=py37h29ff71c_0 137 | - send2trash=1.5.0=py_0 138 | - setuptools=41.0.1=py37_0 139 | - shapely=1.6.4=py37h8921fb9_1004 140 | - sip=4.19.8=py37h6538335_1000 141 | - six=1.12.0=py37_1000 142 | - sqlite=3.28.0=hfa6e2cd_0 143 | - terminado=0.8.2=py37_0 144 | - testpath=0.4.2=py_1001 145 | - tk=8.6.9=hfa6e2cd_1002 146 | - toolz=0.9.0=py_1 147 | - tornado=6.0.2=py37hfa6e2cd_0 148 | - traitlets=4.3.2=py37_1000 149 | - urllib3=1.24.3=py37_0 150 | - vc=14.1=h0510ff6_4 151 | - vincent=0.4.4=py_1 152 | - vs2015_runtime=14.15.26706=h3a45250_4 153 | - wcwidth=0.1.7=py_1 154 | - webencodings=0.5.1=py_1 155 | - wheel=0.33.4=py37_0 156 | - win_inet_pton=1.1.0=py37_0 157 | - wincertstore=0.2=py37_1002 158 | - winpty=0.4.3=4 159 | - xerces-c=3.2.2=h6538335_1001 160 | - xz=5.2.4=h2fa13f4_1001 161 | - zeromq=4.3.1=he025d50_1000 162 | - zlib=1.2.11=h2fa13f4_1004 163 | - zstd=1.4.0=hd8a0e53_0 164 | prefix: C:\Anaconda\envs\GEO 165 | 166 | -------------------------------------------------------------------------------- /python/notebooks/data/postcode_strokes.csv: -------------------------------------------------------------------------------- 1 | POA_CODE,strokes 2 | POA3000,24.66 3 | POA3002,16.77 4 | POA3003,5.14 5 | POA3004,28.14 6 | POA3005,0.58 7 | POA3006,20.49 8 | POA3008,11.18 9 | POA3010,0.29 10 | POA3011,47.44 11 | POA3012,59.59 12 | POA3013,37.59 13 | POA3015,41.1 14 | POA3016,46.78 15 | POA3018,48.51 16 | POA3019,18.98 17 | POA3020,131.99 18 | POA3021,147.76 19 | POA3022,10.93 20 | POA3023,102.22 21 | POA3024,31.62 22 | POA3025,55.0 23 | POA3026,0.24 24 | POA3027,4.7 25 | POA3028,71.72 26 | POA3029,129.78 27 | POA3030,175.19 28 | POA3031,36.39 29 | POA3032,58.67 30 | POA3033,60.49 31 | POA3034,55.17 32 | POA3036,23.24 33 | POA3037,88.09 34 | POA3038,70.88 35 | POA3039,45.5 36 | POA3040,77.96 37 | POA3041,39.31 38 | POA3042,60.12 39 | POA3043,59.32 40 | POA3044,92.52 41 | POA3045,0.14 42 | POA3046,120.15 43 | POA3047,48.2 44 | POA3048,35.27 45 | POA3049,22.84 46 | POA3050,0.38 47 | POA3051,21.87 48 | POA3052,22.01 49 | POA3053,23.68 50 | POA3054,21.18 51 | POA3055,37.0 52 | POA3056,55.19 53 | POA3057,22.38 54 | POA3058,101.42 55 | POA3059,26.8 56 | POA3060,55.05 57 | POA3061,15.89 58 | POA3062,0.0 59 | POA3063,1.47 60 | POA3064,94.22 61 | POA3065,22.33 62 | POA3066,13.18 63 | POA3067,15.15 64 | POA3068,44.68 65 | POA3070,66.31 66 | POA3071,53.28 67 | POA3072,90.65 68 | POA3073,190.6 69 | POA3074,72.56 70 | POA3075,73.32 71 | POA3076,65.08 72 | POA3078,28.93 73 | POA3079,53.59 74 | POA3081,38.8 75 | POA3082,70.05 76 | POA3083,95.63 77 | POA3084,100.1 78 | POA3085,46.89 79 | POA3086,0.09 80 | POA3087,30.27 81 | POA3088,84.67 82 | POA3089,23.67 83 | POA3090,4.74 84 | POA3091,4.11 85 | POA3093,16.85 86 | POA3094,25.69 87 | POA3095,69.88 88 | POA3096,6.74 89 | POA3097,3.53 90 | POA3099,12.01 91 | POA3101,85.52 92 | POA3102,20.89 93 | POA3103,64.4 94 | POA3104,73.53 95 | POA3105,51.59 96 | POA3106,70.35 97 | POA3107,64.02 98 | POA3108,92.82 99 | POA3109,110.3 100 | POA3111,56.24 101 | POA3113,20.08 102 | POA3114,9.9 103 | POA3115,8.81 104 | POA3116,29.67 105 | POA3121,62.71 106 | POA3122,52.44 107 | POA3123,38.33 108 | POA3124,91.07 109 | POA3125,52.66 110 | POA3126,33.12 111 | POA3127,58.08 112 | POA3128,58.96 113 | POA3129,55.98 114 | POA3130,130.19 115 | POA3131,88.27 116 | POA3132,47.89 117 | POA3133,83.18 118 | POA3134,103.67 119 | POA3135,63.07 120 | POA3136,146.83 121 | POA3137,49.24 122 | POA3138,55.75 123 | POA3139,33.16 124 | POA3140,51.96 125 | POA3141,54.14 126 | POA3142,60.02 127 | POA3143,30.11 128 | POA3144,44.43 129 | POA3145,66.42 130 | POA3146,71.5 131 | POA3147,48.36 132 | POA3148,22.04 133 | POA3149,131.25 134 | POA3150,239.38 135 | POA3151,41.62 136 | POA3152,125.79 137 | POA3153,66.3 138 | POA3154,13.14 139 | POA3155,70.55 140 | POA3156,103.78 141 | POA3158,13.42 142 | POA3159,5.32 143 | POA3160,19.37 144 | POA3161,60.23 145 | POA3162,69.66 146 | POA3163,88.37 147 | POA3165,93.48 148 | POA3166,72.76 149 | POA3167,31.76 150 | POA3168,48.47 151 | POA3169,65.22 152 | POA3170,67.63 153 | POA3171,69.66 154 | POA3172,70.93 155 | POA3173,62.2 156 | POA3174,112.8 157 | POA3175,153.15 158 | POA3177,32.16 159 | POA3178,79.5 160 | POA3179,12.65 161 | POA3180,22.55 162 | POA3181,58.8 163 | POA3182,43.53 164 | POA3183,36.64 165 | POA3184,27.24 166 | POA3185,37.28 167 | POA3186,100.97 168 | POA3187,75.96 169 | POA3188,56.55 170 | POA3189,21.23 171 | POA3190,33.7 172 | POA3191,47.31 173 | POA3192,90.78 174 | POA3193,81.16 175 | POA3194,49.38 176 | POA3195,105.63 177 | POA3196,90.11 178 | POA3197,39.53 179 | POA3198,53.68 180 | POA3199,194.36 181 | POA3200,22.77 182 | POA3201,53.88 183 | POA3202,8.4 184 | POA3204,86.14 185 | POA3205,30.63 186 | POA3206,34.89 187 | POA3207,42.61 188 | POA3211,2.96 189 | POA3212,41.43 190 | POA3213,9.37 191 | POA3214,69.48 192 | POA3215,91.26 193 | POA3216,211.55 194 | POA3217,10.23 195 | POA3218,38.02 196 | POA3219,77.06 197 | POA3220,56.33 198 | POA3221,1.95 199 | POA3222,63.81 200 | POA3223,44.23 201 | POA3224,50.45 202 | POA3225,28.67 203 | POA3226,49.79 204 | POA3227,15.02 205 | POA3228,48.04 206 | POA3230,12.95 207 | POA3231,5.41 208 | POA3232,5.84 209 | POA3233,9.32 210 | POA3234,0.42 211 | POA3235,0.96 212 | POA3236,0.8 213 | POA3237,0.67 214 | POA3238,0.79 215 | POA3239,1.56 216 | POA3240,4.44 217 | POA3241,11.01 218 | POA3242,2.76 219 | POA3243,1.09 220 | POA3249,10.02 221 | POA3250,53.43 222 | POA3251,1.95 223 | POA3254,0.54 224 | POA3260,22.0 225 | POA3264,10.86 226 | POA3265,6.49 227 | POA3266,12.73 228 | POA3267,0.51 229 | POA3268,8.87 230 | POA3269,1.69 231 | POA3270,1.11 232 | POA3271,0.42 233 | POA3272,7.81 234 | POA3273,0.49 235 | POA3274,0.85 236 | POA3275,0.92 237 | POA3276,1.34 238 | POA3277,5.09 239 | POA3278,0.76 240 | POA3279,0.56 241 | POA3280,118.93 242 | POA3281,3.45 243 | POA3282,6.66 244 | POA3283,3.05 245 | POA3284,17.88 246 | POA3285,3.62 247 | POA3286,2.5 248 | POA3287,1.06 249 | POA3289,3.57 250 | POA3292,1.05 251 | POA3293,1.13 252 | POA3294,3.71 253 | POA3300,47.06 254 | POA3301,3.62 255 | POA3302,1.38 256 | POA3303,0.85 257 | POA3304,10.76 258 | POA3305,49.51 259 | POA3309,0.47 260 | POA3310,1.26 261 | POA3311,11.19 262 | POA3312,4.04 263 | POA3314,1.52 264 | POA3315,8.63 265 | POA3317,0.58 266 | POA3318,6.93 267 | POA3319,1.53 268 | POA3321,3.23 269 | POA3322,0.91 270 | POA3323,0.51 271 | POA3324,2.58 272 | POA3325,2.64 273 | POA3328,3.62 274 | POA3329,0.83 275 | POA3330,0.8 276 | POA3331,14.6 277 | POA3332,2.19 278 | POA3333,2.11 279 | POA3334,2.03 280 | POA3335,7.37 281 | POA3337,70.83 282 | POA3338,48.1 283 | POA3340,57.63 284 | POA3341,3.57 285 | POA3342,11.8 286 | POA3345,2.9 287 | POA3350,186.66 288 | POA3351,21.06 289 | POA3352,39.98 290 | POA3355,59.29 291 | POA3356,56.04 292 | POA3357,11.1 293 | POA3360,2.96 294 | POA3361,3.49 295 | POA3363,17.25 296 | POA3364,7.47 297 | POA3370,7.75 298 | POA3371,4.08 299 | POA3373,10.91 300 | POA3374,1.12 301 | POA3375,0.64 302 | POA3377,39.8 303 | POA3378,0.32 304 | POA3379,3.56 305 | POA3380,28.99 306 | POA3381,4.33 307 | POA3384,2.18 308 | POA3385,1.78 309 | POA3387,0.98 310 | POA3388,3.14 311 | POA3390,4.13 312 | POA3391,0.76 313 | POA3392,3.86 314 | POA3393,14.57 315 | POA3395,1.9 316 | POA3396,4.97 317 | POA3400,62.19 318 | POA3401,11.9 319 | POA3407,1.77 320 | POA3409,4.58 321 | POA3412,1.38 322 | POA3413,0.64 323 | POA3414,9.83 324 | POA3415,0.03 325 | POA3418,14.39 326 | POA3419,4.2 327 | POA3420,0.69 328 | POA3423,2.55 329 | POA3424,4.08 330 | POA3427,5.98 331 | POA3428,2.26 332 | POA3429,95.78 333 | POA3430,0.94 334 | POA3431,9.72 335 | POA3432,0.55 336 | POA3433,0.55 337 | POA3434,13.23 338 | POA3435,8.37 339 | POA3437,28.96 340 | POA3438,6.97 341 | POA3440,5.63 342 | POA3441,4.19 343 | POA3442,22.61 344 | POA3444,38.49 345 | POA3446,2.94 346 | POA3447,1.32 347 | POA3448,3.35 348 | POA3450,38.73 349 | POA3451,16.7 350 | POA3453,5.8 351 | POA3458,9.11 352 | POA3460,14.24 353 | POA3461,14.35 354 | POA3462,4.59 355 | POA3463,13.09 356 | POA3464,3.73 357 | POA3465,50.69 358 | POA3467,5.91 359 | POA3468,1.37 360 | POA3469,1.42 361 | POA3472,7.51 362 | POA3475,1.75 363 | POA3477,3.01 364 | POA3478,14.13 365 | POA3480,11.15 366 | POA3482,0.85 367 | POA3483,4.45 368 | POA3485,1.39 369 | POA3487,0.2 370 | POA3488,0.33 371 | POA3489,0.17 372 | POA3490,7.81 373 | POA3491,0.71 374 | POA3494,1.27 375 | POA3496,21.85 376 | POA3498,16.76 377 | POA3500,119.61 378 | POA3501,4.59 379 | POA3505,17.86 380 | POA3506,0.13 381 | POA3507,0.59 382 | POA3509,1.22 383 | POA3512,1.43 384 | POA3515,3.61 385 | POA3516,2.82 386 | POA3517,7.45 387 | POA3518,6.39 388 | POA3520,1.09 389 | POA3521,1.75 390 | POA3522,1.05 391 | POA3523,19.07 392 | POA3525,8.0 393 | POA3527,5.42 394 | POA3529,0.24 395 | POA3530,0.53 396 | POA3531,0.99 397 | POA3533,4.1 398 | POA3537,6.48 399 | POA3540,1.43 400 | POA3542,0.9 401 | POA3544,0.79 402 | POA3546,1.99 403 | POA3549,10.15 404 | POA3550,159.31 405 | POA3551,63.22 406 | POA3555,81.76 407 | POA3556,42.13 408 | POA3557,1.97 409 | POA3558,4.76 410 | POA3559,2.08 411 | POA3561,20.63 412 | POA3562,0.67 413 | POA3563,3.09 414 | POA3564,64.51 415 | POA3565,0.34 416 | POA3566,1.98 417 | POA3567,2.81 418 | POA3568,16.02 419 | POA3570,2.49 420 | POA3571,1.14 421 | POA3572,0.3 422 | POA3573,0.87 423 | POA3575,3.72 424 | POA3576,0.16 425 | POA3579,27.03 426 | POA3580,4.28 427 | POA3581,0.43 428 | POA3583,0.41 429 | POA3584,3.92 430 | POA3585,45.37 431 | POA3586,1.61 432 | POA3588,1.11 433 | POA3589,1.22 434 | POA3590,0.74 435 | POA3591,0.45 436 | POA3594,2.04 437 | POA3595,3.45 438 | POA3596,0.27 439 | POA3597,1.41 440 | POA3599,0.61 441 | POA3607,0.26 442 | POA3608,12.12 443 | POA3610,7.27 444 | POA3612,8.84 445 | POA3614,1.71 446 | POA3616,21.07 447 | POA3617,0.34 448 | POA3618,1.79 449 | POA3620,37.58 450 | POA3621,9.48 451 | POA3622,1.27 452 | POA3623,4.19 453 | POA3624,1.93 454 | POA3629,32.46 455 | POA3630,106.54 456 | POA3631,33.5 457 | POA3633,1.54 458 | POA3634,4.71 459 | POA3635,1.37 460 | POA3636,24.1 461 | POA3637,1.44 462 | POA3638,9.96 463 | POA3639,2.35 464 | POA3640,2.06 465 | POA3641,3.18 466 | POA3644,41.21 467 | POA3646,1.85 468 | POA3647,0.01 469 | POA3649,1.58 470 | POA3658,17.07 471 | POA3659,2.45 472 | POA3660,30.81 473 | POA3662,0.3 474 | POA3663,0.53 475 | POA3664,3.81 476 | POA3665,1.22 477 | POA3666,25.61 478 | POA3669,7.6 479 | POA3670,1.83 480 | POA3672,55.03 481 | POA3673,5.82 482 | POA3675,5.66 483 | POA3677,87.01 484 | POA3678,21.48 485 | POA3682,1.85 486 | POA3683,6.17 487 | POA3685,11.38 488 | POA3687,3.86 489 | POA3688,3.08 490 | POA3690,98.05 491 | POA3691,25.91 492 | POA3694,0.02 493 | POA3695,1.28 494 | POA3697,2.24 495 | POA3698,3.89 496 | POA3699,4.29 497 | POA3700,6.19 498 | POA3701,4.21 499 | POA3704,0.09 500 | POA3705,0.86 501 | POA3707,10.29 502 | POA3708,0.43 503 | POA3709,1.44 504 | POA3711,2.07 505 | POA3712,1.71 506 | POA3713,6.49 507 | POA3714,18.04 508 | POA3715,1.21 509 | POA3717,12.23 510 | POA3718,0.46 511 | POA3719,1.67 512 | POA3720,2.25 513 | POA3722,21.65 514 | POA3723,10.35 515 | POA3725,2.2 516 | POA3726,1.59 517 | POA3727,1.13 518 | POA3728,2.37 519 | POA3730,47.66 520 | POA3732,2.16 521 | POA3733,0.79 522 | POA3735,2.54 523 | POA3737,21.8 524 | POA3738,0.47 525 | POA3739,0.56 526 | POA3740,3.79 527 | POA3741,14.26 528 | POA3744,1.47 529 | POA3746,1.56 530 | POA3747,19.34 531 | POA3749,8.3 532 | POA3750,8.72 533 | POA3751,1.24 534 | POA3752,47.49 535 | POA3753,3.06 536 | POA3754,45.24 537 | POA3755,0.96 538 | POA3756,20.25 539 | POA3757,26.63 540 | POA3758,4.24 541 | POA3759,2.82 542 | POA3760,0.76 543 | POA3761,2.85 544 | POA3762,0.66 545 | POA3763,2.98 546 | POA3764,29.41 547 | POA3765,25.73 548 | POA3766,3.05 549 | POA3767,4.44 550 | POA3770,8.48 551 | POA3775,11.04 552 | POA3777,38.93 553 | POA3778,0.59 554 | POA3779,1.76 555 | POA3781,7.84 556 | POA3782,22.25 557 | POA3783,5.29 558 | POA3785,0.21 559 | POA3786,4.18 560 | POA3787,2.88 561 | POA3788,4.53 562 | POA3789,1.0 563 | POA3791,3.62 564 | POA3792,2.87 565 | POA3793,10.55 566 | POA3795,4.17 567 | POA3796,22.01 568 | POA3797,12.62 569 | POA3799,18.2 570 | POA3800,0.19 571 | POA3802,61.07 572 | POA3803,30.14 573 | POA3804,22.85 574 | POA3805,101.87 575 | POA3806,123.09 576 | POA3807,14.65 577 | POA3808,8.03 578 | POA3809,8.51 579 | POA3810,103.59 580 | POA3812,6.17 581 | POA3813,2.32 582 | POA3814,6.2 583 | POA3815,9.38 584 | POA3816,6.83 585 | POA3818,54.02 586 | POA3820,61.33 587 | POA3821,8.44 588 | POA3822,2.62 589 | POA3823,5.98 590 | POA3824,18.83 591 | POA3825,80.14 592 | POA3831,7.26 593 | POA3832,0.91 594 | POA3833,1.39 595 | POA3835,1.31 596 | POA3840,64.31 597 | POA3842,12.9 598 | POA3844,93.15 599 | POA3847,6.47 600 | POA3850,55.57 601 | POA3851,18.05 602 | POA3852,0.06 603 | POA3854,2.95 604 | POA3856,1.86 605 | POA3857,0.9 606 | POA3858,11.99 607 | POA3859,2.59 608 | POA3860,26.41 609 | POA3862,10.16 610 | POA3864,0.68 611 | POA3865,1.67 612 | POA3869,4.64 613 | POA3870,4.44 614 | POA3871,10.97 615 | POA3873,0.92 616 | POA3874,2.41 617 | POA3875,82.04 618 | POA3878,5.39 619 | POA3880,28.37 620 | POA3882,4.77 621 | POA3885,6.16 622 | POA3886,1.14 623 | POA3887,1.08 624 | POA3888,16.95 625 | POA3889,0.89 626 | POA3890,1.04 627 | POA3891,0.74 628 | POA3892,6.17 629 | POA3893,0.08 630 | POA3895,0.93 631 | POA3896,1.39 632 | POA3898,2.34 633 | POA3900,0.81 634 | POA3902,1.82 635 | POA3903,3.24 636 | POA3904,7.37 637 | POA3909,40.76 638 | POA3910,51.66 639 | POA3911,8.58 640 | POA3912,44.94 641 | POA3913,9.1 642 | POA3915,36.57 643 | POA3916,5.98 644 | POA3918,10.09 645 | POA3919,7.12 646 | POA3920,0.19 647 | POA3921,0.4 648 | POA3922,41.0 649 | POA3923,2.62 650 | POA3925,13.95 651 | POA3926,13.68 652 | POA3927,7.32 653 | POA3928,1.82 654 | POA3929,5.14 655 | POA3930,66.43 656 | POA3931,125.2 657 | POA3933,3.26 658 | POA3934,70.88 659 | POA3936,55.66 660 | POA3937,5.75 661 | POA3938,14.42 662 | POA3939,79.47 663 | POA3940,48.79 664 | POA3941,52.34 665 | POA3942,13.91 666 | POA3943,13.1 667 | POA3944,4.43 668 | POA3945,3.4 669 | POA3946,1.12 670 | POA3950,21.52 671 | POA3951,3.18 672 | POA3953,32.35 673 | POA3954,1.24 674 | POA3956,11.67 675 | POA3957,0.63 676 | POA3958,0.9 677 | POA3959,5.06 678 | POA3960,15.44 679 | POA3962,4.11 680 | POA3964,0.53 681 | POA3965,1.56 682 | POA3966,1.81 683 | POA3967,0.43 684 | POA3971,20.44 685 | POA3975,22.29 686 | POA3976,43.94 687 | POA3977,172.05 688 | POA3978,12.72 689 | POA3979,0.81 690 | POA3980,8.27 691 | POA3981,14.91 692 | POA3984,20.86 693 | POA3987,3.66 694 | POA3988,3.3 695 | POA3990,0.31 696 | POA3991,1.19 697 | POA3992,2.7 698 | POA3995,50.3 699 | POA3996,30.27 700 | -------------------------------------------------------------------------------- /python/readme.md: -------------------------------------------------------------------------------- 1 | # Geospatial analysis with Python 2 | 3 | ## Setup 4 | 5 | To reproduce the analytical environment, the easiest method is with conda + conda-forge. 6 | 7 | First, install the latest version of [miniconda](https://docs.conda.io/en/latest/miniconda.html). 8 | 9 | Next, create/activate the virtual environment by opening a terminal (Mac/Linux) or Anaconda prompt (Windows), changing directories to this `python` folder, then running these commands: 10 | 11 | ``` 12 | conda config --prepend channels conda-forge 13 | conda create -n GEO --strict-channel-priority --yes python=3 --file requirements.txt 14 | conda activate GEO 15 | python -m ipykernel install --user --name GEO --display-name "Python (GEO)" 16 | ``` 17 | 18 | To run the notebooks, change directories in your terminal/Anaconda prompt to the location of the notebook files, then run the command: `jupyter lab`. 19 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | folium 2 | geopandas 3 | jupyterlab 4 | matplotlib 5 | networkx 6 | numpy 7 | osmnx 8 | pandas 9 | scipy 10 | shapely -------------------------------------------------------------------------------- /python/screenshots/01-open-cmd-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/python/screenshots/01-open-cmd-prompt.png -------------------------------------------------------------------------------- /python/screenshots/02-change-directories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/python/screenshots/02-change-directories.png -------------------------------------------------------------------------------- /python/screenshots/03-create-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/python/screenshots/03-create-env.png -------------------------------------------------------------------------------- /python/screenshots/04-activate-env-install-kernelspec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/python/screenshots/04-activate-env-install-kernelspec.png -------------------------------------------------------------------------------- /python/screenshots/05-cd-notebooks-jupyter-lab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbeare/GeospatialStroke/bf2855b5fa5a796c3c7a1b0ac47fc4d7e54bf0d7/python/screenshots/05-cd-notebooks-jupyter-lab.png --------------------------------------------------------------------------------