Changelog#
v0.25.0 (Oct 25, 2026)#
This release makes crops and merges faster and leaner, and changes the licence: pyrosm is licensed under the BSD 3-Clause License instead of MIT. get_buildings(keep_nodes=True) also returns the buildings mapped as single nodes. With workers, crops read only the blocks each pass needs, and re-packed crops cut, decode and encode their blocks on the threads: on Finland (737 MB), a crop of the Helsinki region takes 3.8 s instead of 5.0 s, and a re-packed one 6.2 s instead of 11.6 s. Merges write on the threads, also without a region, select their elements as the threads do when they have one worker, and hold far less memory for a large region: merging two Finland extracts with 8 workers takes about 17 s instead of 25 s cut to the Helsinki region and 55 s instead of 75 s whole, and cut to all of Finland it peaks at 7 GB instead of 19 GB. Crops and merges refuse a block whose string table comes in two parts instead of writing it wrong, and a new docs page, Upgrading, lists release by release the changes that need action.
NEW:
get_buildings(keep_nodes=True)also returns the buildings mapped as single nodes, as Points. Nodes taggedbuilding=entranceorbuilding=window, parts of a building, andbuilding=noare left out, so the result holds only features that represent buildings;get_data_by_custom_criteria({"building": True})returns every node taggedbuilding. By defaultget_buildings()returns the building ways and relations, as before (#706)CHANGED: pyrosm is licensed under the BSD 3-Clause License instead of MIT. The vendored khash and libdeflate keep their own licences (#718)
CHANGED: The package metadata states the licences as the SPDX expression
BSD-3-Clause AND MIT(License-Expression, metadata version 2.4), pyrosm’s licence and that of the vendored khash and libdeflate, instead of a licence classifier, which setuptools 77 and later warn about; building pyrosm needs setuptools 77 or later (#721)CHANGED: The repository’s default branch is
main, renamed frommaster. The tests run on pushes tomain, and the README, the guides to contributing and releasing, the links to files of the repository and the docs’ links to their sources namemain(#723)CHANGED: Crops with
repack=Trueandworkersread, decompress and decode the source blocks of their write on the worker threads; the re-packed blocks are still encoded and written in order on one thread, so the output is the same byte for byte. On Finland (737 MB), a re-packed crop of the Helsinki region with 8 workers takes 9.2-9.5 s instead of 11.4-11.6 s. WithPYROSM_DECODE_BACKEND=processesthe write runs in one process, as before (#725)CHANGED: With
workers, the node pass of a crop or merge notes which blocks hold ways and relations, and the later selection passes read only those. On Finland (737 MB), a crop of the Helsinki region with 8 workers takes 3.75-3.85 s instead of 5.0-5.1 s, and 8.4 s instead of 9.2-9.4 s withrepack=True; the output is the same byte for byte (#729)CHANGED: The threads of a crop with
repack=Truecut each source block down to its kept elements without the GIL, as the default crop does, and decode only those for the re-packed write; they decoded every element of the block. On Finland (737 MB), a re-packed crop of the Helsinki region with 8 workers takes 6.3-6.5 s instead of 8.4-9.2 s, with the same peak memory; the output is the same byte for byte (#731)CHANGED: The threads of a crop with
repack=Truealso encode and compress the re-packed blocks, which the calling thread writes in order. On Finland (737 MB), a re-packed crop of the Helsinki region with 8 workers takes 6.16 s instead of 6.55 s (medians of 8 runs); the output is the same byte for byte (#733)CHANGED: A merge with a
bounding_boxorpolygonandworkerswrites with them too: the threads crop and decode the inputs’ blocks and encode the merged ones, written in order; every element is still checked to be sorted by type then id. On two overlapping Finland extracts (737 and 769 MB) cut to the Helsinki region, a merge with 8 workers takes 16.7-16.9 s instead of 24.2-26.4 s; the output is the same byte for byte (#735)CHANGED: Merges use their workers more and hold less memory. A merge without
bounding_boxorpolygonruns on the worker threads, and the threads of a merge share their read-ahead among the inputs: on two overlapping Finland extracts (737 and 769 MB), a merge of the whole files with 8 workers takes 53-59 s instead of 73-76 s. The selection of a merge with a region holds each set of ids only while it needs it: cut to a box around all of Finland, the same merge peaks at 7.3 GB instead of 18.9 GB with 8 workers and at 7.0 GB instead of 17.3 GB with one. With one worker, the selection runs the passes of the threads on the calling thread and reads only the blocks each pass needs: cut to the Helsinki region, the merge takes 42 s instead of 67 s. The output is the same byte for byte (#737, #739, #741)FIXED: Crops and merges refuse a block whose string table comes in two parts, which protobuf reads as one, with an
InvalidOSMFileErrorthat names the file. They read only the last part: a crop wrote the block’s elements with tags and roles beyond the written string table, and a re-packed crop failed withIndexError(#727)DOCS: A new page, Upgrading, lists release by release the changes since 0.6.2 that alter what reads, downloads and graph exports return or that need a change in code, with what to do about each (#713)
DOCS: The links of the quickstart and of the guide to reading OSM data open their sections, and the
OSM.to_pbfAPI page gives its paragraph about the crop’s progress as text instead of four parameters that do not exist (#716)DOCS: The citations on the How to cite page, on the start page of the docs, in the README and in
CITATION.cffname the current version, 0.25.0, instead of 0.13.1, and the release steps include updating them (#746)
v0.24.0 (Oct 23, 2026)#
This release finishes behaviour that worked on the common path but not everywhere. OSM.write_pbf writes an edit made in any row of an element and writes its file sorted, and written files and crops keep the members of complete relations and replace their output only when complete. Every function that takes a bounding box takes the same forms, area downloads reuse their crops, get_data_by_bbox and get_data_by_geocoding choose among Geofabrik, BBBike and Movisda extracts, and both engines give the same columns and dtypes. Reads, crops, merges, writes and graph exports show one progress from 0 to 100 % through all their steps; history reads take a tenth of the time and OSM.write_pbf about half. Several changes alter results: every read has a bool visible column, readers keep the keys of their filter as columns, a plain-dict custom_filter of get_network keeps its matches, get_boundaries(name=...) finds plain text, NetworkX graphs carry oneway and reversed as booleans, default graphs are fully simplified, a progress function is called as progress(done, 100), and closed ways are areas or lines by the OSM wiki’s polygon features, as in OSMnx, so fenced areas are Polygons again and closed coastlines and cliffs are LineStrings. Several fixes change output too, among them (the entries below list every change): get_graph(tags_to_keep=...) keeps the direction tags, so driving graphs are no longer two-way everywhere; crops by a polygon follow the polygon; get_data("europe/georgia") no longer returns the US state; graph exports leave out the edges whose end nodes are missing and take node coordinates from the geometry; and get_data_by_custom_criteria(osm_keys_to_keep=...) returns only the elements with those keys.
NEW:
OSM.write_pbfshows its progress: one bar “Writing <file>” from 0 to 100 % through reading the file when it is not read yet, applying the edits, preparing the nodes, ways and relations and writing the file. It takesprogress=, and by default follows theprogressof itsOSMobject (#633)NEW:
find_extractstakesprogress=and shows the search, “Finding the extracts”, while it downloads the extract indexes on first use and reads the extract sizes;get_data_by_areapasses itsprogresson (#645)NEW:
get_datagains the BBBike cities Damaskus, LosAngeles and Minsk (#665)FIXED:
OSM.write_pbfwarns when written elements lose the tag their read selected them by, e.g. buildings written without abuildingcolumn. The warning names the reader that will no longer find them in the written file (get_buildings()); for a frame that no longer notes how it was read (e.g. afterpd.merge) it names the tags that pyrosm’s readers detect features by. The file is written as the frame says, as before (#589)FIXED:
OSM.write_pbfmerges the rows of an element: a change made in one segment of a way read withget_network(nodes=True), or in one of two frames that hold the same element, is written for the whole element, with aUserWarningwhen other rows of the same frame keep the old value, and rows that give a tag different new values raiseValueError. The last row was written and the other changes were lost. It also no longer writes the network columnsuandvas tags, keeps real tags such aslength, and writes a tag keyedidback asidinstead ofid_tag(#590)FIXED:
OSM(bounding_box=polygon).to_pbf()crops to the polygon, as a read with it does, instead of the polygon’s bounding rectangle, and so doesget_data_by_geocoding;to_pbfusesOSM(workers=...)when the call gives none (#591)FIXED: Downloads and crops never share a file name. The two Geofabrik extracts named
georgia-latest.osm.pbfare saved asus-georgia-latest.osm.pbfandeurope-georgia-latest.osm.pbf, soget_data("europe/georgia")no longer returns the US state; other names are unchanged.get_data_by_geocodingnames its cropsarea_<place>.osm.pbf: on case-insensitive file systemshelsinki.osm.pbfreplaced theHelsinki.osm.pbfofget_data("Helsinki")(#592)FIXED: Graph exports leave out the edges whose end nodes are not in the node frame for every graph type: igraph gave the other edges wrong attributes and pandarm failed. After the edge frame is filtered, pandarm and pandana get the same edges and weights as NetworkX; the node
x/yof NetworkX, pandarm and pandana graphs come from the node geometry, so projected graphs have projected coordinates; and every graph type identifies the nodes bynode_id_col(#604)FIXED: Reading a history file without a
timestampchooses the current time once perOSMobject, so later calls reuse the parsed file and the warning appears once; every call parsed the whole file again.keep_other_tags=Falseon a history read says that history reads do not support it (#608)FIXED:
get_graph(tags_to_keep=...)also reads the tags the graph is built from:oneway,junctionand, for cycling,oneway:bicycle, and when simplifying theedge_attrs_differtags. Without them a driving graph was two-way everywhere and the simplification merged edges across changes of speed limit and road class (#611)FIXED:
get_data_by_custom_criteria(osm_keys_to_keep=...)without acustom_filterreturns the elements with one of the keys instead of every element (#612)FIXED: A crop or merge shows one progress from 0 to 100 % over all its passes, with the current pass in the bar’s label (“Cropping finland-latest.osm.pbf: finding the ways”). The bar was reset for every pass, which restarted its 2-second delay, so it stood at 0 % through passes shorter than that. An open bar is redrawn every second, so its time moves on during a long step (#621)
FIXED: Out-of-core reads show their progress through the whole read: indexing the file, decoding it, collecting the elements, building the geometries or edges, writing the result and loading it back, each named in the bar’s label. The bar moved only while the file was decoded (0.8 s of a 6.7 s
get_buildings()of Paris), and a read served from the cache showed nothing (#623)FIXED: In-memory and history reads show their progress through the whole read as well: decoding the file, selecting the versions of a history read, the passes that complete the elements a bounding box cuts, collecting the elements and building the ways and relations or the edges. An in-memory
get_buildings()of Paris moved the bar during 2.9 of its 17.5 s, and a history read stood at 100 % while it selected the versions (#627)FIXED:
get_graphshows one progress from 0 to 100 % over the read and the graph, as a bar “Building the graph of <file>”, and follows theprogressof itsOSMobject: the graph steps followed onlypyrosm.enable_logging, soOSM(..., progress=False).get_graph()still drew a bar, and they were a second bar that counted the steps and stood still during a long one.OSM.to_graph,pyrosm.graphs.graph_tablesand the graph exporters takeprogress=(#637)FIXED: A download reports its progress when the server starts sending the file, and then every 256 KiB instead of every MiB, so a slow download shows its bar from the start.
get_data,get_data_by_bbox,get_data_by_geocoding,get_data_by_areaandfind_extractsrefuse an invalidprogressbefore any request;get_datareturned a file bundled with pyrosm without checking it (#645)FIXED: Crops, merges and
OSM.write_pbflog one line each, with their files, output and seconds, as reads do. A bounding-box read served from the out-of-core cache gives the warnings of the read that built it, such as the one about relations that reach beyond the box. A bounding box without data gives the same single warning in both engines; the in-memory engine gave two. The guides turn the progress off withpyrosm.enable_logging("WARNING", progress=False), sinceenable_logging(progress=False)also turns on the INFO log messages (#646)FIXED:
get_data_by_areauses a file it cropped again while the file, the extracts it was cut from and the crop region are unchanged, as a record next to it (<file>.sources) shows; it cropped again on every call, so the out-of-core cache missed and kept one more result per call.update=Trueand an explicitoutput_pathcrop again. Withcrop=Falseand one extract, the extract is copied tooutput_path(a hard link where the file system allows one), which was ignored (#652)FIXED:
geocodetimes out after 60 s by default and takesheaders,timeoutandopener; it waited without limit for the geocoding service.get_data_by_geocodingchecks its options before the request (#657)FIXED: With
update=True,find_extractsandget_data_by_arearefresh Geofabrik’s index and BBBike’s list of cities as they do Movisda’s indexes: with the network options given, kept next to the downloads, and replaced by the kept copy or pyrosm’s own, with a warning, when they cannot be fetched. Geofabrik’s index was fetched without a timeout or fallback and not kept, and BBBike’s list was never refreshed.pyrosm run --forcedownloads the extract again, a recipe’s place is geocoded with theheadersandtimeoutof itsextractsection, andget_datadownloads Geofabrik extracts over https (#662)FIXED:
OSM.clear_downloads(path)removes exactly the file atpath, and only a.osm.pbfor.osh.pbffile; it removed the file of that name in the download folder. Clearing also removes the partial files of interrupted downloads and the records kept beside crops. An unknown dataset name suggests the closest names instead of listing about 800, and a region-qualified name such asusa/new yorktakes the same spellings as a bare name (#665)FIXED: With
complete_relations=True,OSM.to_pbfandOSM.write_pbfwrite the member ways outside the box of the relations that reach into it, and their nodes, so the written file reads back with the same relation geometries.write_pbfof a read with a bounding box writes only the relations with a member among the nodes and ways read, asto_pbfdoes; it wrote every relation of the whole file (#668)FIXED: Every function that takes a bounding box takes the same forms: four numbers (a list, tuple or array such as
total_bounds), a Shapely Polygon or MultiPolygon, or a GeoDataFrame or GeoSeries, united and reprojected to lon/lat.OSM()refused a tuple, an array and a GeoDataFrame, crops and merges failed on them with unrelated errors, andget_data_by_bboxread a projected GeoDataFrame’s metres as degrees. Crops and merges warn when the written file holds no node (#673)FIXED:
OSM.write_pbfwrites every element sorted by id and marks the file sorted, somerge_pbfaccepts it; new ways came after the source ways. It writes the metadata that was read, node by node, and none where none was read, where ways had changeset 0 and nodes read withkeep_metadata=Falseversion 1 and a 1970 timestamp; new elements get version 1 and the time of writing. Crops andwrite_pbfwrite next tooutput_pathand replace it only when the file is complete, keeping its permissions, so an interrupted call no longer leaves a truncated file that reads as a smaller valid one (#676)FIXED:
simplify_kwargs={"node_attrs_include": [...]}finds node tags as well as node columns, and warns about a name no node has; naming a node tag kept no node (#679)FIXED: With
simplify=False, the line of an edge travelled against its way’s node order (the reverse of a two-way street,oneway=-1) runs fromutov; it kept the way’s line (#687)FIXED: In-memory reads with a bounding box and
complete_relations=Trueno longer keep an empty column of a feature that lies outside the box. Errors name the parameter given (extra_attributes,tags_to_keep) instead oftags_as_columns, and every function that takesworkersaccepts a NumPy integer and refuses other values with one message (#693)FIXED: A closed way that one of its tags makes an area, such as
amenity,building,landuseorleisure, is a Polygon also when it has abarrier,highwayorroutetag. Since 0.23.0 any such tag made it a LineString in every reader unless it was taggedarea=yes: in the Helsinki region extract, 274 areas, among them 160 fenced sports pitches and other leisure areas, 65 landuse areas such as fenced allotments, 34 parking lots and other POIs and a fenced school (#704)CHANGED:
get_boundaries(name=...)finds the name as plain text, ignoring case, and returnsNonewith a warning when no name contains it. The name was a case-sensitive regular expression, so “Frankfurt (Oder)” was never found and a name with “(” raised an error (#612)CHANGED: Every reader keeps the keys of the filter it selects by as columns, also with
tags_to_keeporkeep_other_tags=False:get_buildings(tags_to_keep=["name"])has abuildingcolumn.get_data_by_custom_criteriatakestags_to_keepas the other readers do, and no reader changes the lists it is given (#616)CHANGED: A plain-dict
custom_filterofget_networkkeeps its matches, as the other forms of a filter and the other readers do;get_network(custom_filter={"highway": ["footway"]})returned every way but the footways. Passfilter_type="exclude"for that (#618)CHANGED: History reads choose each element’s version from the ids and deletion flags of the ways instead of a table with a column for every tag key of the file:
get_buildings(timestamp=...)of the 13 MB Helsinki history sample takes 2.3 s instead of 23.3 s. The keys in the JSONtagscolumn of a history read follow the order of the tags in the file (#629)CHANGED:
OSM.write_pbfreads the tags of the frame column by column and copies the nodes as arrays: writing the buildings of the Helsinki extract back takes 17.2 s instead of 32.8 s, and 10.0 s instead of 23.9 s withsubset_only=True. The written files are the same (#636)CHANGED: A progress function given to
OSM,merge_pbf,OSM.write_pbf,OSM.to_graph, the graph exporters,find_extractsorpyrosm.enable_loggingis called asprogress(done, 100), with the percentage of the whole operation done, from 0 to 100 and never back. It got the bytes read in each pass over the file of a read, crop or merge, starting from 0 again at each pass, and the steps done of a graph export. Downloads still reportprogress(written, total)with their bytes (#621, #623, #627, #637)CHANGED:
get_data_by_bboxandget_data_by_geocodingare built onget_data_by_area: they choose among Geofabrik, BBBike and Movisda extracts instead of Geofabrik’s alone, choose again after a failed download, and takestrategy,must_cover,workers,headers,timeoutandopener. They still return a path; withdownload=False, the URL, or a list of URLs for a set (#657)CHANGED: Crops and merges refuse a bounding box that
OSM()refuses and coordinates outside lon/lat; they wrote an empty file.get_data_by_bboxrefuses an out-of-range box instead of warning (#673)CHANGED: NetworkX graphs with
osmnx_compatible=True(the default) carryonewayandreversedas booleans, as OSMnx gives them, soosmnx.load_graphmlreads a saved graph:onewayby the rule the graph is directed by,reversedfor an edge against its way’s node order, and on a simplified edge whose ways differ the list of their values. The OSM text of the tag isoneway_tag(#679)CHANGED: Graphs keep their largest connected part before they are simplified, as OSMnx does, so default graphs are fully simplified; a node whose side street the connected-part step removed stayed between two edges of one street (#687)
CHANGED: Both engines give the same columns with the same dtypes.
visibleis a bool column of every read: network, landuse, natural and custom reads had it in the JSONtags. Out-of-core node rows have int32version, uint32timestampand int32changeset, as in-memory ones do. Results that the out-of-core engine cached with an older version are not reused (#693)CHANGED: Whether a closed way is a Polygon follows the OSM wiki’s list of polygon features, as in OSMnx: it is an area when it is not tagged
area=noand one of its tags is an area tag (any value ofamenity,building,landuse,leisureand the other area keys, some values ofbarrier,highway,natural,railway,waterwayand a few other keys), and otherwise a LineString. Closed coastlines, cliffs, tree rows, streams, railway loops and ways without an area tag are LineStrings, where they were Polygons; closed hedges, city walls, retaining walls and motorway service areas are Polygons, where they were LineStrings. Results that the out-of-core engine cached with an older version are not reused (#704)DOCS: The readers of
pyrosm.engineare documented as the out-of-core engine’s internals behindOSM, without an API promise: they do not check their parameters and they read every version of a history file. Read data withOSM(#693)DOCS: The
OSM.write_pbfexample runs, the guide to custom filters namesextra_attributesandget_data_by_custom_criteriaand says what a filter does with each reader, the graphs guide says that cycling graphs follow the one-way tags, the guide to reading OSM data shows network ways as LineStrings and says a bounding box keeps the features with a node inside it, and theget_poisdocstring listsboundaryamong the keys ofConf.tags.available(#694)
v0.23.0 (Oct 19, 2026)#
This release runs out-of-core reads, crops and merges on threads instead of worker processes, so parallel reads need no if __name__ == "__main__": guard, and builds their results in C on those threads: on Finland (737 MB), get_network("driving") takes 4.1 s on 8 threads, against 19.8 s in one process with 0.22.0. They default to workers="auto", which reads files from about 20 MB in parallel, and no longer warn about that change. The worker-process backend (PYROSM_DECODE_BACKEND=processes) is deprecated. Several fixes change results: a missing visible flag reads as True (every node and way of a file that is not a history file had visible=False) and relations have a visible value; closed ways are lines or polygons by all their tags, also those that are not columns; a node’s or relation’s tag keyed id comes out as id_tag instead of replacing its id; filters read that tag as id_tag in every reader, while the filter key id raises a ValueError; a custom_filter narrows get_boundaries, get_buildings, get_landuse and get_natural instead of widening them; and driving networks leave out busways, ladders and trails. Out-of-core results cached by earlier versions are read again.
NEW: Graph simplification takes OSMnx’s
edge_attr_aggs(insimplify_kwargsofto_graph, and insimplify_graph): a dict of column -> function, called with the list of the merged segments’ values, e.g.{"travel_time_s": sum}. By default atravel_timecolumn is summed, as in OSMnx;lengthand the pandana weights stay summed (#575)FIXED: Closed ways are polygons or lines by all their tags. A closed way is a Polygon unless it is tagged
area=no, or, withoutarea=yes, it has ahighway,barrierorroutetag; the readers read these tags only from the read’s columns, soget_buildings(), whose columns lackarea, returned a building taggedarea=noas a Polygon (#553)FIXED: A relation’s tag keyed
idis kept asid_tag, as a way’s is: a column when requested (extra_attributes=["id_tag"]), otherwise a key of the JSONtags. It replaced the relation’s OSM id in every reader (#554)FIXED: A node’s tag keyed
idis kept asid_tag, as a way’s is. Requestingidas a column (extra_attributes=["id"]) replaced the node ids with the tag’s values, and out of core a read of nodes and ways then failed withArrowTypeError;extra_attributes=["id_tag"]gave no column, and the JSONtagskey wasid(#555)FIXED: Out-of-core reads take a requested tag key that UTF-8 cannot encode, such as a lone surrogate, as the in-memory reader does: no tag has it, so its column is absent and a filter by it keeps nothing. They raised
UnicodeEncodeError(#556)FIXED: The raw block decoder sums the delta-coded refs of ways and member ids of relations with wrap-around, as numpy’s int64 arithmetic and the batch decoder do; a sum past the int64 range was a signed overflow, which C leaves undefined (#557)
FIXED: A missing
visibleflag reads asTrue, as the PBF format reads it: every node and way of a file that is not a history file hadvisible=Falsein every reader (on the Helsinki region extract, the 1613 nodes ofget_pois()and the 423 ways ofget_buildings()). Relations have avisiblevalue too, a column when requested and a key of the JSONtagsotherwise. History files, which write the flag, read as before (#558)FIXED: Filters read a tag keyed
idasid_tag, the column it comes out in, for nodes, ways and relations in every reader, also in bracket filters,osm_keys_to_keepand network filters; they differed by reader (out of core{"id_tag": True}kept nothing, in memory{"id": True}kept every tagged way). The filter keyid, the element’s OSM id, raises aValueErrorthat namesid_tag. Withkeep_other_tags=False, a requestedid_tagcolumn is returned (#559)FIXED: A pandarm export (
to_graph(..., graph_type="pandarm")orpyrosm.graphs.to_pandarm) gives pandarm the CRS of the nodes. pandarm warned on every export that no CRS was passed and assumed geographic coordinates (#564)FIXED: The check that the
timestampof a history-file read is not in the future takes the current time withpd.Timestamp.now("UTC"). It usedpd.Timestamp.utcnow(), which pandas 3 deprecates and will remove (#565)FIXED:
OSM.list_cacheandOSM.clear_cachetake the bare name of a file thatget_datadownloaded, asOSM.clear_downloadsdoes. A bare name was read only as a path in the working directory, soOSM.clear_cache("finland-latest.osm.pbf")matched no cached layer and removed nothing (#569)FIXED:
get_boundaries,get_buildings,get_landuseandget_naturalnarrow their result by acustom_filter, as their docstrings say: every part of the filter without the layer key also requires it, andget_boundarieskeepsboundary_typeunless the filter namesboundary. The layer key was added as an OR term, so a filter on another key widened the result:get_boundaries(custom_filter={"admin_level": ["10"]})returned everyboundary=*feature plus everything withadmin_level=10(#571)FIXED:
OSM.to_pbf,crop_pbfandmerge_pbftakeworkers="auto", choosing the worker count as out-of-core reads do; they raisedTypeError. They also validateworkersas reads do (#574)FIXED:
pyrosm.geocodeandget_data_by_geocodingfetch the Nominatim reply again when it fails part way, as downloads do; a reply cut off mid-transfer raised a barehttp.client.IncompleteRead(#579)FIXED:
workers="auto"and the cap on an explicitworkerscount the CPU cores the process may use (os.process_cpu_count()on Python 3.13+, the CPU affinity on Linux). They counted every core of the machine, so in a job limited to 4 cores of a 64-core node a read of a large file started 62 threads (#583)FIXED:
get_data_by_areatakesworkers="auto", ascrop_pbfandmerge_pbfdo; it raisedValueError(#584)CHANGED: Out-of-core reads, crops and merges run in parallel on threads instead of worker processes, so with
workers > 1they need noif __name__ == "__main__":guard on macOS and Windows. The blobs are decompressed in batches and decoded by a C decoder with the GIL released, and crops write their blocks with a C writer; a block either one does not handle goes to the existing code, so results and crop output bytes are unchanged. On Finland (737 MB),get_network("driving")takes 14.6 s on one thread instead of 19.8 s in one process, and 9.8 s on 8 threads; a box crop takes 5.7 s on 8 threads instead of 17.5 s on one. Setting the environment variablePYROSM_DECODE_BACKEND=processesbrings back the worker processes (#525, #526, #527, #528, #529, #530, #531, #532)CHANGED: Out-of-core reads on threads collect and assemble their ways and relations in less time. The threaded decode keeps each element’s tags as string ids, and the ways a read outputs are selected, filtered by value and given their tag columns, JSON
tagscolumn and geometries in C on the read’s threads; network edges are built as Arrow tables, and relations are assembled with vectorized Shapely calls. The results are unchanged. On Finland (737 MB),get_network("driving")takes 4.1 s on 8 threads instead of 9.8 s and 11.3 s on one instead of 14.6 s, andget_buildings()9.4 s on 8 threads instead of 34.0 s and 17.3 s on one instead of 39.5 s (#536, #537, #538, #539, #540, #541, #542, #543, #544, #545, #546)CHANGED: Out-of-core reads on threads build their point features (the tagged nodes a layer read returns, as in
get_pois()) from string ids: the nodes are filtered by value and given their tag columns and JSONtagscolumn in C, as the ways are, and written as Arrow tables. The results are unchanged. On the Helsinki region extract (37 MB),get_pois()takes 0.62 s instead of 0.81 s (#548)CHANGED: Selecting the worker-process backend with
PYROSM_DECODE_BACKEND=processesgives aFutureWarning, once per process: the backend is deprecated and will be removed in a future release. Reads, crops and merges run on threads by default (#551)CHANGED:
get_network("driving")and"driving+service"leave outhighway=busway,ladderandtrailways, which cars cannot use; OSMnx’sdrivekeeps them (#572)CHANGED: Reads, crops and merges that leave
workersunset run withworkers="auto": one thread below about 20 MB of source data, more threads for larger files. They ran on one core, and theFutureWarningof 0.22.0 that pyrosm 1.0 would run them in parallel by default is removed. Results and written files are the same for every worker count. WithPYROSM_DECODE_BACKEND=processesthe default stays one process (#573, #584)CHANGED: Simplifying a graph warns when a numeric column that differs along a merged edge becomes lists, naming the column and the
edge_attr_aggsthat would aggregate it (#576)CHANGED:
workers="auto"reads files of about 20 to 70 MB on up to 4 threads; it read every file below 70 MB on one thread. Smaller files stay on one thread, and larger files use the CPU cores less two, as before. On a 10-core laptop, a 34.5 MB file reads in 16 to 22 % less time on 4 threads than on 1 (#580)DOCS: The docstrings of
OSM(itsworkersparameter),OSM.to_pbf,get_data_by_areaand the out-of-core engine say that parallel reads, crops and merges run on threads, which need noif __name__ == "__main__":guard, and that onlyPYROSM_DECODE_BACKEND=processesuses worker processes that need it on macOS and Windows (#549)DOCS: The guide to reading OSM data and the FAQ explain how reads, crops and merges choose their number of threads, and that
workers=1keeps them on one core. The list in the 0.2.0 notes of the changelog page shows as one list again (#524, #584)DOCS: The code examples of the guides run on real data and show their output, and the guide notebooks are re-run with this release (#563, #567, #568, #577, #581, #585)
v0.22.0 (Oct 18, 2026)#
This release adds OSM.get_graph, which reads a street network as a routable graph in less memory: on Finland’s driving network (one process), exporting to NetworkX peaks at about 4 GB and takes 41 s, against 13.6 GB and 83 s for get_network(nodes=True) followed by to_graph. Graph exports run faster, take less memory and show their progress, and pyrosm.enable_logging(progress=...) sets the progress of every read, crop, merge and download. get_natural() on Finland takes 77 s instead of 72 min. pyrosm supports Python 3.11–3.15; Python 3.10 is no longer supported. Two changes alter results: NetworkX graphs leave out attributes that have no value instead of storing NaN or None, and nodes whose tags all became columns have no tags value. get_path() and the pandana graph backend warn that they will be removed in pyrosm 1.0, and reads, crops and merges that leave workers unset warn that pyrosm 1.0 runs them in parallel by default.
NEW:
pyrosm.enable_logging(progress=...)sets the progress of every read, crop, merge and download whose ownprogressis not given:True(as without the call) shows the bars,Falseshows none, and a function is called asprogress(done, total). It applies toOSMobjects made before the call too. TheprogressofOSM,crop_pbf,merge_pbf,get_dataand the other download functions defaults toNone, which follows this setting, and still overrides it for one object or call (#502)NEW: Graph exports show their progress as
pyrosm.enable_logging(progress=...)sets it.OSM.to_graphandpyrosm.graphs.to_networkx,to_igraph,to_pandana,to_pandarmandgraph_tablesdraw a bar of steps that names the current one (directing the edges, simplifying, keeping the largest connected part, building the graph), shown after 2 seconds and cleared at the end, or without a terminal one line with the time it took; a function set withenable_loggingis called with the steps done and the steps (#504)NEW:
OSM.get_graph(network_type, graph_type, ...)reads a street network as a routable graph, the graph ofOSM.to_graph(*osm.get_network(network_type, nodes=True), graph_type, ...), and takes the parameters of both. With the out-of-core engine it builds the simplified graph from the ways and nodes of the read, without the tables of every street segment and every node; withsimplify=False, the in-memory engine or a history read it callsget_networkandto_graph. On Finland’s driving network (one process), exporting to NetworkX peaks at about 4 GB of memory (3.7-4.5 GB over several runs) and takes 41 s, against 13.6 GB and 83 s forget_network(nodes=True)followed byto_graph(#509, #510)FIXED: The
progressgiven toget_data_by_area,get_data_by_bboxorget_data_by_geocodingcovers their crop or merge too; it covered only the download, soprogress=Falsestill showed the crop’s bar (#502)FIXED:
crop_pbfandOSM.to_pbfrefuse an output path that is their source file, under the same name or through a link, with aValueError, asmerge_pbfdoes; they truncated the source (#503)FIXED:
get_data_by_custom_criteria,get_poisand the other readers that take acustom_filterleave the caller’s dict as given; a key’sTruebecame[True]. Nodes whose tags all became columns have notagsvalue, as other elements without leftover tags: both readers kept each such node’s tag dict there, and the out-of-core reader raisedArrowTypeErrorwhen those nodes came with ways or relations that have leftover tags (#516, #517)FIXED: The readers refuse a data blob that decompresses to more than the 32 MiB the PBF format allows, with
InvalidOSMFileError, as crops and merges do; they decompressed it whole, however large, so a small file could take all the memory. This holds for zlib blobs without araw_sizeor with a wrong one, which are still read, and for LZMA blobs (#519)FIXED: Out-of-core reads that decode at the same time in one process, each in its own thread, keep their own settings. A read that decodes in the calling process (
workers=1, the default;workers="auto"on files under 70 MB; the fallback to one process) kept its file, filter keys, bounding box and shard folder in variables the whole process shares, so a read that started while another was decoding changed them: the other read returned too few rows without an error, read the wrong file or wrote its shards to a deleted folder (#520)CHANGED:
get_path()and the pandana graph backend (graph_type="pandana"andpyrosm.graphs.to_pandana) warn with aFutureWarning, which Python shows by default, that they will be removed in pyrosm 1.0; they warned with aDeprecationWarning, which Python shows only for calls made directly from the script it runs, andto_pandanadid not warn. A read, crop or merge that leavesworkersunset warns once per process that pyrosm 1.0 will run reads, crops and merges in parallel by default; passingworkers=1keeps them on one core and silences it (#521)CHANGED:
to_graph(graph_type="networkx")andpyrosm.graphs.to_networkxbuild the graph in less time. The node and edge attributes are read from whole columns instead of row by row, and the edges whose end node is missing and the keys of parallel edges are found with column operations instead of a Python loop. On Finland’s driving network (one process), building the graph takes 7.8 s instead of 14.6 s, andto_graph47 s instead of 60 s; the graph is the same (#501)CHANGED: Graph exports simplify the graph in less time. Each node’s distinct neighbours are counted with a hash of node pairs instead of a sort, the nodes where
maxspeedorhighwaychanges are found from each column’s lowest and highest code, and the other columns of a merged edge are compared only where neighbouring segments both have a value. On Finland’s driving network (one process) simplifying takes 14 s instead of 28 s, andto_graph33 s instead of 48 s; the graph is the same (#505)CHANGED: Graph exports that simplify the graph (the default of
to_graphandpyrosm.graphs.graph_tables) take less memory and time. The directed edges are row numbers of the edges with a direction each instead of a copy of the edge table, which repeated every column for both directions of a two-way street, and only the simplified edges get rows of their own. On Finland’s driving network (one process) the graph tables peak at 13.2 GB instead of 17.2 GB of memory and take 15 s instead of 20 s; the graph is the same (#507)CHANGED: Graph simplification takes less memory. Neighbouring segments are compared by the codes of each column instead of their values, merged columns hold each distinct string once, the working indices are 32-bit when they fit (OSM ids stay 64-bit), and working arrays are freed after their last use;
get_graphfrees the arrays of its read once the simplified tables exist. The graph is the same (#508, #511)CHANGED: NetworkX graphs (
to_graph(graph_type="networkx"),pyrosm.graphs.to_networkxandOSM.get_graph) store only the attributes an edge or node has a value for, as OSMnx’s graphs do: an attribute without a value is left out instead of being stored as NaN or None. Equal strings are stored once (#512)CHANGED:
get_network(nodes=True)andOSM.get_graphgather the tags and metadata of the network’s nodes in less time and memory. The second pass over the file tests each block’s node ids against a hash set of the wanted ids in one call, instead of each node against a Python set, and keeps the node tags as string ids, as the in-memory reader does, instead of one dict per node. On Finland’s driving network (one process) the pass takes 8 s instead of 20 s andget_graph’s read peaks at 2.3 GB instead of 3.3 GB; the tables are the same (#514)CHANGED: Multipolygon relations with many rings are assembled in less time. The even-odd overlay of a relation’s rings is taken in pairs, round by round, instead of adding each ring in turn to the area built so far, which made the time grow with the square of the ring count. On Finland (one process),
get_natural()takes 77 s instead of 72 min; its largest lake, Saimaa, has 9,489 rings. The geometries are the same; the rings and vertices of a multipolygon can come in another order (#515)CHANGED: pyrosm supports Python 3.11–3.15. Wheels are published for Python 3.15, and the tests run on it. Python 3.10, whose support ended in October 2026, is no longer supported; on Python 3.10,
pipinstalls the last release that supports it (#522)
v0.21.0 (Oct 15, 2026)#
This release crops PBF files several times faster and merges them in about half the time: on Finland (771 MB, one process), a crop of the Helsinki region takes about 15 s instead of 100-111 s, and 16-17 s instead of 104-122 s with repack=True. Crops and merges show progress, and pyrosm decompresses and compresses PBF blocks with libdeflate, compiled in from vendor/libdeflate, which speeds up reads too. The raw block decoder rejects corrupt blocks instead of reading past their data.
NEW: Cropping and merging show progress.
crop_pbfandmerge_pbftakeprogressasOSMdoes, andOSM.to_pbfuses theprogressgiven toOSM(...): by default a bar on stderr, “Cropping <file>” or “Merging <files>”, once the work has run 2 seconds, cleared at the end, or without a terminal one line with the time it took. A function is called asprogress(done, total)with the bytes of the inputs read, from(0, total)to(total, total)for each pass over them: a crop makes two passes or more, and a merge one, or more with a bounding box or polygon (#496)FIXED: The raw block decoder, which every reader and the crop use, raises
InvalidOSMFileErrorfor a corrupt block instead of reading or writing past the block’s data: a field or a number that does not fit in its data, a field number protobuf does not allow, ways or relations whose keys and values (or member ids, types and roles) differ in number, and dense node groups whose coordinates, metadata or tag stream do not match their ids. A packed field written in several runs is read whole, as protobuf reads it (#483)FIXED: The raw block decoder reads a way or relation without its id, and a plain node without a coordinate, as 0, the protobuf default, instead of leaving the value undefined (#491)
CHANGED:
crop_pbfandOSM.to_pbfcrop faster. The selection decodes blocks with the raw block decoder and tests the elements of a block together, reading a file whose blocks hold nodes, then ways, then relations once instead of three times. The write pass copies the kept ways and relations as they are stored and encodes the kept dense nodes from arrays instead of rebuilding each block through protobuf messages. On Finland (771 MB, one process) a crop of the Helsinki region takes 28-33 s instead of 100-111 s, and 53-61 s instead of 104-122 s withrepack=True; the output is the same byte for byte (#484, #485)CHANGED: Re-packed crops (
repack=True) andmerge_pbfread their input faster. They find the dense nodes to keep in the arrays of the raw block decoder and parse with protobuf only the plain nodes, ways and relations they keep, instead of parsing every block and testing each element in Python. On Finland (771 MB, one process) a re-packed crop of the Helsinki region takes 36-37 s instead of 55-56 s; the output is the same byte for byte (#487)CHANGED: Crops (
crop_pbf,OSM.to_pbf) andmerge_pbfwith a box find the distinct ids they keep with a hash set instead of sorting them withnp.unique. On Finland (771 MB, one process) a crop of the Helsinki region takes 25-26 s instead of 31-32 s, and 31-33 s instead of 37-38 s withrepack=True; the output is the same byte for byte (#488)CHANGED: Crops and
merge_pbftake less time. Each block’s node ids are tested against the sorted kept ids in one walk, other id lookups load their hash buckets ahead, and re-packed blocks (repack=Trueandmerge_pbf) are encoded from arrays instead of protobuf messages, with the kept ways and relations decoded by the raw block decoder. On Finland (771 MB, one process) a crop of the Helsinki region takes 21-22 s instead of 24-26 s, and 23-24 s instead of 32-33 s withrepack=True; merging two Finland-derived files takes 9 s instead of 18-19 s. The output is the same byte for byte (#489, #490, #491)CHANGED: pyrosm decompresses the blocks of a PBF with libdeflate, which is compiled into pyrosm from
vendor/libdeflate(release 1.26, MIT licence) and is about 2.5 times as fast as Python’s zlib; a block that does not declare its size is still read with zlib. On Finland (771 MB, one process) a crop of the Helsinki region takes 16 s instead of 21-22 s, a re-packed crop 17-18 s instead of 22 s, and an in-memory read withget_boundaries()16-17 s instead of 19-20 s; the results are the same (#494)CHANGED: Crops,
merge_pbfandwrite_pbfcompress the blocks they write with the vendored libdeflate instead of Python’s zlib, at the same level (6). The files hold the same data in slightly fewer bytes, so they no longer match earlier versions byte for byte. The SHA-256 of a cropped or merged extract (AreaExtract.sha256) changes accordingly, so a recipe whoseextract.sha256was pinned with an earlier version stops until the pin is updated. On Finland (771 MB, one process) a crop of the Helsinki region takes 15.4-15.5 s instead of 16.9-17.6 s and is 85.3 MB instead of 86.8 MB, and a re-packed crop takes 16.4-16.8 s instead of 17.3-18.0 s and is 60.9 MB instead of 61.7 MB (#495)DOCS: The menu button of the documentation on phones opens the navigation sidebar again. The documentation is built with sphinx-book-theme 1.2.0, since its later versions use pydata-sphinx-theme 0.17-0.20, whose mobile sidebar button does nothing (#482)
v0.20.0 (Oct 12, 2026)#
This release removes the dependency on cykhash, so pip installs pyrosm’s wheels on glibc-based Linux, macOS and Windows on x86_64 without compiling anything, and the installation guide puts pip first.
CHANGED: pyrosm no longer depends on
cykhash, which PyPI offers only as source code, so pip installs pyrosm’s wheels without compiling anything. The sets of OSM ids and the map from node id to row that pyrosm uses are now its own, built on klib’skhash.h, the hash tablecykhashuses too. Reads return the same results; on Finland’s 95 million node ids, building and querying these tables takes 10-40 % less time (#478)DOCS: The installation guide, the README and the FAQ put
pip install pyrosmfirst and list the platforms with wheels (CPython 3.10–3.14 on glibc-based Linux on x86_64 and aarch64, macOS, and Windows on x86_64). The advice to install GeoPandas first on Windows and the list of dependencies that needed installing beforehand, which still namedcykhash, are removed (#479)
v0.19.1 (Oct 12, 2026)#
This release makes the predefined networks follow OSM’s access rules: a way that access=no closes to the public is left out unless a tag of the network’s mode of transport allows the use.
FIXED: The predefined networks no longer include ways that
access=nocloses to the public, unless a tag of the network’s mode of transport allows the use. The most specific access tag present decides, as in OSM’s access hierarchy (walking:foot,access; cycling:bicycle,vehicle,access; driving:motorcar,motor_vehicle,vehicle,access), and a way is left out when that tag isnoorprivate:access=nowithfoot=yesstays in the walking network,access=privatewithfoot=yesis now kept, andall_publicleaves outaccess=noas well. In the walking network of central Helsinki, the Suomenlinna maintenance tunnel is no longer included, so the island is no longer joined to the mainland (#474)
v0.19.0 (Oct 12, 2026)#
This release reads with the out-of-core engine by default and makes both readers faster and leaner: OSM(...) reads country-sized files with bounded memory, and both engines return the same columns in the same order. get_network() returns each edge as one LineString and to_graph simplifies the graph by default. It also fixes history reads that returned deleted elements, graphs that took the network type of the last read, and out-of-core reads of files whose nodes are not in the dense format.
CHANGED:
get_network()returns each edge as one LineString through its way’s nodes instead of a MultiLineString of two-point segments, and the other layers return each line way (an open way, or a closed way with ahighway,barrierorroutetag orarea=no) as one LineString too; relations keep their MultiLineStrings. The coordinates are the same and in the same order, and thelengthcolumn keeps its values. Results that the out-of-core engine cached with an older version are not reused (#446, #447)CHANGED:
to_graphsimplifies the graph by default, and so dopyrosm.graphs.to_networkx,to_igraph,to_pandana,to_pandarmandgraph_tables, which the graph stage of recipes uses. A chain is no longer merged across a change ofmaxspeedorhighway(a missing value next to a set one counts as a change), so each edge has one speed limit and one road class, and pandana and pandarm graphs sum their weight columns along a merged chain likelength.simplify=Falsekeeps one edge per segment, andsimplify_kwargs={"edge_attrs_differ": None}merges as OSMnx does by default (#449)CHANGED: Reading networks and line layers takes less time and memory. On Finland (737 MB, one process),
get_network("all")takes 45-46 s instead of 90-101 s with the out-of-core engine and peaks at 7.5-8.4 GB instead of 14.9 GB of memory, and takes 102 s instead of 206 s with the in-memory engine, peaking at 28.4 GB instead of 40.4 GB. The in-memory parse joins its per-block arrays without a Python object per value, polygon orientation returns points and lines as they are instead of copying them, lines and the edges ofget_network(nodes=True)are built in batches, and the out-of-core decode of a network no longer writes every way’s node refs to disk (#444, #445, #446, #447, #448, #450)CHANGED:
OSM(...)reads with the out-of-core engine by default. It decodes the file in a streaming pass with bounded memory and caches each read’s result in the temporary folder, so an identical read returns it without decoding the file again (OSM.clear_cache()removes the cached results).workers=Nonereads on one core without a warning;workers="auto"decodes in parallel.OSM(..., engine="in_memory")keeps the previous behaviour, and history reads (an.osh.pbffile or atimestamp) andwrite_pbfuse the in-memory reader with either engine. The two engines return the same elements and values, but the order of rows and the integer width of some metadata columns can differ (#460)CHANGED: Out-of-core reads take less time and memory. On Finland (737 MB, one process),
get_network("all")takes 36 s instead of 48 s and peaks at 6.8 GB instead of 7.7 GB, andget_buildings()takes 43 s instead of 53 s and peaks at 5.0 GB instead of 6.0 GB. The node coordinates a read needs are looked up within each spilled block’s id range (0.4 s instead of 5.8 s forget_pois()), a block’s tags are resolved through its string table decoded once, and relations find their member ways through an index instead of scanning all of them for each relation (21 s instead of 30 s to assembleget_landuse()), which the in-memory reader uses too (#454, #455, #456)CHANGED: Parallel out-of-core reads (
workers="auto"orworkers=N) decode the file in about eight tasks per worker, each with a similar share of the file’s bytes, which the workers take as they become free; each worker used to decode one equal share of the file’s blocks, and the last share held all the ways. The ways and node coordinates a read keeps are collected in the main process, as in a one-process read, instead of in two more process pools, which were slower than one process and raised its memory peak.workers="auto"uses the CPU cores less two, up to eight, instead of every core (#462)CHANGED: The in-memory reader (
engine="in_memory", history reads andwrite_pbf) reads faster with less memory. It decodes the file with the raw block decoder the out-of-core engine uses instead of protobuf messages, keeps ways and node tags as arrays and builds their dicts only for the elements a read can keep, and keeps the node columns without a DataFrame copy. On Finland (737 MB) the parse takes 15 s instead of 81 s, peaks at 11.2 GB instead of 28.5 GB and keeps 8.3 GB instead of 19.9 GB in memory;get_network("all")after it takes 18 s instead of 23 s andget_pois()4 s instead of 13 s. Blobs stored raw or lzma-compressed are now read, untagged nodes of plain (non-dense) node groups getNonetags like dense ones instead of an empty dict, andparse_osm_datano longer warns about protobuf’s pure-Python backend (#463, #464, #466, #467, #468)CHANGED: Reads build their result in less time. The way columns are no longer copied to drop the ways without nodes when every way has nodes, the in-memory reader puts the tags of the ways into their columns in one pass, and the out-of-core engine writes its chunks of ways straight into the result instead of writing each to a temporary file first. On Finland (737 MB, one process), reading all highway ways with
get_data_by_custom_criteriatakes 27 s instead of 34 s with the in-memory engine and 29 s instead of 35 s with the out-of-core engine, andget_buildings()takes 34 s and 36 s instead of 44 s with the two engines. The in-memory engine peaks 0.6-0.9 GB lower; the peak of the out-of-core engine does not change (#469, #470, #471)CHANGED: The out-of-core engine returns the columns in the order of the in-memory reader:
timestampbeforeversion,visible(a default column ofget_buildings()) andnodes(withkeep_node_info=True) at their place among the other columns,tagsafterlatin the node frame ofget_network(nodes=True), and a tag that first occurs after the first 250,000 ways of a result at its place instead of at the end. Results that the out-of-core engine cached with an older version are not reused (#471)FIXED: History reads (an
.osh.pbffile, or atimestamp) no longer return elements that were deleted before the timestamp: a deleted element used to come back as its last version before the deletion. With abounding_box, they no longer return elements that had left the box by then, at their old position: the box is now applied to each element’s state at the timestamp, not to every version. On the Helsinki test history file at 2010-01-01, three buildings and 33 network ways that had been deleted are gone (#443)FIXED:
to_graphtakes the network type from the frames it is given.get_networkrecorded the type in a list shared by every GeoDataFrame in the process, so a graph got the type of the network read last: driving edges exported after a walking read became a two-way graph. Out-of-core frames recorded no type, soto_graphwithoutnetwork_typefailed. Each frame now keeps its type inattrs["network_type"]; a givennetwork_typetakes precedence, and an unknown one raises aValueError(#452)FIXED: The out-of-core engine reads PBF files whose nodes are not in the dense format (for example written by osmium with
pbf_dense_nodes=false); it returned nothing for them (#453)FIXED: The out-of-core engine keeps each way’s
nodescolumn withkeep_node_info=True, whichwrite_pbf(..., apply_geometry=True)needs to reshape ways; it dropped the column (#457)FIXED:
get_data_by_custom_criteria(custom_filter=None)reads every tagged element with the out-of-core engine too; it raisedNotImplementedError(#458)FIXED: Out-of-core reads that find nothing warn as in-memory reads do, and
get_data_by_custom_criteriaraises aValueErrorfor a non-stringfilter_typewith either engine (#459)
v0.18.0 (Oct 10, 2026)#
This release adds recipes, YAML files that describe how the OSM data of an analysis is made and that pyrosm run carries out with a provenance record, and pyrosm.enable_logging() to show pyrosm’s log messages. It also fixes the column order of node layers, tags lost when write_pbf gets a frame without tag columns, and history reads that mixed values from different versions.
NEW: Recipes. A recipe is a YAML file that says how the OSM data of an analysis is made: an extract to download (by bounding box, place name, area file or dataset name), layers to read from it, a graph to build, and where each output goes.
pyrosm run RECIPE [-o DIR](orpyrosm.recipes.run) writes the extract as PBF, each layer and the graph’s node and edge tables as GeoParquet, and a provenance record with the package versions, the resolved recipe, the SHA-256 of every input and output and where the extract came from. Outputs are moved into place only when every stage has succeeded; a rerun reuses the stages that did not change, andsha256andsnapshotpins refuse data that differs.pyrosm validate RECIPEchecks a recipe without running it, andpyrosm.graphs.graph_tablesreturns a graph’s node and edge tables as the graph exporters build them. Recipes need PyYAML and pyarrow (pip install "pyrosm[recipes]"), and the user guide has a page on them (#432, #433, #434, #435, #436, #437)NEW:
pyrosm.enable_logging(level="INFO", to_file=None)shows pyrosm’s log messages on stderr or in a file. At INFO eachOSMobject logs its options, and each read (get_network,get_buildings, …,to_graph) logs one line with its arguments, the number of features and the seconds it took; DEBUG adds the time of reading the PBF, the out-of-core engine’s phases and its cache hits and misses. Messages areop key=value ...lines, and the existing download, geocoding and extract messages now use the same form (#431)FIXED:
get_pois,get_naturalandget_data_by_custom_criteriareturn their columns in the same order on every run. The node columns (id,lon,lat,tags,visible,version,timestamp,changeset) came in an order that changed with Python’s hash seed, so the same read could give differently ordered frames and files with different SHA-256 from run to run. They now come in the order the out-of-core engine uses (#439)FIXED:
write_pbfkeeps the tags of the elements a frame matches when the frame has no tag columns, such as one that names elements byidandosm_typeonly. Such a frame used to strip their tags, sosubset_only=Truewrote valid but tagless elements (#440, #383)FIXED: History reads (an
.osh.pbffile, or atimestamp) take each element’s latest version whole. A tag that the latest version had removed used to come back from an older version, so a row could mix two versions. On the Helsinki test history file at 2010-01-01, three ways that had lost theirbuildingtag are no longer buildings, and one way that had lost itshighwaytag leaves the network (#441, #379)
v0.17.0 (Oct 9, 2026)#
This release shows progress while pyrosm reads a PBF, and fixes a failure of get_mask_by_osmid on NumPy 1.24–1.26 and of NodeLocations.gather on an empty coordinate store.
NEW: Reading a PBF shows progress.
OSM(..., progress=True)(the default) shows a bar on stderr once a read has run 2 seconds, a widget in Jupyter whenipywidgetsis installed, and clears it when the read finishes; without a terminal, a read that took 2 seconds or more prints one line with the time it took.progress=Falseshows nothing, and a function is called asprogress(done, total)with the bytes of the file read so far, from(0, total)to(total, total)for each pass over the file. Both the in-memory reader and the out-of-core engine report it, including the second pass ofget_network(nodes=True); thepyrosm.enginereaders takeprogresstoo (#426, #202)FIXED:
pyrosm.data_filter.get_mask_by_osmid, whichpyrosm.frames.create_nodes_gdfuses to filter nodes byosmids_to_keep, no longer fails withAttributeError: module 'numpy' has no attribute 'bool'on NumPy 1.24–1.26 (#427, #377)FIXED:
NodeLocations.gather, which looks up node coordinates while geometries are built, returns-1(absent) and NaN coordinates for every id when no node coordinates were read, instead of raisingIndexError. Ids that are absent from a non-empty store now get NaN coordinates instead of those of the store’s first node (#428, #378)
v0.16.0 (Oct 9, 2026)#
This release shows the progress of downloads in terminals and in Jupyter, and lets a calling library draw its own progress bar. pyrosm now requires tqdm.
NEW: Downloads show a progress bar on stderr, drawn as a widget in Jupyter when
ipywidgetsis installed (pip install "pyrosm[notebook]") and as a text bar elsewhere. Without a terminal, as in logs and CI, only a line naming the file is printed.get_data,get_data_by_area,get_data_by_bbox,get_data_by_geocodinganddownloadacceptprogress:True(default),Falsefor no output, or a function called asprogress(written, total)after each chunk, so a calling library can draw its own bar. pyrosm now requirestqdm(#424)FIXED: The test suite runs from the source package.
test_page_size_parsertests a maintainer script that the source package does not include; it is now skipped when the script is missing instead of failing withFileNotFoundError(#423)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
v0.15.0 (Oct 1, 2026)#
This release extends get_data_by_area for callers that fetch the data of many areas: it can list the candidate extracts without downloading, merge a smaller set of extracts, require coverage only of what matters, crop to the area’s polygon with several processes, and record the hash and snapshot time of the file it returns. Downloads are retried and resumed, and accept the caller’s headers, timeouts and opener.
NEW: Add
pyrosm.find_extracts(area, contains_only=False, update=False, directory=None)to list the Geofabrik, BBBike and Movisda extracts that overlap an area, with their download sizes and whether each contains the whole area, without downloading any of them. Extracts that contain the area come first, smallest download first.get_data_by_areatakes its candidates from it (#401)NEW:
get_data_by_area(..., strategy="smallest_total")covers an area with several extracts and merges them withmerge_pbfwhen their total download is smaller than the smallest extract that contains the area, or when that extract’s size is unknown. The set is chosen greedily (lowest size per area newly covered) from Geofabrik, BBBike and Movisda administrative extracts, with at most one Movisda extract, which goes last in the merge; Movisda grid tiles are never combined. The default,strategy="single", never merges.AreaExtract.sourceslists the downloaded extracts (#408)NEW:
find_extractsandget_data_by_areaacceptmust_cover, a geometry that an extract must cover in place of the whole area, such as the transit stops routing needs, so sea or unserved edges of the area no longer force a larger extract. Points and lines are covered exactly, polygons except for less than 1 m². The crop still follows the area (#409)NEW:
AreaExtractrecordssha256, the SHA-256 of the returned file, andsnapshot, the data time from the PBF header’sosmosis_replication_timestamp(for a merged file the oldest of its sources), and each record insourcescarries its own. They are read after the file is written, and read again when a file changes meanwhile (#410)NEW:
get_data_by_area(..., crop="polygon")crops the extract to the area itself instead of its bounding box, andmerge_pbf(..., polygon=...)crops one or several inputs to a polygon. A node is kept when it lies inside the polygon or on its boundary, and a way with a kept node is kept whole (#411)NEW:
get_data_by_area(..., workers=N)crops and merges with N worker processes, and writes the same file as one process. Cropping the 64 MB Helsinki test extract to a city took 6.6 s with one worker, 3.9 s with two and 2.5 s with four (#412)NEW: An interrupted download resumes where it stopped, using
RangeandIf-Rangewith the server’s strongETagor an old enoughLast-Modified, instead of starting over. A response with a content coding is never resumed (#404)NEW:
get_data,find_extracts,get_data_by_areaanddownloadacceptheaders(a caller’s User-Agent, for example),timeout(seconds, or a(connect, read)pair) andopener(such asurllib.request.build_opener()). Caller credentials, cookies andHostare not sent to the target of a redirect.downloadcreates a missing target directory instead of raisingValueError(#405)CHANGED: Downloads, Movisda index fetches and size requests retry network errors and HTTP status 408, 425, 429 and 5xx up to three times, waiting 1 s, 2 s or the server’s
Retry-After. A failed download raisespyrosm.exceptions.DownloadError, which is also aValueErrorand anOSErroras before, withurl,statusandattempts;ExtractDownloadError.errorsholds one for each extract tried (#403)CHANGED:
get_data_by_arearaisespyrosm.exceptions.ExtractNotFoundError, aValueErrorsubclass, when no extract contains the area.download,get_data_by_area,get_data_by_bboxandgeocodereport their progress through thepyrosmlogger at INFO level instead of printing it (#402)FIXED: An extract whose size Geofabrik does not give (a failed or looping HEAD request) is ranked by the size recorded in pyrosm’s Geofabrik index, with a warning, instead of last; a redirect loop is not retried.
scripts/update_extract_indexes.py geofabrik-sizesrefreshes the recorded sizes (#406)FIXED: When Movisda’s grid index cannot be fetched and no copy is cached,
find_extractsandget_data_by_arearead the copy that ships with pyrosm (28,350 tiles), with a warning, instead of leaving out every Movisda extract. When the administrative index cannot be fetched, only the administrative areas are left out (#407)FIXED: A crop or merge with
workers > 1(OSM.to_pbf,merge_pbf,get_data_by_area) no longer hangs when its worker processes cannot start, as on macOS and Windows for a script without anif __name__ == "__main__":guard or one read from stdin; it runs in one process with aRuntimeWarning(#416)DOCS: The
get_data_by_areadocstring says which features a Movisda grid tile leaves out at its edges. The downloading guide and the docstrings show how to require the streets around transit stops (must_coverwith buffered stops or a hull of them), how to see the extractget_data_by_areawould download before downloading it, and how to download a large region resumably and crop it withmerge_pbf(#414, #415)
v0.14.0 (Sep 30, 2026)#
This release adds two ways to get the data for an area that crosses the borders of the published extracts: get_data_by_area downloads the smallest single extract from Geofabrik, BBBike or Movisda that contains the area, and merge_pbf merges overlapping extracts into one file. get_data can now download every extract Geofabrik publishes.
NEW:
get_datacan download every extract Geofabrik publishes. The catalogue gains the regions of China, India, Indonesia, Australia, the Czech Republic and Norway (listed underpyrosm.data.available["subregions"]), the administrative regions of British Columbia and Nunavut, the London boroughenfield, the cross-border extractsalps,britain_and_ireland,dach,guernsey_jerseyandsea, andamerican_oceania,bermuda,falklands,guyanaandus_virgin_islands.get_data("beijing")andget_data("victoria")are now ambiguous, as each names both a BBBike city and a Geofabrik region: use"cities/beijing"or"china/beijing", and"cities/victoria"or"australia/victoria".sources.asia.china, and the same attribute of the other countries that gained regions, now returns the country’s region group, assources.europe.francedoes; the whole-country file stays available throughget_data("china")and the group’scountryrecord (#394)NEW: Add
pyrosm.get_data_by_area(area, crop=True, update=False, directory=None, output_path=None)to download the smallest single OSM extract that contains an area and, by default, crop it to the area’s bounding box. It compares Geofabrik extracts, BBBike city extracts and Movisda’s administrative areas and 1°/10° grid tiles by download size and never merges extracts; when a download fails, the next smallest extract is tried. The area may be a polygon, a GeoDataFrame/GeoSeries or a bounding box. It returns anAreaExtractrecord (the file, provider, extract, URL, size and timings) thatOSM()accepts as a path, and raisespyrosm.exceptions.ExtractDownloadErrorwhen no extract could be downloaded. The BBBike city list ships with pyrosm; Movisda’s index files are fetched and cached next to the downloads. All pyrosm downloads now send a pyrosm User-Agent and give up on a server that stops responding for 60 seconds, and a download that fails midway no longer leaves a partial file that later calls reuse (#389, #390, #391, #392)NEW: Add
pyrosm.merge_pbf(inputs, output_path=None, bounding_box=None, keep_relations=True, workers=1)to merge overlapping.osm.pbfextracts, for example two country extracts that together cover a border city, into one file sorted by type then id. An element found in several inputs is written once (the highestversion, then the latesttimestamp, then the earliest input), and inputs with and without element metadata can be mixed. Withbounding_boxthe crop rule ofOSM.to_pbfis applied to the union of the inputs, and a kept way keeps its full node list whichever input holds the nodes. The inputs are streamed block by block andworkersparallelises the crop selection. The output is written to a temporary file and moved into place when complete, and the merge fails if an input’s size or modification time changes, or the file is replaced, while it runs (a sync client such as OneDrive touching only a file’s metadata does not count as a change).to_pbf(repack=True)now writes a file that mixes nodes with and without element metadata instead of raising, and re-packed blocks are split to stay below the 16 MiB the PBF format recommends (#388, #398)FIXED: Cropping a truncated or corrupt PBF with
OSM.to_pbfraises anInvalidOSMFileErrorthat names the file. The crop read the data blocks without checking them, so a truncated download failed with a bare protobufDecodeError,zlib.errororstruct.error. The crop and the header check ofOSM()now share one blob reader, which also rejects blobs declared or decompressed beyond the 32 MiB PBF limit. The crop’sValueErrorfor an unsupported compression or required feature, and thePBFNotImplementedofOSM()for an unsupported required feature, name the file too (#385)FIXED: Graph simplification (
OSM.to_graph(..., simplify=True)) keeps one edge where two OSM ways overlap node for node along a simplified chain, as OSMnx does. It kept a parallel copy of the stretch for each way, which counted it twice in the edge count and the total length (#386)FIXED: Spain’s Geofabrik regions download by name. They were listed with accents and spaces (“Castilla y León”), which produced URLs Geofabrik does not serve; they now use ASCII names (
castilla_y_leon,pais_vasco) and are also available assources.europe.spain.get_data("madrid")is now ambiguous, as it names both a BBBike city and a Geofabrik region: use"cities/madrid"or"spain/madrid"(#381)FIXED: Russia’s whole-country record (
sources.europe.russia.country) points at Geofabrik’srussia-latest.osm.pbf. It pointed atrussia/russia-latest.osm.pbf, which Geofabrik does not serve;get_data("russia")was not affected (#395)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
rgreinho (#381)
v0.13.1 (Aug 1, 2026)#
A packaging release: the wheels are considerably smaller and Linux ARM wheels are published. The library itself is unchanged.
CHANGED: The wheels no longer carry the debug information and the generated C sources. The manylinux images compile with
-gandauditwheeldoes not strip, so the DWARF sections made up 88% of every compiled module, andinclude_package_datashipped the Cython-generated.cfiles next to the.pyxsources inside every binary wheel. The Linux wheel drops from 12.0 MB to about 3 MB (~44.7 MB to ~6 MB installed), and the macOS and Windows wheels from about 5.1 MB to 3.1 MB. The source distribution still contains the C sources, so a source build needs no Cython (#374)NEW: Publish Linux ARM (
aarch64) wheels, built natively on an ARM runner. Installing on Linux ARM no longer builds pyrosm from source (#374)
v0.13.0 (Jul 29, 2026)#
This release aligns the street-network reading and the networkx graph export with OSMnx: the predefined network_type filters now match the OSMnx way filters one-to-one, and an exported graph works with the OSMnx functions. The other readers are unchanged.
CHANGED: The predefined network types match the way filters of OSMnx one-to-one, so the same query returns the same streets in both libraries.
drivingis OSMnx’sdriveand no longer includes service roads (usedriving+service, which is OSMnx’sdrive_service, to keep them),allis filtered rather than returning every way, and the newall_publicleaves out the privately accessible ways. Every mode-specific network now leaves outaccess=privateways,walkingleaves out ways whose sidewalk is mapped separately, and the lifecycle and service-area highway values are excluded throughout. The OSMnx namesdrive,drive_service,walkandbikeare accepted as network types as well (#369, #372)CHANGED:
drivingno longer excludes ways taggedpsv=yes. The tag means public service vehicles are allowed rather than others banned, so the exclusion dropped ordinary drivable streets carrying a bus route.driving+serviceused to resolve to that filter, which added public service vehicle ways instead of service roads (#369, #372)FIXED: A
networkxgraph exported withosmnx_compatible=Trueworks with OSMnx.keyis no longer an edge attribute, where it collided with the parallel-edge key ofadd_edge, andosmidis the key of a node rather than also a node attribute, where it collided with the node index when writing the nodes. Nodetagsare stored as a JSON string like the edge tags, and the graph carriessimplified(#370, #371)FIXED: Parallel edges between the same pair of nodes survive the
networkxexport. Every edge was written with the multi-edge key0, so all but the last one were dropped from the graph (#370, #371)FIXED: An edges GeoDataFrame with gaps or duplicate labels in its index exports to a graph. The edge attributes were read by index label, and the one-way masks were selected by label, which raised for a filtered or concatenated frame (#370, #371)
FIXED: An unknown
network_typeraises aValueErrorin every reader.network_type="driving_psv"was accepted but unhandled, and returned the whole unfiltered network (#369, #372)FIXED: A tag holding several values (
access=private;delivery) is matched by the predefined network filters, as the OSMnx filters match it. Acustom_filteris unaffected and keeps matching values exactly (#369, #372)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
v0.12.0 (Jul 22, 2026)#
This release makes OSM.write_pbf able to edit geometry – moving nodes, reshaping ways and deleting elements – so a network read with pyrosm can be edited and written back to a *.osm.pbf in full. The reading engines are unchanged.
NEW:
OSM.write_pbfcan edit geometry, not just tags. Passapply_geometry=Trueto move an existing node (a node row’sPointsets its coordinates, and every way and relation referencing it follows), to reshape an existing way through itsnodesmember-id column (inserting, removing and reordering vertices), and to add a new element under a caller-chosen negative id so a way’snodeslist can reference a vertex added in the same call. The newdeleteparameter takes(osm_type, id)pairs to omit elements from the output, withon_orphan_node('remove'/'keep'/'error') andon_deleted_member('drop'/'error') deciding what happens to the references a removal breaks. Defaults are unchanged: withoutapply_geometry/delete,write_pbfwrites exactly what it wrote before (#367)NEW: Add a
keep_node_infoparameter toOSM(...), which keeps thenodescolumn of each way’s ordered member node ids in the returned GeoDataFrames. It was previously only reachable by setting the attribute after construction, and it is whatwrite_pbf(..., apply_geometry=True)reshapes a way through (#367)NEW: Add California to the US state sources, so
get_data("california")downloads the whole-state Geofabrik extract. Thenorthern_californiaandsouthern_californiasub-regions are unchanged (#366)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
mitchellhenke (#366)
v0.11.0 (Jul 1, 2026)#
This release adds topological graph simplification for network exports, opt-in regular-expression and Overpass-style custom filters, and selective-layer PBF export, and rewrites multipolygon relation assembly to follow the OSM even-odd algorithm so relation geometries match osmium. It also reorganizes the documentation with a GeoPandas-style API reference. The default in-memory and out-of-core readers are otherwise unchanged.
NEW: Add topological graph simplification to
OSM.to_graphviasimplify=True(off by default). It removes interstitial (degree-two) nodes and merges each chain of edges between true intersections and dead-ends into a single edge – consolidating their geometry and length – so the exported graph matches the topology produced by OSMnx’ssimplify_graph. Passsimplify_kwargsto forward options, and the kept nodes carry an OSMnx-compatiblestreet_count. It is implemented vectorised with a single Cython chain-walk kernel and needs nonetworkx/igraphin the path (#89, #351, #352)NEW:
custom_filteraccepts two opt-in advanced forms in addition to the exact-value dict, on every reading method: a compiled regular expression (re.compile(...)) as a value, matched against the tag value withre.search; and Overpass-style bracket-filter strings – a single string or a list of them, the syntax OSMnx users already know – where each string is the AND of its brackets and a list is their OR, supporting=,!=,~,!~, key-present and key-absent operators and a case-insensitive flag. Advanced network filters select ways by the keys they reference, soget_networkcan now build non-highwaynetworks (e.g.railwayorcycleway). A plain-dict filter behaves exactly as before (#116, #341, #357, #359)NEW: Add a
subset_onlyparameter toOSM.write_pbffor exporting only selected layers to a PBF. Withsubset_only=Truethe writer keeps only the elements in the supplied GeoDataFrame(s) and the nodes/ways they reference, rather than the whole source file, so e.g. just the buildings and the network can be written to a new*.osm.pbf(#348, #353)FIXED: Assemble multipolygon and boundary relations with the OSM even-odd rule – a point is inside the area when an odd number of the relation’s rings enclose it – instead of the previous role-based outer/inner classification. This matches osmium and GDAL and corrects relation geometries with islands-in-holes, touching inner rings, or mis-tagged member roles, and drops spurious force-closed artifacts. It also fixes the out-of-core engine returning fewer elements under
keep_other_tags=False, which had omitted the relation linestring-decision tag keys (#21, #354, #355)DOCS: Reorganize the documentation with a grouped table of contents and a new GeoPandas-style API reference (category tables with a one-line summary per method and a page per object); add a Frequently Asked Questions page (including how to find street intersections and their coordinates); document graph simplification and the advanced custom filters; render the API reference correctly on Read the Docs; and fix broken documentation links in the README (#93, #177, #347, #349, #350, #356, #358, #360)
TESTS: Add a parity test suite cross-validating pyrosm against OSMnx on identical data, area and time – OSMnx reference layers are fetched from the Overpass API at the PBF’s historical moment (an Overpass attic query) and compared against pyrosm’s read of the same bounding box and filter, asserting at least 0.995 Jaccard similarity on the selected OSM ids for Overpass-style network filters and building filters (#362)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
v0.10.0 (Jun 26, 2026)#
This release adds an opt-in out-of-core (“streaming”) reading engine that decodes large PBF files in a single streaming pass with bounded memory – the decode, the node-coordinate gather and the standalone-way read all run in parallel across a worker pool, and each layer’s result is cached automatically – making whole-country and even whole-continent extracts readable quickly on modest machines without running out of memory (tested on Macbook Air with 24 GB of RAM). get_data can now also download whole-continent extracts. The default in-memory reader is unchanged.
NEW: Add an opt-in out-of-core reading engine, selected with
OSM(filepath, engine="out_of_core")(the defaultengine="in_memory"is unchanged). It decodes the PBF in a single streaming pass – each block’s node coordinates and matching features are spilled to per-block files on disk, then only the coordinates the kept features reference are gathered to assemble geometries – so peak memory is bounded by the working set rather than the whole file, making whole-country extracts readable on modest machines, resolving the out-of-memory errors and kernel crashes previously reported when reading large extracts (#111, #147, #166, #205). Every feature method (get_network,get_buildings,get_pois,get_landuse,get_natural,get_boundaries,get_data_by_custom_criteria) and the optionscustom_filter,bounding_box,complete_relations,extra_attributes/tags_to_keepandkeep_metadatabehave as on the in-memory reader and return column-for-column identical GeoDataFrames;get_network(nodes=True)returns the graph-export nodes and edges. History (.osh.pbf) files and timestamped reads are served by the in-memory reader (#321, #322, #323, #324, #325, #327, #329)NEW: The out-of-core engine caches each layer’s result to a GeoParquet file under a temporary directory, keyed by the source file and the read parameters, and reuses it on identical later reads – even in a later Python session – instead of re-decoding the PBF. Caching uses the optional
pyarrowpackage; without it the reader still works but returns an in-memory GeoDataFrame without caching. Passingoutput="path"streams a layer straight to a GeoParquet file instead (#330, #331)NEW: Control out-of-core parallelism with a
workersparameter onOSM(...). By default the engine reads on a single core and the first out-of-core read reports how many CPU cores are available; passworkers="auto"to let pyrosm pick the count by file size (one worker per CPU core above ~70 MB, capped at the data-blob count), orworkers=Nfor an explicit count (reduced to the CPU-core count, with a warning, if it exceeds it). On macOS and Windows a parallel read launched from a standalone script must run under anif __name__ == "__main__":guard; without it the read falls back to a single process with a warning (#326, #328, #334)NEW: Add
OSMhelper methods for managing the out-of-core engine’s temporary files.OSM.list_cache()andOSM.clear_cache()list and remove the cached layer GeoParquet files, andOSM.list_downloads()andOSM.clear_downloads()list and remove the*.osm.pbfextractsget_datadownloads to the temp directory.list_cache/clear_cacheandclear_downloadstake an optional source file to scope the operation to a single file; the bundled package datasets are never touched (#336)NEW: Add a
keep_other_tagsparameter (defaultTrue) to the out-of-coreget_data_by_custom_criteria. Withkeep_other_tags=Falsethe engine resolves only the requested tags (tags_as_columnsplus the filter keys) and returns no JSONtagscolumn of leftover tags, so a geometry-and-key read skips the long-tail tag parsing (and the leftover column that would have been dropped is no longer built at all). Only the out-of-core engine supports it – the in-memory reader raises (#337, #340)NEW:
get_datanow downloads whole-continent extracts –get_data("south_america")(and the other continents) resolves to the continent-wide Geofabrik*-latest.osm.pbfinstead of raising, so an entire continent can be fetched in one call and then read with the out-of-core engine’s bounded memory (#339)NEW: Add Spain’s autonomous communities as Geofabrik sub-region sources, so each can be downloaded individually with
get_data(#203)CHANGED: Parallelise the out-of-core engine’s collect phase. After the parallel decode, the node-coordinate gather and the standalone-way read are split across the same worker pool (the way records are carried as columnar arrays so they cross process boundaries cheaply) instead of running single-process. On country-scale reads this collect phase is the dominant post-decode cost, so parallelising it markedly speeds up large reads, with byte-identical output (#338)
CHANGED: Spill the out-of-core engine’s intermediate shards coarser – decode now batches several PrimitiveBlocks into one shard (up to a byte target) rather than writing one shard per ~8k-element block, so a country file produces a few hundred shards instead of tens of thousands of tiny files. This cuts the per-file open/read overhead the collect phase pays re-reading them (roughly halving the collect time on a Finland buildings read), with no change to the output, and keeps peak memory bounded by about one shard per worker (#342)
Thanks for all the contributors who helped to improve the library either via PRs, or by raising or participating in an issue:
v0.9.1 (Jun 18, 2026)#
This release adds reading all tagged data without a filter and correct relation geometries for bounding-box reads, speeds up area-feature (Polygon) geometry building, and fixes incomplete-boundary handling and ambiguous region-name lookups.
NEW: Add a
complete_relationsparameter toOSM(...)(defaultFalse). When reading with abounding_box, a relation (e.g. a multipolygon or boundary) is assembled from only the member ways inside the box, so a relation straddling the box edge gets a partial geometry. Withcomplete_relations=Truethe reader fetches each such relation’s full member set (member ways and their nodes, even outside the box) so the geometry is correct, applying to every relation-returning layer (get_buildings,get_landuse,get_natural,get_boundaries,get_pois,get_data_by_custom_criteria). It is opt-in: the default reproduces the existing output, and completion adds two streaming passes over the file only when a relation actually has missing members. The fetched member ways are kept out of the normal way features, so other layers (e.g.get_network) are unaffected. Only member ways are completed; relations whose members are themselves relations (super-relations) are not. A whole-file read (nobounding_box) already holds every member, so the option is a no-op there. When a bounding-box read returns relations the box cut andcomplete_relationswas not enabled, aUserWarningreports how many relations were returned with incomplete geometry and points to the option (#236)NEW:
get_data_by_custom_criterianow acceptscustom_filter=None(the new default) to read every tagged element without enumerating tag keys – tagged nodes as Points, ways as Lines/Polygons and relations as (Multi)Polygons/Lines; standalone untagged ways are dropped (matching GDAL’s OSM driver). Whentags_as_columnsis not given it defaults to the union of the per-feature default tag columns, so common keys become columns and the rest land in the JSONtagscolumn, andkeep_metadata/keep_nodes/keep_ways/keep_relationsstill apply (#113)FIXED:
get_boundaries()no longer force-closes incomplete administrative boundaries into polygons. A boundary relation whose member ways run off the PBF extent cannot form a closed ring, and was previously closed with a spurious straight edge bridging the gap (the stray lines crossing boundary plots); such incomplete boundaries are now dropped, matching how osmium and GDAL skip areas they cannot assemble (#154)FIXED:
get_data()no longer silently returns the wrong extract when a dataset name is shared by multiple regions – e.g.get_data("georgia")(the US state vs. the country) now raises aValueErrorlisting the region-qualified alternatives instead of returning the first match. Region-qualified names are accepted (get_data("usa/georgia")vs.get_data("europe/georgia")), and names that resolve to the same file (e.g. a UK county reachable via bothgreat_britainandunited_kingdom) still resolve as before (#162)CHANGED: Vectorise closed-area (Polygon) way geometry construction – build the geometries for closed area ways (e.g.
get_buildings,get_landuse,get_natural) with a single batchedshapely.polygons/shapely.linearringscall instead of a per-way Python loop, falling back to the exact per-way builder for the rest (open ways, closed ways tagged as linear features, ways with dropped nodes), so the output is identical. Cutsget_buildingswall-clock ~28% on an Estonia extract (52.0 s -> 37.4 s), complementing the network-geometry vectorisation added in v0.9.0 (#315)
Thanks for all the contributors who helped to improve the library either via PRs or reporting bugs:
v0.9.0 (Jun 16, 2026)#
This release lowers the read path’s memory use and speeds it up, adds fetching data by bounding box and by place name, and writes OSM data back to PBF (editing attributes/tags and cropping).
NEW: Add a
tags_to_keepparameter to the feature methods (get_network,get_buildings,get_pois,get_landuse,get_natural,get_boundaries). When given, only those OSM tag keys are kept as columns (replacing the default tag-column set), reducing memory; structural columns, filtering andextra_attributesare unaffected and the default behaviour is unchanged (#87)NEW: Add a
keep_metadataparameter toOSM(...)(defaultTrue). Setkeep_metadata=Falseto drop the element metadata columns (timestamp,version,changeset) from the returned GeoDataFrames and skip decoding the per-node metadata while parsing, lowering memory use and parse time on node-heavy files; the default behaviour is unchanged and history (.osh.pbf) files keep the metadata they require (#87, #150)CHANGED: Replace the per-node coordinate lookup (previously a dict-of-dicts) with a compact
cykhashid->index map plus contiguous column arrays, cutting the read path’s peak memory (~12% on a 138 MB extract) and making coordinate lookups during geometry building a little faster, with no change to the returned data (#53)CHANGED: Build network way geometries with a single batched
shapely.linestringscall across ways (and skip the from/to ids and node-attribute records that a plainget_networkdiscards), cutting network geometry construction ~37% and the multi-layer wall-clock ~17% on a 138 MB extract, with no change to the returned data (#53)CHANGED: Stream PBF blocks through a generator (each block is parsed then discarded) instead of holding the whole decompressed file in a list, cutting the read path’s peak memory – ~20% for a whole-file read and ~50-70% for bounding-box reads, which no longer pay the whole-file cost. Output is unchanged; bounding-box reads re-stream the file once to complete boundary geometries, which adds roughly 10% time to those reads (#53)
CHANGED: When splitting node tags into columns, build only the tag-columns that actually occur in the data instead of materialising every candidate column and dropping the all-empty ones, cutting
get_pois(and other node-feature) assembly ~37% and the multi-layer wall-clock ~14% on a 138 MB extract, with no change to the returned data (#53)CHANGED: Apply the same build-only-occurring-columns approach when splitting way and relation tags into columns (
get_network,get_buildings,get_landuse,get_naturaland custom-criteria reads), so candidate tag-columns absent from the data are no longer materialised and dropped, cutting the way/relation tag-assembly step ~12-16% on a 138 MB extract (the full feature call improves less, as geometry construction dominates these features), with no change to the returned data (#53)NEW: Add
OSM.write_pbf(data, output_path)to write the OSM data back to a valid, re-readable PBF after modifying attributes/tags in pandas (e.g. fillmaxspeed, add atravel_timetag). The whole dataset that was read is written; each row ofdata(a GeoDataFrame or list of them) updates the tags of the matching element byosm_type``+``id, and rows whoseidis not in the source are added as new elements synthesized from their geometry (Point->node, LineString->way, hole-less Polygon->closed way; negative ids). Topology and coordinates come from the parsed data, so the output round-trips coordinates exactly and is read by pyrosm, osmium, GDAL and r5py/R5 (a modified pedestrian/car network exported from pyrosm builds a routable R5 network). v1 applies edits and additions, not deletions (#286, #285)NEW: Add
OSM.to_pbf(output_path=None, keep_relations=True, workers=1, compact=False, repack=False)to crop the source.osm.pbfby the object’sbounding_boxand write a valid, re-readable PBF to disk (a temp file when no path is given). Cropping is memory-efficient (streams the file blob-by-blob, holds only id sets) and “complete-ways” (a way is kept when >=1 node is inside the box and keeps its full node list); coordinates round-trip exactly.workers>1parallelizes the per-block work over a single process pool and produces byte-identical output (with a sequential fallback for files too small to amortize pool startup).compact=Trueprunes each output block’s string table to only the strings its kept elements reference, producing a smaller file (~18% smaller on a Helsinki-region crop) at the cost of some extra per-block work.repack=Truegoes further and re-chunks the kept elements into canonical, densely packed blocks (asosmium/Osmosis produce) for the smallest output, at the cost of speed (the re-pack write is sequential, thoughworkersstill parallelizes the selection); it supersedescompact. The defaultscompact=False/repack=Falsekeep the faster current behaviour, and the written OSM data – coordinates, tags and element metadata – is identical for every combination (#284, #6)CHANGED: Import
pyrosmlazily soimport pyrosmno longer eagerly loads geopandas/shapely (~2 s);OSM,get_dataandget_pathare still importable as before and load on first use (#284, #6)NEW: Add
pyrosm.get_data_by_bbox(bbox, crop=True, download=True, update=False, directory=None, output_path=None)to download – and by default crop – the OSM data covering a bounding box. It finds the smallest Geofabrik extract whose extent fully covers the box, downloads it, and (withcrop=True, the default) crops it to the box, returning the cropped file namedbbox_<minx>_<miny>_<maxx>_<maxy>.osm.pbf;crop=Falsereturns the full extract, anddownload=Falsereturns the covering extract’s PBF URL without downloading. The bounding box may be a[minx, miny, maxx, maxy]list/tuple/array in lon/lat, a Shapely geometry, or a GeoDataFrame/GeoSeries. It is backed by a vendored snapshot of Geofabrik’sindex-v1.json, so the extract lookup works offline (refresh it withscripts/update_geofabrik_index.py);update=Truerefreshes the index and re-downloads (#165, #197)NEW: Add
pyrosm.geocode(query)andpyrosm.get_data_by_geocoding(query, crop=True, download=True, update=False, directory=None, output_path=None)to fetch data by place name.geocodereturns a Shapely polygon for a place (e.g."Brighton and Hove, UK") via OpenStreetMap’s Nominatim service – its boundary polygon when available, otherwise its bounding-box rectangle.get_data_by_geocodinggeocodes the place, then downloads – and by default crops – the smallest Geofabrik extract that covers it, returning the cropped file named after the place (e.g.brighton-and-hove-uk.osm.pbf);crop=Falsereturns the full extract anddownload=Falsereturns the covering extract’s PBF URL. No new dependencies (stdliburllib/jsonwith the bundledcertifi, andshapely) (#165)
Thanks for all the contributors who helped to improve the library either via PRs or reporting bugs:
v0.8.0 (Jun 10, 2026)#
This is a major release that changes the PBF parsing backend.
CHANGED: Replace the Pyrobuf PBF backend with Google’s Protobuf (its fast C
upbbackend) for parsing the protocol-buffer messages. Pyrobuf is unmaintained and its source build fails with modernsetuptools(breakingpip install pyrosm); Google’s Protobuf is actively maintained and ships wheels and conda-forge packages for Python 3.10–3.14. Parsing speed is unchanged — see the backend benchmark. v0.8.0 is the first release built on Google’s Protobuf; v0.7.0 was the last to use Pyrobuf. (#276)NEW: Automate PyPI releases — a GitHub Actions
releaseworkflow builds binary wheels (cibuildwheel; Linux/macOS/Windows × CPython 3.10–3.14) and an sdist, then publishes them to PyPI via Trusted Publishing and creates a GitHub release when avX.Y.Ztag is pushed (#288, #287)NEW: Expose relation members under the
memberskey of the JSONtagscolumn (each{member_id, member_type, member_role}), so relations carry their members in the returned GeoDataFrame (#281, #216)NEW: Raise a clear
InvalidOSMFileErrorwhen the input.pbfis not a valid OSM PBF file, instead of a cryptic zlib/protobuf error (#280, #160)NEW: Accept
pathlib.Path(and anyos.PathLike) filepaths in theOSMconstructor, not just strings (#279, #145)FIXED: Decode node coordinates at full float64 precision (exact OSM 7-decimal values, matching GDAL/osmium); they were truncated to float32, introducing a ~0.1 m error, false extra precision, and visible distortion of straight geometry edges (#283, #245, #225)
FIXED: Normalize polygon/multipolygon ring orientation to the OGC/GeoJSON right-hand rule (exterior counter-clockwise, holes clockwise), matching osmium and QGIS; previously rings inherited the OSM way node order and were inconsistently wound (#282, #230)
FIXED:
get_bounding_boxnow reads the header bounding box correctly; it returnedNonefor every file after the protobuf backend migration (#280, #160)FIXED: Download data over HTTPS using certifi’s CA bundle instead of the OS trust store, so fetching datasets no longer fails on Windows with
ssl.SSLError [ASN1: NOT_ENOUGH_DATA](a CPython bug triggered by a malformed entry in the Windows certificate store) (#294)Refresh the README badges (#278)
Thanks for all the contributors who helped to improve the library either via PRs or reporting bugs:
v0.7.0 (Jun 7, 2026)#
NEW: Add
pandarmgraph-export backend (the maintained, NumPy 2-compatible fork of pandana); deprecategraph_type="pandana"(#271)NEW: Make cycling networks directed and honour
oneway:bicycle(#255)NEW: Add
custom_filtertoget_networkso custom-filtered networks also return graph nodes (#264)NEW: Add
street_countnode attribute to the NetworkX export (compatible with OSMnxbasic_stats) (#265)NEW: Support combining
custom_filterTruewith explicit tag values (#251)Support Python 3.10–3.14 (drop 3.9) and fix OSH parsing under pandas 3.0 (#248)
Return complete (uncut) geometries for ways/edges that straddle a bounding-box edge (#268)
Keep bounding-box network
nodesconsistent with the keptedgesso graph export works without manual cleanup (#269)Fix non-dense PBF node parsing (
parse_nodes) (#275)Handle bounding boxes that select no nodes instead of raising
KeyError(#267)Fix
custom_filterwithhighwayturning closed-way polygons into lines (#266)Fix network exclude/keep filters leaking on multi-key filters (#263)
Fix duplicate “phantom” nodes in the NetworkX export (#259)
Correct relation ids and surface a colliding
idtag asid_tag(#234, #249)Stop
get_*methods from mutating the shared default-tag config (#252)Fix spurious pandas chained-assignment warnings from the Cython frame builders (#256)
Fix Geofabrik UK sub-region downloads (moved under
united-kingdom) (#258)Fix reading PBF produced by
osmconvert(#238)Fix documentation URL (#223)
Measure Cython (
.pyx) coverage and raise overall test coverage (#273)Document the
pandarmgraph backend and thepandanadeprecation, and reading OSM history files (.osh.pbf) (#257)Fix the Read the Docs build; run live download tests on a single CI runner; bump GitHub Actions to Node 24 (#250, #254, #260)
Thanks for all the contributors who helped to improve the library either via PRs or reporting bugs:
v0.6.2 (Oct 26, 2023)#
Fix installation issues and support only Python >= 3.9 (#221)
Fix GA actions and use micromamba to install environments (#221)
Use Shapely 2.0 instead of pygeos (#214)
Thanks for the following contributors:
v0.6.1 (Oct 11, 2021)#
v0.6.0 (Nov 18, 2020)#
NEW: Adds possibility to export street networks to igraph, networkx and pandana (#57, #58, #70) - Add functionality to parse/return the nodes of the network when requested (#52) - Calculate length of the edge for networks in meters (#56, #70) - Filter out weakly connected component by default when exporting to graph (#59) - Add (vectorized) functionality to create directed edges according oneway rules (#68)
Fix installation issue with pip on Windows (#61)
Fix numpy deprecation warning (#50)
Update the documentation to use new theme (#74)
Add possibility to test the tool using JupyterLab in browser (#75)
Fix issue when parsing POIs using rare tags as a custom filter (#47)
Fix issue when filtering with bounding box polygon (#54)
Add documentation about exporting the networks to graphs (#69)
Improve documentation overall
v0.5.3 (Sep 13, 2020)#
Changes:
Ensures that geometry construction works with new Pygeos release v0.8.0 (#46)
v0.5.1/2 (May 11, 2020)#
Fix multi-level filtering
Add support for using “exclude” also with nodes and relations
Fix data source for New York City
v0.5.0 (May 7, 2020)#
Adds a function to download PBF data from Geofabrik and BBBike easily from hundreds of locations across the world
Improved geometry parsing for relations
Parse boundary geometries as Polygons instead of LinearRings (following OSM definition)
Fix invalid geometries automatically (self-intersection and “bowties”)
Add better documentation about custom filters
Make parsing more robust for incorrectly tagged OSM entries.
Bug fixes
Update website to a new theme.
v0.4.3 (April 27, 2020)#
Fixes a bug related to filtering with custom filters (see details here.)
v0.4.2 (April 23, 2020)#
Add functionality to parse boundaries from PBF (+ integrate name search for finding e.g. specific administrative boundary)
Support using Shapely Polygon / MultiPolygon to filter the data spatially
add possibility to add “extra attributes” (i.e. OSM keys) that will be parsed as columns.
improve documentation
v0.4.1 (April 17, 2020)#
add documentation
create website: https://pyrosm.readthedocs.io
v0.4.0 (April 16, 2020)#
read PBF using custom queries (allows anything to be fetched)
read landuse from PBF
read natural from PBF
improve geometry parsing so that geometry type is read automatically according OSM rules
modularize code-base
improve test coverage
v0.3.1 (April 15, 2020)#
generalize code base
read Points of Interest (POI) from PBF
v0.2.0 (April 13, 2020)#
read buildings from PBF into GeoDataFrame
enable applying custom filter to filter data: e.g. with buildings you can filter specific types of buildings with {‘building’: [‘residential’, ‘retail’]}
handle Relations as well
handle cases where data is not available (warn user and return empty GeoDataFrame)
v0.1.8 (April 8, 2020)#
read street networks from PBF into GeoDataFrame (separately for driving, cycling, walking and all-combined)
filter data based on bounding box
v0.1.0 (April 7, 2020)#
first release on PyPi