How do I solve "ValueError: Found no graph nodes within the requested polygon" in OSMnx

How do I solve "ValueError: Found no graph nodes within the requested polygon" in OSMnx

我正在为一些学校制作等时线,以显示可访问性。这是我的代码:

# importing the required libraries
import osmnx as ox
import pandas as pd
import warnings
import networkx as nx
import geopandas as gpd
from shapely.geometry import Point, Polygon
import matplotlib.cm as cm
import matplotlib.colors as colors
import folium

warnings.simplefilter(action="ignore", category=FutureWarning)
warnings.simplefilter(action="ignore", category=PendingDeprecationWarning)
ox.config(use_cache=True, log_console=False)

# reading the data
sec_data = pd.read_csv('H:/WORK/Upwork/Project 7 - Python School Data Analysis/Analysis By Country/Tanzania/CSV Files/secondary.csv')
sec_schools = gpd.GeoDataFrame(
    sec_data, geometry=gpd.points_from_xy(sec_data.longitude, sec_data.latitude)
)
sec_schools.crs = "EPSG:4326"

# we'll test it using the first 20 records
sec_schools = sec_schools.head(20)

# function to get isochrones
def get_isochrone(lon, lat, walk_times=[15, 30], speed=4.5, name=None, point_index=None):
    loc = (lat, lon)
    G = ox.graph_from_point(loc, simplify=True, network_type="walk")
    gdf_nodes = ox.graph_to_gdfs(G, edges=False)
    center_node = ox.distance.nearest_nodes(G, lon, lat)

    meters_per_minute = speed * 1000 / 60  # km per hour to m per minute
    for u, v, k, data in G.edges(data=True, keys=True):
        data["time"] = data["length"] / meters_per_minute
    polys = []
    for walk_time in walk_times:
        subgraph = nx.ego_graph(G, center_node, radius=walk_time, distance="time")
        node_points = [
            Point(data["x"], data["y"]) for node, data in subgraph.nodes(data=True)
        ]
        polys.append(gpd.GeoSeries(node_points).unary_union.convex_hull)
    info = {}
    if name:
        info["name"] = [name for t in walk_times]
    if point_index:
        info["point_index"] = [point_index for t in walk_times]
    return {**{"geometry": polys, "time": walk_times}, **info}

# walk time list of minutes
WT = [30, 45, 60]

# build geopandas data frame of isochrone polygons for each school
isochrones = pd.concat(
    [
        gpd.GeoDataFrame(
            get_isochrone(
                r["geometry"].x,
                r["geometry"].y,
                name=r["school name"],
                point_index=i,
                walk_times=WT,
            ),
            crs=sec_schools.crs,
        )
        for i, r in sec_schools.iterrows()
    ]
)

# merge overlapping polygons
# https://gis.stackexchange.com/questions/334459/how-to-dissolve-overlapping-polygons-using-geopandas
mergedpolys = gpd.GeoDataFrame(
    geometry=isochrones.groupby("time")["geometry"]
    .agg(lambda g: g.unary_union)
    .apply(lambda g: [g] if isinstance(g, Polygon) else g.geoms)
    .explode(),
    crs=isochrones.crs,
)

# visualize merged polygons
m = None
for i, wt in enumerate(WT[::-1]):
    m = mergedpolys.loc[[wt]].explore(
        m=m,
        color=colors.to_hex(cm.get_cmap("tab20b", len(WT))(i)),
        name=wt,
        height=300,
        width=500,
    )

m = sec_schools.head(SCHOOLS).explore(
    m=m, marker_kwds={"radius": 3, "color": "red"}, name="schools"
)
folium.LayerControl().add_to(m)

当我 运行 上面的代码时,我得到一个错误,“ValueError:在请求的多边形内找不到图形节点”。我有充分的理由相信这个错误可能取决于地方。我该怎么做?我将不胜感激任何帮助。我在想 try...catch 来捕获错误,但我不知道在代码中放置什么位置,或者我需要做的任何其他解决方案。这里是GitHub Link前20所学校。请注意,public 可以从他们的资源页面获得这些数据。

此外,我如何将超过 30 分钟的旅行时间绘制在地图上?就像上面的代码一样,有三个旅行时间,但只返回旅行时间为 30 分钟的多边形,而其余的则不返回。如果有办法解决这个问题,我将非常感谢您的帮助。

我看到三个sub-questions

  1. 错误“ValueError:在请求的多边形内找不到图形节点”。
  • 使用您提供的20个样本点没有出现这样的错误。确保您使用的是完全最新的 omnx
  • 另请参考:OSMNX graph from point and geometry information
  1. 性能
  • 预计ox.graph_from_point()需要时间。这是花费时间的地方。使用 pickle 文件实现了缓存,因此不会连续请求相同的结果
  1. 缺少地图图层
  • 没有丢失的地图层
  • ox.graph_from_point() 有一个 dist 参数。默认为 1000 米,不足以以 4.5 公里/小时的速度步行 60 分钟。已修改以定义足够的距离
