Designate Bike Routes for Commuting Professionals

Traffic congestion has been increasing in cities due to long commutes to and from work. A simple measure of using bicycles for commuting can prove to be a very effective solution for this problem. This will also be a healthy option for individuals by making them physically active. In order to encourage citizens to use bicycles for commute, cities need to ensure that there are adequate number of safe bike lanes.

This sample uses ArcGIS API for Python to analyze and select streets for making bike routes for people commuting to and from work in the City of Seattle, Washington. It will demostrate the use tools such as find_existing_locations, create_buffers, merge_layers, dissolve_boundaries, overlay_layers, enrich_layer, and attribute query. We will also be using Esri Tapestry Segmentation to perform this analysis.

The aim of this analysis is to:

  • inrease the number of bike lanes in the city
  • ensure that every residence is within 1/2 mile of a bike route
  • add protected bike lanes on busy streets that offer a barrier between the bike lanes and cars on roads, and
  • make sure that atleast 5 percent of the total length of streets have bicycle lanes. This is a figure that is at par with that of Portland, Oregon, which is one of the most bicycle-friendly cities in the United States.

Workflow

Steps

We will use the following steps for the analysis:

1) Retrieve the SeattleBikeRoutes layer.

2) Filter Seattle's zoning layer by downtown office.

3) Identify high demand neighborhood. We use the tapestry segmentation layer and visualize it on the map. We click some of the segments in the vicinity of Downtown area and choose a segment that contains young, health-conscious and environmentally-conscious professionals, i.e, Capitol Hill which serves as a good target to get started.

4) Filter neighborhoods layer by Capitol Hill.

5) Check if the bike lanes can be made on trails.

5) Use find existing_locations to get arterial streets layer. We use this tool to select the streets which will be the most appropriate for making bike lanes.

6) Select the first street from the results we got from step 5.

7) Calculate the length of the street.

8) Apply a Buffer of 0.5 miles to this street.

9) Repeat step 6, 7, 8 for the second and third street.

10) Calculate the percentage of road length used for making bike lanes

11) Merge the buffered layers and dissolve boundaries to get a single polygon.

12) Use overlay layers to clip the buffers into the shape of Capitol Hill

13) Enrich the clipped layer by population data using enrich_layer method.

14) Calculate the total population of Capitol Hill that will benefit

Necessary Imports

Input
from datetime import datetime as dt

from arcgis import GIS
from arcgis.features import FeatureLayerCollection
from arcgis.features.find_locations import find_existing_locations
from arcgis.features.use_proximity import create_buffers
from arcgis.features.manage_data import merge_layers
from arcgis.features.manage_data import dissolve_boundaries
from arcgis.features.manage_data import overlay_layers
from arcgis.features.enrich_data import enrich_layer

Connect to your ArcGIS online organization

Input
gis = GIS('home')

We can use the search() method of the content property of our gis object to get the Seattle Bike Routes layer. Since this is public content shared by users outside our organization, we will search for SeattleBikeRoutes and set outside_org to True.

Get the data for the analysis.

Input
seattle_bike_routes = gis.content.search('title: SeattleBikeRoutes owner: api_data_owner',
                                         'Feature layer')[0]
seattle_bike_routes
Output
SeattleBikeRoutes
datascienceFeature Layer Collection by api_data_owner
Last Modified: June 13, 2019
0 comments, 0 views

We can print the names of the layers in this item and assign them to variables that will be used in our analysis.

Input
for lyr in seattle_bike_routes.layers:
    print(lyr.properties.name)
SeattleBikeRoutes_street
SeattleBikeRoutes_zoning
SeattleBikeRoutes_Neighborhoods
Input
bike_route_streets = seattle_bike_routes.layers[0]
Input
bike_route_zoning = seattle_bike_routes.layers[1]
Input
bike_route_neighbourhood = seattle_bike_routes.layers[2]

Identify expected commuter neighborhoods

