Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
530 views
in Technique[技术] by (71.8m points)

python 3.x - OSMNX + NetworkX graph_from_gdfs incorrect nodes returned using nx.ego_graph

Attempting to load a geopackage edge and node layers as NetworkX Graph but failing to get the correct nodes returned when using 'nx.ego_graph'. Using version OSMNX 1.0, NetworkX 2.5.

In a previous question G Boeing assisted in using the Geopackage to Graph function.

I now have a Graph object but the analysis doesn't appear to work as it does when using the True Graph object e.g. G = ox.graph_from_point((53.956748, -1.081676)).

I want to create isochrones for different travel times and reuse the clever work Mr Boeing has already done in this example. I would like to use the 'buffer' option predominantly- rather than the convex hull.

The analysis from the example above currently works OK when using G = ox.graph_from_point((53.956748, -1.081676),dist=2000, dist_type='network') i.e. dynamically downloading the osm data but I have a specific workflow where I export data to a geopackage and edit, add some nodes, and edges to represent possible new roads.

Even without editing the Geopackage data, I cannot get the analysis to work, the nodes returned for different travel times appear wrong. The isochrone polygon for the 10 min trip time is the same as the 5 min trip time. And I don't believe the 5 min trip time polygon is correct as it's a very small area.

Isochrone polygon seems wrong

If you run the script below, you can already see when setting the node colours there is an issue as trip time 5 and 10 return the same exact nodes (subgraph = nx.ego_graph(G=G, n=origin_node, radius=trip_time, distance='time')).

Does anyone know if this indicates that perhaps the Graph object hasn't been generated properly when using 'ox.graph_from_gdfs'? Or is there a change that is required?

I tried using 'undirected=True' in nx.ego_graph and that returned...possibly the correct nodes in the colour setting section but when generating the polygons, I got this error:

edge_lookup = G.get_edge_data(n_fr, n_to)[0].get('geometry',  LineString([f,t]))
KeyError: 0

In the testing I did I found:

  • using the buffer isochrone method along with nx.ego_graph: undirected=True - gives the error above
  • using the buffer isochrone method along with nx.ego_graph: undirected=False - gives the most bizarre pear shaped polygon which covers half of Northern Europe (should be a small site in the UK < 2000m wide)- same for all trips
  • using the convex hull isochrone method along with nx.ego_graph: undirected=False- gives identical polygons for each trip, and neither polygon is the correct shape - far too small
  • using the convex hull isochrone method along with nx.ego_graph: undirected=True- gives the correct polygons for each trip, 10 mins bigger than 5 mins, etc

See example again for the convex hull method.

Hopefully someone has an idea how to correct the error.

Code:

geopPath = "Transport_Analysis.gpkg"
geopPath2 = "Transport_Analysis2.gpkg"

yG = 53.9582559586775  # EPSG:4326 Geographic
xG = -1.07662649048548 # EPSG:4326 Geographic
yP = 451746.474534685  # EPSG:27700 Projected
xP = 460685.875721401  # EPSG:27700 Projected

distance = 2000
network_type = 'walk'
trip_times = [5, 10, 15, 20, 25, 30]
travel_speed = 4.5

trip_times = trip_times[0:2]  # limit to just first 2 for testing

useWGS84 = True # True False Choose to use Geographic or Projected Coordinates

if useWGS84 == True:
    epsg = 4326
    orig = (yG,xG) # EPSG:4326
    print("Using Geographic coords: {},{}".format(xG,yG))
    graph_attrs = {'crs': 'epsg:4326', 'simplified': True}
else:
    epsg = 27700 # for my project site in the UK
    orig = (yP,xP) # EPSG:27700
    print("Using Projected coords: {},{}".format(xP,yP))
    graph_attrs = {'crs': 'epsg:{}'.format(epsg), 'simplified': True}

download = 0
if download == 1:
    print("Downloading OSMNX")
    G = ox.graph_from_point(orig, dist=distance, dist_type='network')
    origin_node = ox.get_nearest_node(G, orig)
    print("Downloaded OSMNX")                       
    ox.io.save_graph_geopackage(G, filepath=geopPath2, encoding='utf-8')
    geopPath = geopPath2
