BikeDNA(三) OSM数据的内在分析2

news2024/11/27 7:25:51

BikeDNA(三) OSM数据的内在分析2

1.数据完整性

见上一篇BikeDNA(二) OSM数据的内在分析1

2.OSM标签分析

见上一篇BikeDNA(二) OSM数据的内在分析1

3.网络拓扑结构

本节探讨数据的几何和拓扑特征。 例如,这些是网络密度、断开的组件和悬空(一级)节点。 它还包括探索是否存在彼此非常接近但不共享边缘的节点(边缘下冲的潜在迹象),或者是否存在相交边缘而在相交处没有节点,这可能表明存在数字化错误,该错误将导致数字化错误。 扭曲网络上的路由。

由于大多数自行车网络的分散性,许多指标(例如缺失链接或网络间隙)可以简单地反映基础设施的真实范围(Natera Orozco et al., 2020)。 这对于道路网络来说是不同的,例如,断开的组件更容易被解释为数据质量问题。 因此,分析仅将非常小的网络间隙视为潜在的数据质量问题。

3.1 简化结果

为了比较网络中节点和边之间的结构和真实比率,通过删除所有间隙节点,在笔记本“1a”中创建了仅包括端点和交叉点处的节点的简化网络表示。

比较简化前后网络的度分布是对简化例程的快速健全性检查。 通常,非简化网络中的绝大多数节点都是二级节点; 然而,在简化的网络中,大多数节点的度数不是二。 仅在两种情况下保留二级节点:如果它们代表两种不同类型的基础设施之间的连接点; 或者如果需要它们以避免自环(起点和终点相同的边)或同一对节点之间的多个边。

在这里插入图片描述

非简化网络(左)和简化网络(右)

方法

简化前后的度分布如下图所示。

解释

通常,度分布将从高(简化前)到低(简化后)二度节点计数,而对于所有其他度(1 或 3 及更高),它不会改变。 此外,节点总数将出现大幅下降。 如果简化后的图仍然保持相对较高的二度节点数量,或者简化后具有其他度数的节点数量发生变化,则这可能表明图转换或简化过程存在问题。

# Decrease in network elements after simplification

edge_percent_diff = (len(osm_edges) - len(osm_edges_simplified)) / len(osm_edges) * 100
node_percent_diff = (len(osm_nodes) - len(osm_nodes_simplified)) / len(osm_nodes) * 100

simplification_results = {
    "edge_percent_diff": edge_percent_diff,
    "node_percent_diff": node_percent_diff,
}

print(
    f"Simplifying the network decreased the number of edges by {edge_percent_diff:.1f}% and the number of nodes by {node_percent_diff:.1f}%."
)
Simplifying the network decreased the number of edges by 89.0% and the number of nodes by 84.4%.
# Degree distribution

set_renderer(renderer_plot)
fig, ax = plt.subplots(1, 2, figsize=pdict["fsbar_short"], sharey=True)

degree_sequence_before = sorted((d for n, d in osm_graph.degree()), reverse=True)
degree_sequence_after = sorted(
    (d for n, d in osm_graph_simplified.degree()), reverse=True
)

# Plot degree distributions
ax[0].bar(*np.unique(degree_sequence_before, return_counts=True), tick_label = np.unique(degree_sequence_before), color=pdict["osm_base"])
ax[0].set_title("Before simplification")
ax[0].set_xlabel("Degree")
ax[0].set_ylabel("Nodes")

ax[1].bar(*np.unique(degree_sequence_after, return_counts=True), tick_label = np.unique(degree_sequence_after), color=pdict["osm_base"])
ax[1].set_title("After simplification")
ax[1].set_xlabel("Degree")

plt.suptitle(f"{area_name}: OSM degree distributions")

fig.tight_layout()

plot_func.save_fig(fig, osm_results_plots_fp + "degree_dist_osm")

plt.show();

在这里插入图片描述

3.2 悬空节点

悬空节点是一阶节点,即它们仅附有一条边。 大多数网络自然会包含许多悬空节点。 悬空节点可能出现在实际的死胡同(代表死胡同)或某些特征的端点处,例如 当自行车道在街道中间结束时。 但是,在出现过冲/下冲的情况下,悬空节点也可能会作为数据质量问题出现(请参阅下一节)。 网络中悬空节点的数量在某种程度上也取决于数字化方法,如下图所示。

因此,悬空节点的存在本身并不是数据质量低的标志。 然而,在未知包含许多死胡同的区域中存在大量悬空节点可能表明数字化错误和边缘上冲/下冲问题。

在这里插入图片描述

左:悬挂节点出现在道路要素结束处。 右:但是,当最后连接单独的特征时,将不会有悬空节点。 -->

左:悬挂节点出现在道路要素结束处。 右:但是,当最后连接单独的特征时,将不会有悬空节点。

方法

下面,在“get_dangling_nodes”的帮助下获得了所有悬空节点的列表。 然后,绘制包含所有节点的网络。 悬空节点以颜色显示,所有其他节点以黑色显示。

解释

我们建议进行可视化分析,以解释悬挂节点的空间分布,特别注意悬挂节点密度高的区域。 重要的是要了解悬挂节点的来源:它们是真正的死胡同还是数字化错误(例如,过冲/下冲)? 数字化错误数量越多表明数据质量越低。


# Compute number of dangling nodes
dangling_nodes = eval_func.get_dangling_nodes(
    osm_edges_simplified, osm_nodes_simplified
)

# Export results
dangling_nodes.to_file(osm_results_data_fp + "dangling_nodes.gpkg", index=False)