We will set a filter on the Zoning layer to select the downtown office core. The downtown office core is a good starting point for creating commuter bike lanes because it is expected to have a large number of commuters each day.

Input
bike_route_zoning.filter = "(ZONELUT_DE = 'Downtown Office Core 1') OR (ZONELUT_DE = 'Downtown Office Core 2')"

Apply a filter to the Neighborhoods so that only Capitol Hill shows up.

Input
bike_route_neighbourhood.filter = "L_HOOD = 'CAPITOL HILL'"
Input
tapestry_segmentation = gis.content.search('"2018 USA Tapestry segmentation" owner: api_data_owner',
                                           'Map Image Layer')[0]
Input
tapestry_segmentation
Output
2018 USA Tapestry Segmentation
This layer shows the dominant LifeMode Summary Group in the United States in 2018 by state, county, ZIP Code, tract, and block group based on Esri's Tapestry Segmentation system. ArcGIS Online subscription required.Map Image Layer by api_data_owner
Last Modified: June 05, 2019
0 comments, 0 views
Input
tapestry_map = gis.map('Seattle')
tapestry_map
Output
Input
tapestry_map.add_layer(tapestry_segmentation)

The neighborhood in the 98112 ZIP Code, called Capitol Hill, contains the Urban Chic, Laptops and Lattes, and Metro Renters segments. All three of the segments designated in the ZIP Code mention environmental awareness and biking, which makes the ZIP Code a good choice to get the bike lane project started.

Identify potential bike routes

The Streets layer includes main streets, trails, and other street types. Let's look at the trails first to determine if they can be used for commuting.

Filter the Streets so that only the trails are showing, by setting a definition expression that filters the roads by the segment type of trails (SEGMENT_TY = 8).

Input
bike_street_filtered_map = gis.map('Seattle')
bike_street_filtered_map
Output
Input
bike_street_filtered_map.add_layer({"type":"FeatureLayer", 
               "url":bike_route_streets.url,
               "definition_expression" : "SEGMENT_TY = 8", # code for trails
              })

There are only three trails in the neighborhood and they are not long enough to be used for commuting.

As there are not enough trails in the Capitol Hill neighborhood, the bike lanes will have to be created on pre-existing streets. Since Seattle wants to build protected bike lanes, we will look at busier streets first to find the most direct routes.

Find busy streets using find_existing_locations

Let's use find_existing_locations() to select the arterial streets (where SEGMENT_TY is 1 and where ARTERIAL_C is 1).

Input
busy_streets = find_existing_locations(input_layers=[{'url': bike_route_streets.url}], 
                                       expressions=[{"operator":"","layer":0,"where":"SEGMENT_TY = 1"},
                                                    {"operator":"and","layer":0,"where":"ARTERIAL_C = 1"}],
                                       output_name='ArterialStreets'+ str(dt.now().microsecond))
Input
busy_streets.layers[0].query(return_geometry=False, where='1=1')
Output
<FeatureSet> 6605 features
Input
busy_streets_map = gis.map('Seattle')
busy_streets_map
Output
Input
busy_streets_map.add_layer(busy_streets)

Next let's examine the arterial roads in and around the Capitol Hill neighborhood. One road runs through the entire south-eastern side of the neighborhood and into the Downtown Office Core. According to the basemap the street is called E Madison St.

Click the street and check the information in the pop-up box.

The streets are stored as segments, with their breaks at each intersection, rather than as single continuous lines. Therefore, the segments need to be collected together to be used as a continuous street. The ORD_STREET and ORD_STNAME fields will be used to make street selections.

Input
busy_streets_layer = busy_streets.layers[0]

Get Madison street using find_existing_locations tool

We will use find_existing_locations to select the segments of Madison Street that are within 3.5 feet of Capitol Hill. This distance has to be used because the street shares a border with Capitol Hill and some segments are located just outside of the boundary. Note that the actual bike lane would run all the way to the Downtown Office Core, but in this analysis we determine how well the lanes service a specific neighborhood. So only sections of the road within that neighborhood are required. We will apply the Neighborhoods filter to the tool.

