diff --git a/.gitignore b/.gitignore index acacedc..38aec13 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ npt_net_qgis scottraffic/ *.geojson osm_os/ +nptscot.github.io/ diff --git a/R/corenet_build_OS.R b/R/corenet_build_OS.R index e6ec8ff..19848f6 100644 --- a/R/corenet_build_OS.R +++ b/R/corenet_build_OS.R @@ -353,12 +353,17 @@ corenet_build_OS = function(os_scotland, osm_scotland, region_names,cities_regio return(NULL) } })) - # Define the file path for the combined GeoJSON - combined_CN_file = glue::glue("{output_folder}/combined_CN_{number}_{date_folder}_OS.geojson") + # Define the file paths for the combined GeoJSON and GeoPackage + combined_CN_geojson_file = glue::glue("{output_folder}/combined_CN_{number}_{date_folder}_OS.geojson") + combined_CN_gpkg_file = glue::glue("{output_folder}/combined_CN_{number}_{date_folder}_OS.gpkg") - # Write the combined GeoJSON to a file - sf::st_write(combined_CN_geojson, combined_CN_file, delete_dsn = TRUE) - cat("Combined cohesive networks GeoJSON file for group", number, "has been saved to:", combined_CN_file, "\n") + # Write the combined data to GeoJSON and GeoPackage files + sf::st_write(combined_CN_geojson, combined_CN_geojson_file, delete_dsn = TRUE) + sf::st_write(combined_CN_geojson, combined_CN_gpkg_file, delete_dsn = TRUE) + + # Print messages indicating where the files have been saved + cat("Combined cohesive networks GeoJSON file for group", number, "has been saved to:", combined_CN_geojson_file, "\n") + cat("Combined cohesive networks GeoPackage file for group", number, "has been saved to:", combined_CN_gpkg_file, "\n") # Define the path for the PMtiles combined_CN_pmtiles = glue::glue("{output_folder}/combined_CN_{number}_{date_folder}_OS.pmtiles") @@ -376,7 +381,7 @@ corenet_build_OS = function(os_scotland, osm_scotland, region_names,cities_regio ' --buffer=5', ' -rg', ' --force ', - combined_CN_file + combined_CN_geojson_file ) # Execute the command and capture output diff --git a/R/pkgs.R b/R/pkgs.R index 7829073..a95e12e 100644 --- a/R/pkgs.R +++ b/R/pkgs.R @@ -21,6 +21,7 @@ get_pkgs = function() { "tidyverse", # Includes dplyr, ggplot2, tidyr, stringr etc. "zonebuilder", # For creating zones for spatial analysis "iterators", # For creating iterators - "doParallel" # For parallel processing + "doParallel", # For parallel processing + "httr" ) } diff --git a/R/simplify_network.R b/R/simplify_network.R index 2f76c9a..d131680 100644 --- a/R/simplify_network.R +++ b/R/simplify_network.R @@ -8,12 +8,9 @@ simplify_network = function(rnet_y, parameters, region_boundary) { region_snake_case = snakecase::to_snake_case(parameters$region[[1]]) base_name = paste0("OS_Scotland_Network_", region_snake_case, ".geojson") rnet_x_f = file.path("inputdata", base_name) - rnet_x = sf::read_sf(rnet_x_f) - # rnet_x = geojsonsf::geojson_sf(rnet_x_f) # bit faster + rnet_x = sf::read_sf(rnet_x_f) |> sf::st_transform(crs = "EPSG:27700") - # Transform the spatial data to a different coordinate reference system (EPSG:27700) - # TODO: uncomment: - rnet_x = rnet_x[region_boundary, ] # TODO: is this needed? Can remove if not + rnet_x = rnet_x[region_boundary |> sf::st_transform(crs = "EPSG:27700") , ] # TODO: is this needed? Can remove if not rnet_xp = sf::st_transform(rnet_x, "EPSG:27700") rnet_yp = sf::st_transform(rnet_y, "EPSG:27700") @@ -69,17 +66,8 @@ simplify_network = function(rnet_y, parameters, region_boundary) { rnet_merged_all = rnet_merged_all |> dplyr::filter_at(columns_to_check, any_vars(!is.na(.))) - # Selecting only the geometry column from the 'rnet_merged_all' dataset. - rnet_merged_all_only_geometry = rnet_merged_all |> dplyr::select(geometry) - - # Merging all geometries into a single geometry using st_union from the sf package. - rnet_merged_all_union = sf::st_union(rnet_merged_all_only_geometry) - - # Transforming the merged geometry to a specific coordinate reference system (CRS), EPSG:27700. - rnet_merged_all_projected = sf::st_transform(rnet_merged_all_union, "EPSG:27700") - # Converting the projected geometry into a GEOS geometry. GEOS is a library used for spatial operations. - rnet_merged_all_geos = geos::as_geos_geometry(rnet_merged_all_projected) + rnet_merged_all_geos = geos::as_geos_geometry(rnet_merged_all) # Creating a buffer around the GEOS geometry. This expands the geometry by a specified distance (in meters). rnet_merged_all_geos_buffer = geos::geos_buffer(rnet_merged_all_geos, distance = 30, params = geos::geos_buffer_params(quad_segs = 4)) @@ -87,18 +75,20 @@ simplify_network = function(rnet_y, parameters, region_boundary) { # Converting the buffered GEOS geometry back to an sf object. rnet_merged_all_projected_buffer = sf::st_as_sf(rnet_merged_all_geos_buffer) - # Transform the coordinate reference system of 'rnet_merged_all' to WGS 84 (EPSG:4326). - rnet_merged_all_buffer = sf::st_transform(rnet_merged_all_projected_buffer, "EPSG:4326") - # Subsetting another dataset 'rnet_y' based on the spatial relation with 'rnet_merged_all_buffer'. # It selects features from 'rnet_y' that are within the boundaries of 'rnet_merged_all_buffer'. - rnet_y_subset = rnet_y[rnet_merged_all_buffer, , op = sf::st_within] + # rnet_y_subset = sf::st_intersection(rnet_yp, rnet_merged_all_projected_buffer) + # browser() + rnet_yp_points = sf::st_point_on_surface(rnet_yp) + rnet_yp_points_subset = rnet_yp_points[rnet_merged_all_projected_buffer, ] + rnet_y_subset = rnet_yp[rnet_yp_points_subset, ] # Filter 'rnet_y' to exclude geometries within 'within_join' - rnet_y_rest = rnet_y[!rnet_y$geometry %in% rnet_y_subset$geometry, ] + rnet_y_rest = rnet_yp[!rnet_yp$geometry %in% rnet_y_subset$geometry, ] # Transform the CRS of the 'rnet_merged_all' object to WGS 84 (EPSG:4326) rnet_merged_all = sf::st_transform(rnet_merged_all, "EPSG:4326") + rnet_y_rest = sf::st_transform(rnet_y_rest, "EPSG:4326") # Combine 'rnet_y_rest' and 'rnet_merged_all' into a single dataset simplified_network = dplyr::bind_rows(rnet_y_rest, rnet_merged_all) diff --git a/_targets.R b/_targets.R index 98dd799..146db88 100644 --- a/_targets.R +++ b/_targets.R @@ -20,6 +20,7 @@ library(targets) # Needed to make targets work library(magrittr) # Light load of |> library(sf) # Needed for sf support set.seed(2023) +httr::set_config(httr::timeout(seconds = 6000)) tar_source() pkgs = get_pkgs() @@ -726,7 +727,6 @@ list( os_pois_subset[region_boundary_buffered, ] |> sf::st_transform("EPSG:27700") }), - tar_target(grid, { grid = readRDS("./inputdata/grid_scot.Rds") grid = sf::st_transform(grid, "EPSG:4326") @@ -776,14 +776,13 @@ list( # Combined utility trip purposes -------------------------------------------- tar_target(od_utility_combined, { - od_utility_combined = rbind(od_shopping, od_visiting, od_leisure) |> dplyr::slice_max(n = parameters$max_to_route, order_by = all, with_ties = FALSE) sum(od_utility_combined$bicycle) / sum(od_utility_combined$all) # Get % cycling for commuting per zone # pcycle_regional = sum(commute_stats$comm_orig_bicycle, na.rm = TRUE) / - # sum(commute_stats$comm_orig_all, na.rm = TRUE) + # sum(commute_stats$comm_orig_all, na.rm = TRUE) pcycle_national = 0.016 commute_stats_minimal = commute_stats |> @@ -792,7 +791,7 @@ list( dplyr::transmute( DataZone, multiplier = (comm_orig_bicycle / comm_orig_all) / - pcycle_national + pcycle_national ) |> # 0 to 0.1: dplyr::mutate(multiplier = case_when( @@ -978,7 +977,7 @@ list( tar_target(utility_stats_baseline, { stats = sf::st_drop_geometry(od_utility_combined) - stats = stats[, c( + stats = stats[, c( "startDZ", "endDZ", "purpose", "all", "car", "foot", "bicycle", "public_transport", "taxi" )] @@ -1243,6 +1242,7 @@ list( # TODO (nice to have): replace with global setting (needs testing): use_sf_s2_status = sf::sf_use_s2() sf::sf_use_s2(FALSE) + # Debugging the function: rnet_simple = simplify_network(combined_network_tile, parameters, region_boundary) make_geojson_zones(rnet_simple, paste0(region_folder, "/simplified_network.geojson")) # Restore previous status @@ -1279,7 +1279,6 @@ list( responce = system(command_all, intern = TRUE) responce }), - tar_target(pmtiles_buildings, { check = length(zones_dasymetric_tile) @@ -1440,7 +1439,7 @@ list( message("Saving outputs for ", parameters$date_routing) - saveRDS(od_commute_subset, file.path(region_folder, "od_commute_subset.Rds")) + saveRDS(od_commute_subset, file.path(region_folder, "od_commute_subset.Rds")) saveRDS(zones_stats, file.path(region_folder, "zones_stats.Rds")) saveRDS(school_stats, file.path(region_folder, "school_stats.Rds")) @@ -1452,7 +1451,6 @@ list( sf::write_sf(combined_network, file.path(region_folder, "combined_network.gpkg"), delete_dsn = TRUE) as.character(Sys.Date()) }), - tar_target(metadata, { # TODO: generate build summary # metadata_all = tar_meta() diff --git a/code/build.R b/code/build.R index 97a423e..61a9c1c 100644 --- a/code/build.R +++ b/code/build.R @@ -11,7 +11,7 @@ library(doParallel) tar_source() parameters = jsonlite::read_json("parameters.json", simplifyVector = T) -lads = sf::read_sf("inputdata/boundaries/la_regions_2023.geojson") +lads = sf::read_sf("inputdata/boundaries/la_regions_scotland_bfe_simplified_2023.geojson") date_folder = parameters$date_routing output_folder = file.path("outputdata", date_folder) @@ -57,18 +57,20 @@ if (GENERATE_CDB) { library(osmactive) # See https://github.com/nptscot/osmactive/blob/main/code/classify-roads.R and traffic-volumes.R - f_traffic = "scottraffic/final_estimates_Scotland_higherror_discarded.gpkg" + f_traffic = "scottraffic/final_estimates_Scotland.gpkg" if (!file.exists(f_traffic)) { system("gh repo clone nptscot/scottraffic") + file.remove(f_traffic) setwd("scottraffic") system("gh release list") - system("gh release download v5 --clobber") + system("gh release download v6") setwd("..") } traffic_volumes_scotland = sf::read_sf(f_traffic) - # Generate cycle_net - this is slow, we should save the file - osm_national = get_travel_network("Scotland", force_download = T) + # Generate cycle_net: forcing update: + # osm_national = get_travel_network("Scotland", force_download = TRUE) + osm_national = get_travel_network("Scotland") # saveRDS(osm_national, "inputdata/osm_national_2024_05_23") # Generate road segment midpoints @@ -198,15 +200,20 @@ if (GENERATE_CDB) { # cycle_net_traffic = level_of_service(cycle_net_traffic) - + cbd_layer = cycle_net_traffic |> transmute( osm_id, highway, - `Traffic volume` = final_traffic, `Speed limit` = final_speed, `Infrastructure type` = cycle_segregation, - `Level of Service` + `Level of Service`, + `Traffic volume category` = case_when( + final_traffic >= 0 & final_traffic < 1999.5 ~ "0 to 1999", + final_traffic >= 1999.5 & final_traffic < 3999.5 ~ "2000 to 3999", + final_traffic >= 3999.5 ~ "4000+", + TRUE ~ NA_character_ + ) ) # save file for individual district district_name = district_geom$LAD23NM |> @@ -221,6 +228,10 @@ if (GENERATE_CDB) { } # Combine all CBD files into a single file + # Remove combined file if it already exists: + if (file.exists(cbd_filename)) { + file.remove(cbd_filename) + } cbd_files = list.files(output_folder, pattern = "cbd_layer_.*\\.geojson$", full.names = TRUE) # Create an empty cbd_layers and cbd_layer cbd_layers = sf::st_sf(geometry = st_sfc()) @@ -228,9 +239,15 @@ if (GENERATE_CDB) { cbd_layers = lapply(cbd_files, sf::read_sf) cbd_layer = do.call(rbind, cbd_layers) cbd_filename = paste0(output_folder, "/cbd_layer_", date_folder, ".geojson") - if (file.exists(cbd_filename)) { - file.remove(cbd_filename) - } + # Update traffic volumes for off road cycleways + cbd_layer = cbd_layer |> + mutate( + `Traffic volume category` = case_when( + `Infrastructure type` == "Off Road Cycleway" ~ NA_character_, + highway %in% c("footway", "path", "pedestrian", "steps") ~ NA_character_, + TRUE ~ `Traffic volume category` + ) + ) sf::write_sf(cbd_layer, cbd_filename) fs::file_size(cbd_filename) @@ -304,7 +321,7 @@ if (parameters$generate_CN_start) { # mapview::mapview(cn_test, zcol = "road_function") # Combine regional outputs ---------------------------------------------------- + GENERATE_PMTILES = TRUE if (GENERATE_PMTILES) { @@ -684,9 +701,6 @@ if (PUSH_TO_GITHUB) { parameters$max_to_route > 20e3 is_linux = Sys.info()[["sysname"]] == "Linux" if (full_build) { - v = paste0("v", Sys.Date(), "_commit_", commit$commit) - v = gsub(pattern = " |:", replacement = "-", x = v) - # Or latest release: setwd(glue::glue(getwd(),"/", output_folder)) system("gh release list") v = glue::glue("v{date_folder}") diff --git a/code/prep_admin_bounds.R b/code/prep_admin_bounds.R index ec74816..44e693e 100644 --- a/code/prep_admin_bounds.R +++ b/code/prep_admin_bounds.R @@ -18,7 +18,31 @@ library(sf) # la = sf::read_sf("la.gpkg") # plot(la) -la = sf::read_sf("https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Local_Authority_Districts_December_2023_Boundaries_UK_BSC/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") + +# Boundary options: +# https://www.ons.gov.uk/methodology/geography/geographicalproducts/digitalboundaries + # (BFC) Full resolution - clipped to the coastline (Mean High Water mark) + # (BFE) Full resolution - extent of the realm (usually this is the Mean Low Water mark but, in some cases, boundaries extend beyond this to include offshore islands) + # (BGC) Generalised (20m) - clipped to the coastline (Mean High Water mark) + # (BUC) Ultra Generalised (500m) - clipped to the coastline (Mean High Water mark) + + +la_bsc = sf::read_sf("https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Local_Authority_Districts_December_2023_Boundaries_UK_BSC/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") +la_bfe = sf::read_sf("https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Local_Authority_Districts_December_2023_Boundaries_UK_BFE/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") + +# BFE version is 50x larger than BSC +object.size(la_bfe) |> as.numeric() / + object.size(la_bsc) |> as.numeric() + +la_bfe_simplified = rmapshaper::ms_simplify(la_bfe, keep = 0.02) + +# BFE version is now 1.4x larger than BSC +object.size(la_bfe_simplified) |> as.numeric() / + object.size(la_bsc) |> as.numeric() + +mapview::mapview(la_bsc) + mapview::mapview(la_bfe_simplified) + +la = la_bfe_simplified la = la[, c("LAD23CD", "LAD23NM")] # LAs in Scotland, CD starts with "S": @@ -75,10 +99,11 @@ la_regions |> # check for NAs: la_regions[is.na(la_regions$Region),] +sf::write_sf(la_regions, "la_regions_scotland_bfe_simplified_2023.geojson", delete_dsn = TRUE) +sf::write_sf(la, "la_uk_bfe_simplified_2023.geojson", delete_dsn = TRUE) + # system("gh release upload boundaries-2024 las_scotland_2023.geojson las_2023.geojson --clobber") -dir.create("inputdata/boundaries", showWarnings = FALSE) -sf::write_sf(la_regions, "inputdata/boundaries/la_regions_2023.geojson", delete_dsn = TRUE) -sf::write_sf(la, "inputdata/boundaries/las_2023.geojson", delete_dsn = TRUE) +system("gh release upload boundaries-2024 la_regions_scotland_bfe_simplified_2023.geojson la_uk_bfe_simplified_2023.geojson --clobber") # https://github.com/nptscot/npt/releases/download/boundaries-2024/las_2023.geojson diff --git a/code/tests/simplified_network_test.qmd b/code/tests/simplified_network_test.qmd new file mode 100644 index 0000000..ee0bb70 --- /dev/null +++ b/code/tests/simplified_network_test.qmd @@ -0,0 +1,105 @@ +--- +format: gfm +--- + +# create a exmaple network + +```{r} +library(tidyverse) +library(targets) +library(tidygraph) +library(osmextract) +library(foreach) +library(iterators) +library(parallel) +library(doParallel) +library(mapview) +tar_source() +``` + + +```{r} +# get off road from osm to os, simple combine +zone = zonebuilder::zb_zone("Edinburgh", n_circles = 3) |> sf::st_transform(crs = "EPSG:27700") + +os_file_path = "inputdata/open_roads_scotland.gpkg" +os_scotland = sf::read_sf(os_file_path) +sf::st_geometry(os_scotland) = "geometry" + +os_scotland = os_scotland[sf::st_union(zone), , op = sf::st_within] + +osm_file_path = "inputdata/connectivity_fixed_osm.gpkg" +osm_scotland = sf::read_sf(osm_file_path) +sf::st_geometry(osm_scotland) = "geometry" + +osm_scotland = osm_scotland[sf::st_union(zone), , op = sf::st_within] + +os_scotland = os_scotland |> sf::st_transform(crs = "EPSG:27700") |> dplyr::select(geometry) +osm_scotland = osm_scotland |> sf::st_transform(crs = "EPSG:27700") + +rnet_merged_all_geos = geos::as_geos_geometry(os_scotland) + +rnet_merged_all_geos_buffer = geos::geos_buffer(rnet_merged_all_geos, distance = 30, params = geos::geos_buffer_params(quad_segs = 4)) + +rnet_merged_all_projected_buffer = sf::st_as_sf(rnet_merged_all_geos_buffer) + +osm_scotland_subset = osm_scotland[sf::st_union(rnet_merged_all_projected_buffer), , op = sf::st_within] + +osm_scotland_rest = osm_scotland[!osm_scotland$geometry %in% osm_scotland_subset$geometry, ] + +os_scotland = dplyr::bind_rows(osm_scotland_rest, os_scotland) +``` + +```{r} +parameters = jsonlite::read_json("parameters.json", simplifyVector = T) +lads = sf::read_sf("inputdata/boundaries/la_regions_2023.geojson") +# To test for a single local authority: +# lads = lads |> filter(LAD23NM %in% c("City of Edinburgh", "Clackmannanshire")) +date_folder = parameters$date_routing +la_names = lads$LAD23NM +output_folder = file.path("outputdata", date_folder) +la_names_lowercase = snakecase::to_snake_case(la_names) +``` + +```{r} +la_name = la_names[1] +for (la_name in la_names) { + message("Processing la_name: ", la_name) + la_name_lowercase = snakecase::to_snake_case(la_name) + lads_la_name = lads |> filter(LAD23NM == la_name) + os_scotland_la_name = os_scotland[sf::st_union(lads_la_name), , op = sf::st_intersects] + # save os_scotland_la_name + os_scotland_la_name_f = file.path("inputdata", paste0("os_", la_name_lowercase, "_osm_orcp.geojson")) + + sf::st_write(os_scotland_la_name, os_scotland_la_name_f, delete_dsn = T) +} +``` + +```{r} +region_snake_case = snakecase::to_snake_case(region_names[1]) +base_name = paste0("OS_Scotland_Network_", region_snake_case, ".geojson") +rnet_x_f = file.path("inputdata", base_name) +rnet_x = sf::read_sf(rnet_x_f) |> sf::st_transform(crs = "EPSG:27700") + +osm_file_path = "inputdata/connectivity_fixed_osm.gpkg" +osm_scotland = sf::read_sf(osm_file_path) +sf::st_geometry(osm_scotland) = "geometry" + + + +``` + + +```{r} +zone = zonebuilder::zb_zone("Edinburgh", n_circles = 3) |> sf::st_transform(crs = "EPSG:27700") +rnet_edingburgh = sf::st_read("outputdata/2024-11-01/edinburgh_and_lothians/combined_network_tile.geojson") |> sf::st_transform(crs = "EPSG:27700") +rnet_edingburgh_zone = rnet_edingburgh[sf::st_union(zone), , op = sf::st_intersects] +parameters$region[[1]] = "Edinburgh and Lothians" + +os_file_path = "inputdata/open_roads_scotland.gpkg" +sf::st_geometry(os_scotland) = "geometry" + +system.time({ +rnet_simple = simplify_network(rnet_edingburgh_zone, parameters, region_boundary = zone) +}) +``` \ No newline at end of file diff --git a/parameters.json b/parameters.json index 78e18dc..a276eed 100644 --- a/parameters.json +++ b/parameters.json @@ -8,7 +8,7 @@ "disag_threshold": [100], "min_distance_meters": [500], "region_buffer_distance_meters": [5000], - "region": ["Edinburgh and Lothians"], + "region": ["Glasgow and Strathclyde"], "generate_CN_start": [true], "coherent_sources": ["OS"], "coherent_percentile": [0.94, 0.9] diff --git a/route_ids.csv b/route_ids.csv index 14265d3..e4c8149 100644 --- a/route_ids.csv +++ b/route_ids.csv @@ -1,7 +1,87 @@ nrow,plan,purpose,region,date,id -100,ebike,commute,City of Edinburgh,2024-10-01,10793 -100,balanced,school,City of Edinburgh,2024-10-01,10789 -100,quietest,school,City of Edinburgh,2024-10-01,10785 +93472,ebike,utility,Glasgow and Strathclyde,2024-12-01,11541 +93472,quietest,utility,Glasgow and Strathclyde,2024-12-01,11540 +93472,fastest,utility,Glasgow and Strathclyde,2024-12-01,11539 +93472,balanced,utility,Glasgow and Strathclyde,2024-12-01,11538 +218877,ebike,commute,Glasgow and Strathclyde,2024-12-01,11536 +218877,balanced,commute,Glasgow and Strathclyde,2024-12-01,11534 +218877,fastest,commute,Glasgow and Strathclyde,2024-12-01,11532 +29032,ebike,school,Glasgow and Strathclyde,2024-12-01,11531 +29032,balanced,school,Glasgow and Strathclyde,2024-12-01,11530 +29032,quietest,school,Glasgow and Strathclyde,2024-12-01,11529 +29032,fastest,school,Glasgow and Strathclyde,2024-12-01,11528 +93472,ebike,utility,Glasgow and Strathclyde,2024-11-01,11354 +93472,quietest,utility,Glasgow and Strathclyde,2024-11-01,11353 +93472,fastest,utility,Glasgow and Strathclyde,2024-11-01,11352 +93472,balanced,utility,Glasgow and Strathclyde,2024-11-01,11349 +218877,ebike,commute,Glasgow and Strathclyde,2024-11-01,11312 +218877,balanced,commute,Glasgow and Strathclyde,2024-11-01,11303 +218877,quietest,commute,Glasgow and Strathclyde,2024-11-01,11302 +111504,ebike,utility,Edinburgh and Lothians,2024-11-01,11221 +111504,quietest,utility,Edinburgh and Lothians,2024-11-01,11220 +111504,fastest,utility,Edinburgh and Lothians,2024-11-01,11218 +111504,balanced,utility,Edinburgh and Lothians,2024-11-01,11217 +90716,ebike,commute,Edinburgh and Lothians,2024-11-01,11216 +218877,fastest,commute,Glasgow and Strathclyde,2024-11-01,11214 +29032,ebike,school,Glasgow and Strathclyde,2024-11-01,11213 +29032,balanced,school,Glasgow and Strathclyde,2024-11-01,11212 +29032,quietest,school,Glasgow and Strathclyde,2024-11-01,11211 +29032,fastest,school,Glasgow and Strathclyde,2024-11-01,11202 +90716,balanced,commute,Edinburgh and Lothians,2024-11-01,11116 +90716,quietest,commute,Edinburgh and Lothians,2024-11-01,11105 +90716,fastest,commute,Edinburgh and Lothians,2024-11-01,11104 +9425,ebike,school,Edinburgh and Lothians,2024-11-01,10897 +9425,balanced,school,Edinburgh and Lothians,2024-11-01,10896 +9425,quietest,school,Edinburgh and Lothians,2024-11-01,10895 +9425,fastest,school,Edinburgh and Lothians,2024-11-01,10894 +51181,ebike,utility,"Tayside, Central and Fife",2024-11-01,10893 +51181,quietest,utility,"Tayside, Central and Fife",2024-11-01,10892 +51181,fastest,utility,"Tayside, Central and Fife",2024-11-01,10891 +51181,balanced,utility,"Tayside, Central and Fife",2024-11-01,10890 +65662,ebike,commute,"Tayside, Central and Fife",2024-11-01,10889 +65662,balanced,commute,"Tayside, Central and Fife",2024-11-01,10888 +65662,quietest,commute,"Tayside, Central and Fife",2024-11-01,10887 +65662,fastest,commute,"Tayside, Central and Fife",2024-11-01,10886 +11451,ebike,school,"Tayside, Central and Fife",2024-11-01,10885 +11451,balanced,school,"Tayside, Central and Fife",2024-11-01,10884 +11451,quietest,school,"Tayside, Central and Fife",2024-11-01,10883 +11451,fastest,school,"Tayside, Central and Fife",2024-11-01,10882 +21026,ebike,utility,Aberdeen and North East,2024-11-01,10855 +21026,quietest,utility,Aberdeen and North East,2024-11-01,10854 +21026,fastest,utility,Aberdeen and North East,2024-11-01,10853 +21026,balanced,utility,Aberdeen and North East,2024-11-01,10852 +32967,ebike,commute,Aberdeen and North East,2024-11-01,10851 +32967,balanced,commute,Aberdeen and North East,2024-11-01,10850 +32967,quietest,commute,Aberdeen and North East,2024-11-01,10849 +32967,fastest,commute,Aberdeen and North East,2024-11-01,10848 +4525,ebike,school,Aberdeen and North East,2024-11-01,10847 +4525,balanced,school,Aberdeen and North East,2024-11-01,10846 +4525,quietest,school,Aberdeen and North East,2024-11-01,10845 +4525,fastest,school,Aberdeen and North East,2024-11-01,10844 +9536,ebike,utility,Scotland South,2024-11-01,10843 +9536,quietest,utility,Scotland South,2024-11-01,10842 +9536,fastest,utility,Scotland South,2024-11-01,10841 +9536,balanced,utility,Scotland South,2024-11-01,10840 +6943,ebike,commute,Scotland South,2024-11-01,10839 +6943,balanced,commute,Scotland South,2024-11-01,10838 +6943,quietest,commute,Scotland South,2024-11-01,10837 +6943,fastest,commute,Scotland South,2024-11-01,10836 +1607,ebike,school,Scotland South,2024-11-01,10835 +1607,balanced,school,Scotland South,2024-11-01,10834 +1607,quietest,school,Scotland South,2024-11-01,10833 +1607,fastest,school,Scotland South,2024-11-01,10832 +14963,ebike,utility,Highlands and Islands,2024-11-01,10831 +14963,quietest,utility,Highlands and Islands,2024-11-01,10830 +14963,fastest,utility,Highlands and Islands,2024-11-01,10829 +14963,balanced,utility,Highlands and Islands,2024-11-01,10828 +7976,ebike,commute,Highlands and Islands,2024-11-01,10827 +7976,balanced,commute,Highlands and Islands,2024-11-01,10826 +7976,quietest,commute,Highlands and Islands,2024-11-01,10825 +7976,fastest,commute,Highlands and Islands,2024-11-01,10824 +3228,ebike,school,Highlands and Islands,2024-11-01,10823 +3228,balanced,school,Highlands and Islands,2024-11-01,10822 +3228,quietest,school,Highlands and Islands,2024-11-01,10821 +3228,fastest,school,Highlands and Islands,2024-11-01,10820 100,ebike,utility,Edinburgh and Lothians,2024-09-30,10779 100,quietest,utility,Edinburgh and Lothians,2024-09-30,10778 100,fastest,utility,Edinburgh and Lothians,2024-09-30,10777