From e8ed6c61af0daaf67e81cafb136aee125133699e Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Fri, 19 Dec 2014 15:25:37 -0800 Subject: [PATCH] Function that connects nodes and their distances Connect adjacent intersection nodes as pairs and calculate the distance from pair to pair. --- pandana/loaders/osm.py | 68 ++++++++++++++++++++++ pandana/loaders/tests/test_osm.py | 93 +++++++++++++++++++++++++------ 2 files changed, 144 insertions(+), 17 deletions(-) diff --git a/pandana/loaders/osm.py b/pandana/loaders/osm.py index f6ada942..b7fd5a08 100644 --- a/pandana/loaders/osm.py +++ b/pandana/loaders/osm.py @@ -2,9 +2,13 @@ Tools for creating Pandana networks from Open Street Map. """ +from itertools import islice, izip + import pandas as pd import requests +from ..utils import great_circle_dist as gcd + uninteresting_tags = { 'source', 'source_ref', @@ -190,3 +194,67 @@ def intersection_nodes(waynodes): """ counts = waynodes.node_id.value_counts() return set(counts[counts > 1].index.values) + + +def node_pairs(nodes, ways, waynodes, two_way=True): + """ + Create a table of node pairs with the distances between them. + + Parameters + ---------- + nodes : pandas.DataFrame + Must have 'lat' and 'lon' columns. + ways : pandas.DataFrame + Table of way metadata. + waynodes : pandas.DataFrame + Table linking way IDs to node IDs. Way IDs should be in the index, + with a column called 'node_ids'. + two_way : bool, optional + Whether the routes are two-way. If True, node pairs will only + occur once. + + Returns + ------- + pairs : pandas.DataFrame + Will have columns of 'from_id', 'to_id', and 'distance'. + The index will be a MultiIndex of (from id, to id). + The distance metric is in meters. + + """ + pairwise = lambda l: izip(islice(l, 0, len(l)), islice(l, 1, None)) + intersections = intersection_nodes(waynodes) + waymap = waynodes.groupby(level=0, sort=False) + pairs = [] + + for id, row in ways.iterrows(): + nodes_in_way = waymap.get_group(id).node_id.values + nodes_in_way = filter(lambda x: x in intersections, nodes_in_way) + + if len(nodes_in_way) < 2: + # no nodes to connect in this way + continue + + for from_node, to_node in pairwise(nodes_in_way): + fn = nodes.loc[from_node] + tn = nodes.loc[to_node] + + distance = gcd(fn.lat, fn.lon, tn.lat, tn.lon) + + pairs.append({ + 'from_id': from_node, + 'to_id': to_node, + 'distance': distance + }) + + if not two_way: + pairs.append({ + 'from_id': to_node, + 'to_id': from_node, + 'distance': distance + }) + + pairs = pd.DataFrame.from_records(pairs) + pairs.index = pd.MultiIndex.from_arrays( + [pairs.from_id.values, pairs.to_id.values]) + + return pairs diff --git a/pandana/loaders/tests/test_osm.py b/pandana/loaders/tests/test_osm.py index 5aa740d4..60adc652 100644 --- a/pandana/loaders/tests/test_osm.py +++ b/pandana/loaders/tests/test_osm.py @@ -1,3 +1,4 @@ +import numpy.testing as npt import pandas.util.testing as pdt import pytest @@ -5,27 +6,45 @@ @pytest.fixture(scope='module') -def bbox(): +def bbox1(): # Intersection of Telegraph and Haste in Berkeley + # Sample query: http://overpass-turbo.eu/s/6AK return 37.8659303546, -122.2588003879, 37.8661598571, -122.2585062512 @pytest.fixture(scope='module') -def query_data(bbox): - return osm.make_osm_query(*bbox) +def bbox2(): + # Telegraph Channing to Durant in Berkeley + # Sample query: http://overpass-turbo.eu/s/6B0 + return 37.8668405874, -122.2590948685, 37.8679028054, -122.2586363885 @pytest.fixture(scope='module') -def dataframes(query_data): - return osm.parse_osm_query(query_data) +def query_data1(bbox1): + return osm.make_osm_query(*bbox1) -def test_make_osm_query(query_data): - assert isinstance(query_data, dict) - assert len(query_data['elements']) == 24 +@pytest.fixture(scope='module') +def query_data2(bbox2): + return osm.make_osm_query(*bbox2) + + +@pytest.fixture(scope='module') +def dataframes1(query_data1): + return osm.parse_osm_query(query_data1) + + +@pytest.fixture(scope='module') +def dataframes2(query_data2): + return osm.parse_osm_query(query_data2) + + +def test_make_osm_query(query_data1): + assert isinstance(query_data1, dict) + assert len(query_data1['elements']) == 24 assert len( - [e for e in query_data['elements'] if e['type'] == 'node']) == 22 - assert len([e for e in query_data['elements'] if e['type'] == 'way']) == 2 + [e for e in query_data1['elements'] if e['type'] == 'node']) == 22 + assert len([e for e in query_data1['elements'] if e['type'] == 'way']) == 2 def test_process_node(): @@ -89,25 +108,65 @@ def test_process_way(): assert waynodes == expected_waynodes -def test_parse_osm_query(dataframes): - nodes, ways, waynodes = dataframes +def test_parse_osm_query(dataframes1): + nodes, ways, waynodes = dataframes1 assert len(nodes) == 22 assert len(ways) == 2 assert len(waynodes.index.unique()) == 2 -def test_ways_in_bbox(bbox, dataframes): - nodes, ways, waynodes = osm.ways_in_bbox(*bbox) - exp_nodes, exp_ways, exp_waynodes = dataframes +def test_ways_in_bbox(bbox1, dataframes1): + nodes, ways, waynodes = osm.ways_in_bbox(*bbox1) + exp_nodes, exp_ways, exp_waynodes = dataframes1 pdt.assert_frame_equal(nodes, exp_nodes) pdt.assert_frame_equal(ways, exp_ways) pdt.assert_frame_equal(waynodes, exp_waynodes) -def test_intersection_nodes(dataframes): - _, _, waynodes = dataframes +def test_intersection_nodes1(dataframes1): + _, _, waynodes = dataframes1 intersections = osm.intersection_nodes(waynodes) assert intersections == {53041093} + + +def test_intersection_nodes2(dataframes2): + _, _, waynodes = dataframes2 + intersections = osm.intersection_nodes(waynodes) + + assert intersections == {53099275, 53063555} + + +def test_node_pairs_two_way(dataframes2): + nodes, ways, waynodes = dataframes2 + pairs = osm.node_pairs(nodes, ways, waynodes) + + assert len(pairs) == 1 + + fn = 53063555 + tn = 53099275 + + pair = pairs.loc[(fn, tn)] + + assert pair.from_id == fn + assert pair.to_id == tn + npt.assert_allclose(pair.distance, 101.20535797547758) + + +def test_node_pairs_one_way(dataframes2): + nodes, ways, waynodes = dataframes2 + pairs = osm.node_pairs(nodes, ways, waynodes, two_way=False) + + assert len(pairs) == 2 + + n1 = 53063555 + n2 = 53099275 + + for p1, p2 in [(n1, n2), (n2, n1)]: + pair = pairs.loc[(p1, p2)] + + assert pair.from_id == p1 + assert pair.to_id == p2 + npt.assert_allclose(pair.distance, 101.20535797547758)