Input
madison_street = find_existing_locations(input_layers=[{'url': busy_streets_layer.url},
                                                       {'url': bike_route_neighbourhood.url, 'filter':"L_HOOD = 'CAPITOL HILL'"}],
                                         expressions=[{"operator":"","layer":0,"where":"ORD_STREET = 'MADISON'"},
                                                      {"operator":"and","layer":0,"selectingLayer":1,
                                                       "spatialRel":"withinDistance","distance":3.5,"units":"Feet"}],
                                         output_name='MadisonStreet'+ str(dt.now().microsecond))
Input
madison_street
Output
MadisonStreet715882
Feature Layer Collection by arcgis_python
Last Modified: June 13, 2019
0 comments, 0 views
Input
madison_street_lyr = madison_street.layers[0]
Input
stat_1 = madison_street_lyr.query(where='1=1',
                                  returnGeometry=False,
                                  spatialRel='esriSpatialRelIntersects',
                                  outFields='Length',
                                  outStatistics=[{"statisticType":"count","onStatisticField":"Length","outStatisticFieldName":"countField"},
                                              {"statisticType":"sum","onStatisticField":"Length","outStatisticFieldName":"sumField"},
                                              {"statisticType":"min","onStatisticField":"Length","outStatisticFieldName":"minField"},
                                              {"statisticType":"max","onStatisticField":"Length","outStatisticFieldName":"maxField"},
                                              {"statisticType":"avg","onStatisticField":"Length","outStatisticFieldName":"avgField"},
                                              {"statisticType":"stddev","onStatisticField":"Length","outStatisticFieldName":"stddevField"}])
Input
df1 = stat_1.sdf # field statistics
Input
len1 = df1.sumField.values[0]
Input
len1
Output
2.47538356

The length of Madison street is approximately 2.48 miles.

Input
madison_street_layer = madison_street.layers[0]
Input
madison_map = gis.map('Seattle')
madison_map
Output
Input
madison_map.add_layer(madison_street)

Create buffer of Madison street

We will apply a Buffer of 0.5 miles to Madison Street to determine the area that the bike route would service.

Input
buffer_street = create_buffers(madison_street_layer,
                               dissolve_type='Dissolve',
                               distances=[0.5],
                               ring_type='Rings',
                               units='Miles',
                               output_name="BufferMadisonStreet"+ str(dt.now().microsecond))
Input
buffer_street_layer = buffer_street.layers[0]
Input
buffer_street_map = gis.map('Seattle')
buffer_street_map
Output
Input
buffer_street_map.add_layer(buffer_street)

Now we repeat the process (selecting, buffering, and calculating the length of the segments) for 10th Ave E/Broadway E.

Get '10th Ave E'/'Broadway E' street using find_existing_locations tool

Input
broadway_ave = find_existing_locations(input_layers=[{'url': busy_streets_layer.url},
                                                     {'url': bike_route_neighbourhood.url, 'filter':"L_HOOD = 'CAPITOL HILL'"}],
                                       expressions=[{"operator":"","layer":0,"where":"ORD_STNAME = '10TH AVE E'"},
                                                    {"operator":"or","layer":0,"where":"OBJECTID = 789"},
                                                    {"operator":"or","layer":0,"where":"ORD_STNAME = 'BROADWAY'"},
                                                    {"operator":"or","layer":0,"where":"ORD_STNAME = 'BROADWAY E'"},
                                                    {"operator":"and","layer":0,"selectingLayer":1,"spatialRel":"withinDistance","distance":0.001,"units":"Feet"}],
                                       output_name='Broadway10thAVE'+ str(dt.now().microsecond))