# Compute local count and pct of dangling nodes
dn_osm_joined = gpd.overlay(
    dangling_nodes, grid[["geometry", "grid_id"]], how="intersection"
)
df = eval_func.count_features_in_grid(dn_osm_joined, "osm_dangling_nodes")
grid = eval_func.merge_results(grid, df, "left")

grid["osm_dangling_nodes_pct"] = np.round(
    100 * grid.count_osm_dangling_nodes / grid.count_osm_simplified_nodes, 2
)

# set to zero where there are simplified nodes but no dangling nodes
grid["osm_dangling_nodes_pct"].loc[
    grid.count_osm_simplified_nodes.notnull() & grid.osm_dangling_nodes_pct.isnull()
] = 0
# Plot dangling nodes

set_renderer(renderer_map)
fig, ax = plt.subplots(1, figsize=pdict["fsmap"])

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="3.5%", pad="1%")

grid.plot(
    cax=cax,
    column="osm_dangling_nodes_pct",
    ax=ax,
    alpha=pdict["alpha_grid"],
    cmap=pdict["pos"],
    legend=True,
)

# add no data patches
grid[grid["count_osm_simplified_nodes"].isnull()].plot(
    cax=cax,
    ax=ax,
    facecolor=pdict["nodata_face"],
    edgecolor=pdict["nodata_edge"],
    linewidth= pdict["line_nodata"],
    hatch=pdict["nodata_hatch"],
    alpha=pdict["alpha_nodata"],
)

ax.legend(handles=[nodata_patch], loc="upper right")
ax.set_title(f"{area_name}: OSM percent of dangling nodes")
ax.set_axis_off()
cx.add_basemap(ax=ax, crs=study_crs, source=cx_tile_2)

plot_func.save_fig(fig, osm_results_static_maps_fp + "pct_dangling_nodes_osm")

在这里插入图片描述

# Interactive plot of dangling nodes

edges_simplified_folium = plot_func.make_edgefeaturegroup(
    gdf=osm_edges_simplified,
    mycolor=pdict["base"],
    myweight=pdict["line_base"],
    nametag="Edges",
    show_edges=True,
)

nodes_simplified_folium = plot_func.make_nodefeaturegroup(
    gdf=osm_nodes_simplified,
    mysize=pdict["mark_base"],
    mycolor=pdict["base"],
    nametag="All nodes",
    show_nodes=True,
)

dangling_nodes_folium = plot_func.make_nodefeaturegroup(
    gdf=dangling_nodes,
    mysize=pdict["mark_emp"],
    mycolor= pdict["osm_base"],
    nametag="Dangling nodes",
    show_nodes=True,
)

m = plot_func.make_foliumplot(
    feature_groups=[
        edges_simplified_folium,
        nodes_simplified_folium,
        dangling_nodes_folium,
    ],
    layers_dict=folium_layers,
    center_gdf=osm_nodes_simplified,
    center_crs=osm_nodes_simplified.crs,
)

bounds = plot_func.compute_folium_bounds(osm_nodes_simplified)
m.fit_bounds(bounds)

m.save(osm_results_inter_maps_fp + "danglingmap_osm.html")

display(m)

在这里插入图片描述

print("Interactive map saved at " + osm_results_inter_maps_fp.lstrip("../") + "danglingmap_osm.html")
Interactive map saved at results/OSM/cph_geodk/maps_interactive/danglingmap_osm.html

3.3 下冲/过冲

当简化网络中的两个节点放置在几米距离内但不共享公共边缘时,通常是由于边缘上冲/下冲或其他数字化错误造成的。 当两个特征应该相交,但实际上彼此非常接近时,就会发生下冲。 当两个特征相遇并且其中一个特征超出另一个特征时,就会发生超调。 请参见下图的说明。 有关过冲/下冲的更详细说明,请参阅 GIS Lounge 网站。

在这里插入图片描述

左:当两条线要素未正确连接时(例如在交叉点处),会发生下冲。 右图:过冲是指线要素在相交线处延伸太远,而不是在相交处结束的情况。

方法

*下冲:*首先,“length_tolerance”(以米为单位)在下面的单元格中定义。 然后,使用“find_undershoots”,所有之间距离最大为“length_tolerance”的悬空节点对都被识别为下冲,并绘制结果。

*超调:*首先,“长度公差”(以米为单位)在下面的单元格中定义。 然后,使用“find_overshoots”,所有连接有悬空节点且最大长度为“length_tolerance”的网络边都被识别为过冲,并绘制结果。

过冲/下冲检测方法的灵感来自于 Neis et al. (2012)。

解释

欠调/过调不一定总是数据质量问题 - 它们可能是网络状况或数字化策略的准确表示。 例如,自行车道可能在转弯后不久突然结束,从而导致超调。 受保护的自行车道有时会在 OSM 中数字化,因为在交叉口处中断,从而导致交叉口下冲。

过冲/下冲对数据质量影响的解释取决于上下文。 对于某些应用,例如路由,过冲并不构成特殊的挑战; 然而,鉴于它们扭曲了网络结构,它们可能会给网络分析等其他应用带来问题。 相反,下冲对于路线应用来说是一个严重的问题,特别是如果只考虑自行车基础设施的话。 它们还给网络分析带来了问题,例如对于任何基于路径的度量,例如大多数中心性度量,如介数中心性。


User configurations

