OpenStreetMap Overpass API in Python#
by Michael T. Moen
Please see the following resources for more information on API usage for the OpenStreetMap Overpass API:
- Documentation 
- Terms 
- Data Reuse - OpenStreetMap Copyright and License: The OpenStreetMap API is licensed under the Open Data Commons Open Database License, which allows users to share, create, and adapt the database as long OpenStreetMap is attributed and the data is made available under the Open Database License. 
 
For more information on the usage limitations of this service, please see the Nominatim Usage Policy and the Overpass Commons documentation. Please ensure that if you use an alternative Nominatim or Overpass instance, you obey their usage policies.
This tutorial uses the OSMnx Python package to access the OpenStreetMap API. The examples used in this tutorial are inspired by those found in the official OSMnx Examples Gallery. Please see the following resources for more information on API usage for the OSMnx package:
- Documentation 
- Terms 
If you use OSMnx in your work, please cite the journal article:
Boeing, G. 2017. “OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks.” Computers, Environment and Urban Systems 65, 126-139.
These recipe examples were tested on May 6, 2025.
Setup#
The following external libraries need to be installed into your environment to run the code examples in this tutorial:
We import the libraries used in this tutorial below:
import osmnx as ox
import matplotlib.pyplot as plt
1. Retrieve and Download Feature and Boundary Data#
The OSMnx contains several options for retrieving GIS data associated with locations, including:
- geocode_to_gdf
- features_from_address
- features_from_bbox
- features_from_place
- features_from_point
- features_from_polygon
- features_from_xml
These methods return GeoDataFrames that can be used to work with GIS data in Python or exported and saved as other formats.
Using geocode_to_gdf to Download Boundary Data#
This example uses the geocode_to_gdf method to retrieve the boundary data for the given query. This method returns a GeoDataFrame with a row for each query given. Note that the geocode_to_gdf method can also take a list of queries as an argument. In this case, the returned GeoDataFrame has a row for each query. An example of this is given later in this tutorial.
For more information on how you can use the geocode_to_gdf method, please see the OSMnx User Reference.
place = 'Alabama, USA'
alabama_gdf = ox.geocode_to_gdf(place)
# Display GeoDataFrame
alabama_gdf
| geometry | bbox_west | bbox_south | bbox_east | bbox_north | place_id | osm_type | osm_id | lat | lon | class | type | place_rank | importance | addresstype | name | display_name | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | POLYGON ((-88.4731 31.8939, -88.47264 31.87512... | -88.473101 | 30.143376 | -84.888289 | 35.008112 | 317707064 | relation | 161950 | 33.258882 | -86.829534 | boundary | administrative | 8 | 0.762782 | state | Alabama | Alabama, United States | 
By default, GeoPandas saves data as an ESRI Shapefile. This can also be explicitly defined by setting the driver to ESRI Shapefile:
# Save the files to a folder called 'alabama-shapefile'
alabama_gdf.to_file('alabama-shapefile')
# This does the same thing
# alabama_gdf.to_file('alabama-shapefile', driver='ESRI Shapefile')
The data can also be saved as GeoJSON by setting the driver to GeoJSON:
alabama_gdf.to_file('alabama.json', driver='GeoJSON')
We can also save the data as a GeoPackage file by setting the driver to GPKG:
alabama_gdf.to_file('alabama.gpkg', driver='GPKG')
Using features_from_address to Get Building Data#
The features_from_address method takes three arguments:
- addressis a string containing the address of the location of interest. This example uses the address of The University of Alabama.
- tagsis a dictionary of map elements of interest in the given area. To see what elements can be queried, please see the OpenStreetMap Wiki. This example retrieves buildings of all types by using the- Truevalue for the- buildingkey.
- dist(optional) is the distance in meters from the address that is searched. By default, this value is set to 1000.
For more information on how you can use the features_from_address method, please see the OSMnx User Reference.
address = '425 Stadium Dr, Tuscaloosa, AL 35401, United States'
tags = {
    'building': True
}
gdf = ox.features_from_address(address, tags, dist=500)
# Display GeoPandas DataFrame
gdf.head()
| geometry | access | amenity | building | name | operator | operator:type | parking | addr:city | addr:housenumber | ... | layer | source | addr:country | name:etymology:wikidata | shelter_type | bridge | level | alt_name | type | sport | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element | id | |||||||||||||||||||||
| relation | 1639197 | POLYGON ((-87.55057 33.20647, -87.55061 33.206... | NaN | NaN | stadium | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | multipolygon | american_football | 
| 8507062 | POLYGON ((-87.55343 33.20767, -87.55342 33.207... | NaN | NaN | apartments | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | multipolygon | NaN | |
| 8507063 | POLYGON ((-87.55464 33.20698, -87.55504 33.206... | NaN | NaN | apartments | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | multipolygon | NaN | |
| 8507064 | POLYGON ((-87.55466 33.20739, -87.55505 33.207... | NaN | NaN | apartments | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | multipolygon | NaN | |
| 8507065 | POLYGON ((-87.55472 33.209, -87.55465 33.20899... | NaN | NaN | apartments | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | multipolygon | NaN | 
5 rows × 59 columns
Using features_from_bbox to Get Street Features#
The features_from_bbox method takes two arguments:
- bboxis tuple of floats describing the boundaries of an area of interest. This tuple must be structured as- (west, south, east, north). This example looks at the area around St. Louis, MO.
- tagsis a dictionary of map elements of interest in the given area. To see what elements can be queried, please see the OpenStreetMap Wiki. This example retrieves the five largest classifications of- highwayas defined by OpenStreetMap.
For more information on how you can use the features_from_bbox method, please see the OSMnx User Reference.
north = 39.3
south = 38.1
east = -89.6
west = -91.1
bbox = (west, south, east, north)
tags = {
    'highway': ['motorway', 'trunk', 'primary', 'secondary', 'tertiary']
}
gdf = ox.features_from_bbox(bbox, tags)
Now we can graph the features above, which will be explored further in the next section.
ax = gdf.plot(fc='gray', figsize=(10, 10))
ax.axis('off')
ax.set_title('Major Roads of the St. Louis Area')
plt.show()
 
2. Graphing with GeoPandas#
GeoPandas allows for data to be printed using the plot() function:
alabama_gdf.plot()
<Axes: >
 
By default, GeoPandas includes latitude and longitude as the axes of the figure. This can be turned off by specifying .axis('off'). Additionally, we can suppress the output of metadata, like <Axes: > in the example above, by adding a semicolon ; to the end of the line.
Additionally, we can modify the color of the figure with plot()’s fill color fc parameter. The figsize parameter can also be set with a tuple containing (width, height). Note that aspect ratio of image is maintained, and only the more restrictive number between width and height is used for the graph.
alabama_gdf.plot(fc='gray', figsize=(2.5, 5)).axis('off');
 
Mapping the Buildings of The University of Alabama#
OpenStreetMap contains building footprint data, allowing for individual building to be plotted.
In this example, the features_from_place method is used to retrieve the data for all buildings at The University of Alabama.
place = 'University of Alabama, AL USA'
tags = {
    'building': True
}
buildings = ox.features_from_place(place, tags)
buildings.plot(fc='gray', figsize=(10, 10)).axis('off');
 
We can use the metadata of the buildings to color them according to their purpose. The unique tags that we must consider are given below:
buildings['building'].unique()
array(['yes', 'stadium', 'university', 'dormitory', 'residential',
       'yes;dormitory', 'church', 'sports_centre', 'hotel', 'parking',
       'school', 'construction', 'commercial', 'service', 'college',
       'apartments', 'house', 'shed', 'roof', 'carport'], dtype=object)
Now, we can construct a dictionary assigning a color to each building type:
education_color = 'royalblue'
sports_color = 'tomato'
housing_color = 'forestgreen'
religious_color = 'darkmagenta'
services_color = 'sienna'
other_color = 'dimgray'
undefined_color = 'lightgray'
colors = {
    'university': education_color,
    'college': education_color,
    'school': education_color,
    'dormitory': housing_color,
    'hotel': housing_color,
    'residential': housing_color,
    'apartments': housing_color,
    'house': housing_color,
    'yes;dormitory': housing_color,
    'stadium': sports_color,
    'sports_centre': sports_color,
    'grandstand': sports_color,
    'church': religious_color,
    'service': services_color,
    'parking': services_color,
    'carport': services_color,
    'commercial': other_color,
    'roof': other_color,
    'shed': other_color,
    'construction': other_color,
    'yes': undefined_color,
}
Now, we can plot the buildings using the colors determined above.
import matplotlib.patches as mpatches
fig, ax = plt.subplots(figsize=(10, 10))
# Plot each building type with the appropriate color
for type in buildings['building'].unique():
    color = colors[type]
    buildings[buildings['building'] == type].plot(ax=ax, color=color)
ax.axis('off')
# Create legend
legend_handles = [
    mpatches.Patch(color=education_color, label='Education'),
    mpatches.Patch(color=housing_color, label='Housing'),
    mpatches.Patch(color=sports_color, label='Sports'),
    mpatches.Patch(color=religious_color, label='Religious'),
    mpatches.Patch(color=services_color, label='Services'),
    mpatches.Patch(color=other_color, label='Other'),
    mpatches.Patch(color=undefined_color, label='No Data'),
]
ax.legend(handles=legend_handles)
plt.show();
 
Applying a Projection on a Map#
As mentioned above, the geocode_to_gdf function can take a list of places to look up as an argument. Doing so returns a single GeoDataFrame that contains the data for all of the locations. Note that each state queried is represented by a row in the GeoDataFrame.
The example below looks at the states designated as part of the South by the U.S. Census Bureau.
places = [
    'Alabama, USA', 'Mississippi, USA', 'Louisiana, USA', 'Arkansas, USA', 'Tennessee, USA',
    'Florida, USA', 'Georgia, USA', 'South Carolina, USA', 'North Carolina, USA',
    'Kentucky, USA', 'Texas, USA', 'Oklahoma, USA', 'Virginia, USA', 'West Virginia, USA',
    'Maryland, USA', 'District of Columbia, USA', 'Delaware, USA'
]
southern_states = ox.geocode_to_gdf(places)
# Display the top rows of the GeoPandas DataFrame
southern_states.head()
| geometry | bbox_west | bbox_south | bbox_east | bbox_north | place_id | osm_type | osm_id | lat | lon | class | type | place_rank | importance | addresstype | name | display_name | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | POLYGON ((-88.4731 31.8939, -88.47264 31.87512... | -88.473101 | 30.143376 | -84.888289 | 35.008112 | 317707064 | relation | 161950 | 33.258882 | -86.829534 | boundary | administrative | 8 | 0.762782 | state | Alabama | Alabama, United States | 
| 1 | POLYGON ((-91.65501 31.25178, -91.65491 31.250... | -91.655009 | 30.143677 | -88.097795 | 34.996017 | 307325698 | relation | 161943 | 32.971528 | -89.734850 | boundary | administrative | 8 | 0.742110 | state | Mississippi | Mississippi, United States | 
| 2 | POLYGON ((-94.04319 32.62108, -94.04309 32.592... | -94.043187 | 28.854289 | -88.758331 | 33.019594 | 282786027 | relation | 224922 | 30.870388 | -92.007126 | boundary | administrative | 8 | 0.756099 | state | Louisiana | Louisiana, United States | 
| 3 | POLYGON ((-94.61788 36.4995, -94.61772 36.4987... | -94.617875 | 33.004246 | -89.644395 | 36.499600 | 309107808 | relation | 161646 | 35.204888 | -92.447911 | boundary | administrative | 8 | 0.746014 | state | Arkansas | Arkansas, United States | 
| 4 | POLYGON ((-90.3103 35.0043, -90.31008 35.00105... | -90.310298 | 34.982938 | -81.647219 | 36.678118 | 313673419 | relation | 161838 | 35.773008 | -86.282008 | boundary | administrative | 8 | 0.764411 | state | Tennessee | Tennessee, United States | 
southern_states.plot(fc='gray');
 
The project_gdf method allows us to project a GeoDataFrame to a coordinate reference system (CRS). By default, OSMnx uses UTM projection centered on your data, so please note that distance measurements may be inaccurate for areas over 620 mi (1000 km) wide. However, this can be modified by setting the to_crs tag with a string or a CRS object from pyproj.
If you would like to learn more about UTM zones, please see this article from GISGeography.com. For more information on how you can use the project_gdf method, please see the OSMnx User Reference.
projected_southern_states = ox.projection.project_gdf(southern_states)
projected_southern_states.plot(fc='gray');
 
Since the projection above uses a UTM zone, the origin of the graph is where the central meridian intersects the equator. Additionally, the values on the axes are in millions of meters (or thousands of kilometers), so the distance from central Texas to central Louisiana is approximately 500 km.
3. Street Networks#
The street data for a location can be retrieved with the following methods:
- graph_from_address
- graph_from_bbox
- graph_from_place
- graph_from_point
- graph_from_polygon
- graph_from_xml
The examples below all use the graph_from_place method.
Plotting Street Networks#
The graph_from_place method can be used to generate a variety of street networks by setting the network_type:
- drive
- bike
- walk
- drive_service
- all
- all_private
For more information on how you can use the graph_from_place method, please see the OSMnx User Reference.
drive_graph = ox.graph_from_place('University of Alabama, Tuscaloosa', network_type='drive')
ox.plot_graph(drive_graph, figsize=(10, 10));
 
bike_graph = ox.graph_from_place('University of Alabama, Tuscaloosa', network_type='bike')
ox.plot_graph(bike_graph, figsize=(10, 10));
 
walk_graph = ox.graph_from_place('University of Alabama, Tuscaloosa', network_type='walk')
ox.plot_graph(walk_graph, figsize=(10, 10));
 
Finding the Shortest Route Between Two Nodes#
Now that we have created street networks, we can use the shortest_path method to find the shortest path between two nodes.
Note that in order to use this method, the scikit-learn Python library must be installed.
origin = (33.211078599884964, -87.54927369336447)       # Angelo Bruno Business Library
destination = (33.21344833663583, -87.54291076951604)   # Rodgers Library
# Find the nodes nearest to each set of coordinates
origin_node = ox.distance.nearest_nodes(walk_graph, origin[1], origin[0])
destination_node = ox.distance.nearest_nodes(walk_graph, destination[1], destination[0])
# Find the shortest path and graph it
route = ox.shortest_path(walk_graph, origin_node, destination_node)
fig, ax = ox.plot_graph_route(walk_graph, route, node_size=0, figsize=(10, 10))
 
Using the route_to_gdf method, we can also find the distance of this route.
length = int(sum(ox.routing.route_to_gdf(walk_graph, route, weight="length")["length"]))
print(f'The route is {length} meters.')
The route is 850 meters.
