This vignette is currently a work in progress and changes are likely (20/6/22)

This Vignette looks at the pyrosm, OSMnx, and osmsxtract packages for obtaining network data from OpenStreetMaps.

Here, we look at how each package defines one of three pre-defined networks:

  • Driving Network
  • Walking Network
  • Cycling Network


With OpenInfra’s specific focus on active travel and infrastructure our focus is on Cycling and Walking networks.
Driving networks have been included for completeness.

Understanding how such packages define networks will allow us to analyse and combine the tag filters used by each package to define, hopefully, the best defined active travel networks.

osmextract network filters


Firstly we go through the osmextract (R) package function documentation to see which filters are applied to each of the three network types.

osmextract creates networks with the following workflow. A place name string (i.e. “Leeds”) is matched to one of two dataset providers, BBBike or Geofabrik before downloading a compressed .pbf file of OSM data for the specified place (Leeds). Next the downloaded file is converted into the open standard .gpkg format. This file is then parsed with an SQL query to obtain network features specified by the user (i.e. “walking”, “cycling”, or “driving”).

Here we look at the three pre-defined filters offered by osmextract and how these are parsed with an SQL query, we then try to translate these queries into a python format, similar to the filtering usilised by pyrosm/OSMnx.

After, we compare the tag filtering applied by each package to obtain our final networks.
Note that as the osmextract package defines the osm layer it is looking at (layer="lines") as being the lines (OSM ways - roads, footpaths, etc.) then we do not need to specify area=“no”, as by default, no areas (polygons) are returned within the "lines" layer.

Driving Filter


osmextract (R & SQL)
# A motorcar/motorcycle mode of transport includes the following scenarios:
# - highway IS NOT NULL (since usually that means that's not a road);
# - highway NOT IN ('bus_guideway', 'byway' (not sure what it means), 'construction',
# 'corridor', 'cycleway', 'elevator', 'fixme', 'footway', 'gallop', 'historic',
# 'no', 'pedestrian', 'platform', 'proposed', 'steps', 'pedestrian',
# 'bridleway', 'path', 'platform');
# - access NOT IN ('private', 'no');
# - service NOT ILIKE 'private%';
load_options_driving = function(place) {
  list(
    place = place,
    layer = "lines",
    extra_tags = c("access", "service"),
    vectortranslate_options = c(
    "-where", "
    (highway IS NOT NULL)
    AND
    (highway NOT IN (
    'abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform',
    'proposed', 'cycleway', 'pedestrian', 'bridleway', 'path', 'footway',
    'steps'
    ))
    AND
    (access NOT IN ('private', 'no'))
    AND
    (service NOT ILIKE 'private%')
    "
    )
  )
}



readable SQL translation

Features must have a valid value for highway tag [i.e. highway!=None] (highway IS NOT NULL)

Features are removed if highway tag = any of the following values:
('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform',
    'proposed', 'cycleway', 'pedestrian', 'bridleway', 'path', 'footway',
    'steps')
    
Features are removed if access tag = any of the following values: ("private", "no")

Features are removed if the service tag value (a string) contains the sub-string ("private") 



python representation

osmextract_filter = dict() 
osmextract_filter["driving"] = (
  f'["highway]'
  f'["highway!~"abandoned|bus_guideway|byway|construction|corridor|elevator|fixme|escalator|gallop|historic|no|'
  f'planned|platform|proposed|cycleway|pedestrian|bridleway|path|footway|steps"]'
  f'["access"!~"private|no"]'
  f'["service"!~"private"]' #Not too sure how to represent this
)

# Note that the bottom line (f'["service"!~"private"]') should be any value for the service tag should not contain the pattern "private", no matter the actual whole string




Walking Filter

osmextract (R & SQL)

# See also https://wiki.openstreetmap.org/wiki/Key:footway and
# https://wiki.openstreetmap.org/wiki/Key:foot
# A walking mode of transport includes the following scenarios:
# - highway IS NOT NULL (since usually that means that's not a road);
# - highway NOT IN ('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor',
# 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned',
# 'platform', 'proposed', 'raceway', 'motorway', 'motorway_link');
# - highway != 'cycleway' OR foot IN ('yes', 'designated', 'permissive', 'destination');
# - access NOT IN ('no', 'private');
# - foot NOT IN ('no', 'use_sidepath', 'private', 'restricted')
# - service does not look like 'private' (ILIKE is string matching case insensitive)
load_options_walking = function(place) {
  list(
    place = place,
    layer = "lines",
    extra_tags = c("access", "foot", "service"),
    vectortranslate_options = c(
    "-where", "
    (highway IS NOT NULL)
    AND
    (highway NOT IN ('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway',
    'motorway', 'motorway_link'))
    AND
    (highway <> 'cycleway' OR foot IN ('yes', 'designated', 'permissive', 'destination'))
    AND
    (access NOT IN ('private', 'no'))
    AND
    (foot NOT IN ('private', 'no', 'use_sidepath', 'restricted'))
    AND
    (service NOT ILIKE 'private%')
    "
    )
  )
}