在分析过冲和下冲时,用户可以修改过冲和下冲的长度公差。
例如,过冲的长度容差为 3 米,这意味着只有长度为 3 米或更小的边缘片段才被视为过冲。
下冲容差为 5 米,意味着只有 5 米或更小的间隙才被视为下冲。
# USER INPUT: LENGTH TOLERANCE FOR OVER- AND UNDERSHOOTS
length_tolerance_over = 3
length_tolerance_under = 3

for s in [length_tolerance_over, length_tolerance_under]:
    assert isinstance(s, int) or isinstance(s, float), print(
        "Settings must be integer or float values!"
    )

print(f"Running overshoot analysis with a tolerance threshold of {length_tolerance_over} m.")
print(f"Running undershoot analysis with a tolerance threshold of {length_tolerance_under} m.")
Running overshoot analysis with a tolerance threshold of 3 m.
Running undershoot analysis with a tolerance threshold of 3 m.
### Overshoots

overshoots = eval_func.find_overshoots(
    dangling_nodes,
    osm_edges_simplified,
    length_tolerance_over,
    return_overshoot_edges=True,
)

print(
    f"{len(overshoots)} potential overshoots were identified using a length tolerance of {length_tolerance_over} m."
)

### Undershoots
undershoot_dict, undershoot_nodes = eval_func.find_undershoots(
    dangling_nodes,
    osm_edges_simplified,
    length_tolerance_under,
    "edge_id",
    return_undershoot_nodes=True,
)

print(
    f"{len(undershoot_nodes)} potential undershoots were identified using a length tolerance of {length_tolerance_under} m."
)
8 potential overshoots were identified using a length tolerance of 3 m.
18 potential undershoots were identified using a length tolerance of 3 m.
# Save to csv

overshoots[["edge_id", "length"]].to_csv(
    osm_results_data_fp + f"overshoot_edges_{length_tolerance_over}.csv", header = ["edge_id", "length (m)"], index = False
)

pd.DataFrame(undershoot_nodes["osmid"].to_list(), columns=["node_id"]).to_csv(
    osm_results_data_fp + f"undershoot_nodes_{length_tolerance_under}.csv", index=False
)
# Interactive plot of under/overshoots

simplified_edges_folium = plot_func.make_edgefeaturegroup(
    gdf=osm_edges_simplified,
    mycolor=pdict["base"],
    myweight=pdict["line_base"],
    nametag="Edges",
    show_edges=True,
)

fg = [simplified_edges_folium]

if len(overshoots) > 0 or len(undershoot_nodes) > 0:

    if len(overshoots) > 0:

        overshoots_folium = plot_func.make_edgefeaturegroup(
            gdf=overshoots,
            mycolor=pdict["osm_contrast"],
            myweight=pdict["line_emp2"],
            nametag="Overshoots",
            show_edges=True,
        )

        fg.append(overshoots_folium)

    if len(undershoot_nodes) > 0:

        undershoot_nodes_folium = plot_func.make_nodefeaturegroup(
            gdf=undershoot_nodes,
            mysize=pdict["mark_emp"],
            mycolor=pdict["osm_contrast2"],
            nametag="Undershoot nodes",
            show_nodes=True,
        )

        fg.append(undershoot_nodes_folium)

    m = plot_func.make_foliumplot(
        feature_groups=fg,
        layers_dict=folium_layers,
        center_gdf=osm_nodes_simplified,
        center_crs=osm_nodes_simplified.crs,
    )

    bounds = plot_func.compute_folium_bounds(osm_nodes_simplified)
    m.fit_bounds(bounds)

    m.save(
        osm_results_inter_maps_fp
        + f"underovershoots_{length_tolerance_under}_{length_tolerance_over}_osm.html"
    )

    display(m)

if len(undershoot_nodes) == 0:
    print("There are no undershoots to plot.")
if len(overshoots) == 0:
    print("There are no overshoots to plot.")

在这里插入图片描述

if len(overshoots) > 0 or len(undershoot_nodes) > 0:
    print("Interactive map saved at " + osm_results_inter_maps_fp.lstrip("../") + f"underovershoots_{length_tolerance_under}_{length_tolerance_over}_osm.html")
else:
    print("There are no under/overshoots to plot.")
Interactive map saved at results/OSM/cph_geodk/maps_interactive/underovershoots_3_3_osm.html

3.4 缺少交叉点

当两条边相交而相交处没有节点时 - 并且如果两条边都没有标记为桥或隧道 - 则明确指示存在拓扑错误。

方法

首先,在“check_intersection”的帮助下,检查未标记为隧道或桥的每个边缘是否与网络的另一个边缘有任何“交叉”。 如果是这种情况,则该边将被标记为存在相交问题。 打印发现的相交问题的数量,并绘制结果以进行可视化分析。 该方法的灵感来自 Neis et al. (2012)。

解释

交叉点问题数量越多表明数据质量越低。 但是,建议在对该区域有一定了解的情况下对所有交叉口问题进行手动目视检查,以确定交叉口问题的根源并确认/纠正/拒绝它们。

Warning

这是该笔记本中计算量最大的操作。 它可能比所有其他部分花费的时间长几倍。

missing_nodes_edge_ids, edges_with_missing_nodes = eval_func.find_missing_intersections(
    osm_edges, "edge_id"
)

count_intersection_issues = (
    len(missing_nodes_edge_ids) / 2
)  # The number of issues is counted twice since both intersecting osm_edges are returned

print(
    f"{count_intersection_issues:.0f} place(s) appear to be missing an intersection node or a bridge/tunnel tag."
)
0 place(s) appear to be missing an intersection node or a bridge/tunnel tag.
# Save to csv

