OpenStreetMap and the OSMnx add-on for viewing streets as a network can be used to find the steepest streets in a neighborhood. Loosely based on their tutorial, we find the steepest streets in my home San Francisco Bay Area suburb of Millbrae, make a map of street grades that turned out to be useful for walking the area, and show views looking up the streets via the Google Street View API. The same code can be applied to any named area present in the well-known Nominatim geocoding tool.
Why the steepest streets?
The main motivations for finding the steepest streets in Millbrae are
- Curiosity: I live there, and there are a lot of hills to walk or cycle on. Which is steepest?
- Use OpenStreetMap, OSMnx, and Street View
- The resulting code is applicable to any named neighborhood
OpenStreetMap + OSMnx
OpenStreetMap is the well-known and widely used open source project to provide street maps of the world. Here is provides a suitable base map for us, and is integrated with the second tool we need, OSMnx (docs, citation).
OSMnx allows you to work with the streets, or more accurately street segments between intersections, to be worked with as a NetworkX MultiDiGraph of nodes and connections, each of which can have properties.
When the locations of nodes are combined with elevation data, the grade of each street segment can be obtained using OSMnx’s function for the grade along the line segments corresponding to the street segment between 2 nodes. This gives us grades for every street segment in the network, and segment with the largest value is the steepest street.
OSMnx has an extensive set of tutorials, one of which includes finding steepest streets. The code in this project is loosely based on it, but is mostly original. Some of the lines are:
G = ox.graph_from_place(place_query, network_type="drive")
G = ox.elevation.add_node_elevations_google(G, api_key=api_key)
G = ox.elevation.add_edge_grades(G)
grades_gdf = gpd.GeoDataFrame([d for _, _, d in ox.convert.to_undirected(G).edges(data=True)])
grades_gdf_sorted = grades_gdf.sort_values(by='grade_abs', ascending=False)I found some detail improvements in OSM that could be submitted for Millbrae, such as speed limits information and presence of sidewalks.
Finding the steepest
OSMnx allows a map of the street network with the street segments colored by grade to be produced straightforwardly:

Interestingly, when first running this project, I was considering doing more walking around Millbrae after dropping our 1-year-old at daycare, and seeing the areas nearby to the northwest of the neighborhood in the above plot with graded but not-too-steep streets encouraged me to do so.
According to this analysis, the top 10 steepest streets in Millbrae are:

Now, though, comes a reminder that your analysis is only as good as your data. The steepest street is not, in fact, Mullins Court. The street segment in question happens to be one of very few in the neighborhood that at the time of writing are mislabeled on Google Maps. Having now walked there, I can confirm that, while it is definitely steep, it is part of Aura Vista. This makes sense, as Mullins Court is the no-through street to the west of the intersection. The rest of the top 10 are correct as far as I know.
We can see the location of the Aura Vista segment as well, either on the street network:

or with an OSM basemap (with the Mullins Court again mislabeled at the time of writing):

See it on Street View
To view the steepest street on Google Street View, we need two steps
- Access the Street View API at the location of the street
- Rotate the view direction and angle to be looking up or down the street in question
After setting up the required API key, accessing the Street View location is achieved by constructing the correct URL:
URL = (f'https://maps.googleapis.com/maps/api/streetview?size=600x300&location={lat_origin},{lon_origin}'
f'&heading={heading:.0f}&pitch={pitch}&key={api_key}')which for Millbrae looks like this (API key not shown)
'https://maps.googleapis.com/maps/api/streetview?size=600x300&location=37.5911979,-122.4058984&heading=254&pitch=8.527488864617514&key=<API key>'To rotate the view direction and angle, we need to extract and transform the direction and grade of the street segment from the OSMnx network into direction and angle above horizontal to pass to Street View. This is done by considering latitude and longitude for the direction, along with the x and y distances and elevations for the viewing angle.

Here we always select the view of the bottom of the street looking up. This gives us our view looking up the steepest street segment in Millbrae, as at the start of the text.
The approach fails if the street segment has a sharp curve between the two nodes, because you are looking directly towards the next node and not necessarily the direction the street immediately heads. This could be solved by decomposing the segment into its component linestrings and aligning with the first of them, but in practice this rarely comes up in Millbrae.
Other neighborhoods
The code is in principle generic to any neighborhood worldwide that can be named in the widely-used OpenStreetMap Nominatim geocoding tool. Simply swap out the name in the settings and explore. Here are some others nearby in the Bay Area:
Limits of the data: San Francisco
As is common in GIS, however, the real world has a way of bringing on limitations on workflows that naively seem straightforward, often in instructive ways. In this project, we see it if we set the place considered to be not a medium-sized suburb, but the whole of San Francisco.
Because of its many steep hills, streets navigating them, and large size, San Francisco is particularly prone to street segments with spurious grades:
- Limited latitude + longitude accuracy of the elevation data results in street node points on hillsides close to the street, not on the street
- Very short segments of only a few meters connecting one-way halves of streets at an intersection (common in San Francisco)
The grades plot itself looks good:

but it thinks that this wall is the steepest street (71% grade!):
Appropriately the name of this “street” is NaN.
There are some detail ways that these artifacts could be reduced:
- Filter out any segments of obviously spurious grade, say over 40%, and too-short length, say less than 10 meters
- Manually inspect remaining Street View images
- Manually adjust street node points so they correspond to the satellite image of the intersection (being sure that the network and raster are precisely aligned using a local CRS projection)
- Remove the OSMnx default consolidation of intersections, which may over-simplify some short steep segments
Ultimately addressing all of these in detail would constitute a full research project.
Summary & Future work
We found the steepest streets in my local neighborhood of Millbrae using OpenStreetMap and OSMnx, and viewed each one using Google Street View. The steepest street is Aura Vista, with a grade of 18%.
The code can in principle be run on any neighborhood worldwide that is named in the well-known Nominatim geocoding tool.
The main improvements to the results here would be
- Make a webapp so users can see their neighborhood without writing code
- Refine the analysis to be more robust in places such as San Francisco, using the ideas detailed in the main text above
- Share it directly to target communities, e.g., OSM User Diaries
- Add an option to use Open Topo data instead of Google elevation data so that the code can optionally be run without requiring the user to set up a Google API key
There are smaller improvements as well. For example, normalizing the grades colormap to be absolute and not calibrated to only the range of grades in that neighborhood. As currently coded, for example, somewhere very flat like Foster City would still show yellow for the streets with the highest grades, even though those grades are not high. The GitHub repository has the full set of ideas.
Interestingly, one consequence of these improvements might be, if a minimum threshold length for consistent grade is introduced to remove short street segments, such as the short ~ 10m segments of Hillcrest in Millbrae that place second and third, this might result in the 65m Aura Vista getting dropped, and the much longer (307m) but almost-as-steep Larkspur Drive taking the title. The whole segment between Pinehurst Court and Crestview Drive has an average grade of just under 16%, and in a way is a much worthier steepest street. Ultimately it depends on what we would like to highlight as a steep street.
