def test_synthetic_network_with_custom_stops(): # Load in the GeoJSON as a JSON and convert to a dictionary geojson_path = fixture('synthetic_east_bay.geojson') with open(geojson_path, 'r') as gjf: reference_geojson = json.load(gjf) # Add in specific, custom stops under new properties key custom_stops = [[-122.29225158691406, 37.80876678753658], [-122.28886127471924, 37.82341261847038], [-122.2701072692871, 37.83005652796547]] reference_geojson['features'][0]['properties']['stops'] = custom_stops G1 = load_synthetic_network_as_graph(reference_geojson) # Sanity check the outputs against the custom stops input assert len(list(G1.nodes())) == (len(custom_stops) + 2) assert len(list(G1.edges())) == (len(custom_stops) + 1) # Go back to the GeoJSON and set optional bidirectional flag reference_geojson['features'][0]['properties']['bidirectional'] = True G2 = load_synthetic_network_as_graph(reference_geojson) # We re-use the same stop nodes for both directions nodes = list(G2.nodes()) assert len(nodes) == (len(custom_stops) + 2) # Double the number of edges as before edges = list(G2.edges()) assert len(edges) == (len(custom_stops) + 1) * 2 # But now, by asking for a bidirectional graph, we can assert strong assert nx.is_strongly_connected(G2)
def test_conversion_to_graph_tool(): # To be quick, load in the synthetic graph geojson geojson_path = fixture('synthetic_san_bruno.geojson') with open(geojson_path, 'r') as gjf: reference_geojson = json.load(gjf) # Then load it onto the graph, as well G = load_synthetic_network_as_graph(reference_geojson) # For now, just run the operation and ensure it completes as intended gtG = nx_to_gt(G) # Make sure that the result is indeed a graph-tool Graph class object gt = _import_graph_tool() assert isinstance(gtG, gt.Graph) # Also make sure that the attributes for all parameters have been preserved assert set(gtG.vp.keys()) == set( ('boarding_cost', 'modes', 'id', 'x', 'y')) assert set(gtG.gp.keys()) == set(('crs', 'name')) assert set(gtG.ep.keys()) == set(('length', 'mode')) # Make sure the edge count is the same as the NetworkX graph nx_edges_len = len([x for x in G.edges()]) gt_edges_len = len([x for x in gtG.ep['length']]) assert nx_edges_len == gt_edges_len # And same for the vertices count nx_nodes_len = len([x for x in G.nodes()]) gt_nodes_len = len([x for x in gtG.vp['id']]) assert nx_nodes_len == gt_nodes_len
def test_synthetic_network(): # Load in the GeoJSON as a JSON and convert to a dictionary geojson_path = fixture('synthetic_east_bay.geojson') with open(geojson_path, 'r') as gjf: reference_geojson = json.load(gjf) G1 = load_synthetic_network_as_graph(reference_geojson) # This fixture gets broken into 15 chunks, so 15 + 1 = 16 nodes = list(G1.nodes()) assert len(nodes) == 16 # And since it is one-directional, it gets the same edges as chunks edges = list(G1.edges()) assert len(edges) == 15 # Since this is a one-way graph, with no other context, the # graph will be weakly connected assert nx.is_strongly_connected(G1) is False # Go back to the GeoJSON and set optional bidirectional flag for i in range(len(reference_geojson['features'])): reference_geojson['features'][i]['properties']['bidirectional'] = True G2 = load_synthetic_network_as_graph(reference_geojson) # We re-use the same stop nodes for both directions nodes = list(G2.nodes()) assert len(nodes) == 16 # Double the number of edges as before edges = list(G2.edges()) assert len(edges) == 15 * 2 # But now, by asking for a bidirectional graph, we can assert strong assert nx.is_strongly_connected(G2)
def test_feed_to_graph_path(): path_1 = fixture('caltrain-2017-07-24.zip') feed_1 = get_representative_feed(path_1) start = 7 * 60 * 60 end = 10 * 60 * 60 G = load_feed_as_graph(feed_1, start, end, 'foo') # We should assume all routes do not have segments that exceed some # given length (measured in seconds) max_reasonable_segment_length = 60 * 60 _check_unreasonable_lengths(G, max_reasonable_segment_length) # Sanity check that the number of nodes and edges go up orig_node_len = len(G.nodes()) orig_edge_len = len(G.edges()) orig_node_list = list(G.nodes()) path_2 = fixture('samtrans-2017-11-28.zip') feed_2 = get_representative_feed(path_2) G = load_feed_as_graph(feed_2, start, end, 'bar', G) assert isinstance(G, nx.MultiDiGraph) _check_unreasonable_lengths(G, max_reasonable_segment_length) # Part 2 of sanity check that the number of nodes and edges go up node_len_2 = len(G.nodes()) edge_len_2 = len(G.edges()) assert node_len_2 > orig_node_len assert edge_len_2 > orig_edge_len connector_edge_count = 0 for from_node, to_node, edge in G.edges(data=True): # Make sure that a length measure has been calculated for each # edge in the resulting graph, also sanity check that all are # positive values assert 'length' in edge.keys() assert isinstance(edge['length'], float) assert edge['length'] >= 0 # Also, we should also make sure that edges were also created that # connect the two feeds from_orig_a = from_node in orig_node_list from_orig_b = to_node in orig_node_list one_valid_fr = from_orig_a and (not from_orig_b) one_valid_to = (not from_orig_a) and from_orig_b if one_valid_fr or one_valid_to: connector_edge_count += 1 # We know that there should be 9 new edges that are created to connect # the two GTFS feeds in the joint graph assert connector_edge_count == 9 # Now reload in the synthetic graph geojson geojson_path = fixture('synthetic_san_bruno.geojson') with open(geojson_path, 'r') as gjf: reference_geojson = json.load(gjf) # Then load it onto the graph, as well G = load_synthetic_network_as_graph(reference_geojson, existing_graph=G) # And make sure it connected correctly node_len_3 = len(G.nodes()) edge_len_3 = len(G.edges()) assert node_len_3 - node_len_2 == 74 assert edge_len_3 - edge_len_2 == 80
def test_synthetic_stop_assignment_adjustment(): target_stop_dist_override = 50 # meters # First load original San Bruno line geojson_path_1 = fixture('synthetic_san_bruno.geojson') with open(geojson_path_1, 'r') as gjf: reference_geojson_sb1 = json.load(gjf) # We want there to be a lot of stops along # both of the routes (reference_geojson_sb1['features'][0]['properties'] ['stop_distance_distribution']) = target_stop_dist_override G1 = load_synthetic_network_as_graph(reference_geojson_sb1) G1_copy = G1.copy() # Now load in the extension further down to Burlingame geojson_path_2 = fixture('synthetic_san_bruno_addition.geojson') with open(geojson_path_2, 'r') as gjf: reference_geojson_sb2 = json.load(gjf) # Also override this stop distance distribution value (reference_geojson_sb2['features'][0]['properties'] ['stop_distance_distribution']) = target_stop_dist_override G2 = load_synthetic_network_as_graph(reference_geojson_sb2, existing_graph=G1_copy) assert len(G1.nodes()) == 293 assert len(G2.nodes()) == 495 # Now remove nodes that were in the original graph for i in G1.nodes(): G2.remove_node(i) # The new graph should now be just (495 - 293) nodes assert len(G2.nodes()) == 495 - 293 # We will use this as a reference distance limit for checking # nearest points to ensure there are some adjusted stops matches dist_limit = target_stop_dist_override * 0.5 # Then reassess matches match_count = 0 for i2, n2 in G2.nodes(data=True): found_match = False for i1, n1 in G1.nodes(data=True): # First check if exact match x_match = n1['x'] == n2['x'] y_match = n1['y'] == n2['y'] if x_match and y_match: found_match = True continue # Then check if near enough gc_dist = great_circle_vec(n1['x'], n1['y'], n2['x'], n2['y']) if gc_dist <= dist_limit: found_match = True # Stop looping once we find at least one match if found_match: break # If one constraint satisfied, add to tally if found_match: match_count += 1 # This value will be high because the stop distance for the addition is # set at 50 meters, which means that we expect there to be a lot of # stops along the overlapping segment assert match_count == 8