Я использую OSMnx для упрощения направленной дорожной сети, построенной на основе данных OpenStreetMap.
График представляет собой MultiDiGraph, где улицы с двусторонним движением представлены двумя взаимно направленными ребрами (
Код: Выделить всё
u→vКогда я применяю упрощение OSMnx по умолчанию, геометрия ребер перестраивается и становится согласованной по направлению: геометрия u→v упорядочивается от u до v, а обратное ребро v→u получает обратную геометрию.
Однако, когда я ослабляю определение конечной точки с помощью Edge_attrs_differ, я наблюдаю противоречивый результат:
- Геометрия некоторых ребер перестраивается и ориентируется в соответствии с направлением ребер.
- Другие ребра (состоящие ровно из двух конечных точек) не перестраиваются и сохраняют свою исходную ориентацию геометрии.
После проверки simplification.py кажется, что это происходит потому, что _get_paths_to_simplify() не генерирует путь, когда ребро соединяет два узла конечных точек напрямую, поэтому его геометрия не восстанавливается с помощью:
Код: Выделить всё
# construct the new consolidated edge's geometry for this path
path_attributes["geometry"] = LineString(
[Point((G.nodes[node]["x"], G.nodes[node]["y"])) for node in path],
)
- Является ли эта смешанная геометрическая ориентация ожидаемым поведением simple_graph при использовании Edge_attrs_differ или это ошибка?
- Если это ожидается, существует ли рекомендуемый или поддерживаемый способ принудительной реконструкции геометрии (или согласованности ориентации) для ребер, состоящих только из двух конечные точки?
Минимально воспроизводимый пример и результаты, отображаемые в QGis:
Код: Выделить всё
import pyproj
from pyrosm import OSM
import osmnx as ox
import networkx as nx
import geopandas as gpd
from shapely.geometry import Point
def save_geoparquets(graph: nx.MultiDiGraph, base_name) -> None:
# Transform nodes in GeoDataFrame
nodes_data = [
{**data, "node": node, "geometry": Point(data["x"], data["y"])}
for node, data in graph.nodes(data=True)
]
gdf_nodes = gpd.GeoDataFrame(nodes_data, geometry="geometry", crs=4326)
# Transform edges in GeoDataFrame
edges_data = []
for u, v, key, data in graph.edges(keys=True, data=True):
rec = {"u": u, "v": v, "key": key}
for k, val in data.items():
if k == "geometry":
continue
# Stringify lists for Arrow/Parquet compatibility
if isinstance(val, (list, tuple)):
rec[k] = ",".join(map(str, val))
else:
rec[k] = val
rec["geometry"] = data.get("geometry")
edges_data.append(rec)
gdf_edges = gpd.GeoDataFrame(edges_data, geometry="geometry", crs=4326)
# Force object columns to string type for Arrow compatibility
for col in gdf_edges.columns:
if col != "geometry" and gdf_edges[col].dtype == object:
gdf_edges[col] = gdf_edges[col].astype("string")
# Save to Parquet
gdf_nodes.to_parquet(f"{base_name}_nodes.geoparquet")
gdf_edges.to_parquet(f"{base_name}_edges.geoparquet")
# Read OSM PBF file (bbbike extract on AOI)
osm_data = OSM("planet_1.658_43.481_ded13b9b.osm.pbf")
# Get nodes and edges from OSM network
gdf_nodes, gdf_edges = osm_data.get_network(network_type="driving", nodes=True)
print(f"Extracted: {len(gdf_nodes)} nodes | {len(gdf_edges)} edges")
# Transform nodes and edges into a networkx MultiDiGraph
# Geometries in one direction even for bidirectional edges as specified in documentation
graph = osm_data.to_graph(gdf_nodes, gdf_edges, graph_type="networkx")
print(f"Graph created: {graph.number_of_nodes()} nodes | {graph.number_of_edges()} edges")
graph.graph["crs"] = pyproj.CRS("EPSG:4326")
save_geoparquets(graph, "raw")
# Base simplification, got correct geometries in both directions for bidirectional edges
graph_simplified = ox.simplification.simplify_graph(graph)
save_geoparquets(graph_simplified, "simplified")
# Release simplification strictness. "tags" in this example to ease demonstration, more complex for real: "stop", "bus".
graph_custom_simplification = ox.simplification.simplify_graph(graph, edge_attrs_differ=["tags"])
save_geoparquets(graph_custom_simplification, "custom_simplification")

Мобильная версия