Mapping College Campuses with OpenStreetMap and Python¶
In this lesson, we will explore how to use OpenStreetMap (OSM) and Python to create maps of college campuses. OpenStreetMap is a free, community-created map of the world that contains information about buildings, roads, trails, parks, and many other geographic features. Because OSM data are open and publicly available, they provide an excellent resource for learning spatial data science and geographic visualization.
We will use the Python package OSMnx to download and work with OSM data. OSMnx allows us to search for geographic features such as buildings and roads using simple Python commands. Throughout this lesson, we will experiment with several different ways to query OSM data, including searching by address, place name, geographic point, and polygon boundary.
Our goal is to build interactive campus maps that help us explore how colleges and universities are represented in OpenStreetMap. Along the way, we will also learn some important ideas in geographic information science (GIS), including spatial features, geometry types, and map visualization.
Throughout the notebook we'll be demonstrating with the University of Colorado Boulder, and then provide space for you try it on your own for a campus you are interested in exploring. By the end of this notebook, you will be able to:
- Query OpenStreetMap data using Python
- Extract buildings and paths from OSM
- Create interactive maps using hvplot
- Explore and visualize college campuses spatially
- Understand how geographic features are stored in OSM
No prior GIS experience is required for this lesson.
# Import python libraries
# Work with vector data
import geopandas as gpd
import pandas as pd
# Save maps and plots to files
import holoviews as hv
# Create interactive maps and plots
import hvplot.pandas
# Search for locations by name - this might take a moment
import osmnx as osm
Part 1 - Searching by address¶
Here we will show how to query OSM using a known address of a college campus using the osm.features_from_address() function. This approach can be tricky given that campus names are not always valid addresses, rural colleges sometimes geocode poorly, and slight wording differences can break the query leading students to get frustrated with geocoder errors.
# Search OSM for CU Boulder using features_by_address
cu_gdf = osm.features_from_address(
'University of Colorado Boulder, Boulder, CO, United States',
{'amenity': ['university']})
cu_gdf
| geometry | nodes | amenity | boundary | internet_access | name | operator | short_name | website | wikidata | wikipedia | ele | gnis:feature_id | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||
| way | 46226108 | POLYGON ((-105.27589 40.01047, -105.27597 40.0... | [589514700, 2188984094, 2188984095, 326889012,... | university | administrative | wlan | University of Colorado Boulder (Main Campus) | University of Colorado Boulder | CU Boulder | https://www.colorado.edu/ | Q736674 | en:University of Colorado Boulder | NaN | NaN |
| 391232001 | POLYGON ((-105.26632 40.01452, -105.26631 40.0... | [3944423259, 3944423260, 3944423261, 394442326... | university | NaN | NaN | Naropa University | Naropa University | NaN | https://www.naropa.edu | Q2975783 | en:Naropa University | 1620 | 178660 |
Notice here that in addition to returning CU Boulder like we had asked for it also includes information about Naropa University, also located in Boulder. We can remove that entry if we only want to focus on the data for CU as follows. Here we select by the 'name' column, and provide the unique ID for CU (University of Colorado Boulder (Main Campus)).
# Select only CU
cu_gdf_filtered = cu_gdf[cu_gdf["name"] == "University of Colorado Boulder (Main Campus)"]
cu_gdf_filtered
| geometry | nodes | amenity | boundary | internet_access | name | operator | short_name | website | wikidata | wikipedia | ele | gnis:feature_id | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||
| way | 46226108 | POLYGON ((-105.27589 40.01047, -105.27597 40.0... | [589514700, 2188984094, 2188984095, 326889012,... | university | administrative | wlan | University of Colorado Boulder (Main Campus) | University of Colorado Boulder | CU Boulder | https://www.colorado.edu/ | Q736674 | en:University of Colorado Boulder | NaN | NaN |
Now let's plot an interactive map of the boundary of our data using hvplot.
cu_gdf_filtered.hvplot(
geo=True,
tiles="EsriImagery",
line_color="black",
fill_color="lightgray",
alpha=0.7,
width=800,
height=600,
title="University of Colorado Boulder (Main Campus)"
)
/opt/conda/lib/python3.11/site-packages/dask/dataframe/__init__.py:31: FutureWarning: Dask dataframe query planning is disabled because dask-expr is not installed. You can install it with `pip install dask[dataframe]` or `conda install dask`. This will raise in a future version. warnings.warn(msg, FutureWarning)
Part 2 - Searching by point¶
Here we'll demonstrate how to set up a query using the features_from_point() function. We'll estimate the lat/lon of the center of CU Main Campus using Google Maps or another mapping tool of your choice. From there, we'll look for everything that is tagged as a 'building' within a specified distance and then plot the results. You could also look for things tagged as parks, waterways, highways (anything that is road-like), etc. depending on how much information you want your map to contain. Here is your opportunity to be creative. Read more about how tags are used in OSM here: https://wiki.openstreetmap.org/wiki/Tags.
# Approximate center of the University of Colorado Boulder campus
cu_point = (40.0076, -105.2659) # latitude, longitude
# Search for buildings near the campus point
cu_buildings = osm.features_from_point(
cu_point,
tags={"building": True},
dist=750
)
cu_buildings
| geometry | access | addr:city | addr:housenumber | addr:postcode | addr:state | addr:street | description | phone | website | ... | location | cooling:method | check_date | opening_date | ways | type | construction | roof:colour | internet_access | internet_access:fee | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 28775431 | POLYGON ((-105.26585 40.00596, -105.26585 40.0... | NaN | Boulder | 2200 | NaN | CO | Baker Drive | Residence hall with a new food court; home of ... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 28775448 | POLYGON ((-105.26486 40.00597, -105.26469 40.0... | NaN | Boulder | 1001 | NaN | CO | Cockerell Drive | Residence hall with buffet apartments, for sen... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775533 | POLYGON ((-105.26486 40.00654, -105.26481 40.0... | NaN | Boulder | 1015 | NaN | CO | Cockerell Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775536 | POLYGON ((-105.26461 40.00653, -105.26441 40.0... | NaN | Boulder | 2370 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775541 | POLYGON ((-105.26530 40.00707, -105.26515 40.0... | NaN | Boulder | 2350 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| relation | 12097289 | POLYGON ((-105.26285 40.00283, -105.26285 40.0... | NaN | Boulder | 2480 | NaN | NaN | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | [46938900, 888721100] | multipolygon | residential | NaN | NaN | NaN |
| 12098405 | POLYGON ((-105.26289 40.00134, -105.26289 40.0... | NaN | Boulder | 2450 | NaN | CO | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | [168647687, 888779941, 888779946] | multipolygon | NaN | red | NaN | NaN | |
| 12903697 | MULTIPOLYGON (((-105.26872 40.01444, -105.2686... | NaN | Boulder | 2020 | NaN | CO | Arapahoe Avenue | NaN | NaN | https://www.choicehotels.com/colorado/boulder/... | ... | NaN | NaN | NaN | NaN | [119524745, 452957200] | multipolygon | NaN | NaN | wlan | customers | |
| 18555106 | MULTIPOLYGON (((-105.26781 40.00893, -105.2678... | NaN | Boulder | 2085 | NaN | NaN | Colorado Avenue | Athletics and events stadium, expanded with sk... | NaN | NaN | ... | NaN | NaN | NaN | NaN | [46371962, 318135984] | multipolygon | NaN | NaN | NaN | NaN | |
| 18723484 | POLYGON ((-105.27360 40.01321, -105.27366 40.0... | NaN | Boulder | 1604 | 80302 | CO | Arapahoe Avenue | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | [52429583, 1360976244] | multipolygon | NaN | NaN | NaN | NaN |
752 rows × 97 columns
# Plot the buildings near CU Main campus
cu_buildings.hvplot(
geo=True,
tiles="EsriImagery",
line_color="black",
fill_color="yellow",
alpha=1,
width=800,
height=600,
title="University of Colorado Boulder Buildings (by point)"
)
Here we were fortunate that all of the buildings that were returned in this search were geocoded as polygons. You may encounter situations where your query returns a mix of points (nodes) and polygons (ways) which will require filtering before the data can be plotted interacatively with hvplot. We'll try to demonstrate how to resolve this issue when it comes up.
Part 3 - Searching by place¶
In this example, we will use the features_from_place() function from OSMnx to search for geographic features inside the boundary of the University of Colorado Boulder campus.
Unlike features_from_point(), which searches around a coordinate location, features_from_place() first searches OpenStreetMap for the boundary of a named place and then retrieves features that fall inside that boundary.
# Define a variable name for our place
place = "University of Colorado Boulder (Main Campus), Boulder, Colorado, USA"
# Search for all buildings within the boundary of this place
cu_buildings = osm.features_from_place(
place,
tags={"building": True}
)
cu_buildings
| geometry | access | addr:city | addr:housenumber | addr:postcode | addr:state | addr:street | description | website | amenity | ... | covered | bench | location | cooling:method | check_date | opening_date | ways | type | construction | roof:colour | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 28775431 | POLYGON ((-105.26585 40.00596, -105.26585 40.0... | NaN | Boulder | 2200 | NaN | CO | Baker Drive | Residence hall with a new food court; home of ... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 28775448 | POLYGON ((-105.26486 40.00597, -105.26469 40.0... | NaN | Boulder | 1001 | NaN | CO | Cockerell Drive | Residence hall with buffet apartments, for sen... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775533 | POLYGON ((-105.26486 40.00654, -105.26481 40.0... | NaN | Boulder | 1015 | NaN | CO | Cockerell Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775536 | POLYGON ((-105.26461 40.00653, -105.26441 40.0... | NaN | Boulder | 2370 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775541 | POLYGON ((-105.26530 40.00707, -105.26515 40.0... | NaN | Boulder | 2350 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| relation | 9826677 | POLYGON ((-105.26199 40.00749, -105.26199 40.0... | NaN | Boulder | 1095 | NaN | NaN | Regent Drive | State-of-the-art research and learning laborat... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [706728927, 706728926] | multipolygon | NaN | NaN |
| 11964909 | POLYGON ((-105.26163 40.00346, -105.26129 40.0... | NaN | Boulder | 25890 | 80310 | CO | Kitterege Loop Road | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [879469306, 879469307, 879469308, 879469305, 8... | multipolygon | NaN | NaN | |
| 12097289 | POLYGON ((-105.26285 40.00283, -105.26285 40.0... | NaN | Boulder | 2480 | NaN | NaN | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [46938900, 888721100] | multipolygon | residential | NaN | |
| 12098405 | POLYGON ((-105.26289 40.00134, -105.26289 40.0... | NaN | Boulder | 2450 | NaN | CO | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [168647687, 888779941, 888779946] | multipolygon | NaN | red | |
| 18555106 | MULTIPOLYGON (((-105.26781 40.00893, -105.2678... | NaN | Boulder | 2085 | NaN | NaN | Colorado Avenue | Athletics and events stadium, expanded with sk... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [46371962, 318135984] | multipolygon | NaN | NaN |
164 rows × 76 columns
# Plot the data interactively using hvplot
cu_buildings.hvplot(
geo=True,
tiles="EsriImagery",
line_color="black",
fill_color="yellow",
alpha=1,
width=800,
height=600,
title="University of Colorado Boulder Buildings (from place)"
)
Observe that there are now fewer buildings than resulted from the previous example that used features_by_point, and are now strictly contained within the boundary of the CU Boulder Main Campus. This may be preferred depending on the information you are trying to convey with your map.
NOTE:
features_from_place()works best when OpenStreetMap contains a clearly defined boundary for the place being searched. Some campuses and institutions may not have complete or well-defined boundaries in OSM. When this happens,features_from_point()is often a more reliable alternative.
Part 4 - Searching by polygon¶
Here we will extract polygon geometry and then query OSM for all buildings inside the specified boundary. Polygons can come from many different sources including features_from_place like we just saw, OSM way ID (obtained from the interactive web map), geoJSON or shapefile, or from a boundary that you define with coordinates. For this demonstration we will use the OSM way ID (https://www.openstreetmap.org/way/46226108) and the geocode_to_gdf() function, but you can try playing around with these other approaches if you are curious and have time. Once we have our polygon geometry, we can then use features_from_polygon to search for buildings or other OSM objects.
# OSM way ID
osm_way_id = 46226108
# Retrieve polygon boundary
campus_boundary = osm.geocode_to_gdf(
"W46226108",
by_osmid=True
)
campus_boundary
| geometry | bbox_north | bbox_south | bbox_east | bbox_west | place_id | osm_type | osm_id | lat | lon | class | type | place_rank | importance | addresstype | name | display_name | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | POLYGON ((-105.27660 40.01029, -105.27660 40.0... | 40.013632 | 40.000279 | -105.258821 | -105.276605 | 329820921 | way | 46226108 | 40.007006 | -105.266442 | amenity | university | 30 | 0.583709 | amenity | University of Colorado Boulder (Main Campus) | University of Colorado Boulder (Main Campus), ... |
# Extract polygon geometry
campus_polygon = campus_boundary.geometry.iloc[0]
campus_polygon
# Search for everthing tagged as 'building' within the specified boundary
campus_buildings = osm.features_from_polygon(
campus_polygon,
tags={"building": True}
)
campus_buildings
| geometry | access | addr:city | addr:housenumber | addr:postcode | addr:state | addr:street | description | website | amenity | ... | covered | bench | location | cooling:method | check_date | opening_date | ways | type | construction | roof:colour | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 28775431 | POLYGON ((-105.26585 40.00596, -105.26585 40.0... | NaN | Boulder | 2200 | NaN | CO | Baker Drive | Residence hall with a new food court; home of ... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 28775448 | POLYGON ((-105.26486 40.00597, -105.26469 40.0... | NaN | Boulder | 1001 | NaN | CO | Cockerell Drive | Residence hall with buffet apartments, for sen... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775533 | POLYGON ((-105.26486 40.00654, -105.26481 40.0... | NaN | Boulder | 1015 | NaN | CO | Cockerell Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775536 | POLYGON ((-105.26461 40.00653, -105.26441 40.0... | NaN | Boulder | 2370 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 28775541 | POLYGON ((-105.26530 40.00707, -105.26515 40.0... | NaN | Boulder | 2350 | NaN | CO | Libby Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| relation | 9826677 | POLYGON ((-105.26199 40.00749, -105.26199 40.0... | NaN | Boulder | 1095 | NaN | NaN | Regent Drive | State-of-the-art research and learning laborat... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [706728927, 706728926] | multipolygon | NaN | NaN |
| 11964909 | POLYGON ((-105.26163 40.00346, -105.26129 40.0... | NaN | Boulder | 25890 | 80310 | CO | Kitterege Loop Road | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [879469306, 879469307, 879469308, 879469305, 8... | multipolygon | NaN | NaN | |
| 12097289 | POLYGON ((-105.26285 40.00283, -105.26285 40.0... | NaN | Boulder | 2480 | NaN | NaN | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [46938900, 888721100] | multipolygon | residential | NaN | |
| 12098405 | POLYGON ((-105.26289 40.00134, -105.26289 40.0... | NaN | Boulder | 2450 | NaN | CO | Kittredge Loop Drive | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [168647687, 888779941, 888779946] | multipolygon | NaN | red | |
| 18555106 | MULTIPOLYGON (((-105.26781 40.00893, -105.2678... | NaN | Boulder | 2085 | NaN | NaN | Colorado Avenue | Athletics and events stadium, expanded with sk... | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | [46371962, 318135984] | multipolygon | NaN | NaN |
164 rows × 76 columns
Let's try adding the water bodies that fall within the campus boundary to this map.
# Search for everthing tagged as 'water' within the specified boundary
campus_water = osm.features_from_polygon(
campus_polygon,
tags={"natural":"water",
"waterway": True,
"water":True}
)
campus_water
| nodes | ele | gnis:feature_id | name | natural | short_name | water | geometry | description | intermittent | waterway | source | usage | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||
| way | 46197729 | [589040165, 2496839092, 589040178, 589040176, ... | 1649 | 178656 | Varsity Lake | water | W-VL | reservoir | POLYGON ((-105.27394 40.00972, -105.27393 40.0... | NaN | NaN | NaN | NaN | NaN |
| 46938874 | [599116490, 599116491, 599116492, 599116493, 5... | NaN | NaN | NaN | water | NaN | NaN | POLYGON ((-105.25921 40.00682, -105.25919 40.0... | NaN | NaN | NaN | NaN | NaN | |
| 46938875 | [599116499, 4753724662, 599116500, 599116501, ... | NaN | NaN | NaN | water | NaN | NaN | POLYGON ((-105.25954 40.00786, -105.25957 40.0... | NaN | NaN | NaN | NaN | NaN | |
| 46938882 | [599116565, 599116566, 599116567, 599116568, 5... | NaN | NaN | Kitt Pond | water | NaN | pond | POLYGON ((-105.26259 40.00270, -105.26258 40.0... | No fishing allowed, frequented by canadian gee... | no | NaN | NaN | NaN | |
| 61758946 | [434098986, 434098987, 434098988, 434098989, 4... | NaN | NaN | Boulder Creek | NaN | NaN | NaN | LINESTRING (-105.29443 40.01364, -105.29417 40... | NaN | no | stream | NaN | NaN | |
| 194217260 | [2047352879, 2047352903, 2047352859, 204735286... | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26105 40.00291, -105.26101 40... | NaN | NaN | drain | bing | NaN | |
| 482560081 | [4753724705, 4753724706] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.25917 40.00785, -105.25913 40... | NaN | yes | canal | NaN | spillway | |
| 888538283 | [8260987529, 8260987530] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26306 40.00090, -105.26300 40... | NaN | NaN | drain | NaN | NaN |
campus_water.plot()
<Axes: >
Here you'll notice that the query returns polygons (lakes/ponds) and linestrings (streams/creeks). This will complicate things when we try to map them interactively later on with hvplot. We can separate out the lines from the polygons as outlined below for easier plotting.
# Splitting water into lines and polygons for easier plotting
water_polygons = campus_water[
campus_water.geometry.geom_type.isin(
["Polygon", "MultiPolygon"])]
water_polygons
water_lines = campus_water[
campus_water.geometry.geom_type.isin(
["LineString", "MultiLineString"])]
water_lines
| nodes | ele | gnis:feature_id | name | natural | short_name | water | geometry | description | intermittent | waterway | source | usage | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||
| way | 61758946 | [434098986, 434098987, 434098988, 434098989, 4... | NaN | NaN | Boulder Creek | NaN | NaN | NaN | LINESTRING (-105.29443 40.01364, -105.29417 40... | NaN | no | stream | NaN | NaN |
| 194217260 | [2047352879, 2047352903, 2047352859, 204735286... | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26105 40.00291, -105.26101 40... | NaN | NaN | drain | bing | NaN | |
| 482560081 | [4753724705, 4753724706] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.25917 40.00785, -105.25913 40... | NaN | yes | canal | NaN | spillway | |
| 888538283 | [8260987529, 8260987530] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26306 40.00090, -105.26300 40... | NaN | NaN | drain | NaN | NaN |
# Clip water lines to campus boundary
water_lines_clipped = gpd.clip(
water_lines,
campus_boundary
)
water_lines_clipped
| nodes | ele | gnis:feature_id | name | natural | short_name | water | geometry | description | intermittent | waterway | source | usage | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||
| way | 888538283 | [8260987529, 8260987530] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26306 40.00090, -105.26300 40... | NaN | NaN | drain | NaN | NaN |
| 194217260 | [2047352879, 2047352903, 2047352859, 204735286... | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.26105 40.00291, -105.26101 40... | NaN | NaN | drain | bing | NaN | |
| 482560081 | [4753724705, 4753724706] | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (-105.25917 40.00785, -105.25913 40... | NaN | yes | canal | NaN | spillway | |
| 61758946 | [434098986, 434098987, 434098988, 434098989, 4... | NaN | NaN | Boulder Creek | NaN | NaN | NaN | LINESTRING (-105.27247 40.01180, -105.27216 40... | NaN | no | stream | NaN | NaN |
Let's try adding the roads that fall within the campus boundary to this map.
# Search for everthing tagged as 'highway' within the specified boundary
campus_roads = osm.features_from_polygon(
campus_polygon,
tags={"highway": True}
)
campus_roads
| crossing | crossing:markings | highway | geometry | traffic_signals | button_operated | crossing:signals | traffic_signals:direction | direction | tactile_paving | ... | area | informal | smoothness | cycleway:right:buffer | covered | loc_name | junction | cutting | source:width | traffic_calming | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| node | 176407697 | uncontrolled | yes | crossing | POINT (-105.26847 40.00650) | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 176526906 | uncontrolled | zebra | crossing | POINT (-105.27277 40.00943) | NaN | NaN | no | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 176565498 | zebra | NaN | crossing | POINT (-105.26954 40.00788) | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 176567142 | NaN | NaN | stop | POINT (-105.26485 40.00331) | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 176567160 | NaN | NaN | give_way | POINT (-105.26401 40.00159) | NaN | NaN | NaN | NaN | backward | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| way | 1476032523 | NaN | NaN | tertiary | LINESTRING (-105.27277 40.00943, -105.27307 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1476932185 | NaN | NaN | cycleway | LINESTRING (-105.26038 40.00059, -105.26039 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | table | |
| 1476932544 | uncontrolled | surface | cycleway | LINESTRING (-105.26949 40.00455, -105.26953 40... | NaN | NaN | no | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1476932545 | NaN | NaN | cycleway | LINESTRING (-105.26956 40.00454, -105.26960 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1506480041 | NaN | NaN | path | LINESTRING (-105.26732 40.01184, -105.26729 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1438 rows × 113 columns
# Create simple plot with .plot()
campus_roads.plot()
<Axes: >
Again, like with the water data, you'll notice there are lines and points in this roads data. They can be plotted together using a .plot() function as above, but will need to be separated by geometry if we want to plot them together interactively using hvplot. We'll separate out the linestrings below and use those for interactive plotting.
# Select the road objects represented with LineString/MultiLineString
road_lines = campus_roads[
campus_roads.geometry.geom_type.isin(
["LineString", "MultiLineString"])]
road_lines
| crossing | crossing:markings | highway | geometry | traffic_signals | button_operated | crossing:signals | traffic_signals:direction | direction | tactile_paving | ... | area | informal | smoothness | cycleway:right:buffer | covered | loc_name | junction | cutting | source:width | traffic_calming | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 4325623 | NaN | NaN | service | LINESTRING (-105.26497 40.00734, -105.26519 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 8051748 | NaN | NaN | service | LINESTRING (-105.26296 40.00331, -105.26287 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 17018618 | NaN | NaN | service | LINESTRING (-105.26862 40.00495, -105.26855 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 17019971 | NaN | NaN | service | LINESTRING (-105.26879 40.00987, -105.26868 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 17020517 | NaN | NaN | tertiary | LINESTRING (-105.26136 40.00735, -105.26136 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 1476032523 | NaN | NaN | tertiary | LINESTRING (-105.27277 40.00943, -105.27307 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1476932185 | NaN | NaN | cycleway | LINESTRING (-105.26038 40.00059, -105.26039 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | table | |
| 1476932544 | uncontrolled | surface | cycleway | LINESTRING (-105.26949 40.00455, -105.26953 40... | NaN | NaN | no | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1476932545 | NaN | NaN | cycleway | LINESTRING (-105.26956 40.00454, -105.26960 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 1506480041 | NaN | NaN | path | LINESTRING (-105.26732 40.01184, -105.26729 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1241 rows × 113 columns
# Clip road lines to campus boundary
road_lines_clipped = gpd.clip(
road_lines,
campus_boundary
)
road_lines_clipped
| crossing | crossing:markings | highway | geometry | traffic_signals | button_operated | crossing:signals | traffic_signals:direction | direction | tactile_paving | ... | area | informal | smoothness | cycleway:right:buffer | covered | loc_name | junction | cutting | source:width | traffic_calming | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 194216335 | NaN | NaN | cycleway | LINESTRING (-105.26260 40.00053, -105.26251 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 33969475 | NaN | NaN | path | LINESTRING (-105.26324 40.00060, -105.26335 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 194216330 | NaN | NaN | cycleway | LINESTRING (-105.26260 40.00053, -105.26273 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 216703143 | NaN | NaN | footway | LINESTRING (-105.26294 40.00078, -105.26282 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 878796413 | NaN | NaN | service | LINESTRING (-105.26315 40.00093, -105.26313 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 767654726 | NaN | NaN | footway | LINESTRING (-105.26769 40.01340, -105.26761 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006625 | NaN | NaN | footway | LINESTRING (-105.26850 40.01316, -105.26856 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 767654728 | NaN | NaN | footway | LINESTRING (-105.26894 40.01334, -105.26896 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006627 | NaN | NaN | footway | LINESTRING (-105.26851 40.01361, -105.26851 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006617 | NaN | NaN | footway | LINESTRING (-105.26836 40.01334, -105.26837 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1241 rows × 113 columns
# Select the clipped road objects represented with LineString/MultiLineString
clipped_road_lines = road_lines_clipped[
road_lines_clipped.geometry.geom_type.isin(
["LineString", "MultiLineString"])]
clipped_road_lines
| crossing | crossing:markings | highway | geometry | traffic_signals | button_operated | crossing:signals | traffic_signals:direction | direction | tactile_paving | ... | area | informal | smoothness | cycleway:right:buffer | covered | loc_name | junction | cutting | source:width | traffic_calming | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_type | osmid | |||||||||||||||||||||
| way | 194216335 | NaN | NaN | cycleway | LINESTRING (-105.26260 40.00053, -105.26251 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 33969475 | NaN | NaN | path | LINESTRING (-105.26324 40.00060, -105.26335 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 194216330 | NaN | NaN | cycleway | LINESTRING (-105.26260 40.00053, -105.26273 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 216703143 | NaN | NaN | footway | LINESTRING (-105.26294 40.00078, -105.26282 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 878796413 | NaN | NaN | service | LINESTRING (-105.26315 40.00093, -105.26313 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 767654726 | NaN | NaN | footway | LINESTRING (-105.26769 40.01340, -105.26761 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006625 | NaN | NaN | footway | LINESTRING (-105.26850 40.01316, -105.26856 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 767654728 | NaN | NaN | footway | LINESTRING (-105.26894 40.01334, -105.26896 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006627 | NaN | NaN | footway | LINESTRING (-105.26851 40.01361, -105.26851 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
| 214006617 | NaN | NaN | footway | LINESTRING (-105.26836 40.01334, -105.26837 40... | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1239 rows × 113 columns
# Create an interactive map of the boundary and buildings
building_layer = campus_buildings.hvplot(
geo=True,
tiles="EsriImagery",
line_color="black",
fill_color="yellow",
alpha=0.9,
width=850,
height=650,
title="CU Boulder (Main Campus)"
)
road_line_layer = clipped_road_lines.hvplot(
geo=True,
line_color="grey",
line_width=1
)
boundary_layer = campus_boundary.hvplot(
geo=True,
line_color="yellow",
fill_color="yellow",
fill_alpha=0.2,
line_width=4
)
water_boundary_layer = water_polygons.hvplot(
geo=True,
line_color="blue",
line_width=1.5,
fill_color="lightblue",
fill_alpha=0.8
)
water_line_layer = water_lines_clipped.hvplot(
geo=True,
line_color="blue",
line_width=2
)
building_layer * road_line_layer * boundary_layer * water_boundary_layer * water_line_layer
Why Use an OSM Object ID?
Advantages:
- avoids geocoding problems
- uses an exact OSM boundary
- highly reproducible
- common in GIS and research workflows
This workflow is especially useful when:
- place names fail
- campuses have multiple boundaries
- you want consistent study areas across analyses
Now it is your turn¶
You have seen 4 different approaches to querying OSM for information about CU Boulder through seaching by address, point, place, and polygon. Now it is your turn to try this workflow out for a campus you are intersted in. Try adding buildings, highways, waterways, parks, etc. if you are feeling creative. Try adding more code and markdown cells below to get started creating your campus maps, and have some fun!
%%capture
%%bash
jupyter nbconvert new-map-workflow.ipynb --to html