Downloading OSM data#

Pyrosm is designed to work with Protocolbuffer Binary Format (PBF) -files. This is an efficient format that OpenStreetMap contributors use to distribute the OSM data. A few free providers distribute OSM data as PBF:

  • Geofabrik provides data for countries and other regions,

  • BBBike provides data for various cities across the world, and

  • Movisda provides data for administrative areas and 1°/10° grid tiles (used by get_data_by_area()).

Pyrosm can download any of these datasets for you, and it can also pick the right extract for a bounding box or a place name, so you rarely need to visit the providers’ websites by hand.

How to?

Download data by name#

Pyrosm provides a function get_data() that can download any PBF dataset available at Geofabrik or BBBike to your local machine without the need to go to the website and do this manually. Currently, PBF data can be downloaded from hundreds of regions in the world. To download data from a specific city such as "Helsinki", you can simply call:

from pyrosm import get_data
# Download data for the city of Helsinki
fp = get_data("Helsinki")
print(fp)
/tmp/pyrosm/Helsinki.osm.pbf

By default, the get_data() function downloads the PBF file into a local TEMP directory and returns a filepath to the location where the data was downloaded. While it downloads, a progress bar shows how much has arrived, drawn as a widget in Jupyter when ipywidgets is installed. progress=False turns the bar off for that download, and pyrosm.enable_logging("WARNING", progress=False) for every download, read and crop, without turning on pyrosm’s log messages; a function passed as progress is called as progress(written, total) after each chunk instead, with the bytes written so far and the file size. Messages such as the name, size and download time of each file go to Python’s logging module under the pyrosm logger; to see them, run pyrosm.enable_logging() first.

It is also possible to define your own directory where the data will be downloaded if you don’t want to store it in TEMP.

# Download the data into specified directory
fp = get_data("Helsinki", directory="/Users/Shared/MyFolder")
print("Data was downloaded to:", fp)
Data was downloaded to: /Users/Shared/MyFolder/Helsinki.osm.pbf

If you have downloaded the data previously into your computer, pyrosm will by default use that same data file. However, if you want to update the data, it is possible to specify update=True which will remove the old PBF file and download a fresh version from Geofabrik or BBBike.

# Refresh the data
# ----------------

# The first call won't download the data because it was already downloaded earlier
fp = get_data("Helsinki")
print(fp)

# This one will update the data and download the data
print("\nDownload will happen:")
fp = get_data("Helsinki", update=True)
print(fp)
/tmp/pyrosm/Helsinki.osm.pbf

Download will happen:
/tmp/pyrosm/Helsinki.osm.pbf

Available datasets#

You can investigate the available datasets in Geofabrik and BBBike easily by calling:

from pyrosm.data import sources

# Print available source categories
sources.available.keys()
dict_keys(['africa', 'antarctica', 'asia', 'australia_oceania', 'central_america', 'europe', 'north_america', 'south_america', 'cities', 'subregions'])

The available datasets have been divided into categories which makes it easier to navigate through the available PBF files.

The datasets are divided under continents, cities and subregions (countries with data divided into smaller subregions).

  • As an example, you can see all available data sources in Africa by calling:

# Prints a list of countries in Africa that can be downloaded
print(sources.africa.available)
['algeria', 'angola', 'benin', 'botswana', 'burkina_faso', 'burundi', 'cameroon', 'canary_islands', 'cape_verde', 'central_african_republic', 'chad', 'comores', 'congo_brazzaville', 'congo_democratic_republic', 'djibouti', 'egypt', 'equatorial_guinea', 'eritrea', 'ethiopia', 'gabon', 'ghana', 'guinea', 'guinea_bissau', 'ivory_coast', 'kenya', 'lesotho', 'liberia', 'libya', 'madagascar', 'malawi', 'mali', 'mauritania', 'mauritius', 'morocco', 'mozambique', 'namibia', 'niger', 'nigeria', 'rwanda', 'saint_helena_ascension_and_tristan_da_cunha', 'sao_tome_and_principe', 'senegal_and_gambia', 'seychelles', 'sierra_leone', 'somalia', 'south_africa', 'south_africa_and_lesotho', 'south_sudan', 'sudan', 'swaziland', 'tanzania', 'togo', 'tunisia', 'uganda', 'zambia', 'zimbabwe']
  • If you want to see all available cities that can be downloaded, call:

