The purpose of the inclusive_mobility_get function is to recategorize OSM data based on the recent Inclusive Mobility (IM) guide UK. The goal behind this is to encourage the use of crowd-sourced open data in the inclusive infrastructure planning. Indeed, the function has been written to encourage discussions around the applicability of open data and act as a starting point.

It has to be noted that this function is a simplification of all the requirements outlined in the guide. There are a couple of reasons for this. First, while there is a large number of tags in OSM to create detailed maps, but the values imputed are not always IM guide-friendly. For example, in the guide 6 different types of tactile paving are outlined, yet those types are not key values in the OSM as it focused on the presence/absence of tactile paving. This, then, leads not only to the fact that not all OSM data can be sensibly recategorized based on the IM guide but also that it is highly unlikely that a single function could capture the detailed specifications outlined in the IM guide.

Function updates

Please note that, as per #129, the oi_inclusive_mobility function, based on the function defined below, has now been split into a number of sub-functions as described below:

New Function Assesses
oi_im_flush_kerb Flush Kerb
oi_im_pavement_width Pavement Width
Estimated Width
oi_im_pedestrian_infra Footways
Footpaths
Implied Footways
Crossings
oi_im_surfaces Paved Surface
Even Surface
oi_im_tactile_paving Tactile Pacing
oi_is_lit Lighting
oi_clean_maxspeed_uk Speed


For further information, please see documentation for the functions starting oi_im_*

get osm

# region_wy = "West Yorkshire"
# tags_needed = c("cycleway",
#                 "bicycle",
#                 "wheelchair",
#                 "kerb",
#                 "disabled",
#                 "mobility_scooter",
#                 "handicap",
#                 "foot",
#                 "lit", 
#                 "access",
#                 "sidewalk",
#                 "footway",
#                 "incline",
#                 "smoothness",
#                 "est_width",
#                 "width",
#                 "ramp",
#                 "sidewalk_left",
#                 "sidewalk_right",
#                 "ramp_wheelchair",
#                 "footway_left",
#                 "footway_right",
#                 "footway_surface",
#                 "priority",
#                 "sidewalk_both_surface",
#                 "sidewalk_both_width",
#                 "path",
#                 "pedestrian",
#                 "sidewalk_left_width",
#                 "sidewalk_right_width",
#                 "sidewalk_right_surface",
#                 "sidewalk_left_surface",
#                 "maxspeed",
#                 "segregated",
#                 "sloped_curb",
#                 "surface",
#                 "tactile_paving",
#                 "crossing"
#                 )
# wy_lines = osmextract::oe_get(
#   place = region_wy,
#   force_download = TRUE,
#   force_vectortranslate = TRUE,
#   extra_tags = tags_needed
# )

# saveRDS(wy_lines,
#         "wy_lines_21-03-2022.RDS")

wy_lines = readRDS(url("https://github.com/udsleeds/openinfra/releases/download/v0.1/wy_lines_21-03-2022.RDS"))

# wy_points = osmextract::oe_get(
#   place = region_wy,
#   layer = "points",
#   force_download = TRUE,
#   force_vectortranslate = TRUE,
#   extra_tags = tags_needed
# )

# saveRDS(wy_points,
#         "wy_points_21-03-2022.RDS")

wy_points = readRDS(url("https://github.com/udsleeds/openinfra/releases/download/v0.1/wy_points_21-03-2022.RDS"))

# osm_query = "SELECT * FROM 'lines', 'points'"
# 
# wy_points_lanes = osmextract::oe_get(
#   place = region_wy,
#   force_download = TRUE,
#   force_vectortranslate = TRUE,
#   extra_tags = tags_needed,
#   query = osm_query
# )
# join points and lines into one df

# homogenizing columns
# names_lines = wy_lines %>% names 
# names_points = wy_points %>% names
# setdiff(names_lines, names_points)
# setdiff(names_points, names_lines)

wy_lines_short = wy_lines %>% select(-c("waterway",  "aerialway", "z_order"))
wy_points_short = wy_points %>% select(-c("ref", "address", "is_in", "place"))

wy_joined = rbind(wy_lines_short, wy_points_short)