readable SQL translation

Features must have a valid value for highway tag [i.e. highway!=None] (highway IS NOT NULL)

Features are removed if they contain highway tag = any of the following values:
('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway',
    'motorway', 'motorway_link')
    
Features are removed if they contain highway tag = ("cycleway") UNLESS these features contain the tag:value 
    combination of foot = ("yes", "designated", "permissive", "destination") (i.e this cycleway IS pedestrian friendly too)
    
Features are removed if they contain access tag = ("private", "no")

Features are removed if they contain foot tag = ("private", no", "use_sidepath", "restricted")

Features are removed if the service tag value (a string) contains the sub-string ("private") 



python representation

osmextract_filter = dict()
osmextract_filter["walking"] = (
  f'["highway"]'
  f'["highway"!~"abandoned|bus_guideway|byway|construction|corridor|elevator|fixme|'
  f'escalator|gallop|historic|no|planned|platform|proposed|raceway|motorway|motorway_link"]'
  f'["highway!~"cycleway"] UNLESS ["foot"~"yes|designated|permissive|destination"]' # I know this is not proper python. will update once I know more. 
  f'["access"!~"private|no"]'
  f'["foot"!~"private|no|use_sidepath|restricted"]'
  f'["service"!~"private"]' # Not too sure how to represent this
  
)




Cycling Filter

osmextract (R & SQL)

# A cycling mode of transport includes the following scenarios:
# - highway IS NOT NULL (since usually that means that's not a road);
# - highway NOT IN ('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor',
# 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned',
# 'platform', 'proposed', 'raceway', 'steps');
# - highway NOT IN IN ('motorway', 'motorway_link', 'bridleway', 'footway',
# 'pedestrian) OR bicycle IN ('yes', 'designated', 'permissive', 'destination');
# - access NOT IN ('no', 'private');
# - bicycle NOT IN ('no', 'use_sidepath')
# - service does not look like 'private' (ILIKE is string matching case insensitive)
load_options_cycling = function(place) {
  list(
    place = place,
    layer = "lines",
    extra_tags = c("access", "bicycle", "service"),
    vectortranslate_options = c(
    "-where", "
    (highway IS NOT NULL)
    AND
    (highway NOT IN (
    'abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform',
    'proposed', 'raceway', 'steps'
    ))
    AND
    (highway NOT IN ('motorway', 'motorway_link', 'footway', 'bridleway',
    'pedestrian') OR bicycle IN ('yes', 'designated', 'permissive', 'destination')
    )
    AND
    (access NOT IN ('private', 'no'))
    AND
    (bicycle NOT IN ('private', 'no', 'use_sidepath', 'restricted'))
    AND
    (service NOT ILIKE 'private%')
    "
    )
  )
}



readable SQL translation

Features must have a valid value for highway tag [i.e. highway!=None] (highway IS NOT NULL)

Features are removed if they contain highway tag = any of the following values:
('abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator',
    'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform',
    'proposed', 'raceway', 'steps')
    
Features are removed if they contain highway tag = ("motorway", "motorway_link", "footway", "bridleway") UNLESS these
    features ALSO contain the tag:value combination of bicycle = ("yes", "designated", "permissive", "destination")
    
Features are removed if they contain the tag access = ("private", "no")

Features are removed if they contain the tag bicycle = ("private", "no", "use_sidepath", "restricted")

Features are removed if the service tag value (a string) contains the sub-string ("private") [service NOT ILIKE 'private%']



python representation

osmextract_filter = dict() 
osmextract_filter["cycling"] = (
  f'["highway"]'
  f'["highway"!~"abandoned|bus_guideway|byway|construction|corridor|elevator|fixme|escalator|gallop'
  f'historic|no|planned|platform|proposed|raceway|steps"]'
  f'["highway"!~"motorway|motorway_link|footway|bridleway|pedestrian] UNLESS ["bicycle"~"yes|designated|permissive|destination"]' # I know this is not correct Python - looking into this (i.e. A unless B)
  f'["access"!~"private|no"]'
  f'["bicycle"!~"private|no|use_sidepath|restricted"]'
  f'["service"!~"private"]' # Not sure how to represent in Python (grepl in R)
)





General Network Definitions (by package)

The pyrosm package defines networks by first taking a complete network from OSM. It then recursively removes features that contain tags matching specific networks described below:

Definitions have been obtained from pyrosm source code, the OSMnx source code, and the osmextract source code.

It should be noted that for all OSMnx OSM API queries the “highway” tag is specified.
specifying way[“highway”] means that all ways returned must have a highway tag. the specific filters then remove ways by tag/value.

Cycling Filter removed tags

Tag Pyrosm Values OSMnx Values osmextract Proposed Filter
area “yes” “yes” None N/A
access None “private” “private”, “no” “private”, “no”
highway “footway”, “steps”, “corridor”, “elevator”, “escalator”, “motor”, “proposed”, “construction”, “abandoned”, “platform”, “raceway”, “motorway”, “motorway_link” “abandoned”, “bus_guideway”, “construction”, “corridor”, “elevator”, “escalator”, “footway”, “motor”, “planned”, “platform”, “proposed”, “raceway”, “steps” ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘raceway’, ‘steps’ ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘raceway’, ‘steps’, “motor”
highway not needed not needed Remove feature if highway = (“motorway”, “motorway_link”, “footway”, “bridleway”) UNLESS bicycle = (“yes”, “designated”, “permissive”, “destination”) Remove if highway= (“motorway”, “motorway_link”, “footway”, “bridleway”) UNLESS bicycle = (“yes”, “designated”, “permissive”, “destination)
bicycle “no” “no” “private”, “no”, “use_sidepath”, “restricted” “private”, “no”, “use_sidepath”, “restricted”
service “private” “private” “[str for str in values if ”private” in str]”, (in other words if ‘private’ is in a value for this tag, remove this feature) where values = network[‘service’].keys() remove if key value contains substring “private”



Driving Filter removed tags

Tag Pyrosm Values OSMnx Values osmextract Proposed Filter
area “yes” “yes” None N/A
access None “private” “private”, “no” “private”, “no”
highway “cycleway”, “footway”, “path”, “pedestrian”, “steps”, “track”, “corridor”, “elevator”, “escalator”, “proposed”, “construction”, “bridleway”, “abandoned”, “platform”, “raceway” “abandoned”, “bridleway”, “bus_guideway”, “construction”, “corridor”, “cycleway”, “elevator”, “escalator”, “footway”, “path”, “pedestrian”, “planned”, “platform”, “proposed”, “raceway”, “service”, “steps”, “track” ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘cycleway’, ‘pedestrian’, ‘bridleway’, ‘path’, ‘footway’, ‘steps’ ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘cycleway’, ‘pedestrian’, ‘bridleway’, ‘path’, ‘footway’, ‘steps’, “track”, “raceway”, “service”
motor_vehicle “no” “no” None “no”
motorcar “no” “no” None “no”
service “parking”, “parking_aisle”, “private”, “emergency_access” “alley”, “driveway”, “emergency_access”, “parking”, “parking_aisle”, “private” “[str for str in values if ”private” in str]”, (in other words if ‘private’ is in a value for this tag, remove this feature) where values = network[‘service’].keys() “alley”, “driveway”, “emergency_access”, “parking”, “parking_aisle”, “private”/[str for str in values if ”private” in str]



Driving & Service Filter removed tags

Tag Pyrosm Values OSMnx Values osmextract
access None “yes” unspecified
area “yes” “yes” unspecified
highway “cycleway”, “footway”, “path”, “pedestrian”, “steps”, “track”, “corridor”, “elevator”, “escalator”, “proposed”, “construction”, “bridleway”, “abandoned”, “platform”, “raceway” “abandoned”, “bridleway”, “bus_guideway”, “construction”, “corridor”, “cycleway”, “elevator”, “escalator”, “footway”, “path”, “pedestrian”, “planned”, “platform”, “proposed”, “raceway”, “steps”, “track” unspecified
motor_vehicle “no” “no” unspecified
motorcar “no” “no” unspecified
service “parking”, “parking_isle”, “private”, “emergency_access” “emergency_access”, “parking”, “parking_aisle”, “private” unspecified
psv “yes” None unspecified



Walking Filter removed tags

Tag Pyrosm Values OSMnx Values osmextract Proposed Filter
access None “private” “private”, “no” “private”,“no”
area “yes” “yes” None N/A
highway “cycleway”, “motor”, “proposed”, “construction”, “abandoned”, “platform”, “raceway”, “motorway”, “motorway_link” “abandoned”, “bus_guideway”, “construction”, “cycleway”, “motor”, “planned”, “platform”, “proposed”, “raceway” ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘raceway’, ‘motorway’, ‘motorway_link’ ‘abandoned’, ‘bus_guideway’, ‘byway’, ‘construction’, ‘corridor’, ‘elevator’, ‘fixme’, ‘escalator’, ‘gallop’, ‘historic’, ‘no’, ‘planned’, ‘platform’, ‘proposed’, ‘raceway’, ‘motorway’, ‘motorway_link’, ‘motor’
highway not needed not needed Remove feature if highway = “cycleway” UNLESS feature also has foot = “yes”, “permissive”, “designated”, “destination” remove feature if highway=cycleway UNLESS feature has foot=(“yes”, “permissive”, “designated”, “destination”)
foot “no” “no” “private”, “no”, “use_sidepath”, “restricted” “no”, “private”, “use_sideapth”, “restricted”
service “private” “private” “[str for str in values if ”private” in str]”, (in other words if ‘private’ is in a value for this tag, remove this feature) where values = network[‘service’].keys() “[str for str in values if ”private” in str]”



.

Creating Proposed Tag Filters

Here we show the code used to determine the final combination of proposed tag filters to use as our proposed filters within the tables above.

This was done by creating a set of each packages tags filter and finding the setdifference and combinging this, for each network type. See below:

Drving Filter

# Driving Filter (highway tag)
pyrosm_set_drive = set(["cycleway", "footway", "path", "pedestrian", "steps", "track", "corridor", "elevator", "escalator", "proposed", "construction", "bridleway", "abandoned", "platform", "raceway"])
OSMnx_set_drive = set(["abandoned", "bridleway", "bus_guideway", "construction", "corridor", "cycleway", "elevator", "escalator", "footway", "path", "pedestrian", "planned", "platform", "proposed", "raceway", "service", "steps", "track"])
osmextract_set_drive = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'cycleway', 'pedestrian', 'bridleway', 'path', 'footway', 'steps'])
proposed_set_drive = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'cycleway', 'pedestrian', 'bridleway', 'path', 'footway', 'steps', "track", "raceway", "service"]) 