if count_intersection_issues > 0: 
    pd.DataFrame(data=missing_nodes_edge_ids, columns=["edge_id"]).to_csv(
        osm_results_data_fp + "edges_missing_intersections.csv", index=False
    )
# Interactive plot of intersection issues

if count_intersection_issues > 0:

    simplified_edges_folium = plot_func.make_edgefeaturegroup(
        gdf=osm_edges_simplified,
        mycolor=pdict["base"],
        myweight=pdict["line_base"],
        nametag="All edges",
        show_edges=True,
    )

    intersection_issues_folium = plot_func.make_edgefeaturegroup(
        gdf=edges_with_missing_nodes,
        mycolor=pdict["osm_contrast"],
        myweight=pdict["line_emp"],
        nametag="Intersection issues: edges",
        show_edges=True,
    )

    mfg = plot_func.make_markerfeaturegroup(
        edges_with_missing_nodes, 
        nametag="Intersection issues: marker at missing node", 
        show_markers=True
    )
  
    m = plot_func.make_foliumplot(
        feature_groups=[simplified_edges_folium, intersection_issues_folium, mfg],
        layers_dict=folium_layers,
        center_gdf=osm_nodes_simplified,
        center_crs=osm_nodes_simplified.crs,
    )

    bounds = plot_func.compute_folium_bounds(osm_nodes_simplified)
    m.fit_bounds(bounds)

    m.save(osm_results_inter_maps_fp + "intersection_issues_osm.html")

    display(m)
if count_intersection_issues > 0:
    print("Interactive map saved at " + osm_results_inter_maps_fp.lstrip("../") + "intersection_issues_osm.html")
else:
    print("There are no intersection problems to plot.")
There are no intersection problems to plot.

4.网络组件

断开连接的组件不共享任何元素(节点/边)。 换句话说,没有网络路径可以从一个断开连接的组件通向另一组件。 如上所述,大多数现实世界的自行车基础设施网络确实由许多断开连接的组件组成(Natera Orozco et al., 2020) 。 然而,当两个断开的组件彼此非常接近时,这可能是边缘缺失或另一个数字化错误的迹象。

方法

首先,在“return_components”的帮助下,获得网络的所有(断开连接的)组件的列表。 打印组件总数,并以不同颜色绘制所有组件以进行视觉分析。 接下来,绘制组件大小分布(组件按其包含的网络长度排序),然后绘制最大连接组件的图。

解释

与之前的许多分析步骤一样,该领域的知识对于正确解释成分分析至关重要。 鉴于数据准确地代表了实际的基础设施,较大的组件表示连贯的网络部分,而较小的组件表示分散的基础设施(例如,沿着街道的一条自行车道,不连接到任何其他自行车基础设施)。 大量彼此邻近的断开组件表明数字化错误或丢失数据。

4.1 断开的组件

osm_components = eval_func.return_components(osm_graph_simplified)
print(
    f"The network in the study area has {len(osm_components)} disconnected components."
)
The network in the study area has 356 disconnected components.
# Plot disconnected components

set_renderer(renderer_map)

# set seed for colors
np.random.seed(42)

# generate enough random colors to plot all components
randcols = np.random.rand(len(osm_components), 3)
randcols[0, :] = col_to_rgb(pdict['osm_base'])

fig, ax = plt.subplots(1, 1, figsize=pdict["fsmap"])

ax.set_title(f"{area_name}: OSM disconnected components")

ax.set_axis_off()

for j, c in enumerate(osm_components):
    if len(c.edges) > 0:
        edges = ox.graph_to_gdfs(c, nodes=False)
        edges.plot(ax=ax, color=randcols[j])

cx.add_basemap(ax=ax, crs=study_crs, source=cx_tile_2)

plot_func.save_fig(fig, osm_results_static_maps_fp + "all_components_osm")

在这里插入图片描述

4.2 每个网格单元的组件

# Assign component ids to grid

grid = eval_func.assign_component_id_to_grid(
    osm_edges_simplified,
    osm_edges_simp_joined,
    osm_components,
    grid,
    prefix="osm",
    edge_id_col="edge_id",
)

fill_na_dict = {"component_ids_osm": ""}
grid.fillna(value=fill_na_dict, inplace=True)

grid["component_count_osm"] = grid.component_ids_osm.apply(lambda x: len(x))

# Plot number of components per grid cell

set_renderer(renderer_map)

fig, ax = plt.subplots(1, 1, figsize=pdict["fsmap"])

ncolors = grid["component_count_osm"].max()

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="3.5%", pad="1%")

mycm = cm.get_cmap(pdict["seq"], ncolors) 
grid[grid.component_count_osm>0].plot(
    cax=cax,
    ax=ax,
    column="component_count_osm",
    legend=True,
    legend_kwds={'ticks': list(range(1, ncolors+1))},
    cmap=mycm,
    alpha=pdict["alpha_grid"],
)

# add no data patches
grid[grid["count_osm_edges"].isnull()].plot(
    cax=cax,
    ax=ax,
    facecolor=pdict["nodata_face"],
    edgecolor=pdict["nodata_edge"],
    linewidth= pdict["line_nodata"],
    hatch=pdict["nodata_hatch"],
    alpha=pdict["alpha_nodata"],
)

ax.legend(handles=[nodata_patch], loc="upper right")

cx.add_basemap(ax=ax, crs=study_crs, source=cx_tile_2)
ax.set_title(area_name + ": OSM number of components in grid cells")
ax.set_axis_off()

