plt.cla() plt.clf() plot.plot_nX(G_decomposed, path='graph_decomposed.png', dpi=150) plt.cla() plt.clf() G_dual = graphs.nX_to_dual(G_simple) plot.plot_nX_primal_or_dual(G_simple, G_dual, 'graph_dual.png', dpi=150) # graph cleanup examples osm_json = mock.mock_osm_data() G_messy = graphs.nX_from_osm(osm_json=osm_json) G_messy = graphs.nX_wgs_to_utm(G_messy) G_messy = graphs.nX_simple_geoms(G_messy) G_messy = graphs.nX_remove_filler_nodes(G_messy) G_messy = graphs.nX_remove_dangling_nodes(G_messy) G_messy_decomp = graphs.nX_decompose(G_messy, 20) plt.cla() plt.clf() plot.plot_nX(G_messy_decomp, 'graph_messy.png', dpi=150, figsize=(20, 20)) # spatial cleanup G_clean_spatial = graphs.nX_consolidate_spatial(G_messy_decomp) plt.cla() plt.clf() plot.plot_nX(G_clean_spatial, 'graph_clean_spatial.png', dpi=150, figsize=(20, 20))
def test_nX_consolidate(): # create a test graph G = nx.Graph() nodes = [ (0, {'x': 700620, 'y': 5719720}), (1, {'x': 700620, 'y': 5719700}), (2, {'x': 700660, 'y': 5719720}), (3, {'x': 700660, 'y': 5719700}), (4, {'x': 700660, 'y': 5719660}), (5, {'x': 700700, 'y': 5719800}), (6, {'x': 700720, 'y': 5719800}), (7, {'x': 700700, 'y': 5719720}), (8, {'x': 700720, 'y': 5719720}), (9, {'x': 700700, 'y': 5719700}), (10, {'x': 700720, 'y': 5719700}), (11, {'x': 700700, 'y': 5719620}), (12, {'x': 700720, 'y': 5719620}), (13, {'x': 700760, 'y': 5719760}), (14, {'x': 700800, 'y': 5719760}), (15, {'x': 700780, 'y': 5719720}), (16, {'x': 700780, 'y': 5719700}), (17, {'x': 700840, 'y': 5719720}), (18, {'x': 700840, 'y': 5719700})] edges = [ (0, 2), (0, 2), (1, 3), (2, 3), (2, 7), (3, 4), (3, 9), (5, 7), (6, 8), (7, 8), (7, 9), (8, 10), (8, 15), (9, 11), (9, 10), (10, 12), (10, 16), (13, 14), (13, 15), (14, 15), (15, 16), (15, 17), (16, 18) ] G.add_nodes_from(nodes) G.add_edges_from(edges) G = graphs.nX_simple_geoms(G) # behaviour confirmed visually # from cityseer.util import plot # plot.plot_nX(G, labels=True) # simplify first to test lollipop self-loop from node 15 G = graphs.nX_remove_filler_nodes(G) # plot.plot_nX(G, labels=True, figsize=(10, 10), dpi=150) G_merged_parallel = graphs.nX_consolidate_parallel(G, buffer_dist=25) # plot.plot_nX(G_merged_parallel, labels=True, figsize=(10, 10), dpi=150) assert G_merged_parallel.number_of_nodes() == 8 assert G_merged_parallel.number_of_edges() == 8 node_coords = [] for n, d in G_merged_parallel.nodes(data=True): node_coords.append((d['x'], d['y'])) assert node_coords == [ (700660, 5719660), (700620.0, 5719710.0), (700660.0, 5719710.0), (700710.0, 5719800.0), (700710.0, 5719710.0), (700710.0, 5719620.0), (700780.0, 5719710.0), (700840.0, 5719710.0)] edge_lens = [] for s, e, d in G_merged_parallel.edges(data=True): edge_lens.append(d['geom'].length) assert edge_lens == [50.0, 40.0, 50.0, 90.0, 90.0, 70.0, 147.70329614269008, 60.0] G_merged_spatial = graphs.nX_consolidate_spatial(G, buffer_dist=25) # plot.plot_nX(G_merged_spatial, labels=True) assert G_merged_spatial.number_of_nodes() == 8 assert G_merged_spatial.number_of_edges() == 8 node_coords = [] for n, d in G_merged_spatial.nodes(data=True): node_coords.append((d['x'], d['y'])) assert node_coords == [ (700660, 5719660), (700620.0, 5719710.0), (700660.0, 5719700.0), (700710.0, 5719800.0), (700710.0, 5719710.0), (700710.0, 5719620.0), (700780.0, 5719720.0), (700840.0, 5719710.0)] edge_lens = [] for s, e, d in G_merged_spatial.edges(data=True): edge_lens.append(d['geom'].length) assert edge_lens == [40.0, 41.23105625617661, 50.99019513592785, 90.0, 90.0, 70.71067811865476, 129.4427190999916, 60.8276253029822] # visual tests on OSM data # TODO: can furnish more extensive tests, e.g. to verify veracity of new geoms osm_json = mock.mock_osm_data() g_1 = graphs.nX_from_osm(osm_json=osm_json) osm_json_alt = mock.mock_osm_data(alt=True) g_2 = graphs.nX_from_osm(osm_json=osm_json_alt) for g in [g_1, g_2]: G_utm = graphs.nX_wgs_to_utm(g) G = graphs.nX_simple_geoms(G_utm) G = graphs.nX_remove_filler_nodes(G) G = graphs.nX_remove_dangling_nodes(G) # G_decomp = graphs.nX_decompose(G, 25) # from cityseer.util import plot # plot.plot_nX(G, figsize=(10, 10), dpi=150) G_spatial = graphs.nX_consolidate_spatial(G, buffer_dist=15) # plot.plot_nX(G_spatial, figsize=(10, 10), dpi=150) G_parallel = graphs.nX_consolidate_parallel(G, buffer_dist=14)
def test_nX_decompose(): # check that missing geoms throw an error G = mock.mock_graph() with pytest.raises(KeyError): graphs.nX_decompose(G, 20) # check that non-LineString geoms throw an error G = mock.mock_graph() for s, e in G.edges(): G[s][e]['geom'] = geometry.Point([G.nodes[s]['x'], G.nodes[s]['y']]) with pytest.raises(TypeError): graphs.nX_decompose(G, 20) # test decomposition G = mock.mock_graph() G = graphs.nX_simple_geoms(G) # first clean the graph to strip disconnected looping component # this gives a start == end node situation for testing G_simple = graphs.nX_remove_filler_nodes(G) G_decompose = graphs.nX_decompose(G_simple, 20) # from cityseer.util import plot # plot.plot_nX(G_simple, labels=True) # plot.plot_nX(G_decompose) assert nx.number_of_nodes(G_decompose) == 661 assert nx.number_of_edges(G_decompose) == 682 # check that total lengths are the same G_lens = 0 for s, e, e_data in G_simple.edges(data=True): G_lens += e_data['geom'].length G_d_lens = 0 for s, e, e_data in G_decompose.edges(data=True): G_d_lens += e_data['geom'].length assert np.allclose(G_lens, G_d_lens, atol=0.001, rtol=0) # check that all ghosted edges have one or two edges for n, n_data in G_decompose.nodes(data=True): if 'ghosted' in n_data and n_data['ghosted']: nbs = list(G_decompose.neighbors(n)) assert len(nbs) == 1 or len(nbs) == 2 # check that all new nodes are ghosted for n, n_data in G_decompose.nodes(data=True): if not G_simple.has_node(n): assert n_data['ghosted'] # check that geoms are correctly flipped G_forward = mock.mock_graph() G_forward = graphs.nX_simple_geoms(G_forward) G_forward_decompose = graphs.nX_decompose(G_forward, 20) G_backward = mock.mock_graph() G_backward = graphs.nX_simple_geoms(G_backward) for i, (s, e, d) in enumerate(G_backward.edges(data=True)): # flip each third geom if i % 3 == 0: flipped_coords = np.fliplr(d['geom'].coords.xy) G[s][e]['geom'] = geometry.LineString([[x, y] for x, y in zip(flipped_coords[0], flipped_coords[1])]) G_backward_decompose = graphs.nX_decompose(G_backward, 20) for n, d in G_forward_decompose.nodes(data=True): assert d['x'] == G_backward_decompose.nodes[n]['x'] assert d['y'] == G_backward_decompose.nodes[n]['y'] # test that geom coordinate mismatch throws an error G = mock.mock_graph() for k in ['x', 'y']: for n in G.nodes(): G.nodes[n][k] = G.nodes[n][k] + 1 break with pytest.raises(KeyError): graphs.nX_decompose(G, 20)
def test_nX_remove_filler_nodes(): # test that redundant intersections are removed, i.e. where degree == 2 G = mock.mock_graph() G = graphs.nX_simple_geoms(G) G_messy = make_messy_graph(G) # from cityseer.util import plot # plot.plot_nX(G_messy, labels=True) # simplify and test G_simplified = graphs.nX_remove_filler_nodes(G_messy) # plot.plot_nX(G_simplified, labels=True) # check that the simplified version matches the original un-messified version # but note the simplified version will have the disconnected loop of 52-53-54-55 now condensed to only #52 g_nodes = set(G.nodes) g_nodes = g_nodes.difference([53, 54, 55]) assert list(g_nodes).sort() == list(G_simplified.nodes).sort() g_edges = set(G.edges) g_edges = g_edges.difference([(52, 53), (53, 54), (54, 55), (52, 55)]) # condensed edges g_edges = g_edges.union([(52, 52)]) # the new self loop assert list(g_edges).sort() == list(G_simplified.edges).sort() # check the integrity of the edges for s, e, d in G_simplified.edges(data=True): # ignore the new self-looping disconnected edge if s == 52 and e == 52: continue assert G_simplified[s][e]['geom'].length == G[s][e]['geom'].length # manually check that the new self-looping edge is equal in length to its original segments l = 0 for s, e in [(52, 53), (53, 54), (54, 55), (52, 55)]: l += G[s][e]['geom'].length assert l == G_simplified[52][52]['geom'].length # check that all nodes still have 'x' and 'y' keys for n, d in G_simplified.nodes(data=True): assert 'x' in d assert 'y' in d # lollipop test - where looping component (all nodes == degree 2) suspend off a node with degree > 2 # lollipops are handled slightly differently from isolated looping components (all nodes == degree 2) # there are no lollipops in the mock graph, so create one here # generate graph G_lollipop = nx.Graph() nodes = [ (1, {'x': 700400, 'y': 5719750}), (2, {'x': 700400, 'y': 5719650}), (3, {'x': 700500, 'y': 5719550}), (4, {'x': 700400, 'y': 5719450}), (5, {'x': 700300, 'y': 5719550}) ] G_lollipop.add_nodes_from(nodes) edges = [ (1, 2), (2, 3), (3, 4), (4, 5), (5, 2) ] G_lollipop.add_edges_from(edges) # add edge geoms G_lollipop = graphs.nX_simple_geoms(G_lollipop) # flip some geometry G_lollipop[2][5]['geom'] = geometry.LineString(G_lollipop[2][5]['geom'].coords[::-1]) # simplify G_lollipop_simpl = graphs.nX_remove_filler_nodes(G_lollipop) # check integrity of graph assert nx.number_of_nodes(G_lollipop_simpl) == 2 assert nx.number_of_edges(G_lollipop_simpl) == 2 # geoms should still be same cumulative length before_len = 0 for s, e, d in G_lollipop.edges(data=True): before_len += d['geom'].length after_len = 0 for s, e, d in G_lollipop_simpl.edges(data=True): after_len += d['geom'].length assert before_len == after_len # end point of stick should match start / end point of lollipop assert G_lollipop_simpl[1][2]['geom'].coords[-1] == G_lollipop_simpl[2][2]['geom'].coords[0] # start and end point of lollipop should match assert G_lollipop_simpl[2][2]['geom'].coords[0] == G_lollipop_simpl[2][2]['geom'].coords[-1] # check that missing geoms throw an error G_k = G_messy.copy() for i, (s, e) in enumerate(G_k.edges()): if i % 2 == 0: del G_k[s][e]['geom'] with pytest.raises(KeyError): graphs.nX_remove_filler_nodes(G_k) # check that non-LineString geoms throw an error G_k = G_messy.copy() for s, e in G_k.edges(): G_k[s][e]['geom'] = geometry.Point([G_k.nodes[s]['x'], G_k.nodes[s]['y']]) with pytest.raises(TypeError): graphs.nX_remove_filler_nodes(G_k) # catch non-touching Linestrings G_corr = G_messy.copy() for s, e in G_corr.edges(): geom = G_corr[s][e]['geom'] start = list(geom.coords[0]) end = list(geom.coords[1]) # corrupt a point start[0] = start[0] - 1 G_corr[s][e]['geom'] = geometry.LineString([start, end]) with pytest.raises(TypeError): graphs.nX_remove_filler_nodes(G_corr)