# Things in osmextract filter, not in OSMnx/pyrosm filters
print(osmextract_set_drive.difference(pyrosm_set_drive))
##>{'historic', 'no', 'gallop', 'planned', 'fixme', 'bus_guideway', 'byway'}

# Things in osmextract filter, not in OSMnx/pyrosm filters
print(osmextract_set_drive.difference(OSMnx_set_drive))
##> {'historic', 'no', 'gallop', 'fixme', 'byway'}

# Things in OSMnx/pyrosm filters, not in osmextract
print(OSMnx_set_drive.difference(osmextract_set_drive))
##> {'raceway', 'service', 'track'}

# Things in OSMnx/pyrosm filters, not in osmextract
print(pyrosm_set_drive.difference(osmextract_set_drive))
##> {'raceway', 'track'}



The proposed filter is the osmextract filter with “track”, “raceway”, “service” keys added.

Cycling Filter

pyrosm_cycle = set(["footway", "steps", "corridor", "elevator", "escalator", "motor", "proposed", "construction", "abandoned", "platform", "raceway", "motorway", "motorway_link"])
OSMnx_cycle = set(["abandoned", "bus_guideway", "construction", "corridor", "elevator", "escalator", "footway", "motor", "planned", "platform", "proposed", "raceway", "steps"])
osmextract_cycle = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway', 'steps'])
osmextract_UNLESS_cycle = set(["motorway", "motorway_link", "footway", "bridleway"])
osmextract_combined = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway', 'steps', "motorway", "motorway_link", "footway", "bridleway"])