# Prints a list of all cities that can be downloaded
print(sources.cities.available)
['Aachen', 'Aarhus', 'Adelaide', 'Albuquerque', 'Alexandria', 'Amsterdam', 'Antwerpen', 'Arnhem', 'Auckland', 'Augsburg', 'Austin', 'Baghdad', 'Baku', 'Balaton', 'Bamberg', 'Bangkok', 'Barcelona', 'Basel', 'Beijing', 'Beirut', 'Berkeley', 'Berlin', 'Bern', 'Bielefeld', 'Birmingham', 'Bochum', 'Bogota', 'Bombay', 'Bonn', 'Bordeaux', 'Boulder', 'BrandenburgHavel', 'Braunschweig', 'Bremen', 'Bremerhaven', 'Brisbane', 'Bristol', 'Brno', 'Bruegge', 'Bruessel', 'Budapest', 'BuenosAires', 'Cairo', 'Calgary', 'Cambridge', 'CambridgeMa', 'Canberra', 'CapeTown', 'Chemnitz', 'Chicago', 'ClermontFerrand', 'Colmar', 'Copenhagen', 'Cork', 'Corsica', 'Corvallis', 'Cottbus', 'Cracow', 'CraterLake', 'Curitiba', 'Cusco', 'Dallas', 'Damaskus', 'Darmstadt', 'Davis', 'DenHaag', 'Denver', 'Dessau', 'Dortmund', 'Dresden', 'Dublin', 'Duesseldorf', 'Duisburg', 'Edinburgh', 'Eindhoven', 'Emden', 'Erfurt', 'Erlangen', 'Eugene', 'Flensburg', 'FortCollins', 'Frankfurt', 'FrankfurtOder', 'Freiburg', 'Gdansk', 'Genf', 'Gent', 'Gera', 'Glasgow', 'Gliwice', 'Goerlitz', 'Goeteborg', 'Goettingen', 'Graz', 'Groningen', 'Halifax', 'Halle', 'Hamburg', 'Hamm', 'Hannover', 'Heilbronn', 'Helsinki', 'Hertogenbosch', 'Huntsville', 'Innsbruck', 'Istanbul', 'Jena', 'Jerusalem', 'Johannesburg', 'Kaiserslautern', 'Karlsruhe', 'Kassel', 'Katowice', 'Kaunas', 'Kiel', 'Kiew', 'Koblenz', 'Koeln', 'Konstanz', 'LaPaz', 'LaPlata', 'LakeGarda', 'Lausanne', 'Leeds', 'Leipzig', 'Lima', 'Linz', 'Lisbon', 'Liverpool', 'Ljubljana', 'Lodz', 'London', 'LosAngeles', 'Luebeck', 'Luxemburg', 'Lyon', 'Maastricht', 'Madison', 'Madrid', 'Magdeburg', 'Mainz', 'Malmoe', 'Manchester', 'Mannheim', 'Marseille', 'Melbourne', 'Memphis', 'MexicoCity', 'Miami', 'Minsk', 'Moenchengladbach', 'Montevideo', 'Montpellier', 'Montreal', 'Moscow', 'Muenchen', 'Muenster', 'NewDelhi', 'NewOrleans', 'NewYorkCity', 'Nuernberg', 'Oldenburg', 'Oranienburg', 'Orlando', 'Oslo', 'Osnabrueck', 'Ostrava', 'Ottawa', 'Paderborn', 'Palma', 'PaloAlto', 'Paris', 'Perth', 'Philadelphia', 'PhnomPenh', 'Portland', 'PortlandME', 'Porto', 'PortoAlegre', 'Potsdam', 'Poznan', 'Prag', 'Providence', 'Regensburg', 'Riga', 'RiodeJaneiro', 'Rostock', 'Rotterdam', 'Ruegen', 'Saarbruecken', 'Sacramento', 'Saigon', 'Salzburg', 'SanFrancisco', 'SanJose', 'SanktPetersburg', 'SantaBarbara', 'SantaCruz', 'Santiago', 'Sarajewo', 'Schwerin', 'Seattle', 'Seoul', 'Sheffield', 'Singapore', 'Sofia', 'Stockholm', 'Stockton', 'Strassburg', 'Stuttgart', 'Sucre', 'Sydney', 'Szczecin', 'Tallinn', 'Tehran', 'Tilburg', 'Tokyo', 'Toronto', 'Toulouse', 'Trondheim', 'Tucson', 'Turin', 'UlanBator', 'Ulm', 'Usedom', 'Utrecht', 'Vancouver', 'Victoria', 'WarenMueritz', 'Warsaw', 'WashingtonDC', 'Waterloo', 'Wien', 'Wroclaw', 'Wuerzburg', 'Wuppertal', 'Zagreb', 'Zuerich']
  • Some of the countries have smaller sub-regions that can be downloaded separately (such as states in USA):