plot_func.save_fig(fig, osm_results_static_maps_fp + f"number_of_components_in_grid_cells_osm")

在这里插入图片描述

4.3 组件长度分布

所有网络组件长度的分布可以在所谓的 Zipf 图 中可视化,该图按等级对每个组件的长度进行排序,在左侧显示最大组件的长度,然后是第二大组件的长度,依此类推,直到 右侧最小组件的长度。 当 Zipf 图遵循 双对数比例 中的直线时,这意味着找到小的不连续组件的机会比传统分布的预期要高得多 (Clauset et al., 2009)。 这可能意味着网络没有合并,只有分段或随机添加 (Szell et al., 2022),或者数据本身存在许多间隙和拓扑错误,导致小的断开组件。

但是,也可能发生最大的连通分量(图中最左边的标记,等级为 1 0 0 10^0 100)是明显的异常值,而图的其余部分则遵循不同的形状。 这可能意味着在基础设施层面,大部分基础设施已连接到一个大型组件,并且数据反映了这一点 - 即数据在很大程度上没有受到间隙和缺失链接的影响。

自行车网络也可能介于两者之间,有几个大型组件作为异常值。

# Zipf plot of component lengths

set_renderer(renderer_plot)

components_length = {}
for i, c in enumerate(osm_components):
    c_length = 0
    for (u, v, l) in c.edges(data="length"):
        c_length += l
    components_length[i] = c_length

components_df = pd.DataFrame.from_dict(components_length, orient="index")
components_df.rename(columns={0: "component_length"}, inplace=True)

fig = plt.figure(figsize=pdict["fsbar_small"])
axes = fig.add_axes([0, 0, 1, 1])

axes.set_axisbelow(True)
axes.grid(True,which="major",ls="dotted")
yvals = sorted(list(components_df["component_length"] / 1000), reverse = True)
axes.scatter(
    x=[i+1 for i in range(len(components_df))],
    y=yvals,
    s=18,
    color=pdict["osm_base"],
)
axes.set_ylim(ymin=10**math.floor(math.log10(min(yvals))), ymax=10**math.ceil(math.log10(max(yvals))))
axes.set_xscale("log")
axes.set_yscale("log")

axes.set_ylabel("Component length [km]")
axes.set_xlabel("Component rank (largest to smallest)")
axes.set_title(area_name+": OSM component length distribution")

plot_func.save_fig(fig, osm_results_plots_fp + "component_length_distribution_osm")

在这里插入图片描述

4.4 最大连通分量

largest_cc = max(osm_components, key=len)

largest_cc_length = 0

for (u, v, l) in largest_cc.edges(data="length"):

    largest_cc_length += l

largest_cc_pct = largest_cc_length / components_df["component_length"].sum() * 100

print(
    f"The largest connected component contains {largest_cc_pct:.2f}% of the network length."
)

# Get edges in largest cc
lcc_edges = ox.graph_to_gdfs(
    G=largest_cc, nodes=False, edges=True, node_geometry=False, fill_edge_geometry=False
)

# Export to GPKG
lcc_edges[["edge_id", "geometry"]].to_file(
    osm_results_data_fp + "largest_connected_component.gpkg"
)
The largest connected component contains 91.47% of the network length.
# Plot of largest connected component

set_renderer(renderer_map)
fig, ax = plt.subplots(1, 1, figsize=pdict["fsmap"])
osm_edges_simplified.plot(ax=ax, color = pdict["base"], linewidth = 1.5, label = "All smaller components")
lcc_edges.plot(ax=ax, color=pdict["osm_base"], linewidth = 2, label = "Largest connected component")
grid.plot(ax=ax,alpha=0)
ax.set_axis_off()
ax.set_title(area_name + ": OSM largest connected component")
ax.legend()

cx.add_basemap(ax=ax, crs=study_crs, source=cx_tile_2)

plot_func.save_fig(fig, osm_results_static_maps_fp + f"largest_conn_comp_osm")

在这里插入图片描述

# Save plot without basemap for potential report titlepage

set_renderer(renderer_map)
fig, ax = plt.subplots(1, 1, figsize=pdict["fsmap"])
osm_edges_simplified.plot(ax=ax, color = pdict["base"], linewidth = 1.5, label = "Disconnected components")
lcc_edges.plot(ax=ax, color=pdict["osm_base"], linewidth = 2, label = "Largest connected component")
ax.set_axis_off()

plot_func.save_fig(fig, osm_results_static_maps_fp + f"titleimage",plot_res="high")
plt.close()

4.5 缺少链接

在组件之间潜在缺失链接的图中,将绘制与另一个组件上的边的指定距离内的所有边。 断开的边缘之间的间隙用标记突出显示。 因此,该地图突出显示了边缘,尽管这些边缘彼此非常接近,但它们是断开连接的,因此不可能在边缘之间的自行车基础设施上骑自行车。

User configuration

在分析组件之间潜在的缺失链接时,用户必须定义两个组件之间的距离被认为足够低以至于怀疑数字化错误的阈值。

# DEFINE MAX BUFFER DISTANCE BETWEEN COMPONENTS CONSIDERED A GAP/MISSING LINK
component_min_distance = 10

assert isinstance(component_min_distance, int) or isinstance(
    component_min_distance, float
), print("Setting must be integer or float value!")

print(f"Running analysis with component distance threshold of {component_min_distance} meters.")
Running analysis with component distance threshold of 10 meters.
component_gaps = eval_func.find_adjacent_components(
    components=osm_components,
    buffer_dist=component_min_distance,
    crs=study_crs,
    edge_id="edge_id",
)
component_gaps_gdf = gpd.GeoDataFrame.from_dict(
    component_gaps, orient="index", geometry="geometry", crs=study_crs
)

