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

2024-01-01 22:28
文章标签 分析 数据 内在 osm bikedna

本文主要是介绍BikeDNA(三) OSM数据的内在分析2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 simplificationedge_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) * 100simplification_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 distributionset_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 nodesset_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 nodesedges_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 = 3for 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.
### Overshootsovershoots = 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 csvovershoots[["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/overshootssimplified_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 returnedprint(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 csvif 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 issuesif 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 componentsset_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 gridgrid = 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 cellset_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 lengthsset_renderer(renderer_plot)components_length = {}
for i, c in enumerate(osm_components):c_length = 0for (u, v, l) in c.edges(data="length"):c_length += lcomponents_length[i] = c_lengthcomponents_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 = 0for (u, v, l) in largest_cc.edges(data="length"):largest_cc_length += llargest_cc_pct = largest_cc_length / components_df["component_length"].sum() * 100print(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 componentset_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 titlepageset_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 = 10assert 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 componentsif 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 reachableset_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 resultssummarize_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() / 1000summarize_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_resultswith 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"))

这篇关于BikeDNA(三) OSM数据的内在分析2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/560669

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav