Изохроны¶
Этот ноутбук строит изохроны на интермодальном графе UrbanGraph из IduEdu (пешком + общественный транспорт):
get_graph_isochronesсgeometry_type="radius"и"ways"для одной точки отправления;get_stepped_graph_isochronesс поясами ширинойstepдля"radius","ways"и"separate".
Метод описан на странице изохрон. Интерактивные карты показывают облегчённую копию каждого результата (for_map: контуры упрощены на 2 м), чтобы ноутбук оставался небольшим.
Последний раздел, Своя территория, показывает, что заменить, чтобы получить такие же картинки для другой территории.
# Install required packages (uncomment if needed)
# !pip install objectnat iduedu
import geopandas as gpd
import iduedu
from iduedu import get_4326_boundary, get_intermodal_graph
from shapely import Point
from objectnat import get_graph_isochrones, get_stepped_graph_isochrones
import folium
def for_map(gdf, tolerance=2.0):
"""A light copy for a web map: outlines simplified by `tolerance` metres, coordinates rounded to about 10 cm."""
local = gdf.to_crs(gdf.estimate_utm_crs())
local["geometry"] = local.simplify(tolerance)
light = local.to_crs(4326)
light["geometry"] = light.geometry.set_precision(1e-6)
return light[~light.is_empty]
def grey_tiles():
"""Light grey base map (Esri World Light Gray); its tiles end at zoom 16 and are stretched beyond it."""
return folium.TileLayer("Esri.WorldGrayCanvas", max_zoom=19, max_native_zoom=16)
1. Интермодальный граф¶
Граф покрывает район и обрезан по его границе. Веса рёбер — time_min (минуты) и length_meter (метры).
# Vasilyevsky Island district, Saint Petersburg (OSM relation 1114252).
# IduEdu cuts the territory out of the Geofabrik extract (pip install "iduedu[pbf]");
# without the `with` block the same calls query the Overpass API.
with iduedu.use_pbf(osm_id=1114252, region="northwestern-fed-district"):
poly = get_4326_boundary(osm_id=1114252)
G_intermodal = get_intermodal_graph(territory=poly, clip_by_territory=True)
2026-11-06 19:15:27.509 | WARNING | iduedu.graph.transformers:keep_largest_connected_component:64 - Removing 338 nodes outside the largest strongly connected component. Retaining 21181 of 21519 nodes.
2026-11-06 19:15:27.542 | WARNING | iduedu.graph_builders.intermodal_builders:join_pt_walk_graph:248 - Public transport nodes dropped with the smaller components: {'trolleybus': 2}
2. Точка отправления¶
Одна точка отправления в WGS 84; она привязывается к ближайшему узлу графа.
# Define a single point of interest
point = gpd.GeoDataFrame(geometry=[Point(30.27060176, 59.93546846)], crs=4326)
3. Изохрона radius¶
Объединение буферов вокруг узлов, достижимых за 10 минут; остаток бюджета каждого узла переводится в расстояние по медианной скорости на рёбрах графа.
isochrones_radius = get_graph_isochrones(
G_intermodal,
geometry_type='radius',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
)
# Visualize
for_map(isochrones_radius).explore(tiles=grey_tiles())
4. Изохрона ways¶
Пешеходные рёбра, достижимые за 10 минут, с буфером road_buffer_size (по умолчанию 5 м).
isochrones_ways = get_graph_isochrones(
G_intermodal,
geometry_type='ways',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
)
# Visualize
for_map(isochrones_ways).explore(tiles=grey_tiles())
5. Сравнение¶
Изохрона radius (синяя) покрывает и места между улицами; изохрона ways (красная) идёт по улицам.
m = for_map(isochrones_radius).explore(tiles=grey_tiles(), color='blue', name='Radius')
for_map(isochrones_ways).explore(m=m, color='red', name='Ways')
6. Ступенчатые изохроны, radius¶
Пояса по 3 минуты до 15 минут; dist — верхняя граница пояса.
stepped_radius = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='radius',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=15,
step=3,
)
for_map(stepped_radius).explore(tiles=grey_tiles(), column='dist')
7. Ступенчатые изохроны, ways¶
Ячейки Вороного достижимых узлов, обрезанные по буферам улиц, поясами по 3 минуты.
stepped_ways = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='ways',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=15,
step=3,
)
for_map(stepped_ways).explore(tiles=grey_tiles(), column='dist')
8. Ступенчатые изохроны, separate¶
Независимые буферы на каждый пояс по 2 минуты до 10 минут.
stepped_separate = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='separate',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
step=2,
)
for_map(stepped_separate).explore(tiles=grey_tiles(), column='dist')
Использованные параметры¶
geometry_type:"radius","ways"или"separate"(ступенчатые изохроны принимают такжеNone— ячейки Вороного);weight_type:"time_min"(минуты) или"length_meter"(метры);weight_value_cutoff: бюджет в единицахweight_type;step: ширина пояса для ступенчатых изохрон;buffer_factor("separate") иroad_buffer_size("ways", метры).
9. Своя территория¶
Территория — отношение OSM (relation района или города на openstreetmap.org); REGION задаёт выгрузку Geofabrik, из которой она вырезается (iduedu.find_geofabrik_region находит её по полигону; без неё IduEdu один раз обращается к Overpass, чтобы выбрать выгрузку). Результат — GeoDataFrame: нарисуйте его в matplotlib (здесь поверх улиц графа, с границей территории), посмотрите на веб-карте или сохраните для ГИС через to_file.
import matplotlib.pyplot as plt
OSM_ID, REGION = 1114252, "northwestern-fed-district" # your territory
my_origins = gpd.GeoDataFrame(geometry=[Point(30.2706, 59.9355), Point(30.2959, 59.9441)], crs=4326) # your points
with iduedu.use_pbf(osm_id=OSM_ID, region=REGION): # downloaded once, then cached
my_boundary = get_4326_boundary(osm_id=OSM_ID)
my_graph = get_intermodal_graph(territory=my_boundary, clip_by_territory=True)
my_bands = get_stepped_graph_isochrones(
my_graph, gdf_origins=my_origins, weight_type="time_min", weight_value_cutoff=15, step=3,
geometry_type="separate",
)
local = my_bands.to_crs(my_graph.nodes_gdf.crs)
streets = my_graph.edges_gdf[my_graph.edges_gdf["type"] == "walk"] # the street network as the background
ax = streets.plot(color="#c2c2c2", linewidth=0.3, figsize=(8, 7))
local.plot(ax=ax, column="dist", cmap="YlGnBu_r", alpha=0.85, legend=True, legend_kwds={"label": "travel time, min"})
gpd.GeoSeries([my_boundary], crs=4326).to_crs(local.crs).boundary.plot(ax=ax, color="#3d3d3d", linewidth=1)
my_origins.to_crs(local.crs).plot(ax=ax, color="#d7301f", markersize=30, edgecolor="white", zorder=5)
ax.set_axis_off()
plt.show()
# my_bands.to_file("my_isochrones.gpkg") # for GIS
2026-11-06 19:15:43.083 | WARNING | iduedu.graph.transformers:keep_largest_connected_component:64 - Removing 338 nodes outside the largest strongly connected component. Retaining 21181 of 21519 nodes.
2026-11-06 19:15:43.117 | WARNING | iduedu.graph_builders.intermodal_builders:join_pt_walk_graph:248 - Public transport nodes dropped with the smaller components: {'trolleybus': 2}
for_map(my_bands).explore(column="dist", cmap="YlGnBu_r", tiles=grey_tiles())