# Check all countries having sub-regions
print("All countries with sub-regions:", sources.subregions.available.keys())

# Check sub-regions in Brazil
print("Sub-regions in Brazil:", sources.subregions.brazil.available)
All countries with sub-regions: dict_keys(['australia', 'brazil', 'canada', 'china', 'czech_republic', 'france', 'germany', 'great_britain', 'india', 'indonesia', 'italy', 'japan', 'netherlands', 'norway', 'poland', 'russia', 'spain', 'united_kingdom', 'usa'])
Sub-regions in Brazil: ['centro_oeste', 'nordeste', 'norte', 'sudeste', 'sul']

In a similar manner, you can easily investigate all other regions that are available for download.

When you want to download data for any of these areas, you just need to pass the name of the area into the get_data() -function:

# Download data for Aachen
fp = get_data("Aachen")
print(fp)
/tmp/pyrosm/Aachen.osm.pbf

Note

Some of the available names e.g. in cities are written in CamelCase format and some of the countries are written with underscore (e.g. canary_islands). Pyrosm tries to automatically identify different styles of writing the place name. For example, writing "Rio de Janeiro" works fine even though the name in cities.available list is written as "RiodeJaneiro". A name that several regions share, such as the country and the US state Georgia, takes its region in front: get_data("europe/georgia"), get_data("usa/georgia"):

# Passing names in slightly different style does not matter
fp = get_data("Rio de Janeiro")
print(fp)
/tmp/pyrosm/RiodeJaneiro.osm.pbf

Find the extract that covers a bounding box#

If your area of interest does not match a named extract, get_data_by_bbox() finds the smallest extract that covers a bounding box among those of Geofabrik, BBBike and Movisda, as get_data_by_area() below does, downloads it, and – by default – crops it to the box. The bounding box can be a [minx, miny, maxx, maxy] list (lon/lat), a Shapely geometry, or a GeoDataFrame/GeoSeries.

Pass download=False to just look up the covering extract’s URL without downloading anything (handy to check what you would get before pulling a possibly large file):

from pyrosm import get_data_by_bbox

# Look up the covering extract for a bounding box (Brighton & Hove); no download
get_data_by_bbox([-0.245, 50.798, -0.016, 50.892], download=False)
'https://osm.download.movisda.io/grid/N50E001-latest.osm.pbf'

By default (crop=True, download=True) it downloads the covering extract and crops it to the box, returning a file named after the bounding box:

# Download the covering extract and crop it to the box
fp = get_data_by_bbox([-0.245, 50.798, -0.016, 50.892])
print("Cropped filename:", fp)

# crop=False keeps the full extract instead
fp = get_data_by_bbox([-0.245, 50.798, -0.016, 50.892], crop=False)
print("No cropping:", fp)
Cropped filename: /tmp/pyrosm/bbox_-0.245_50.798_-0.016_50.892.osm.pbf
No cropping: /tmp/pyrosm/movisda_N50E001-latest.osm.pbf

Download the smallest extract that contains an area#