# Tags in osmextract filter not in OSMnx/pyrosm filters
print(osmextract_combined.difference(OSMnx_cycle))
##> {'historic', 'no', 'gallop', 'bridleway', 'fixme', 'motorway_link', 'motorway', 'byway'}

# Tags in osmextract filter not in OSMnx/pyrosm filters
print(osmextract_combined.difference(pyrosm_cycle))
##> {'historic', 'no', 'gallop', 'planned', 'bridleway', 'fixme', 'bus_guideway', 'byway'}

#Tags in OSMnx/pyrosm filters not in osmextract filters
print(OSMnx_cycle.difference(osmextract_combined))
##> {'motor'}

#Tags in OSMnx/pyrosm filters not in osmextract filters
print(pyrosm_cycle.difference(osmextract_combined))
##> {'motor'}



The proposed filter is the osmextract filter with “motor” key added.

Note this will still include the remove feature if highway=[“motorway”, “motorway_link”, “footway”, “bridleway”] UNLESS bicycle=[“yes”, “designated”, “permissive”, “destination”] clause.

Walking Filter

#Walking Filter
pyrosm_walk = set(["cycleway", "motor", "proposed", "construction", "abandoned", "platform", "raceway", "motorway", "motorway_link"])
OSMnx_walk = set(["abandoned", "bus_guideway", "construction", "cycleway", "motor", "planned", "platform", "proposed", "raceway"])
osmextract_walk = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway', 'motorway', 'motorway_link'])
osmextract_UNLESS_walk = set("cycleway")
osmextract_combined = set(['abandoned', 'bus_guideway', 'byway', 'construction', 'corridor', 'elevator', 'fixme', 'escalator', 'gallop', 'historic', 'no', 'planned', 'platform', 'proposed', 'raceway', 'motorway', 'motorway_link', "cycleway"])

