pyrosm.OSM.write_pbf

Contents

pyrosm.OSM.write_pbf#

OSM.write_pbf(data, output_path, subset_only=False, *, apply_geometry=False, delete=None, on_orphan_node='remove', on_deleted_member='drop', progress=None)#

Write the OSM data this object holds back to a valid, re-readable *.osm.pbf, applying attribute/tag edits from a (modified) GeoDataFrame.

The whole cached dataset (all nodes/ways/relations read from the source) is written; for a read with a bounding_box, its nodes and ways and the relations with a member among them, and with complete_relations=True also the member ways outside the box and their nodes. The tag columns of a row (and its JSON tags) are the tags written for the matching element (by osm_type + id), so a tag whose column is left out of the frame is not written; rows whose id is not in the source are added as new elements synthesized from their geometry (with negative ids). A frame without tag columns (only id, osm_type, the geometry and other structural columns) changes no tags: the elements it matches keep their own. The columns get_network computes (u, v and the numeric length) are not tags, and id_tag, the column of a tag keyed id, is written as id. An element in several rows, such as a way split into segments by get_network(nodes=True) or an element in two frames, gets each tag from its rows: a value changed or removed in only some of them is written for the whole element (with a UserWarning when other rows of the same frame keep the old value), and rows giving a tag different new values raise ValueError. When elements lose the tag their read selected them by, e.g. buildings written without a building column, a UserWarning says how many and which reader will no longer find them in the written file. Topology and coordinates come from the data pyrosm read, so the output is faithful and re-readable (e.g. by pyrosm, osmium, GDAL and r5py/R5). The elements are written sorted by type then id, new ones included, with the version, timestamp and changeset they were read with (none for nodes read with keep_metadata=False); the file is written next to output_path and replaces it only when it is complete.

Pass subset_only=True to instead write a PBF containing only the elements in data (matched by osm_type + id) plus the references they need to stay valid – e.g. export just the buildings or just the road network to a new file. Multiple layers can be combined by passing a list ([buildings, network]); the union of their elements is written.

Typical use is to modify attributes in pandas and save them back (tags are text, so a number is read from them first):

import pandas as pd

osm = OSM("data.osm.pbf")
edges = osm.get_network("driving")
edges["maxspeed"] = edges["maxspeed"].fillna("50")
speed = pd.to_numeric(edges["maxspeed"], errors="coerce")  # km/h
edges["travel_time"] = edges["length"] / (speed / 3.6)
osm.write_pbf(edges, "modified.osm.pbf")

Pass apply_geometry=True and/or delete= to edit the geometry itself. Geometry follows the OSM model: coordinates live on nodes, and a way is an ordered list of member node ids. So moving a node moves every way that references it, and an existing way is reshaped through its nodes column (construct the OSM object with keep_node_info=True to get it) rather than through its LineString, which is derived:

osm = OSM("data.osm.pbf", keep_node_info=True)
nodes, edges = osm.get_network("driving", nodes=True)
# Move an existing node; the ways through it follow.
nodes.loc[nodes["id"] == 12345, "geometry"] = Point(24.94, 60.17)
# Insert a new vertex into a way, referencing a node added in the same call.
new_node = gpd.GeoDataFrame(
    {"id": [-1], "osm_type": ["node"]},
    geometry=[Point(24.95, 60.18)], crs="EPSG:4326",
)
row = edges["id"] == 4732994
edges.loc[row, "nodes"] = edges.loc[row, "nodes"].apply(
    lambda ids: ids[:2] + [-1] + ids[2:]
)
osm.write_pbf(
    [nodes, edges, new_node], "edited.osm.pbf",
    apply_geometry=True, delete=[("way", 4732995)],
)
Parameters:
  • data (GeoDataFrame or list of GeoDataFrame) – The (possibly modified) feature frame(s) whose tag columns are written onto the matching elements; a frame with no tag columns leaves their tags as they are. New rows (ids not in the source) are added from their geometry: Point -> node, LineString -> way, hole-less Polygon -> closed way. Polygons with holes, MultiPolygon and MultiLineString geometries are not supported and raise ValueError.

  • output_path (str) – Where to write the PBF.

  • subset_only (bool (default False)) – If True, write only the elements present in data (and the nodes and relation members they reference) instead of the whole cached dataset.

  • apply_geometry (bool (default False)) –

    If True, also apply the rows’ geometry to the elements that already exist in the source, and honour caller-chosen ids for new ones:

    • a node row’s Point moves that node to those coordinates;

    • a way row’s nodes column replaces that way’s member node ids, which covers inserting, removing and reordering vertices (the way’s own LineString is ignored – it is derived from the nodes, and under get_network(nodes=True) it is a single segment of the way);

    • a row with a negative id that is not in the source is added under exactly that id, instead of a generated one, so a way’s nodes list can reference a vertex added in the same call.

    Removing a vertex from a way’s nodes does not delete the node itself; use delete for that. When False (default), rows only edit tags and add new elements, exactly as before.

  • delete (iterable of (osm_type, id), optional) – Elements to omit from the output, e.g. [("way", 4732994), ("node", 277446336)]. An id that is not in the source raises ValueError; an element that is both deleted and present as a row in data is deleted. Deleting a relation drops only the relation – its members are independent elements and survive.

  • on_orphan_node ({'remove', 'keep', 'error'} (default 'remove')) – What to do with a node that was a member of a dropped way and is left referenced by no kept way or relation: 'remove' drops it too (tagged nodes included), 'keep' writes it as a bare node, 'error' raises. Nodes that no dropped way referenced – standalone POIs, for instance – are never touched.

  • on_deleted_member ({'drop', 'error'} (default 'drop')) – What to do when a kept way or relation still references a deleted element: 'drop' removes it from that member list, 'error' raises. A way left with fewer than two members cannot form a geometry, so it is not written either (with a UserWarning) and its own members go through on_orphan_node; a relation left with no members is still written.

  • progress (bool | callable | None (default: None)) – How the write shows its progress, as progress of OSM does (None follows the progress of this object): one bar “Writing <file>” from 0 to 100 % over the whole write, its current step in the label, including the read of the file when it is not read in memory yet, or progress(done, 100) calls.

Returns:

The path of the written PBF file.

Return type:

str

Notes

Elements not present in data keep their ids, tags, coordinates and member order, so the output re-reads identically in pyrosm, osmium/pyosmium, GDAL and r5py/R5. Edits never introduce a reference to a node that is not in the output; a source way whose members already point outside a clipped extract is re-emitted as it was read.