diff --git a/swmmanywhere/graph_utilities.py b/swmmanywhere/graph_utilities.py index 0934b158..d8d6193a 100644 --- a/swmmanywhere/graph_utilities.py +++ b/swmmanywhere/graph_utilities.py @@ -5,14 +5,17 @@ """ import json import tempfile +from heapq import heappop, heappush from pathlib import Path -from typing import Callable +from typing import Callable, Hashable import geopandas as gpd import networkx as nx import numpy as np import osmnx as ox +import pandas as pd from shapely import geometry as sgeom +from tqdm import tqdm from swmmanywhere import geospatial_utilities as go from swmmanywhere import parameters @@ -162,6 +165,10 @@ def double_directed(G: nx.Graph, **kwargs): Returns: G (nx.Graph): A graph """ + #TODO the geometry is left as is currently - should be reveresed, however + # in original osmnx geometry there are some incorrectly directed ones + # someone with more patience might check start and end Points to check + # which direction the line should be going in... G_new = G.copy() for u, v, data in G.edges(data=True): if (v, u) not in G.edges: @@ -315,6 +322,9 @@ def calculate_contributing_area(G: nx.Graph, Adds the edge attributes: - 'contributing_area' (float) + Adds the node attributes: + - 'contributing_area' (float) + Args: G (nx.Graph): A graph subcatchment_derivation (parameters.SubcatchmentDerivation): A @@ -350,6 +360,10 @@ def calculate_contributing_area(G: nx.Graph, # Assign contributing area imperv_lookup = subs_rc.set_index('id').impervious_area.to_dict() + nx.set_node_attributes(G, imperv_lookup, 'contributing_area') + for u, d in G.nodes(data=True): + if 'contributing_area' not in d.keys(): + d['contributing_area'] = 0.0 for u,v,d in G.edges(data=True): if u in imperv_lookup.keys(): d['contributing_area'] = imperv_lookup[u] @@ -357,6 +371,7 @@ def calculate_contributing_area(G: nx.Graph, d['contributing_area'] = 0.0 return G +@register_graphfcn def set_elevation(G: nx.Graph, addresses: parameters.Addresses, **kwargs) -> nx.Graph: @@ -390,6 +405,7 @@ def set_elevation(G: nx.Graph, nx.set_node_attributes(G, elevations_dict, 'elevation') return G +@register_graphfcn def set_surface_slope(G: nx.Graph, **kwargs) -> nx.Graph: """Set the surface slope for each edge. @@ -416,6 +432,7 @@ def set_surface_slope(G: nx.Graph, d['surface_slope'] = slope return G +@register_graphfcn def set_chahinan_angle(G: nx.Graph, **kwargs) -> nx.Graph: """Set the Chahinan angle for each edge. @@ -459,6 +476,7 @@ def set_chahinan_angle(G: nx.Graph, d['chahinan_angle'] = min_weight return G +@register_graphfcn def calculate_weights(G: nx.Graph, topo_derivation: parameters.TopologyDerivation, **kwargs) -> nx.Graph: @@ -508,6 +526,7 @@ def calculate_weights(G: nx.Graph, d['weight'] = total_weight return G +@register_graphfcn def identify_outlets(G: nx.Graph, outlet_derivation: parameters.OutletDerivation, **kwargs) -> nx.Graph: @@ -595,4 +614,246 @@ def identify_outlets(G: nx.Graph, if (d['edge_type'] == 'outlet') & (v != 'waste'): G.add_edge(u,v,**d) + return G + +@register_graphfcn +def derive_topology(G: nx.Graph, + **kwargs) -> nx.Graph: + """Derive the topology of a graph. + + Runs a djiikstra-based algorithm to identify the shortest path from each + node to its nearest outlet (weighted by the 'weight' edge value). The + returned graph is one that only contains the edges that feature on the + shortest paths. + + Requires a graph with edges that have: + - 'edge_type' ('river' or 'street') + - 'weight' (float) + + Adds the node attributes: + - 'outlet' (str) + - 'shortest_path' (float) + + Args: + G (nx.Graph): A graph + **kwargs: Additional keyword arguments are ignored. + + Returns: + G (nx.Graph): A graph + """ + G = G.copy() + + # Identify outlets + outlets = [u for u,v,d in G.edges(data=True) if d['edge_type'] == 'outlet'] + + # Remove non-street edges/nodes and unconnected nodes + nodes_to_remove = [] + for u, v, d in G.edges(data=True): + if d['edge_type'] != 'street': + if d['edge_type'] == 'outlet': + nodes_to_remove.append(v) + else: + nodes_to_remove.append(u) + nodes_to_remove.append(v) + + isolated_nodes = list(nx.isolates(G)) + + for u in set(nodes_to_remove).union(isolated_nodes): + G.remove_node(u) + + # Initialize the dictionary with infinity for all nodes + shortest_paths = {node: float('inf') for node in G.nodes} + + # Initialize the dictionary to store the paths + paths: dict[Hashable,list] = {node: [] for node in G.nodes} + + # Set the shortest path length to 0 for outlets + for outlet in outlets: + shortest_paths[outlet] = 0 + paths[outlet] = [outlet] + + # Initialize a min-heap with (distance, node) tuples + heap = [(0, outlet) for outlet in outlets] + while heap: + # Pop the node with the smallest distance + dist, node = heappop(heap) + + # For each neighbor of the current node + for neighbor, edge_data in G[node].items(): + # Calculate the distance through the current node + alt_dist = dist + edge_data[0]['weight'] + # If the alternative distance is shorter + + if alt_dist < shortest_paths[neighbor]: + # Update the shortest path length + shortest_paths[neighbor] = alt_dist + # Update the path + paths[neighbor] = paths[node] + [neighbor] + # Push the neighbor to the heap + heappush(heap, (alt_dist, neighbor)) + + edges_to_keep = set() + for path in paths.values(): + # Assign outlet + outlet = path[0] + for node in path: + G.nodes[node]['outlet'] = outlet + G.nodes[node]['shortest_path'] = shortest_paths[node] + + # Store path + for i in range(len(path) - 1): + edges_to_keep.add((path[i+1], path[i])) + + # Remvoe edges not on paths + new_graph = G.copy() + for u,v in G.edges(): + if (u,v) not in edges_to_keep: + new_graph.remove_edge(u,v) + return new_graph + +@register_graphfcn +def pipe_by_pipe(G: nx.Graph, + pipe_design: parameters.HydraulicDesign, + **kwargs + ): + """Pipe by pipe hydraulic design. + + Starting from the most upstream node, design a pipe to the downstream node + specifying a diameter and downstream invert level. A range of diameters and + invert levels are tested (ranging between conditions defined in + pipe_design). From the tested diameters/inverts, a selection is made based + on each pipe's satisfying feasibility constraints on: surcharge velocity, + filling ratio, (and shear stress - not currently implemented). Prioritising + feasibility in this order it identifies pipes with the preferred feasibility + level. If multiple pipes are feasible, it picks the lowest cost pipe. Once + the feasible pipe is identified, the diameter and downstream invert are set + and then the next downstream pipe can be designed. + + This approach is based on the pipe-by-pipe design proposed in: + https://doi.org/10.1016/j.watres.2021.117903 + + Requires a graph with edges that have: + - 'length' (float) + - 'elevation' (float) + + Requires a graph with nodes that have: + - 'contributing_area' (float) + - 'elevation' (float) + + Adds the edge attributes: + - 'diameter' (float) + + Adds the node attributes: + - 'chamber_floor_elevation' (float) + + Args: + G (nx.Graph): A graph + pipe_design (parameters.HydraulicDesign): A HydraulicDesign parameter + object + **kwargs: Additional keyword arguments are ignored. + + Returns: + G (nx.Graph): A graph + """ + # TODO obviously needs a refactor and better testing + + G = G.copy() + surface_elevations = {n : d['elevation'] for n, d in G.nodes(data=True)} + topological_order = list(nx.topological_sort(G)) + chamber_floor = {} + edge_diams = {} + # Iterate over nodes in topological order + for node in tqdm(topological_order): + # Check if there's any nodes upstream, if not set the depth to min_depth + if len(nx.ancestors(G,node)) == 0: + chamber_floor[node] = surface_elevations[node] - pipe_design.min_depth + for ix, ds_node in enumerate(G.successors(node)): + edge = G.get_edge_data(node,ds_node,0) + # Find contributing area with ancestors + # TODO - could do timearea here if i hated myself enough + anc = nx.ancestors(G,node).union([node]) + tot = sum([G.nodes[anc_node]['contributing_area'] for anc_node in anc]) + + M3_PER_HR_TO_M3_PER_S = 1 / 60 / 60 + Q = tot * pipe_design.precipitation * M3_PER_HR_TO_M3_PER_S + + # Within all allowable slopes and diams, calculate the parameters + # of all feasible pipes + feasible_pipes = [] + for diam in pipe_design.diameters: + A = (np.pi * diam ** 2 / 4) + n = 0.012 # mannings n + R = A / (np.pi * diam) # hydraulic radius + for depth in np.linspace(pipe_design.min_depth, + pipe_design.max_depth, + 10): + # TODO... presumably need to check depth > (diam + min_depth) + + elev_diff = chamber_floor[node] - \ + (surface_elevations[ds_node] - depth) + slope = elev_diff / edge['length'] + # Always pick a pipe that is feasible without surcharging + # if available + surcharge_feasibility = 0.0 + # Use surcharged elevation + while slope <= 0: + surcharge_feasibility += 0.05 + slope = (chamber_floor[node] + surcharge_feasibility - \ + (surface_elevations[ds_node] - depth)) / edge['length'] + # TODO could make the feasibility penalisation increase + # when you get above surface_elevation[node]... but + # then you'd need a feasibility tracker and an offset + # tracker + v = (slope ** 0.5) * (R ** (2/3)) / n + filling_ratio = Q / (v * A) + # buffers from: https://www.polypipe.com/sites/default/files/Specification_Clauses_Underground_Drainage.pdf + average_depth = (depth + chamber_floor[node]) / 2 + V = edge['length'] * (diam + 0.3) * (average_depth + 0.1) + cost = 1.32 / 2000 * (9579.31 * diam ** 0.5737 + 1153.77 * V**1.31) + v_feasibility = max(pipe_design.min_v - v, 0) + \ + max(v - pipe_design.max_v, 0) + fr_feasibility = max(filling_ratio - pipe_design.max_fr, 0) + """ + TODO shear stress... got confused here + density = 1000 + dyn_visc = 0.001 + hydraulic_diameter = 4 * (A * filling_ratio**2) / \ + (np.pi * diam * filling_ratio) + Re = density * v * 2 * (diam / 4) * (filling_ratio ** 2) / dyn_visc + fd = 64 / Re + shear_stress = fd * density * v**2 / fd + shear_feasibility = max(min_shear - shear_stress, 0) + """ + slope = (chamber_floor[node] - (surface_elevations[ds_node] -\ + depth)) / edge['length'] + feasible_pipes.append({'diam' : diam, + 'depth' : depth, + 'slope' : slope, + 'v' : v, + 'fr' : filling_ratio, + # 'tau' : shear_stress, + 'cost' : cost, + 'v_feasibility' : v_feasibility, + 'fr_feasibility' : fr_feasibility, + 'surcharge_feasibility' : surcharge_feasibility, + # 'shear_feasibility' : shear_feasibility + }) + feasible_pipes_df = pd.DataFrame(feasible_pipes).dropna() + if feasible_pipes_df.shape[0] > 0: + ideal_pipe = feasible_pipes_df.sort_values(by=['surcharge_feasibility', + 'v_feasibility', + 'fr_feasibility', + # 'shear_feasibility', + 'cost'], + ascending = True).iloc[0] + edge_diams[(node,ds_node,0)] = ideal_pipe.diam + chamber_floor[ds_node] = surface_elevations[ds_node] - ideal_pipe.depth + else: + print('something odd - no non nan pipes') + if ix > 0: + print('''a node has multiple successors, + not sure how that can happen if using shortest path + to derive topology''') + nx.function.set_edge_attributes(G, edge_diams, "diameter") + nx.function.set_node_attributes(G, chamber_floor, "chamber_floor_elevation") return G \ No newline at end of file diff --git a/swmmanywhere/parameters.py b/swmmanywhere/parameters.py index fa61cd8f..1db36e9b 100644 --- a/swmmanywhere/parameters.py +++ b/swmmanywhere/parameters.py @@ -6,6 +6,7 @@ from pathlib import Path +import numpy as np from pydantic import BaseModel, Field, model_validator @@ -130,7 +131,50 @@ class NewTopo(TopologyDerivation): min_items = 1, unit = "-", description = "Weights for topo derivation") - + +class HydraulicDesign(BaseModel): + """Parameters for hydraulic design.""" + diameters: list = Field(default = np.linspace(0.15,3,int((3-0.15)/0.075) + 1), + min_items = 1, + unit = "m", + description = """Diameters to consider in + pipe by pipe method""") + max_fr: float = Field(default = 0.8, + upper_limit = 1, + lower_limit = 0, + unit = "-", + description = "Maximum filling ratio in pipe by pipe method") + min_shear: float = Field(default = 2, + upper_limit = 3, + lower_limit = 0, + unit = "Pa", + description = "Minimum wall shear stress in pipe by pipe method") + min_v: float = Field(default = 0.75, + upper_limit = 2, + lower_limit = 0, + unit = "m/s", + description = "Minimum velocity in pipe by pipe method") + max_v: float = Field(default = 5, + upper_limit = 10, + lower_limit = 3, + unit = "m/s", + description = "Maximum velocity in pipe by pipe method") + min_depth: float = Field(default = 0.5, + upper_limit = 1, + lower_limit = 0, + unit = "m", + description = "Minimum excavation depth in pipe by pipe method") + max_depth: float = Field(default = 5, + upper_limit = 10, + lower_limit = 2, + unit = "m", + description = "Maximum excavation depth in pipe by pipe method") + precipitation: float = Field(default = 0.006, + upper_limit = 0.010, + lower_limit = 0.001, + description = "Depth of design storm in pipe by pipe method", + unit = "m") + class Addresses: """Parameters for address lookup. diff --git a/tests/test_data/graph_topo_derived.json b/tests/test_data/graph_topo_derived.json new file mode 100644 index 00000000..8da72f02 --- /dev/null +++ b/tests/test_data/graph_topo_derived.json @@ -0,0 +1 @@ +{"directed": true, "multigraph": true, "graph": {"created_date": "2024-01-31 12:27:58", "created_with": "OSMnx 1.8.1", "crs": "EPSG:32630", "simplified": true}, "nodes": [{"y": 5709902.110360867, "x": 700277.5569639901, "street_count": 3, "outlet": 12354833, "shortest_path": 51, "elevation": 0, "contributing_area": 0, "id": 107733}, {"y": 5709939.408081475, "x": 700277.6837391303, "street_count": 3, "outlet": 12354833, "shortest_path": 51, "elevation": 1, "contributing_area": 1, "id": 107734}, {"y": 5709950.664565533, "x": 700311.2287216682, "street_count": 3, "outlet": 12354833, "shortest_path": 55, "elevation": 2, "contributing_area": 2, "id": 107735}, {"y": 5709945.54203236, "x": 700328.4358706471, "street_count": 3, "outlet": 12354833, "shortest_path": 61, "elevation": 3, "contributing_area": 3, "id": 107736}, {"y": 5709897.32580564, "x": 700345.9401207004, "street_count": 3, "outlet": 12354833, "shortest_path": 111, "elevation": 4, "contributing_area": 4, "id": 107737}, {"y": 5709878.507201574, "x": 700314.3472000923, "street_count": 3, "outlet": 12354833, "shortest_path": 52, "elevation": 5, "contributing_area": 5, "id": 107738}, {"y": 5710021.9827727135, "x": 700465.4044789741, "street_count": 4, "outlet": 25472347, "shortest_path": 60, "elevation": 6, "contributing_area": 6, "id": 109753}, {"y": 5709694.8741118815, "x": 700044.4345900133, "highway": "turning_circle", "street_count": 3, "outlet": 12354833, "shortest_path": 0, "elevation": 7, "contributing_area": 7, "id": 12354833}, {"y": 5709859.5404103035, "x": 700260.646795129, "street_count": 3, "outlet": 12354833, "shortest_path": 52, "elevation": 8, "contributing_area": 8, "id": 21392086}, {"y": 5709943.283586768, "x": 700130.0316394513, "street_count": 3, "outlet": 25472347, "shortest_path": 0, "elevation": 9, "contributing_area": 9, "id": 25472347}, {"y": 5709918.64998135, "x": 700182.2264369107, "street_count": 1, "outlet": 25472347, "shortest_path": 25, "elevation": 10, "contributing_area": 10, "id": 25472348}, {"y": 5709665.057833289, "x": 700141.1675964806, "street_count": 4, "outlet": 12354833, "shortest_path": 21, "elevation": 11, "contributing_area": 11, "id": 25472373}, {"y": 5709861.753318606, "x": 700486.534735846, "street_count": 3, "outlet": 12354833, "shortest_path": 201, "elevation": 12, "contributing_area": 12, "id": 25472468}, {"y": 5710133.23541422, "x": 700406.4117507454, "street_count": 3, "outlet": 25472347, "shortest_path": 27, "elevation": 13, "contributing_area": 13, "id": 25472854}, {"y": 5709836.784993341, "x": 700419.6382007168, "street_count": 3, "outlet": 12354833, "shortest_path": 166, "elevation": 14, "contributing_area": 14, "id": 25472893}, {"y": 5709805.458949697, "x": 700446.664728181, "street_count": 3, "outlet": 12354833, "shortest_path": 74, "elevation": 15, "contributing_area": 15, "id": 25510321}, {"y": 5709878.072050431, "x": 700330.9734851549, "street_count": 3, "outlet": 12354833, "shortest_path": 69, "elevation": 16, "contributing_area": 16, "id": 32925453}, {"y": 5710484.769616377, "x": 699976.6805692167, "street_count": 1, "outlet": 12354833, "shortest_path": 63, "elevation": 17, "contributing_area": 17, "id": 248122264}, {"y": 5709800.829107233, "x": 700450.6767434096, "street_count": 3, "outlet": 12354833, "shortest_path": 113, "elevation": 18, "contributing_area": 18, "id": 266325461}, {"y": 5709931.922096681, "x": 700343.3585345388, "street_count": 3, "outlet": 12354833, "shortest_path": 71, "elevation": 19, "contributing_area": 19, "id": 770549936}, {"y": 5709959.908314285, "x": 700369.9469864559, "street_count": 3, "outlet": 12354833, "shortest_path": 70, "elevation": 20, "contributing_area": 20, "id": 1696030874}, {"y": 5710395.710567596, "x": 700021.9512225334, "street_count": 3, "outlet": 12354833, "shortest_path": 54, "elevation": 21, "contributing_area": 21, "id": 1881001588}, {"y": 5709815.86155232, "x": 700437.6921531621, "street_count": 2, "outlet": 12354833, "shortest_path": 112, "elevation": 22, "contributing_area": 22, "id": 2623975694}, {"y": 5709873.623166366, "x": 700362.1435140749, "street_count": 3, "outlet": 12354833, "shortest_path": 112, "elevation": 23, "contributing_area": 23, "id": 6277683849}], "links": [{"osmid": [157379106, 5035977, 724068972, 1068268847, 1068268848, 323063155, 371146076], "oneway": true, "lanes": "2", "ref": "A3200", "name": "York Road", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 275.046, "geometry": [[700141.1675964806, 5709665.057833289], [700143.5157680553, 5709677.065346105], [700144.8474277792, 5709679.601053864], [700155.976942681, 5709700.673855638], [700160.7768450535, 5709709.19243132], [700172.0393874501, 5709729.179248003], [700185.4803273565, 5709752.960108612], [700191.8141976679, 5709764.167181919], [700209.5905451218, 5709797.66218869], [700216.9778449582, 5709810.525491558], [700224.2875415371, 5709823.24098586], [700228.962895601, 5709831.387259596], [700233.8496671842, 5709839.98729696], [700236.7225068416, 5709844.499128756], [700241.8766513304, 5709850.548563866], [700261.0016506243, 5709870.979407257], [700265.1289820187, 5709876.632025824], [700267.7934926444, 5709880.4341245955], [700270.6096913561, 5709884.4426481845], [700272.9768698463, 5709889.791986443], [700274.1428695226, 5709894.938038215], [700277.5569639901, 5709902.110360867]], "width": 0, "id": "157379106.reversed", "edge_type": "street", "weight": 2, "source": 107733, "target": 25472373, "key": 0}, {"osmid": 4040995, "oneway": true, "lanes": "3", "ref": "A301", "name": "Tenison Way", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 37.45, "geometry": [[700277.5569639901, 5709902.110360867], [700276.5294887233, 5709907.1921423655], [700276.0456861216, 5709918.397616499], [700277.6837391303, 5709939.408081475]], "width": 0, "id": "4040995.reversed", "edge_type": "street", "weight": 5, "source": 107734, "target": 107733, "key": 0}, {"osmid": [253278314, 24935919], "oneway": true, "lanes": ["2", "1"], "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 38.699000000000005, "geometry": [[700277.6837391303, 5709939.408081475], [700281.0620739642, 5709943.082457772], [700283.3080724685, 5709945.865862207], [700286.8370136549, 5709949.245527317], [700291.5898057348, 5709951.548802148], [700295.19372216, 5709952.147561552], [700298.571576143, 5709953.015790332], [700302.8354936898, 5709952.738624432], [700306.8351147252, 5709951.938800403], [700311.2287216682, 5709950.664565533]], "width": 0, "id": "253278314.reversed", "edge_type": "street", "weight": 7, "source": 107735, "target": 107734, "key": 0}, {"osmid": 24935923, "oneway": true, "lanes": "3", "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 17.9, "geometry": [[700311.2287216682, 5709950.664565533], [700328.4358706471, 5709945.54203236]], "width": 0, "id": "24935923.reversed", "edge_type": "street", "weight": 11, "source": 107736, "target": 107735, "key": 0}, {"osmid": [228563659, 24935916], "oneway": true, "lanes": "3", "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 24.528999999999996, "geometry": [[700345.9401207004, 5709897.32580564], [700343.9882393219, 5709893.429296876], [700342.7545134749, 5709891.409623581], [700341.5918455586, 5709889.5263809515], [700338.673571965, 5709885.992611689], [700337.8972975462, 5709885.060000548], [700330.9734851549, 5709878.072050431]], "width": 0, "id": 228563659, "edge_type": "street", "weight": 12, "source": 107737, "target": 32925453, "key": 0}, {"osmid": 24935915, "oneway": true, "lanes": "2", "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 43.962999999999994, "geometry": [[700314.3472000923, 5709878.507201574], [700306.2843434939, 5709882.42051824], [700299.5883693597, 5709885.508079189], [700294.6641010015, 5709888.253542988], [700289.9725552127, 5709892.322180368], [700284.8256592196, 5709897.363912976], [700277.5569639901, 5709902.110360867]], "width": 0, "id": 24935915, "edge_type": "street", "weight": 16, "source": 107738, "target": 107733, "key": 0}, {"osmid": 3115950, "name": "Cornwall Road", "highway": "unclassified", "maxspeed": "20 mph", "oneway": false, "reversed": true, "length": 125.803, "geometry": [[700465.4044789741, 5710021.9827727135], [700461.4439979517, 5710029.710301515], [700458.6490779112, 5710035.022915902], [700456.6658586246, 5710038.7863448765], [700444.9310978653, 5710061.072771147], [700430.5406586338, 5710086.205261883], [700416.6051415108, 5710113.371273859], [700407.1722169924, 5710131.751011451], [700406.4117507454, 5710133.23541422]], "width": 0, "id": 3115950, "edge_type": "street", "weight": 18, "source": 109753, "target": 25472854, "key": 0}, {"osmid": [725226531, 307619438, 1068268846, 1068268849, 323063156, 42507450], "oneway": true, "lanes": "2", "ref": "A3200", "name": "York Road", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 228.89699999999996, "geometry": [[700260.646795129, 5709859.5404103035], [700253.727705374, 5709851.372400684], [700250.164089411, 5709846.755359551], [700246.2016118084, 5709841.855335842], [700242.4905154916, 5709837.277025176], [700233.1152255475, 5709823.722810672], [700219.3327136479, 5709798.569795305], [700216.9147153466, 5709794.153864184], [700212.9726675999, 5709786.971906629], [700204.4524102686, 5709770.411498058], [700185.1938145162, 5709735.37705126], [700169.0912214798, 5709706.658454267], [700156.226779984, 5709684.114146786], [700153.8828750349, 5709679.578684956], [700151.8880911011, 5709676.059180303], [700150.8420187996, 5709674.035825833], [700141.1675964806, 5709665.057833289]], "width": 0, "id": 725226531, "edge_type": "street", "weight": 23, "source": 21392086, "target": 25472373, "key": 0}, {"osmid": 4253584, "oneway": true, "name": "Concert Hall Approach", "highway": "living_street", "maxspeed": "20 mph", "reversed": false, "length": 57.56, "geometry": [[700130.0316394513, 5709943.283586768], [700182.2264369107, 5709918.64998135]], "width": 0, "id": "4253584.reversed", "edge_type": "street", "weight": 28, "source": 25472348, "target": 25472347, "key": 0}, {"osmid": 4253560, "name": "Chicheley Street", "highway": "residential", "maxspeed": "20 mph", "width": 0, "oneway": false, "reversed": false, "length": 101.119, "geometry": [[700141.1675964806, 5709665.057833289], [700131.2293895038, 5709668.763878157], [700127.7724262028, 5709669.897037534], [700123.7282148367, 5709671.118403503], [700082.5852502533, 5709684.718811786], [700059.2118402369, 5709691.993324356], [700056.0186594417, 5709692.958758778], [700050.1884291518, 5709694.532943944], [700044.4345900133, 5709694.8741118815]], "id": 4253560, "edge_type": "street", "weight": 29, "source": 25472373, "target": 12354833, "key": 0}, {"osmid": 4253650, "name": "Exton Street", "highway": "unclassified", "maxspeed": "20 mph", "oneway": false, "reversed": true, "length": 71.213, "geometry": [[700486.534735846, 5709861.753318606], [700454.4513036673, 5709848.849677348], [700429.4004307707, 5709839.998849332], [700426.7727709545, 5709839.137886363], [700419.6382007168, 5709836.784993341]], "width": 0, "id": 4253650, "edge_type": "street", "weight": 32, "source": 25472468, "target": 25472893, "key": 0}, {"osmid": [243937860, 223569640, 2423885, 1222144887, 376614608, 545538484, 545538485, 1222144886, 753458071, 545538486, 425225428, 1222144890, 753458070], "name": ["Upper Ground", "Belvedere Road"], "highway": ["residential", "unclassified"], "maxspeed": "20 mph", "oneway": false, "reversed": [false, true], "length": 342.27299999999997, "geometry": [[700406.4117507454, 5710133.23541422], [700387.069359366, 5710122.928634459], [700372.9230384012, 5710116.011803224], [700369.1779954545, 5710114.2381824525], [700363.0648608486, 5710111.36889115], [700349.9293818473, 5710105.249243745], [700327.3277679619, 5710097.052294423], [700311.837243554, 5710091.441121343], [700285.8403761584, 5710080.526944091], [700264.3797259327, 5710070.88315461], [700260.658661233, 5710068.676280703], [700255.3352328222, 5710065.515354177], [700249.9942922055, 5710061.741291486], [700245.8203461394, 5710058.859569429], [700242.7033414934, 5710056.665404704], [700224.1362021959, 5710044.307535186], [700221.4609648484, 5710042.364655568], [700218.4947517125, 5710040.399164544], [700199.8158378075, 5710027.5247511575], [700180.0977083285, 5710009.810042807], [700167.8430164627, 5709996.4987158], [700161.3400735646, 5709988.881739712], [700154.4322770827, 5709980.7811199045], [700142.0556038325, 5709963.333798693], [700132.9058145428, 5709947.762005055], [700130.0316394513, 5709943.283586768]], "width": 0, "id": 243937860, "edge_type": "street", "weight": 34, "source": 25472854, "target": 25472347, "key": 0}, {"osmid": [1207319057, 256768302], "oneway": true, "lanes": "1", "ref": "A301", "name": "Waterloo Road", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 29.652, "geometry": [[700419.6382007168, 5709836.784993341], [700427.245395453, 5709833.856050073], [700430.6112952617, 5709829.924490856], [700432.30694177, 5709828.1652200045], [700435.93212929, 5709824.355256594], [700437.6921531621, 5709815.86155232]], "width": 0, "id": 1207319057, "edge_type": "street", "weight": 37, "source": 25472893, "target": 2623975694, "key": 0}, {"osmid": [437211562, 4041071, 409351991, 359842682, 323062908], "oneway": true, "name": "Mepham Street", "highway": "unclassified", "maxspeed": "20 mph", "reversed": false, "length": 202.61500000000004, "geometry": [[700260.646795129, 5709859.5404103035], [700264.606979534, 5709848.115749547], [700270.2421185977, 5709844.262482439], [700276.2468099121, 5709841.258961956], [700280.3130668337, 5709840.005186374], [700290.7579613369, 5709836.486469852], [700317.3910231055, 5709825.499927625], [700334.9227590943, 5709819.911372949], [700341.4646550727, 5709818.265384199], [700350.8427009505, 5709815.89617834], [700383.4195720793, 5709807.683451332], [700407.4894184885, 5709803.36655895], [700438.1292499496, 5709800.5675001955], [700442.1522457196, 5709802.875500894], [700446.664728181, 5709805.458949697]], "width": 0, "id": "437211562.reversed", "edge_type": "street", "weight": 40, "source": 25510321, "target": 21392086, "key": 0}, {"osmid": 207115006, "oneway": true, "lanes": "3", "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 16.852, "geometry": [[700330.9734851549, 5709878.072050431], [700325.9959171354, 5709876.884555874], [700321.5250918429, 5709877.120134854], [700314.3472000923, 5709878.507201574]], "width": 0, "id": 207115006, "edge_type": "street", "weight": 41, "source": 32925453, "target": 107738, "key": 0}, {"osmid": [225025827, 200598186, 200598189, 228564878, 928025711, 35900943, 944217725], "bridge": "yes", "oneway": true, "lanes": ["2", "1"], "ref": "A301", "name": "Waterloo Bridge", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 630.8910000000001, "geometry": [[699976.6805692167, 5710484.769616377], [699995.0223578266, 5710453.266369763], [700014.2757750297, 5710422.968325912], [700023.9057410788, 5710408.003189852], [700042.265373482, 5710374.4741320945], [700154.1424509434, 5710191.497236617], [700176.0592502693, 5710155.5588213885], [700180.562426344, 5710148.153163577], [700195.976710407, 5710122.748642229], [700201.8983423435, 5710115.499165202], [700239.9790628456, 5710051.914439671], [700276.6209293818, 5709991.179535505], [700279.6394946901, 5709986.710817551], [700285.3315518312, 5709977.537060874], [700292.9918154946, 5709966.91541046], [700298.4768209211, 5709960.172166666], [700301.9810694954, 5709956.780495522], [700305.95837635, 5709953.552255062], [700311.2287216682, 5709950.664565533]], "width": 0, "id": 225025827, "edge_type": "street", "weight": 44, "source": 248122264, "target": 107735, "key": 0}, {"osmid": 699097039, "lanes": "4", "ref": "A301", "name": "Waterloo Road", "highway": "primary", "maxspeed": "20 mph", "oneway": false, "reversed": true, "length": 6.116, "geometry": [[700450.6767434096, 5709800.829107233], [700446.664728181, 5709805.458949697]], "width": 0, "id": 699097039, "edge_type": "street", "weight": 45, "source": 266325461, "target": 25510321, "key": 0}, {"osmid": 228562216, "oneway": true, "lanes": "2", "ref": "A301", "highway": "primary", "maxspeed": "20 mph", "junction": "circular", "reversed": false, "length": 20.893, "geometry": [[700328.4358706471, 5709945.54203236], [700333.3858597063, 5709943.3766637165], [700337.0812959784, 5709941.128379782], [700341.0533185115, 5709936.274174798], [700341.9863743042, 5709934.818844887], [700343.3585345388, 5709931.922096681]], "width": 0, "id": "228562216.reversed", "edge_type": "street", "weight": 47, "source": 770549936, "target": 107736, "key": 0}, {"osmid": [83801193, 251642745], "oneway": true, "ref": "A3200", "name": "Stamford Street", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 44.416, "geometry": [[700328.4358706471, 5709945.54203236], [700334.077684354, 5709945.920587545], [700339.4473318071, 5709946.856317236], [700344.5164533333, 5709948.715572506], [700348.4576651177, 5709950.452366831], [700352.1663099085, 5709952.447236735], [700356.7277008864, 5709955.021407143], [700360.3472058966, 5709957.513862129], [700369.9469864559, 5709959.908314285]], "width": 0, "id": "83801193.reversed", "edge_type": "street", "weight": 51, "source": 1696030874, "target": 107736, "key": 0}, {"osmid": [200596577, 224637155, 948685543, 948685544, 670348489, 23013971, 4253331], "oneway": true, "lanes": ["2", "1"], "ref": "A301", "name": "Waterloo Bridge", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 526.335, "bridge": "yes", "geometry": [[700277.6837391303, 5709939.408081475], [700275.7128986834, 5709948.672989107], [700275.6714573554, 5709953.069868358], [700275.1963905097, 5709961.235701989], [700274.8376413824, 5709970.152202144], [700274.4762318035, 5709974.380560487], [700273.746804977, 5709978.249198908], [700272.3189850443, 5709982.557972666], [700270.3512829997, 5709986.812039583], [700243.8802948985, 5710030.354161469], [700218.2029132167, 5710070.876579723], [700212.394118875, 5710080.725057999], [700179.172642919, 5710137.051966281], [700177.2608309576, 5710142.7113350835], [700172.1774497676, 5710151.085170908], [700040.4501411039, 5710365.171293509], [700029.6364467517, 5710382.840209466], [700021.9512225334, 5710395.710567596]], "width": 0, "id": "200596577.reversed", "edge_type": "street", "weight": 52, "source": 1881001588, "target": 107734, "key": 0}, {"osmid": 256768303, "lanes": "4", "ref": "A301", "name": "Waterloo Road", "highway": "primary", "maxspeed": "20 mph", "oneway": false, "reversed": false, "length": 13.715, "geometry": [[700437.6921531621, 5709815.86155232], [700446.664728181, 5709805.458949697]], "width": 0, "id": 256768303, "edge_type": "street", "weight": 53, "source": 2623975694, "target": 25510321, "key": 0}, {"osmid": 228568372, "oneway": true, "lanes": "2", "ref": "A301", "name": "Waterloo Road", "highway": "primary", "maxspeed": "20 mph", "reversed": false, "length": 32.080999999999996, "geometry": [[700362.1435140749, 5709873.623166366], [700356.7236303644, 5709872.37364107], [700356.0138620024, 5709872.04496849], [700351.4285008686, 5709872.36508479], [700346.2485579546, 5709873.6639322005], [700338.4782655792, 5709875.974101273], [700330.9734851549, 5709878.072050431]], "width": 0, "id": 228568372, "edge_type": "street", "weight": 56, "source": 6277683849, "target": 32925453, "key": 0}]} \ No newline at end of file diff --git a/tests/test_graph_utilities.py b/tests/test_graph_utilities.py index 06b9981d..20a4b059 100644 --- a/tests/test_graph_utilities.py +++ b/tests/test_graph_utilities.py @@ -87,6 +87,10 @@ def test_derive_subcatchments(): assert 'contributing_area' in data.keys() assert isinstance(data['contributing_area'], float) + for u, data in G.nodes(data=True): + assert 'contributing_area' in data.keys() + assert isinstance(data['contributing_area'], float) + def test_set_elevation_and_slope(): """Test the set_elevation and set_surface_slope function.""" G, _ = load_street_network() @@ -130,12 +134,14 @@ def test_calculate_weights(): assert 'weight' in data.keys() assert math.isfinite(data['weight']) -def test_identify_outlets(): - """Test the identify_outlets function.""" +def test_identify_outlets_and_derive_topology(): + """Test the identify_outlets and derive_topology functions.""" G, _ = load_street_network() - - for u,v,d in G.edges(data=True): + G = gu.assign_id(G) + G = gu.double_directed(G) + for ix, (u,v,d) in enumerate(G.edges(data=True)): d['edge_type'] = 'street' + d['weight'] = ix params = parameters.OutletDerivation(river_buffer_distance = 300) dummy_river1 = sgeom.LineString([(699913.878,5709769.851), @@ -168,14 +174,39 @@ def test_identify_outlets(): G.nodes['river4']['x'] = 700103.427 G.nodes['river4']['y'] = 5710169.052 + # Test outlet derivation G_ = G.copy() G_ = gu.identify_outlets(G_, params) outlets = [(u,v,d) for u,v,d in G_.edges(data=True) if d['edge_type'] == 'outlet'] assert len(outlets) == 2 + # Test topo derivation + G_ = gu.derive_topology(G_) + assert len(G_.edges) == 22 + + # Test outlet derivation parameters G_ = G.copy() params.outlet_length = 600 G_ = gu.identify_outlets(G_, params) outlets = [(u,v,d) for u,v,d in G_.edges(data=True) if d['edge_type'] == 'outlet'] - assert len(outlets) == 1 \ No newline at end of file + assert len(outlets) == 1 + +def test_pipe_by_pipe(): + """Test the pipe_by_pipe function.""" + G = gu.load_graph(Path(__file__).parent / 'test_data' / 'graph_topo_derived.json') + for ix, (u,d) in enumerate(G.nodes(data=True)): + d['elevation'] = ix + d['contributing_area'] = ix + + params = parameters.HydraulicDesign() + + G = gu.pipe_by_pipe(G, params) + for u, v, d in G.edges(data=True): + assert 'diameter' in d.keys() + assert d['diameter'] in params.diameters + + for u, d in G.nodes(data=True): + assert 'chamber_floor_elevation' in d.keys() + assert math.isfinite(d['chamber_floor_elevation']) + \ No newline at end of file