Input
broadway_ave_layer = broadway_ave.layers[0]
Input
stat_2 = broadway_ave_layer.query(where='1=1',
                                  returnGeometry=False,
                                  spatialRel='esriSpatialRelIntersects',
                                  outFields='Length',
                                  outStatistics=[{"statisticType":"count","onStatisticField":"Length","outStatisticFieldName":"countField"},{"statisticType":"sum","onStatisticField":"Length","outStatisticFieldName":"sumField"},{"statisticType":"min","onStatisticField":"Length","outStatisticFieldName":"minField"},{"statisticType":"max","onStatisticField":"Length","outStatisticFieldName":"maxField"},{"statisticType":"avg","onStatisticField":"Length","outStatisticFieldName":"avgField"},{"statisticType":"stddev","onStatisticField":"Length","outStatisticFieldName":"stddevField"}])
Input
df2 = stat_2.sdf
Input
len2 = df2.sumField.values[0]
Input
len2
Output
1.98774174

The length of brodaway E street is approximately 2 miles.

Input
broadway_ave_map = gis.map('Seattle')
broadway_ave_map
Output
Input
broadway_ave_map.add_layer(broadway_ave)

Create buffer of '10th Ave E'/'Broadway E' street

Input
buffer_street_broadway = create_buffers(broadway_ave_layer,
                                        dissolve_type='Dissolve',
                                        distances=[0.5],
                                        ring_type='Rings',
                                        units='Miles',
                                        output_name="BufferBroadwayStreet"+ str(dt.now().microsecond))
Input
buffer_street_broadway_map = gis.map('Seattle')
buffer_street_broadway_map
Input
buffer_street_broadway_map.add_layer(buffer_street_broadway)

Repeat the process once more for 24th Ave and 23rd Ave.

Get '24th Ave' and '23rd Ave' street using find_existing_locations tool

Input
ave = find_existing_locations(input_layers=[{'url': busy_streets_layer.url},
                                            {'url': bike_route_neighbourhood.url, 'filter':"L_HOOD = 'CAPITOL HILL'"}],
                              expressions=[{"operator":"","layer":0,"where":"ORD_STNAME = '23RD AVE'"},
                                           {"operator":"or","layer":0,"where":"ORD_STNAME = '23RD AVE E'"},
                                           {"operator":"or","layer":0,"where":"ORD_STNAME = '24TH AVE E'"},
                                           {"operator":"or","layer":0,"where":"ORD_STNAME = 'TURNER WAY E'"},
                                           {"operator":"and","layer":0,"selectingLayer":1,"spatialRel":"withinDistance","distance":0.001,"units":"Feet"}],
                              output_name='23rd24thAve'+ str(dt.now().microsecond))
Input
ave_layer = ave.layers[0]
Input
stat_3 = ave_layer.query(where='1=1',
                         returnGeometry=False,
                         spatialRel='esriSpatialRelIntersects',
                         outFields='Length',
                         outStatistics=[{"statisticType":"count","onStatisticField":"Length","outStatisticFieldName":"countField"},
                                        {"statisticType":"sum","onStatisticField":"Length","outStatisticFieldName":"sumField"},
                                        {"statisticType":"min","onStatisticField":"Length","outStatisticFieldName":"minField"},
                                        {"statisticType":"max","onStatisticField":"Length","outStatisticFieldName":"maxField"},
                                        {"statisticType":"avg","onStatisticField":"Length","outStatisticFieldName":"avgField"},
                                        {"statisticType":"stddev","onStatisticField":"Length","outStatisticFieldName":"stddevField"}])
Input
df3 = stat_3.sdf
Input
len3 = df3.sumField.values[0]
Input
len3
Output
1.63694922

The length of 24th /23rd ave street is approximately 1.6 miles.

Input
ave_map = gis.map('Seattle')
ave_map
Output
Input
ave_map.add_layer(ave_layer)

Create buffer of 23th and 24th ave Street

