I like my girlfriend very much, and my girlfriend likes:
- public staircases and passageways in unexpected places
- exploring the city
- maps
- me! 😍
As a present for her, I wanted to create a way of finding these hidden urban staircases and passageways that was easier and more comprehensive than manually scrolling around Google Maps. Enter: Open Street Map open_in_new (OSM) and Overpass Turbo open_in_new . I’m not going to get too into explaining what these are all about—suffice to say that OSM is an open-data world map built through user contributions, akin to Wikipedia; Overpass is a API that can be used to query OSM data; and Overpass Turbo in particular is a handy web tool for easily running Overpass queries. This was my first time using either of these tools, but certainly not my last—this was a super fun project.
For you: I’ve tweaked the query to use the viewport as the search area so you can investigate the stairs and passages wherever you are. It takes a little while to run—the further you’re zoomed in, the faster it will go. Try it yourself! open_in_new
Defining the Search Area
We start by defining the search area. Doing this upfront keeps the code DRYer and allows me to easily apply the query to different regions. For this first round I focused on our local area, but I like the idea that I could run it for any place we were planning to visit.
{{geocodeArea:Massachusetts}}->.Massachusetts;
relation
["boundary"="administrative"]
["name"~"Somerville|Cambridge|Medford|Chelsea|Everett|Boston|Brookline"]
(area.Massachusetts)
->.bostonArea;
.bostonArea map_to_area ->.searchArea;
Defining Our Targets
OSM has three elements: nodes, ways, and relations. Stairs and passages are going to be “ways”, which are really just a string of nodes connected to make a line. Defining stairs is pretty straightforward: we’ll use the “steps” open_in_new tag.
way
["highway"="steps"]
(area.searchArea)
->.stairs;
Defining passages is a little trickier; they could either be a “footway” open_in_new or the more-general “path” open_in_new , but these tags also encompass miles and miles of sidewalks. We eliminate those by excluding the “sidewalk” open_in_new , “crossing” open_in_new , and “traffic island” open_in_new tags.
way
["highway"="footway|path"]
["footway"!="sidewalk"]
["footway"!="crossing"]
["footway"!="traffic_island"]
(area.searchArea)
->.passages;
Running just the above code gives us 1,402 results. This is way too many, and the majority of them are “expected” stairs and passages in places like parks, not the quirky “unexpected” ones that we’re interested in. While I’m planning to manually polish the results as my last step, there’s clearly some additional work to be done on the query before that’s feasible.
Narrowing Our Focus
Let’s define areas that we know are going to be “boring”. Places like parks, monuments, university campuses, shopping malls—these are all absolutely lousy with uninteresting stairs and passages.
(
nwr["leisure"](area.searchArea);
nwr["amenity"](area.searchArea);
nwr["nature"](area.searchArea);
nwr["aeroway"](area.searchArea);
nwr[landuse~"residential|commercial|industrial|retail|grass|recreation_ground|religious|cemetery|military"](area.searchArea);
nwr["boundary"="protected_area"](area.searchArea);
nwr["access"="no"](area.searchArea);
) ->.boringPlaces;
.boringPlaces map_to_area ->.boringAreas;
(
way.stairs(area.boringAreas);
way.passages(area.boringAreas);
) ->.boringFeatures;
I’ll also tack on some code to exclude stairs and passages around transit stops, since I see a lot of those gumming up the results. As far as I can tell, this has to be done separately from the previous chunk of code because the above is excluding features within an area, and here we’re excluding features around an area. I suspect there’s a way to fold this “transit area” into the “boring places” code above, but eh, it’s working fine as is.
(
way["building"="train_station"](area.searchArea);
way["railway"="platform"](area.searchArea);
) ->.transit_buildings;
way
["highway"="steps"]
(around.transit_buildings:50)
->.transitStairs;
way
["highway"="footway|path"]
(around.transit_buildings:50)
->.transitPassages;
Finally, we combine all of these different datasets of boring features into one and subtract that from the original dataset of all features.
(.stairs; .passages;) ->.features;
(.boringFeatures; .transitStairs; .transitPassages;) ->.boring;
(.features; - .boring;);
That reduces our results to 328, which is much more workable. Still, scrolling around the results reveals that there are still some leftovers; short flights of steps going up into buildings, pathways winding around apartment complexes, etc. What these seem to have in common is that they’re either particularly short, in the case of the stairs, or particularly long, in the case of the paths. Let’s update our initial stairs and passages query to filter based on length.
way
["highway"="steps"]
(area.searchArea)
(if: length() > 4)
->.stairs;
way
["highway"="footway|path"]
["footway"!="sidewalk"]
["footway"!="crossing"]
["footway"!="traffic_island"]
(area.searchArea)
(if: length() >3 && length() < 45)
->.passages;
That further reduces our results to 224. At this point, there are probably diminishing returns to developing increasingly specific filters to shave off one or two results, so let’s take the data to manual cleaning.
Manual Cleaning
I exported the results to KML, which carried over a lot of attribute data from OSM that I didn’t care about. I used find and replace to remove this and replace it with fields for description, status, date visited, and rating. Then I imported the results into a Google MyMaps. From here, I used a combination of satellite imagery and Street View on Google Maps to investigate each potential feature, deleting them if they weren’t worthwhile or assigning them a status if they were. (Green: already visited, blue: looks interesting, yellow: looks questionable or maybe less cool.)
At the end of this process, I had 68 really solid, interesting stairs and passages for us to visit in the Boston area. And most importantly, my girlfriend loved it!
Next Steps
Although I’m really pleased with the results of this exercise, it’s certainly not exhaustive. We’ve already discovered a couple stairs and passages that, for one reason or another, were either not captured by or accidentally excluded from this query. For now, it’s easy enough to manually add them to the map—and with each one, I learn a little bit more about how to define the features I’m interested in when “talking” to OSM. I’d like to someday use these insights to further improve this particular query.
Additionally, stairs and passages aren’t the only cool things around here! I myself am partial to wind turbines, radio towers, and power pylons, and I’m hoping I can use the principles I learned during this project to develop a query for mapping those out as well.
I also thought it might be fun to make a little zine of these Boston area points of interest, inspired by the original “Boston Rocks” guide published by the MIT outing club. Not sure when I’d get around to it, though.
Thanks for reading!