Function

There are two requirements for a function:

  1. it must be an sf (simple features) object. It can easily be obtained using osmextract package
  2. it needs to have all the tags needed for recategorization, otherwise an error will be given.

The following tags are used: highway, kerb, footway, path, cycleway, sidewalk, foot, segregated, access, crossing, lit, maxspeed, tactile_paving, surface, width, est_width.

To learn how to get extra tags, see lines 20-94 of this notebook or this vignette.

Finally, it is important to highlight what this function does not do:

  • it does not pre-define a walking/cycling/wheeling network. You can do this manually or rely on, for example, osmextract to download a pre-defined waking or cycling network
  • it does not provide a ground truth evaluation of OSM data. It is important to stay critical of the data and what is being returned (e.g., a negative OSM width value should be suspicious)
inclusive_mobility_get = function(osm_sf) {
  osm_sf_im = osm_sf %>% 
    # kerb: flush or not
    dplyr::mutate(im_kerb = dplyr::if_else(kerb == "flush" | kerb == "no", "flush", "other")) %>% 
    # footway is a ‘pavement’ adjacent to a road
    dplyr::mutate(im_footway = dplyr::case_when(
        footway %in% c("left", "right", "both", "sidewalk") |
        sidewalk %in% c("left", "right", "both", "yes", "separate") |
          # trying to capture footways shared with cyclists
          !is.na(cycleway) & # map cycling infrastructure that is an inherent part of the road
          foot %in% c("yes", "designated") |
          segregated %in% "yes"
      ~ "yes",
      TRUE ~ "no" 
    ) 
    ) %>% 
  # footpath is any other right of way for pedestrians, that does not run adjacent to a road.
  dplyr::mutate(im_footpath = dplyr::case_when(
    highway %in% "footway" & 
      im_footway %in% "no" | 
      # not (always) an inherent part of the road
      highway %in% c("cycleway", "bridleway", "path") & # foot = "designated" is implied
      im_footway %in% "no" &
      ! foot %in% c("no", "private") | 
      ! access %in% c("no", "private") &
      segregated %in% "no" # shared space
    ~ "yes",
    TRUE ~ "no"
    )
    ) %>%
    # presence of a crossing: give-way, signal controlled, none, or yes (but the type is unknown)
    dplyr::mutate(im_crossing = dplyr::case_when(
      stringr::str_detect(crossing, "zebra|uncontr|marked")~ "give-way",
      stringr::str_detect(crossing, "toucan|pedex|puffin|equestrian|light|signal")~ "signal-controlled",
      highway %in% "crossing" | footway  %in% "crossing" | !is.na(crossing) ~ "yes",
      TRUE ~ "no"
      )) %>% 
    # implied footways but there's a lack of data to verify
   dplyr::mutate(im_footway_imp = dplyr::case_when(
      im_footway %in% "no" &
        im_footpath %in% "no" &
        im_crossing %in% "no"
      ~ "yes",
      TRUE ~ "no"
    )
  ) %>% 
    # lighting: yes or no
    dplyr::mutate(im_light = dplyr::case_when( 
      # highway %in% "street_lamp" |
        ! lit %in% c("no", "disused") & ! is.na(lit)
      ~ "yes",
      TRUE ~ "no"
      )
      ) %>% 
    # recategorize speed
    dplyr::mutate(im_maxspeed = maxspeed %>% 
      parse_number(),
      im_maxspeed =  dplyr::case_when(
          im_maxspeed > 1 & im_maxspeed <= 20 ~ "1-20", # up to 20 mph
          im_maxspeed > 20 & im_maxspeed <= 40 ~ "21-40", # 21 - 40 mph
          im_maxspeed > 40 & im_maxspeed <= 60 ~ "41-60", # 41 - 60 mph
          im_maxspeed > 60 ~ "61" # over 60 mph
    )
  ) %>% 
    # tactile paving: yes, no
    dplyr::mutate(im_tactile = dplyr::case_when(
      ! tactile_paving %in% c("no", "incorrect", "bad") & ! is.na(tactile_paving) 
      ~ "yes",
      ! is.na(tactile_paving)
      ~ "no"
      )
      ) %>% 
    # surface: paved, unpaved, or other
    dplyr::mutate(
      im_surface_paved = dplyr::case_when(
        highway %in% "cycleway"
        ~ "paved",

          stringr::str_detect(surface,
                              "pav|asph|chipseal|concrete|paving|sett|cobble|metal|wood|stepping")
        ~ "paved",
        highway %in% c("footway", "bridleway") & # highway = footway implied surface value is unpaved
          ! surface %in% stringr::str_detect(surface, "pav|asph|chipseal|concrete|paving|sett|cobble|metal|wood|stepping")
        ~ "unpaved",
        stringr::str_detect(surface, "unpav|compact|gravel|rock|pebble|ground|dirt|grass|mud|sand|woodchips|snow|ice|salt")
        ~ "unpaved",
        TRUE & !is.na(surface) ~ "other"
    )
  ) %>% 
    # surface: even  or not
    dplyr::mutate(im_surface = dplyr::case_when(
      stringr::str_detect(surface, "asph|concrete")
      ~ "even",
      
      im_surface_paved %in% "paved" &
        smoothness %in% c("excellent", "good")
      ~ "even",
      ! is.na(im_surface_paved) 
      ~ "uneven"
    )
  ) %>% 
    # width: under 1.5 meters, 1.5-2 meters, or over 2 meters
  dplyr::mutate(
    im_width =  width %>% 
      parse_number(),
    im_width = case_when(
      im_width > 0 & im_width < 1.5 ~ " 1.5",
      im_width <= 1.5 & im_width <= 2 ~ "1.5 - 2",
      im_width > 2 ~ "2"
    )
  ) %>% 
    # estimated width: under 1.5 meters, 1.5-2 meters, or over 2 meters
  dplyr::mutate(
    im_width_est = est_width %>% 
      parse_number(),
    im_width_est = case_when(
      im_width_est > 0 & im_width_est < 1.5 ~ " 1.5",
      im_width_est <= 1.5 & im_width_est <= 2 ~ "1.5 - 2",
      im_width_est > 2 ~ "2"
    )
  )
}