Input
buffer_street_ave = create_buffers(ave_layer,
                                   dissolve_type='Dissolve',
                                   distances=[0.5],
                                   ring_type='Rings',
                                   units='Miles',
                                   output_name="Buffer23rd24thAveStreet"+ str(dt.now().microsecond))
Input
buffer_street_ave_map = gis.map('Seattle')
buffer_street_ave_map
Output
Input
buffer_street_ave_map.add_layer(buffer_street_ave)

Determine the effectiveness of the selected routes

Next we will compare the length of potential bike lanes within the neighborhood, to the length of all of the streets within the neighborhood. Select the streets (SEGMENT_TY=1) within Capitol Hill using Find Existing Locations.

Input
capitol_hill_streets = find_existing_locations(input_layers=[{'url': bike_route_streets.url},
                                                             {'url': bike_route_neighbourhood.url, 'filter':"L_HOOD = 'CAPITOL HILL'"}],
                                               expressions=[{"operator":"","layer":0,"where":"SEGMENT_TY = 1"},
                                                            {"operator":"and","layer":0,"selectingLayer":1,
                                                             "spatialRel":"withinDistance","distance":3.5,"units":"Feet"}],
                                               output_name='CapitolHillStreet'+ str(dt.now().microsecond))
Input
capitol_hill_lyr = capitol_hill_streets.layers[0]
Input
stat_4 = capitol_hill_lyr.query(where='1=1',
                                returnGeometry=False,
                                spatialRel='esriSpatialRelIntersects',
                                outFields='Length',
                                outStatistics=[{"statisticType":"count","onStatisticField":"Length",
                                               "outStatisticFieldName":"countField"},
                                               {"statisticType":"sum","onStatisticField":"Length","outStatisticFieldName":"sumField"},
                                               {"statisticType":"min","onStatisticField":"Length","outStatisticFieldName":"minField"},
                                               {"statisticType":"max","onStatisticField":"Length","outStatisticFieldName":"maxField"},
                                               {"statisticType":"avg","onStatisticField":"Length","outStatisticFieldName":"avgField"},
                                               {"statisticType":"stddev","onStatisticField":"Length","outStatisticFieldName":"stddevField"}])