import osmnx as ox
import pandas as pd
import warnings
import networkx as nx
import geopandas as gpd
from shapely.geometry import Point, Polygon
import shapely
import matplotlib.cm as cm
import matplotlib.colors as colors
import folium
from pathlib import Path

# getting isochrones is expensive, cache
f = Path.cwd().joinpath("pkl_cache")
if not f.is_dir():
    f.mkdir()
f = f.joinpath("isochrones.pkl")

if f.exists():
    isochrones = pd.read_pickle(f)
else:
    isochrones = gpd.GeoDataFrame(columns=["point_index", "name"])

warnings.simplefilter(action="ignore", category=FutureWarning)
warnings.simplefilter(action="ignore", category=PendingDeprecationWarning)
ox.config(use_cache=True, log_console=False)

# reading the data
# sec_data = pd.read_csv('H:/WORK/Upwork/Project 7 - Python School Data Analysis/Analysis By Country/Tanzania/CSV Files/secondary.csv')
# sec_schools = gpd.GeoDataFrame(
#     sec_data, geometry=gpd.points_from_xy(sec_data.longitude, sec_data.latitude)
# )
sec_schools = gpd.GeoDataFrame(
    pd.read_csv(
        "https://raw.githubusercontent.com/Evanskip31/isochrones-polygons/master/first_20_secondary_schools.csv",
        index_col=0,
    ).assign(geometry=lambda d: d["geometry"].apply(shapely.wkt.loads)),
    crs="epsg:4326",
)
sec_schools.crs = "EPSG:4326"

# we'll test it using the first few records
SCHOOLS = 20
sec_schools = sec_schools.head(SCHOOLS)

# function to get isochrones
def get_isochrone(
    lon, lat, walk_times=[15, 30], speed=4.5, name=None, point_index=None
):
    loc = (lat, lon)
    # take distance walked into account...
    G = ox.graph_from_point(
        loc,
        simplify=True,
        network_type="walk",
        dist=(max(walk_times) / 60) * (speed + 1) * 1000,
    )
    gdf_nodes = ox.graph_to_gdfs(G, edges=False)
    center_node = ox.distance.nearest_nodes(G, lon, lat)

    meters_per_minute = speed * 1000 / 60  # km per hour to m per minute
    for u, v, k, data in G.edges(data=True, keys=True):
        data["time"] = data["length"] / meters_per_minute
    polys = []
    for walk_time in walk_times:
        subgraph = nx.ego_graph(G, center_node, radius=walk_time, distance="time")
        node_points = [
            Point(data["x"], data["y"]) for node, data in subgraph.nodes(data=True)
        ]
        polys.append(gpd.GeoSeries(node_points).unary_union.convex_hull)
    info = {}
    if name:
        info["name"] = [name for t in walk_times]
    if point_index is not None:
        info["point_index"] = [point_index for t in walk_times]
    return {**{"geometry": polys, "time": walk_times}, **info}


# walk time list of minutes
WT = [30, 45, 60]

# build geopandas data frame of isochrone polygons for each school
isochrones = pd.concat(
    [isochrones]
    + [
        gpd.GeoDataFrame(
            get_isochrone(
                r["geometry"].x,
                r["geometry"].y,
                name=r["school name"],
                point_index=i,
                walk_times=WT,
            ),
            crs=sec_schools.crs,
        )
        for i, r in sec_schools.loc[
            ~sec_schools.index.isin(isochrones["point_index"].tolist())
        ].iterrows()
    ]
)

# save to act as a cache
isochrones.to_pickle(f)

# merge overlapping polygons
# https://gis.stackexchange.com/questions/334459/how-to-dissolve-overlapping-polygons-using-geopandas
mergedpolys = gpd.GeoDataFrame(
    geometry=isochrones.groupby("time")["geometry"]
    .agg(lambda g: g.unary_union)
    .apply(lambda g: [g] if isinstance(g, Polygon) else g.geoms)
    .explode(),
    crs=isochrones.crs,
)

# visualize merged polygons
m = None
for i, wt in enumerate(WT[::-1]):
    m = mergedpolys.loc[[wt]].explore(
        m=m,
        color=colors.to_hex(cm.get_cmap("tab20b", len(WT))(i)),
        name=wt,
        height=300,
        width=500,
    )

m = sec_schools.head(SCHOOLS).explore(
    m=m, marker_kwds={"radius": 3, "color": "red"}, name="schools"
)
folium.LayerControl().add_to(m)
m