For an area on a national border, the smallest Geofabrik extract that covers it can be a whole continent. get_data_by_area(), on which get_data_by_bbox() and get_data_by_geocoding() are built, also considers BBBike’s city extracts and Movisda’s administrative areas and 1°/10° grid tiles, and downloads the single extract with the smallest file that contains the whole area. For Baarle-Hertog, on the border between Belgium and the Netherlands, that is Geofabrik’s Noord-Brabant extract of about 200 MB instead of the 35 GB Europe extract. The area can be a polygon, a GeoDataFrame or a bounding box, and by default the extract is cropped to the area’s bounding box:

from pyrosm import OSM, get_data_by_area

# A few blocks in central Monaco, as [minx, miny, maxx, maxy] in lon/lat
extract = get_data_by_area([7.420, 43.733, 7.424, 43.736])
print(extract.provider, extract.extract, f"{extract.bytes / 1e6:.1f} MB")
Movisda MC 0.5 MB

The result records where the file came from (provider, extract, url, bytes) and works wherever a file path does. By default one extract is downloaded, and if a download fails the next smallest extract is tried. With strategy="smallest_total", several extracts are downloaded and merged into one file when together they are smaller than that extract. To combine extracts yourself, see Merge several PBF files.

To see the candidates without downloading anything, for example to log the choice or to use your own downloader, call find_extracts(). It lists every extract that overlaps the area, those that contain it first, smallest download first; with the default strategy="single", get_data_by_area() downloads the first row of find_extracts(area, contains_only=True):

from pyrosm import find_extracts

candidates = find_extracts([7.420, 43.733, 7.424, 43.736])
candidates[["provider", "id", "bytes", "contains", "url"]].head()
provider id bytes contains url
0 Movisda MC 460658 True https://osm.download.movisda.io/admin/MC-lates...
1 Geofabrik monaco 694443 True https://download.geofabrik.de/europe/monaco-la...
2 Movisda N43E007 42967883 True https://osm.download.movisda.io/grid/N43W007-l...
3 Geofabrik provence-alpes-cote-d-azur 390979219 True https://download.geofabrik.de/europe/france/pr...
4 Geofabrik alps 2332394570 True https://download.geofabrik.de/europe/alps-late...
# The result can be passed to OSM() like a file path
OSM(extract).get_buildings().shape
(62, 24)

Cover only the places that matter#

The extract must contain the whole area by default. When only some places matter, for example the transit stops that routing needs, pass them as must_cover, so sea or unserved edges of the area do not force a larger download. Stops are covered exactly, so an extract may end a few metres from a stop. To also get the streets around each stop, buffer the stops in a local metric coordinate system; for the streets between the stops, pass a hull of them. find_extracts() takes the same must_cover, so the choice can be checked before downloading. Here the area is Helsinki with the sea south of it, and the places that matter are the bus and tram stops in the centre, from the sample data that comes with pyrosm:

from pyrosm import OSM, find_extracts, get_data, get_data_by_area

# Helsinki and the sea south of it, as [minx, miny, maxx, maxy]
area = [24.6, 59.9, 25.2, 60.25]

# Bus and tram stops in the centre of Helsinki
stops = OSM(get_data("helsinki_pbf")).get_pois(
    custom_filter={"highway": ["bus_stop"], "railway": ["tram_stop"]}
)
stops = stops[stops.geom_type == "Point"]

# Buffer the stops by 300 m in a metric CRS
stops_m = stops.to_crs(stops.estimate_utm_crs())
around_stops = stops_m.buffer(300)

# Or, to include the streets between the stops as well:
# around_stops = stops_m.dissolve().concave_hull(ratio=0.3).buffer(300)

# The smallest extract for the whole area, and for the stops only
columns = ["provider", "id", "bytes"]
print(find_extracts(area, contains_only=True)[columns].head(1))
print(find_extracts(area, contains_only=True, must_cover=around_stops)[columns].head(1))
    provider       id      bytes
0  Geofabrik  finland  773558587
  provider        id     bytes