else:
    print("Using existing geopackage: {}".format(geopPath))

# geopPath edges and nodes in native osm EPSG:4326
# load GeoPackage as node/edge GeoDataFrames indexed as described in OSMnx docs
gdf_nodes = gpd.read_file(geopPath, layer='nodes').set_index('osmid')
gdf_edges = gpd.read_file(geopPath, layer='edges').set_index(['u', 'v', 'key'])                
assert gdf_nodes.index.is_unique and gdf_edges.index.is_unique

print("### Loading Existing geopackage road edges and nodes as Graph")
# convert the node/edge GeoDataFrames to a MultiDiGraph
G = ox.graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs)

if useWGS84 != True:  # choose to use projected coordinates for origin/graph instead of geographic
    crsObj = CRS.from_user_input(epsg)
    # Finally converting graph to projected CRS
    G = ox.project_graph(G,to_crs=crsObj)  

origin_node = ox.get_nearest_node(G, orig)
print("closest node to coordinate: {} (coord Y,X: {})".format(origin_node,orig))

# add an edge attribute for time in minutes required to traverse each edge
meters_per_minute = travel_speed * 1000 / 60 #km per hour to m per minute
for u, v, k, data in G.edges(data=True, keys=True):
    #print(data['time'])
    data['time'] = data['length'] / meters_per_minute
iso_colors = ox.plot.get_colors(n=len(trip_times), cmap='plasma', start=0, return_hex=True)

# color the nodes according to isochrone then plot the street network
node_colors = {}
for trip_time, color in zip(sorted(trip_times, reverse=False), iso_colors):
    subgraph = nx.ego_graph(G=G, n=origin_node, radius=trip_time,undirected=True, distance='time') # 
    print("Trip time: {}. Subgraph.nodes: {}. Subnodes: {}".format(trip_time,len(subgraph.nodes()),subgraph.nodes()))
    for node in subgraph.nodes():
        node_colors[node] = color

tripReverse = False

edge_buff=25
node_buff=0
infill=False
isochrone_polys = []

#trip_times = list(trip_times)

for trip_time in sorted(trip_times, reverse=tripReverse):  # ,undirected=True
    subgraph = nx.ego_graph(G=G, n=origin_node, radius=trip_time,undirected=True, distance='time')

    node_points = [Point((data['x'], data['y'])) for node, data in subgraph.nodes(data=True)]
    nodes_gdf = gpd.GeoDataFrame({'id': list(subgraph.nodes())}, geometry=node_points)
    nodes_gdf = nodes_gdf.set_index('id')
    
    print("Trip time: {}. Nodes: {}".format(trip_time,len(node_points)))
    #print("node_points: {}".format(node_points))
    edge_lines = []
    for n_fr, n_to in subgraph.edges():
        f = nodes_gdf.loc[n_fr].geometry
        t = nodes_gdf.loc[n_to].geometry
        edge_lookup = G.get_edge_data(n_fr, n_to)[0].get('geometry',  LineString([f,t]))
        edge_lines.append(edge_lookup)

    n = nodes_gdf.buffer(node_buff).geometry
    e = gpd.GeoSeries(edge_lines).buffer(edge_buff).geometry
    all_gs = list(n) + list(e)
    new_iso = gpd.GeoSeries(all_gs).unary_union
    
    # try to fill in surrounded areas so shapes will appear solid and blocks without white space inside them
    if infill:
        new_iso = Polygon(new_iso.exterior)
    isochrone_polys.append(new_iso)
    #print("new_iso: {}".format(new_iso))

        

iso_df = pd.DataFrame({'trip_times_mins':trip_times[::-1],'color':colors,'geom':polys[::-1]})
iso_df = gpd.GeoDataFrame(iso_df,crs='epsg:{}'.format(epsg),geometry='geom')
# export isochrones to geopackage 
iso_df.to_file(geopPath, driver='GPKG',layer='isochrone_{}'.format(network_type))
print("Written isochrone")

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
等待大神答复

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...