Justification

Footways

Inclusive mobility: pavements adjacent to roads

OSM:

  • Highway = footway: used for mapping minor pathways which are used mainly or exclusively by pedestrians.
  • Add the cycleway=* tag to a highway=* to map cycling infrastructure that is an inherent part of the road. —> If it’s part of the road and it’s permitted to walk/wheel on, then it’s a sidewalk as there’s an implication for the sidewalk running next to the carriageway
  • The highway=cycleway tag indicates a separate way for the use of cyclists. —> is not clear if highway=cycleway goes adjacent to roads
  • There are two main ways of mapping footways, cycleways, and bridleways in the UK —> see https://wiki.openstreetmap.org/wiki/Classic_vs_Alternative_tagging_schemes_in_use_in_the_United_Kingdom
  • Segregated might be used to indicate shared space

Hence, it can be assumed key:footway and key:sidewalk are associated with the footways going adjacent to the road. Also, given that key:cycleway is supposed to be an inherent part of the road, then it should go adjacent to the road, which implies that if one can wheel/walk on it, then it’s a footway too. Moreover, key:segregated might be used instead of sidewalk tag for shared spaces.

Footpaths

Inclusive Mobility:

  • Any other right of way and not adjacent to roads;

OSM:

So, footpaths should be all the paths that are not footways.

Implied footways

Sidewalk can be considered a negative tag because it is implied that roads have sidewalks. Yet, given that it is not explicitly mapped so, there is an assumption that might not always be correct. For this reason, a separate column was created to account for that and indicate which highways might have sidewalks but the user, in the end, can make a decision based on the need.

Crossings

Inclusive Mobility:

  • refers to Traffic Signs Manual
  • highlights give-way and signal controlled crossings
  • notes that the key qualities of a crossing are safety, convenience, and accessibility —> it must improve all of them to some extent

OSM:

