Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method to weigh boulevards for intersection density calculations #49

Closed
kelanstoy opened this issue Apr 26, 2017 · 8 comments
Closed

Method to weigh boulevards for intersection density calculations #49

kelanstoy opened this issue Apr 26, 2017 · 8 comments

Comments

@kelanstoy
Copy link

Problem description (what did you do, what did you expect to happen, and what actually happened)

When calculating intersection density for an area, I would like to be able to identify 'boulevards' - or any sort of split roadway in the network - and either consolidate intersections at these locations into 1 node or reduce the 'weight' of the intersection such that all locations where a road crosses a boulevard only count as 1 intersection in the density calculation.

At first, I thought I could use the one-way attribute of edges in OSM networks to identify nodes that intersect with boulevards, and reduce their weight in the intersection density count by manually counting intersection density using a spatial intersection in geopandas. (see example 1 below, of downtown Merced, CA). However, this method would undercount valid one-way street intersections, such as those that exist when two one-way streets intersect (see example 2 of downtown San Francisco).

My next thought was that I could use the length attribute of edges to identify nodes that are shorter than a certain threshold and also connected to a one-way street. I then could either, reduce the weight applied to nodes that touch these edges based on how many one-way edges connect to it (two intersection boulevards should reduce intersection weight to 0.25, for example).

I'm wondering if anyone has already thought about a method for cleaning up intersection density count for the OSMnx library, and if code already exists within the repo for systematically addressing how to define an 'intersection' for the purpose of generating intersection density stats. Thanks!

Code that reproduces the issue

import osmnx as ox
%matplotlib inline

# Example 1: Downtown Merced, CA network with one boulevard
location_point = (37.3022, -120.4830)
G = ox.graph_from_point(location_point, distance=500)
# highlight one-way streets
ec = ['r' if data['oneway'] else 'b' for u, v, key, data in G.edges(keys=True, data=True)]
fig, ax = ox.plot_graph(G, node_color='w', node_edgecolor='k', node_size=5, node_zorder=3, 
                           edge_color=ec, edge_linewidth=1.5, edge_alpha=0.5)

image

# Example 2: Downtown San Francisco, CA network with many one-way streets
location_point = (37.782961, -122.409978)
G = ox.graph_from_point(location_point, distance=500)
# highlight one-way streets
ec = ['r' if data['oneway'] else 'b' for u, v, key, data in G.edges(keys=True, data=True)]
fig, ax = ox.plot_graph(G, node_color='w', node_edgecolor='k', node_size=5, node_zorder=3, 
                           edge_color=ec, edge_linewidth=1.5, edge_alpha=0.5)

image

@gboeing
Copy link
Owner

gboeing commented Apr 26, 2017

This is primarily an issue with the data model that OSM (and TIGER/Line shapefiles, etc) use to represent street networks. Generally, divided roads get represented as separate centerline edges. I've floated some ideas for dealing with this from a geometric perspective (see #12) but dealing with it topologically is a different story and probably outside the scope of OSMnx for now.

@kuanb
Copy link
Contributor

kuanb commented Apr 26, 2017

Just to add my solution here for anyone else interested in dealing with this, I set up a simple for loop that (hopefully) should not get "out of control" with larger datasets.

Here's what the results look like with a small subset of data:
image

The gist is here with the most critical bit of code in lines 40 - up.

Basically, I create a boolean column and for loop through the geodataframe, marking "off" any intersections whose buffer intersects the one being currently reviewed. As I move through the whole geodataframe, I skip any rows that have already been marked as "off," thereby avoiding repeating the intersection test on any of an already reviewed intersection's "siblings."

# set a bool column to track which intersections to use
gdf_nodes['use'] = True

list_of_nodes = gdf_nodes.index.values

for node_id in list_of_nodes:
    current = gdf_nodes.loc[node_id]

    # only proceed if has not already been marked as False (don't use)
    if current['use']:
        ints_with = gdf_nodes.intersects(current.geometry)

        # review intersecting buffered nodes
        sub_gdf = gdf_nodes[ints_with]
        sub_gdf_sans_current = sub_gdf[sub_gdf.index != node_id]

        # get the indexes of other rows and 'turn them off'
        turn_these_off = sub_gdf_sans_current.index.values
        for sub_node_id in turn_these_off:
            gdf_nodes.loc[sub_node_id, 'use'] = False

# here is a dataframe of unique geometries
cleaned_ints = gdf_nodes[gdf_nodes['use'] == True]

@gboeing
Copy link
Owner

gboeing commented Apr 27, 2017

@kelanstoy @kuanb here's a simple solution using the geometric dissolve method I suggested in #12:

import osmnx as ox, matplotlib.pyplot as plt, numpy as np, geopandas as gpd

# get a street network and plot it with all edge intersections
address = '2700 Shattuck Ave, Berkeley, CA'
G = ox.graph_from_address(address, network_type='drive', distance=750)
G_proj = ox.project_graph(G)
fig, ax = ox.plot_graph(G_proj, fig_height=10, node_color='orange', node_size=30, 
                        node_zorder=2, node_edgecolor='k')

# clean up intersections by buffering 15 meters, dissolving, and using centroid
gdf_nodes = ox.graph_to_gdfs(G_proj, edges=False)
buff = gdf_nodes.buffer(15)
unified = gpd.GeoDataFrame(geometry=list(buff.unary_union))
intersections = unified.centroid

# plot the cleaned-up intersections
fig, ax = ox.plot_graph(G_proj, fig_height=10, show=False, close=False, node_alpha=0)
points = np.array([point.xy for point in intersections])
ax.scatter(x=points[:,0], y=points[:,1], zorder=2, color='#66ccff', edgecolors='k')
plt.show()

...and you get something like this: before (left) and after (right):

It doesn't fix the topology (i.e., reconnect all the streets to the new intersection point), but it improves the intersection density counts.

@kuanb
Copy link
Contributor

kuanb commented Apr 27, 2017

Fantastic!

So a plain language interpretation of your methodology:

  1. Convert network to GeoDataFrame
  2. Create a GeoSeries of buffered nodes (in your case, 15 meters)
  3. Perform a unary union on the GeoSeries (I presume a MultiPolygon is returned)
  4. Extract all centroids from aforementioned MultPolygon

@kuanb
Copy link
Contributor

kuanb commented Apr 27, 2017

Update - Geoff's method took under 2 minutes whereas for the for loop method took over 30 minutes!

@gboeing
Copy link
Owner

gboeing commented Apr 27, 2017

I think this solution is fast, powerful, and useful enough that I'll bake it into OSMnx directly. It'll be pushed shortly and released in the upcoming minor version.

gboeing added a commit that referenced this issue Apr 27, 2017
@gboeing
Copy link
Owner

gboeing commented Apr 27, 2017

@kuanb @kelanstoy see OSMnx's new clean_intersections function and this example notebook demonstrating it.

@kuanb
Copy link
Contributor

kuanb commented Apr 27, 2017

Nice, thanks!!

gboeing added a commit that referenced this issue Jul 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants