import osmium
from collections import defaultdict
fp = osmium.FileProcessor('../data/liechtenstein.osm.pbf', osmium.osm.RELATION)\
.with_filter(osmium.filter.TagFilter(('type', 'route')))\
.with_filter(osmium.filter.TagFilter(('route', 'bicycle')))
routes = {}
members = defaultdict(list)
for rel in fp:
routes[rel.id] = (rel.tags.get('name', ''), rel.tags.get('ref', ''))
for member in rel.members:
if member.type == 'w':
members[member.ref].append(rel.id)
with osmium.SimpleWriter('../data/out/cycling.osm.opl', overwrite=True) as writer:
fp = osmium.FileProcessor('../data/liechtenstein.osm.pbf')\
.with_filter(osmium.filter.IdFilter(members.keys()).enable_for(osmium.osm.WAY))\
.handler_for_filtered(writer)
for way in fp:
assert all(i in routes for i in members[way.id])
# To add tags, first convert the tags into a Python dictionary.
tags = dict(way.tags)
tags['cycle_route:name'] = '|'.join(routes[i][0] for i in members[way.id])[:255]
tags['cycle_route:ref'] = '|'.join(routes[i][1] for i in members[way.id])[:255]
writer.add(way.replace(tags=tags))
Background¶
The objects in an OSM file are usually order by their type: first come nodes, then ways and finally relations. Given that pyosmium always scans files sequentially, it will be necessary to read the OSM file twice when you want to transfer information from relations to ways.
The first pass is all about getting the information from the relations. There are two pieces of information to collect: the information about the relation itself and the information which relations a way belongs to. Lets start with collection the relation information:
fp = osmium.FileProcessor('../data/liechtenstein.osm.pbf', osmium.osm.RELATION)\
.with_filter(osmium.filter.TagFilter(('type', 'route')))\
.with_filter(osmium.filter.TagFilter(('route', 'bicycle')))
routes = {}
for rel in fp:
routes[rel.id] = (rel.tags.get('name', ''), rel.tags.get('ref', ''))
f"Found {len(routes)} routes."
'Found 13 routes.'
It is safe to restrict the FileProcessor to the RELATION type because we are only interested in relations and don't need geometry information. A cycling route comes with two mandatory tags in OSM, type=route
and route=bicycle
. To filter for relations that have both tags in them, simply chain two TagFilters. Don't just use a single filter with two tags like this: osmium.filter.TagFilter(('type', 'route'), ('route', 'bicycle'))
. This would filter for relation that have either the route tag or the type tag. Not exactly what we want.
For each relation that goes through the filter, save the information needed. Resist the temptation to simply save the complete relation. For one thing, a single relation can become quite large. But more importantly, pyosmium will not allow you to access the object anymore once the end of the loop iteraton is reached. You only ever see a temporary view of an object within the processing loop. You need to make a full copy of what you want to keep.
Next we need to save the way-relation membership. This can be done in a simple dictionary. Just keep in mind that a single way can be in multiple relations. The member lookup needs to point to a list:
members = defaultdict(list)
for rel in fp:
for member in rel.members:
if member.type == 'w':
members[member.ref].append(rel.id)
f"Found {len(members)} ways that are part of a cycling relation."
'Found 1023 ways that are part of a cycling relation.'
This is all the information needed to add the cycling information to the ways. Now we can write out the enhanced cycling info file. Only the ways with relations on them need to be modified. So we use an IdFilter to process only these ways and forward all other objects directly to the writer. This works just the same as in the Enhance-Tags cookbook:
with osmium.SimpleWriter('../data/out/cycling.osm.opl', overwrite=True) as writer:
fp = osmium.FileProcessor('../data/liechtenstein.osm.pbf')\
.with_filter(osmium.filter.IdFilter(members.keys()).enable_for(osmium.osm.WAY))\
.handler_for_filtered(writer)
for way in fp:
assert all(i in routes for i in members[way.id])
# To add tags, first convert the tags into a Python dictionary.
tags = dict(way.tags)
tags['cycle_route:name'] = '|'.join(routes[i][0] for i in members[way.id])[:255]
tags['cycle_route:ref'] = '|'.join(routes[i][1] for i in members[way.id])[:255]
writer.add(way.replace(tags=tags))