OSM allows to specify the type of a crossing, yet the three approved tags are traffic_lights, uncontrolled, or none. Given this, OSM was recategorized based on the broad categorization of crossings in the IM guide. If the type of a crossing is unknown but it’s mapped as present, “yes” value was used, if it is explicitly known that there is no crossing, then “no” value is present.

Tactile paving

Inclusive Mobility:

  • outlines 6 different tactile paving surfaces

OSM:

So, OSM does not seem to support the categorization of tactile paving as outlines in the IM guide. For this reason simplified approach was taken in which only the presence or absence of tactile paving is determined.

Surface

Inclusive Mobility:

  • Should be: even, firm, and slip-resistant —> There are requirements for the joint width (i.e., cobbles unlikely to be suitable)

OSM:

So, it is hard to determine if a given surface matches the requirements of IM guide, especially if it’s slip-resistant. For this reason, a simple recategorization was used to only indicate if the surface is even. A basic assumption is that all surfaces, except asphalt and concrete, are not unless they have an excellent or good smoothness level tagged.

Lighting

Inclusive Mobolity:

  • lighting might increase a sense of security, improve the ability to read signs, and navigate the space in general.
  • it is important for both people with and without disabilities
  • different illumination levels for different spaces

OSM:

So, OSM does not seem to provide an ability to tag illumination levels. Given that, a simple present/absent recategorization has been done. To account for potential overlaps between different tagging schemes, only key:lit has been used. However, for a more in-depth examination of lighting, both approaches should, ideally, be taken into consideration.

Note: if you want to include highway=street_lamp in your analysis, you will have to download point data by specifying this in the query before downloading the data.

Speed

Inclusive Mobility:

  • does not explicitly outline (max) speed requirements
  • note that vehicle speed (and volume) have impact on pedestrian’s ability to cross a street given that there are no traffic-control mechanisms

OSM:

So, the speed was categorized into 4 groups: 1 to 20, 21 to 40, and 41 to 60, and over 60 mph. The main focus here might be on the roads with up to 20 mph speed limit as it has a positive effect on increasing active modes of travel.

Kerb

Inclusive Mobility:

  • dropped flush kerbs are essential for wheelchair users
  • flush kerbs should be used with tactile paving
  • dropped kerbs should be flush with the road with a maximum of 6mm tolerance (or a rounded bullnose is provided at the change of level)

OSM:

So, while there is an implication in OSM tagging scheme that kerbs of ~ 3cm height are wheelchair-friendly, it is not a case in the IM guide. Therefore, only kerbs having values of ‘flush’ or ‘no’ have been categorized as “flush”.

Width

Inclusive Mobility:

  • as wide as practicable
  • the width of 2m should be the minimum
    • absolute minimum of footway width should be 1.5m
    • if there is an obstruction (e.g., a tree) absolute minimum width can be 1m but the max length of this should not exceed 6m

OSM:

So, the function recategorizes OSM data into 3 groups: width under 1.5 m, between 1.5 and 2m, and above 2 meters. The same is done for the key:est_width. It’s important to note that two new columns are returned – one for the estimated and another for the actual width. Finally, because of OSM tagging scheme, it is not always easy to differentiate (unless it is explicitly tagged) if the width refers to the carriageway, sidewalk, or include both. The function here does not provide this differentiation either, thus bear in mind that this column might be misleading. To avoid this, it is recommended to process the data further so only sidewalks and/or footways remain in the dataset as much as it is possible given the limitations of OSM.

Notes:

  • im_footway_pot needs to be updated to exclude crossings
  • is it possible to download both lines and points at the same time so I don’t have to join them?
  • [] addressing potential overlaps of crossings (see Park Lane - Westgate)
  • [] can illumination level be mapped?
  • overlaps between points and lines (e.g., adding lit to a highway but then also highway = street_lamp)
  • add key:smoothness
  • add key:width
  • [] im_surface_paved looks bad to me
  • [CANCELLED] add warning if tags are missing
  • [CANCELLED] parsing warning
  • should maxspeed be here? I don’t think it’s in the guide
  • [] accounting for mph/km in speed
  • [] note that not all OSM data is recategorized. E.g., key:smootheness is good as it is

think about:

  • measure of completeness
  • linking to social outcomes/phenomenon