edge_ids = set(
    component_gaps_gdf["edge_id" + "_left"].to_list()
    + component_gaps_gdf["edge_id" + "_right"].to_list()
)

edge_ids = [int(i) for i in edge_ids]
edges_with_gaps = osm_edges_simplified.loc[osm_edges_simplified.edge_id.isin(edge_ids)]
# Save to csv
pd.DataFrame(edge_ids, columns=["edge_id"]).to_csv(
    osm_results_data_fp + f"component_gaps_edges_{component_min_distance}.csv",
    index=False,
)

# Export gaps to GPKG
component_gaps_gdf.to_file(
    osm_results_data_fp + f"component_gaps_centroids_{component_min_distance}.gpkg"
)
# Interactive plot of adjacent, potentially disconnected components

if len(component_gaps) > 0:

    simplified_edges_folium = plot_func.make_edgefeaturegroup(
        gdf=osm_edges_simplified,
        mycolor=pdict["osm_base"],
        myweight=pdict["line_base"],
        nametag="All edges",
        show_edges=True,
    )

    component_issues_edges_folium = plot_func.make_edgefeaturegroup(
        gdf=edges_with_gaps,
        mycolor=pdict["osm_emp"],
        myweight=pdict["line_emp"],
        nametag="Adjacent disconnected edges",
        show_edges=True,
    )

    component_issues_gaps_folium = plot_func.make_markerfeaturegroup(
        gdf=component_gaps_gdf, nametag="Component gaps", show_markers=True
    )

    m = plot_func.make_foliumplot(
        feature_groups=[
            simplified_edges_folium,
            component_issues_edges_folium,
            component_issues_gaps_folium,
        ],
        layers_dict=folium_layers,
        center_gdf=osm_nodes_simplified,
        center_crs=osm_nodes_simplified.crs,
    )

    bounds = plot_func.compute_folium_bounds(osm_nodes_simplified)
    m.fit_bounds(bounds)

    m.save(osm_results_inter_maps_fp + f"component_gaps_{component_min_distance}_osm.html")

    display(m)

在这里插入图片描述

if len(component_gaps) > 0:
    print("Interactive map saved at " + osm_results_inter_maps_fp.lstrip("../") + f"component_gaps_{component_min_distance}_osm.html")
else:
    print("There are no component gaps to plot.")
Interactive map saved at results/OSM/cph_geodk/maps_interactive/component_gaps_10_osm.html

4.6 组件连接

在这里,我们可视化每个单元格可以到达的单元格数量之间的差异。 这是对网络连接性的粗略测量,但具有计算成本低的优点,因此能够快速突出网络连接性的明显差异。

osm_components_cell_count = eval_func.count_component_cell_reach(
    components_df, grid, "component_ids_osm"
)
grid["cells_reached_osm"] = grid["component_ids_osm"].apply(
    lambda x: eval_func.count_cells_reached(x, osm_components_cell_count)
    if x != ""
    else 0
)

grid["cells_reached_osm_pct"] = grid.apply(
    lambda x: np.round((x.cells_reached_osm / len(grid)) * 100, 2), axis=1
)

grid.loc[grid["cells_reached_osm_pct"] == 0, "cells_reached_osm_pct"] = np.NAN
# Plot percent of cells reachable

set_renderer(renderer_map)
fig, ax = plt.subplots(1, 1, figsize=pdict["fsmap"])

# norm for color bars
cbnorm_reach = colors.Normalize(vmin=0, vmax=100)

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="3.5%", pad="1%")

grid[grid.cells_reached_osm_pct > 0].plot(
    cax=cax,
    ax=ax,
    column="cells_reached_osm_pct",
    legend=True,
    cmap=pdict["seq"],
    norm=cbnorm_reach,
    alpha=pdict["alpha_grid"],
)

osm_edges_simplified.plot(ax=ax, color=pdict["osm_emp"], linewidth=1)

# add no data patches
grid[grid["count_osm_edges"].isnull()].plot(
    cax=cax,
    ax=ax,
    facecolor=pdict["nodata_face"],
    edgecolor=pdict["nodata_edge"],
    linewidth= pdict["line_nodata"],
    hatch=pdict["nodata_hatch"],
    alpha=pdict["alpha_nodata"],
)

ax.legend(handles=[nodata_patch], loc="upper right")

cx.add_basemap(ax=ax, crs=study_crs, source=cx_tile_2)
ax.set_title(area_name+": OSM percent of cells reachable")
ax.set_axis_off()

plot_func.save_fig(fig, osm_results_static_maps_fp + "percent_cells_reachable_grid_osm")

在这里插入图片描述

components_results = {}
components_results["component_count"] = len(osm_components)
components_results["largest_cc_pct_size"] = largest_cc_pct
components_results["largest_cc_length"] = largest_cc_length
components_results["count_component_gaps"] = len(component_gaps)

5.概括

# Print out table summary of results

summarize_results = {**density_results, **components_results}

summarize_results["count_dangling_nodes"] = len(dangling_nodes)
summarize_results["count_intersection_issues"] = count_intersection_issues
summarize_results["count_overshoots"] = len(overshoots)
summarize_results["count_undershoots"] = len(undershoot_nodes)
summarize_results["count_incompatible_tags"] = sum(
    len(lst) for lst in incompatible_tags_results.values()
)