0   BBBike  Helsinki  65082181
extract = get_data_by_area(area, must_cover=around_stops)
print(extract.provider, extract.extract, f"{extract.bytes / 1e6:.0f} MB")
BBBike Helsinki 65 MB

Crop to the area, use several cores, and record what was downloaded#

crop=True crops the extract to the area’s bounding box; crop="polygon" crops it to the area itself, keeping every way that has a node inside it whole. workers=4 crops (and merges) on four threads. The result records the SHA-256 of the file (extract.sha256) and the time of its OSM data (extract.snapshot), and for merged extracts each downloaded file in extract.sources. Here the area is the twin town of Valga (Estonia) and Valka (Latvia). strategy="smallest_total" merges at most one Movisda extract, so every set of extracts it could merge here is larger than the Movisda grid tile that covers the whole town, and it downloads that one tile:

from pyrosm import get_data_by_area

# Valga (Estonia) and Valka (Latvia), as [minx, miny, maxx, maxy]
area = [26.02, 57.765, 26.06, 57.785]

extract = get_data_by_area(
    area, crop="polygon", workers=4, strategy="smallest_total"
)
print(extract.sha256, extract.snapshot)

# One row per downloaded extract
for source in extract.sources:
    print(source.provider, source.extract, source.sha256, source.snapshot)
16289daab983f1455e575a32a862b98eddfa811f54ba00cad43fe367579133ab 2026-10-25 00:00:00+00:00
Movisda N57E026 1560287395fcaeb20a86f0050e7e4c4986ef240b97c3389c182e62097b0d2016 2026-10-25 00:00:00+00:00

Download a large region yourself#

pyrosm continues a download where it stopped when the connection drops, but a download that is interrupted (the program stops) starts over on the next call. For regions of several gigabytes, get the URL from find_extracts(), download it with a tool that can continue a partial file, such as curl -L -C - or wget -c in a terminal, and crop the file with merge_pbf(), which also works on a single file. The steps are the same for any size; here they run on Monaco’s small extract, with curl called from Python:

import subprocess
from pyrosm import OSM, find_extracts, merge_pbf

area = [7.420, 43.733, 7.424, 43.736]

# The URL of the smallest Geofabrik extract that contains the area
candidates = find_extracts(area, contains_only=True)
url = candidates.loc[candidates["provider"] == "Geofabrik", "url"].iloc[0]
print(url)

# In a terminal: curl -L -C - -o monaco.osm.pbf <url>  (or: wget -c <url>)
subprocess.run(["curl", "-sSL", "-C", "-", "-o", "monaco.osm.pbf", url], check=True)

# Crop the file to the area; pass polygon=... to crop to a polygon instead
fp = merge_pbf(["monaco.osm.pbf"], "area.osm.pbf", bounding_box=area)
print(f"{len(OSM(fp).get_buildings())} buildings in the area")
https://download.geofabrik.de/europe/monaco-latest.osm.pbf
62 buildings in the area

Download data for a place name#

geocode() turns a place name into a Shapely polygon (via OpenStreetMap’s Nominatim service):

from pyrosm import geocode

area = geocode("Tartu, Estonia")
area.geom_type, [round(b, 3) for b in area.bounds]
('Polygon', [26.667, 58.339, 26.798, 58.411])

get_data_by_geocoding() chains geocoding with the download and crop above – it geocodes the place, downloads the smallest extract that contains it, and (by default) crops it to the place, returning a file named after the place:

from pyrosm import get_data_by_geocoding

# Download the covering extract and crop it to the place
fp = get_data_by_geocoding("Tartu, Estonia")
print("Cropped output:", fp)

# crop=False keeps the full extract; download=False just returns the extract URL
fp = get_data_by_geocoding("Tartu, Estonia", crop=False)
print("No cropping:", fp)

url = get_data_by_geocoding("Tartu, Estonia", download=False)
print("No download, return URL instead:", url)
Cropped output: /tmp/pyrosm/area_tartu-estonia.osm.pbf
No cropping: /tmp/pyrosm/movisda_EE-79-latest.osm.pbf
No download, return URL instead: https://osm.download.movisda.io/admin/EE-79-latest.osm.pbf