Input
stat_4.sdf 
Output
avgField countField maxField minField stddevField sumField SHAPE
0 0.068718 1361 0.944354 0.002864 0.047955 93.52469 {'spatialReference': {'wkid': 2926, 'latestWki...
Input
df4 = stat_4.sdf 
Input
len4 = df4.sumField.values[0]
Input
len4
Output
93.52469044

The total length of roads within Capitol Hill is 93.5 miles.

Percentage of road length used for making bike lanes

Input
((len1 + len2 + len3)/len4)*100
Output
6.522421505228268

The percentage of road length being used within the neighborhood is a little over 6.5 percent which is more than the 5 percent in Portland.

Now we will use the Merge Layers tool to combine the three buffers into a single layer. We will have to run the tool twice to merge all three buffers.

Merge buffer of three streets to a single layer

Input
merge_madison_broadway_buffer = merge_layers(buffer_street,
                                             buffer_street_broadway,
                                             output_name='Merge1'+ str(dt.now().microsecond))
Input
merge_all_buffers = merge_layers(merge_madison_broadway_buffer,
                                 buffer_street_ave,
                                 output_name='Merge2'+ str(dt.now().microsecond))
Input
buffer_map = gis.map('Seattle')
buffer_map
Output
Input
buffer_map.add_layer(merge_all_buffers)

Dissolve boundaries to get single polygon

Next we will use Dissolve Boundaries tool on the merged buffers so that all of the buffers are in a single polygon.

Input
dissolve = dissolve_boundaries(merge_all_buffers,
                               output_name='DissolvedLayers'+ str(dt.now().microsecond))
Input
dissolve_map = gis.map('Seattle')
dissolve_map
Output
Input
dissolve_map.add_layer(dissolve)

We will use Overlay Layers tool to clip the dissolved buffers to the shape of Capitol Hill. The clipped buffers will be used to determine the number of Capitol Hill residents that live within 1/2 mile of the bike routes.

Overlay layers to the shape of Capitol Hill

Input
cliped_buffer = overlay_layers(dissolve,
                               bike_route_neighbourhood,
                               tolerance=0,
                               context={},
                               output_name="ClipedBuffer"+ str(dt.now().microsecond))
Input
cliped_buffer_layer = cliped_buffer.layers[0]
Input
cliped_buffer_map = gis.map('Seattle')
cliped_buffer_map
Output
Input
cliped_buffer_map.add_layer(cliped_buffer)

Enrich with population data

We will use the Enrich Layer tool to add population data to the clipped layer made in the previous step.

Input
clipped_enrich = enrich_layer(cliped_buffer_layer, 
                              analysis_variables=["AtRisk.TOTPOP_CY"], 
                              output_name='EnrichByPop'+ str(dt.now().microsecond))

Use Enrich Layer to add population data to Capitol Hill. Use the same population variable as in the previous step.

Input
capitolhill_enrich = enrich_layer(bike_route_neighbourhood,
                                  analysis_variables=["AtRisk.TOTPOP_CY"],
                                  output_name='CapitolhillEnrichedByPop'+ str(dt.now().microsecond))
Input
clip_lyr = clipped_enrich.layers[0]
Input
stat_5 = clip_lyr.query(where='1=1',
                        returnGeometry=False,
                        spatialRel='esriSpatialRelIntersects',
                        outFields='TOTPOP_CY',
                        outStatistics=[{"statisticType":"count","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"countField"},
                                       {"statisticType":"sum","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"sumField"},
                                       {"statisticType":"min","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"minField"},
                                       {"statisticType":"max","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"maxField"},
                                       {"statisticType":"avg","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"avgField"},
                                       {"statisticType":"stddev","onStatisticField":"TOTPOP_CY","outStatisticFieldName":"stddevField"}])
Input
stat_5.sdf # population statistics
Output
avgField countField maxField minField stddevField sumField SHAPE
0 9886.2 5 27189 1263 10397.063081 49431 {'spatialReference': {'wkid': 2926, 'latestWki...

Approximately 49,431 Capitol Hill residents are within ½ mile of the bike routes, which constitutes approximately 99.3% of the neighborhood.

Data Resources

Shapefile Source
Street_Network_Database City of Seattle. (2014). Street Network Database. data.seattle.gov.
Downloaded November, 2015 from data.seattle.gov
BLDG2012_PLGN City of Seattle. (2014). BLDG2012. data.seattle.gov.
Downloaded November, 2015 from data.seattle.gov
Neighborhoods City of Seattle. (2014). Neighborhoods. data.seattle.gov.
Downloaded November, 2015 from data.seattle.gov
City_of_Seattle_Zoning City of Seattle. (2014). City of Seattle Zoning. data.seattle.gov.
Downloaded November, 2015 from data.seattle.gov
SAEP_Census_Block_Groups Washington Office of Financial Management. (2015).
SAEP Census Block Groups. Small Areas Estimate Program.
Downloaded December, 2015 from ArcGIS Open Data

Summary of methods used

This case study demonstrates the following analytical methods that can be adapted to many different application areas.

Method Generic Question Examples
Attribute Query Which features have the characteristics I'm interested in? Which features have more than 50,000 people and median annual incomes larger than $50,000?
Which hospitals have readmission rates larger than 10 percent?
Location Query Which features are in my area of interest? Which hospitals are within the city limits?
Which species sightings are within 10 feet of a stream? Which roads intersect with Main Street?
Buffer features What is within a desired distance of my feature? How often are deer found within 100 feet of a road?
Which cities felt the earthquake at moderate intensity or higher?
Data Enrich Which attributes give more information about an area? What is the population in each ZIP Code?

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.