# Add total node count and total infrastructure length
summarize_results["total_nodes"] = len(osm_nodes_simplified)
summarize_results["total_length"] = osm_edges_simplified.infrastructure_length.sum() / 1000

summarize_results_df = pd.DataFrame.from_dict(summarize_results, orient="index")

summarize_results_df.rename({0: " "}, axis=1, inplace=True)

# Convert length to km
summarize_results_df.loc["largest_cc_length"] = (
    summarize_results_df.loc["largest_cc_length"] / 1000
)

summarize_results_df = summarize_results_df.reindex([
    'total_length',
    'protected_density_m_sqkm',
    'unprotected_density_m_sqkm',
    'mixed_density_m_sqkm',
    'edge_density_m_sqkm',
    'total_nodes',
    'count_dangling_nodes',
    'node_density_count_sqkm',
    'dangling_node_density_count_sqkm',
    'count_incompatible_tags',
    'count_overshoots',
    'count_undershoots',
    'count_intersection_issues',
    'component_count',
    'largest_cc_length',
    'largest_cc_pct_size', 
    'count_component_gaps'
     ])

rename_metrics = {
    "total_length": "Total infrastructure length (km)",
    "total_nodes": "Nodes",
    "edge_density_m_sqkm": "Bicycle infrastructure density (m/km2)",
    "node_density_count_sqkm": "Nodes per km2",
    "dangling_node_density_count_sqkm": "Dangling nodes per km2",
    "protected_density_m_sqkm": "Protected bicycle infrastructure density (m/km2)",
    "unprotected_density_m_sqkm": "Unprotected bicycle infrastructure density (m/km2)",
    "mixed_density_m_sqkm": "Mixed protection bicycle infrastructure density (m/km2)",
    "component_count": "Components",
    "largest_cc_pct_size": "Largest component's share of network length",
    "largest_cc_length": "Length of largest component (km)",
    "count_component_gaps": "Component gaps",
    "count_dangling_nodes": "Dangling nodes",
    "count_intersection_issues": "Missing intersection nodes",
    "count_overshoots": "Overshoots",
    "count_undershoots": "Undershoots",
    "count_incompatible_tags": "Incompatible tag combinations",
}

summarize_results_df.rename(rename_metrics, inplace=True)
summarize_results_df.style.pipe(format_osm_style)
Intrinsic Quality Metrics - OSM data
 
Total infrastructure length (km)1,056
Protected bicycle infrastructure density (m/km2)5,342
Unprotected bicycle infrastructure density (m/km2)427
Mixed protection bicycle infrastructure density (m/km2)55
Bicycle infrastructure density (m/km2)5,825
Nodes5,016
Dangling nodes1,828
Nodes per km228
Dangling nodes per km210
Incompatible tag combinations2
Overshoots8
Undershoots18
Missing intersection nodes0
Components356
Length of largest component (km)747
Largest component's share of network length91%
Component gaps78

6.保存结果

all_results = {}

all_results["existing_tags"] = existing_tags_results
all_results["incompatible_tags_results"] = incompatible_tags_results
all_results["incompatible_tags_count"] = sum(
    len(lst) for lst in incompatible_tags_results.values()
)
all_results["network_density"] = density_results
all_results["count_intersection_issues"] = count_intersection_issues
all_results["count_overshoots"] = len(overshoots)
all_results["count_undershoots"] = len(undershoot_nodes)
all_results["dangling_node_count"] = len(dangling_nodes)
all_results["simplification_outcome"] = simplification_results
all_results["component_analysis"] = components_results

with open(osm_intrinsic_fp, "w") as outfile:
    json.dump(all_results, outfile)


# Save summary dataframe
summarize_results_df.to_csv(
    osm_results_data_fp + "intrinsic_summary_results.csv", index=True
)

# Save grid with results
with open(osm_intrinsic_grid_fp, "wb") as f:
    pickle.dump(grid, f)

from time import strftime
print("Time of analysis: " + strftime("%a, %d %b %Y %H:%M:%S"))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1349399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项样题卷②

2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项(高职组) 样题(第2套) 目录 2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项(高职组) 样题(第2套) 模块…

基于grpc从零开始搭建一个准生产分布式应用(8) - 01 - 附:GRPC公共库源码

开始前必读&#xff1a;​​基于grpc从零开始搭建一个准生产分布式应用(0) - quickStart​​ common包中的源码&#xff0c;因后续要用所以一次性全建好了。 一、common工程完整结构 二、引入依赖包 <?xml version"1.0" encoding"UTF-8"?> <p…

【Pytorch】Pytorch或者CUDA版本不符合问题解决与分析

NVIDIA CUDA Toolkit Release Notes Package installation issues INSTALL PYTORCH 先声毒人&#xff1a;最好资料就是上面三份资料&#xff0c;可以通过官网明确的获取一手信息&#xff0c;你所遇到的99%的问题都可以找到&#xff0c;明确的解决方案&#xff0c;建议最好看…

2024主流的免费电脑数据恢复软件EasyRecovery

EasyRecovery绿色版是一款非常专业的电脑数据恢复工具。它可以全面恢复删除丢失数据&#xff0c;能对电脑误删文件恢复、格式化硬盘数据恢复、手机U盘数据恢复等&#xff0c;能恢复包括文档、表格、图片、音视频等各种文件&#xff0c;此版本经过处理&#xff0c;可永久免费使用…

[ffmpeg系列 02] 音视频基本知识