# Comparing OSMnx & Pyrosm filter
print(pyrosm_walk.difference(OSMnx_walk))
##> {'motorway', 'motorway_link'}

# Comparing OSMnx & Pyrosm filter
print(OSMnx_walk.difference(pyrosm_walk))
##> {'bus_guideway', 'planned'}

# Update pyrosm/OSMnx filters to match
pyrosm_walk.update(["bus_guideway", "planned"])
OSMnx_walk.update(["motorway_link", "motorway"])

# Comparing updated filters
print(pyrosm_walk.difference(OSMnx_walk))
##> set()

# Comparing updated filters
print(OSMnx_walk.difference(pyrosm_walk))
##> set()

# Comparing difference to osmextract filter
print(OSMnx_walk.difference(osmextract_walk))
##> {'motor', 'cycleway'}

# Comparing difference to osmextract filter
print(pyrosm_walk.difference(osmextract_walk))
##> {'motor', 'cycleway'}



We can see that the osmnextract filter is missing both “motor” and “cycleway”.

For the proposed filter we will add “motor” but not cycleway, as this will be taken care of by the proceeding highway=cycleway UNLESS foot=(“yes”, “designated”, “permissive”, “destination”) clause.



Additional filters

Walking

Cycling


From a pyrosm package pull request it has been proposed to add highway=“busway” to the removed features list.

Additionaly, it was propsed to also use bicycle=“use_sidepath”, however this is already covered by the osmextract package.

Driving



Proposed Data Aquisition Method.

I am looking into how each package (osmextract, pyrosm, OSMnx) acquires OSM data so that we may use the most appropriate method moving forward.

Pyrosm collects pre-formated OSM extracts by custom user query from one of two providers, BBBike or Geofabrik.

It appears that Pyrosm takes all data for a user place query, before iteratively removing features from the parsed .osm.pbf file, in a top-down approach. If features do not satisfy the filter conditions specified by the filter documentation then they are removed. Features are only removed from the downloaded file if they are caught by the pyrosm filtering, suggesting that some incorrect (unknown) features may still remain in the network if they are not caught by this filter.

On the other hand, it seems that osmextract also downloads data from providers (Geofabrik & BBBike) before then filtering data using a bottom up apprpach.
From the downloaded .pbf file which is then converted to the open .gpkg format, osmextract then parses the .gpkg file using SQL queries. The networks features returned from such a request will only be returned if they satisfy the SQL query passed, and as such only known features should be returned, unlike pyrosm which may contain unknown features.

From studying the OSMnx documentation, it seems this may take a similar approach to osmextract making direct queries to the OSM API but I need to study this further.

I propose that we proceed utilizing the osmextract library, making use of the SQL query option so that we can specifically define queries (i.e. highway=cycleway UNLESS foot=yes, permissive, designated, destination etc…)