一 视频 RGB&#xff1a; AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB… Y&#xff1a;明亮度, Luminance或luma, 灰阶图&#xff0c; UV&#xff1a;色度&#xff0c;Chrominance或Chroma。 YCbCr: Cb蓝色分量&#xff0c;Cr是红色分量。 取值范围&#xff…

【索引的数据结构】第1章节:B+Tree存储结构

目录结构 之前整篇文章太长&#xff0c;阅读体验不好&#xff0c;将其拆分为几个子篇章。 本篇章讲解 BTree 存储结构。 什么是索引 可以简单理解为索引好比一本书的目录&#xff0c;通过目录我们可以快速定位到我们要查看的章节。 MySQL 中的数据同样也是根据索引分类&…

ROS TF坐标变换 - 位姿描述与消息类型

目录 一、位姿描述二、位姿相关消息体类型2.1 geometry_msgs/TransformStamped2.2 geometry_msgs/PoseStamped 在机器人系统中&#xff0c;有许多运动机构和传感器&#xff0c;为了描述他们之间的相对位姿关系&#xff0c;分别为他们定义了各自的坐标系&#xff0c;通过坐标系转…

合伙企业法关于合伙企业的要求

合伙协议可以载明合伙企业的经营期限和合伙人争议的解决方式。 合伙协议经全体合伙人签名、盖章后生效。合伙人依照合伙协议享有权利&#xff0c;承担责任。 经全体合伙人协商一致&#xff0c;可以修改或者补充合伙协议。 申请合伙企业设立登记&#xff0c;应当向企业登记机关提…

B2005 字符三角形(python)

a input() print( a) print( a a a) print(a a a a a)python中默认输入的是字符型&#xff0c;第一句就是输入了一个字符赋给a python中单引号内的也是字符串&#xff0c;用print输出需要连接的字符串时用加号加在后面即可

Spring通信传参的方法

Spring通信传参的方法 目录概述需求&#xff1a; 设计思路实现思路分析1.简单参数传递2.复合参数3.动态参数 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better resul…

爬虫基础一(持续更新)

爬虫概念&#xff1a; 通过编写程序&#xff0c;模拟浏览器上网&#xff0c;然后让其去互联网上抓取数据的过程 分类&#xff1a; 1&#xff0c;通用爬虫&#xff1a;抓取一整张页面数据 2&#xff0c;聚焦爬虫&#xff1a;抓取页面中的局部内容 3&#xff0c;增量式爬虫&…

消息中间件常见知识点

一&#xff1a;消息队列的主要作用是什么&#xff1f; 1.消息队列的特性&#xff1a; 业务无关&#xff0c;一个具有普适性质的消息队列组件不需要考虑上层的业务模型&#xff0c;只做好消息的分发就可以了&#xff0c;上层业务的不同模块反而需要依赖消息队列所定义的规范进行…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

go语言语法基础

文章目录 前言一、输入和输出常用的字符串格式化符号 二、注释三、Go常用基本语言数据类型数字类型布尔类型字符类型变量与常量数组和切片数组切片 map类型创建map增删改查特别提醒 指针 四、运算符五、条件判断语句if系列switch六、循环语句for循环标准写法死循环while循环do …

java设计模式学习之【观察者模式】

文章目录 引言观察者模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用观察者示例代码地址 引言 想象你在一个在线拍卖会上竞标一件珍贵的艺术品。每当有人出价更高&#xff0c;系统都会立即通知你。这个实时更新机制使你可以做出快速反应。这种模式&#x…

Linux之进程管理

什么是进程 在linux中每个执行的程序都称为一个进程&#xff0c;每个进程都分配一个ID号&#xff08;pid进程号&#xff09;。每个进程都可能以两种方式存在&#xff0c;即前台和后天。前台进程就是用户目前的屏幕上可以进行操作的。后台进程则是实际在操作&#xff0c;但屏幕…

hugo-theme-kiwi V0.0.2 博客主题上新了时间轴

至此佳节&#xff0c;我在此给正在屏幕前浏览本文的您和您的家人&#xff0c;恭祝元旦快乐&#xff0c;虽然&#xff0c;这声祝福是晚了&#xff0c;但却不妨碍我我由内心深处对您和您的家人的诚挚祝福&#xff01; 新的一年&#xff0c;从这一天逐渐步入我们的生活&#xff0c…

web等保评测需要实机查看的操作系统、服务器、数据库和应用部分

“等保测评”全称是信息安全等级保护测评。是经公安部认证的具有资质的测评机构&#xff0c;依据国家信息安全等级保护规范规定&#xff0c;受有关单位委托&#xff0c;按照有关管理规范和技术标准&#xff0c;对信息系统安全等级保护状况进行检测评估的活动。 本文陆续将遇到的…

『番外篇九』SwiftUI 实战:打造一款“五脏俱全”的网络图片显示 App(上)

概览 俗话说得好:“读书破万卷,下笔如有神”。不过如果把这句话放到编程的学习上可就不那么贴切了。 要想熟练掌握一门编程语言,光看书是绝对不够的。我们还需尽可能的多撸码、早撸码,撸到无路可退、海枯石烂才有可能一窥门径。 在本篇和续篇博文中,我们将和小伙伴们一起…

手把手教你绘制和解读实用R列线图(Nomogram):从入门到精通

一、引言 列线图&#xff08;Nomogram&#xff09;是一种常用的数据可视化工具&#xff0c;它能够直观地展示多个变量之间的关系&#xff0c;并帮助我们理解和解释复杂的数据模式。通过绘制列线图&#xff0c;我们可以将各种变量的影响和相互关联转化为图形化的表示&#xff0c…