naohiro701 commited on
Commit
b102c32
·
verified ·
1 Parent(s): 7712681

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -64
app.py CHANGED
@@ -11,14 +11,7 @@ import networkx as nx
11
  from streamlit_folium import st_folium
12
 
13
  def fetch_station_data(station: str, time_limit: int) -> gpd.GeoDataFrame:
14
- """
15
- Fetch station travel-time data via API and return as GeoDataFrame.
16
- Args:
17
- station (str): Station name to query.
18
- time_limit (int): Maximum travel time (minutes).
19
- Returns:
20
- gpd.GeoDataFrame: station points with 'travel_time' and geometry.
21
- """
22
  api_url = (
23
  f"https://api.nearby-map.com/search_stations"
24
  f"?station={station}&time_limit={time_limit}&max_transfer=2"
@@ -29,15 +22,12 @@ def fetch_station_data(station: str, time_limit: int) -> gpd.GeoDataFrame:
29
  return gpd.GeoDataFrame()
30
  items = res.json().get("data", [])
31
  df = pd.DataFrame(items)
32
- df.rename(
33
- columns={
34
- "name": "station_name",
35
- "time": "travel_time",
36
- "lat": "latitude",
37
- "lon": "longitude",
38
- },
39
- inplace=True,
40
- )
41
  return gpd.GeoDataFrame(
42
  df,
43
  geometry=gpd.points_from_xy(df.longitude, df.latitude),
@@ -49,68 +39,73 @@ def build_isochrone_polygons(
49
  travel_speeds_kmh: float,
50
  time_limits: list[int]
51
  ) -> gpd.GeoDataFrame:
52
- """
53
- Generate isochrone polygons for each time limit.
54
- Args:
55
- stations_gdf (gpd.GeoDataFrame): Stations with travel_time.
56
- travel_speeds_kmh (float): Walking speed in km/h.
57
- time_limits (list[int]): List of total travel times (min).
58
- Returns:
59
- gpd.GeoDataFrame: Polygons with 'time' and 'color'.
60
- """
61
- # prepare colormap
62
- cmap = LinearSegmentedColormap.from_list("iso", ["green","yellow","orange","red"], N=len(time_limits))
63
- colors = [to_hex(cmap(i)) for i in np.linspace(0,1,len(time_limits))]
64
 
65
- def single_isochrone(total_min: int):
 
66
  polys = []
67
  for _, row in stations_gdf.iterrows():
68
- rem = total_min - row["travel_time"]
69
- if rem <= 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  continue
71
- maxd = rem * travel_speeds_kmh*1000/60 # meters
72
- G = ox.graph_from_point((row["latitude"], row["longitude"]), dist=maxd*1.2, network_type="walk")
73
- if not G.edges:
74
  continue
75
- center = ox.nearest_nodes(G, row["longitude"], row["latitude"])
76
- G = ox.project_graph(G)
77
- mpm = travel_speeds_kmh*1000/60
78
- for u,v,k,d in G.edges(keys=True, data=True):
79
- d["time"] = d["length"]/mpm
80
- sub = nx.ego_graph(G, center, radius=rem, distance="time")
81
- if not sub.edges:
82
  continue
83
- edges = ox.graph_to_gdfs(sub, nodes=False, edges=True)["geometry"]
84
- merged = unary_union(edges.unary_union)
85
- polys += list(polygonize(merged))
86
  if polys:
87
- uni = unary_union(polys)
88
- g = gpd.GeoDataFrame(
89
  {"time": [total_min], "color": [colors[time_limits.index(total_min)]]},
90
- geometry=[uni],
91
- crs=G.graph["crs"]
92
- )
93
-
94
- return g.to_crs(epsg=4326)
95
  return None
96
 
97
  iso_list = [single_isochrone(t) for t in time_limits]
98
- return gpd.GeoDataFrame(pd.concat([g for g in iso_list if g is not None], ignore_index=True))
 
 
 
99
 
100
  def main():
 
101
  st.title("Isochrone Map Demo")
102
- station = st.text_input("Station name", "溜池山王")
103
  max_time = st.slider("Max travel time (min)", 10, 60, 30, 10)
104
- step = st.selectbox("Time step (min)", [5,10,15], index=1)
105
- if st.button("Generate Map"):
106
  with st.spinner("Calculating…"):
107
- stations = fetch_station_data(station, max_time)
108
  if stations.empty:
109
  st.warning("No station data.")
110
  return
111
- times = list(range(step, max_time+1, step))
112
- iso_gdf = build_isochrone_polygons(stations, travel_speeds_kmh=4.8, time_limits=times)
113
- m = folium.Map(location=[stations.geometry.y.mean(), stations.geometry.x.mean()], zoom_start=12)
 
114
  for _, row in iso_gdf.iterrows():
115
  folium.GeoJson(
116
  row.geometry,
@@ -120,13 +115,18 @@ def main():
120
  "weight": 2,
121
  "fillOpacity": 0.2,
122
  },
123
- ).add_to(m)
124
  for _, row in stations.iterrows():
125
  folium.Marker(
126
  [row["latitude"], row["longitude"]],
127
  popup=f"{row['station_name']} ({int(row['travel_time'])}分)"
128
- ).add_to(m)
129
- st_folium(m, width=700, height=500)
130
 
 
131
  if __name__ == "__main__":
132
- main()
 
 
 
 
 
11
  from streamlit_folium import st_folium
12
 
13
  def fetch_station_data(station: str, time_limit: int) -> gpd.GeoDataFrame:
14
+ """Fetch station travel-time data via API and return as GeoDataFrame."""
 
 
 
 
 
 
 
15
  api_url = (
16
  f"https://api.nearby-map.com/search_stations"
17
  f"?station={station}&time_limit={time_limit}&max_transfer=2"
 
22
  return gpd.GeoDataFrame()
23
  items = res.json().get("data", [])
24
  df = pd.DataFrame(items)
25
+ df.rename(columns={
26
+ "name": "station_name",
27
+ "time": "travel_time",
28
+ "lat": "latitude",
29
+ "lon": "longitude",
30
+ }, inplace=True)
 
 
 
31
  return gpd.GeoDataFrame(
32
  df,
33
  geometry=gpd.points_from_xy(df.longitude, df.latitude),
 
39
  travel_speeds_kmh: float,
40
  time_limits: list[int]
41
  ) -> gpd.GeoDataFrame:
42
+ """Generate isochrone polygons for each time limit."""
43
+ cmap = LinearSegmentedColormap.from_list("iso", ["green", "yellow", "orange", "red"], N=len(time_limits))
44
+ colors = [to_hex(cmap(i)) for i in np.linspace(0, 1, len(time_limits))]
 
 
 
 
 
 
 
 
 
45
 
46
+ def single_isochrone(total_min: int) -> gpd.GeoDataFrame | None:
47
+ """Generate a single isochrone for a total travel time."""
48
  polys = []
49
  for _, row in stations_gdf.iterrows():
50
+ remaining = total_min - row["travel_time"]
51
+ if remaining <= 0:
52
+ continue
53
+ max_distance = remaining * travel_speeds_kmh * 1000 / 60
54
+ graph = ox.graph_from_point(
55
+ (row["latitude"], row["longitude"]),
56
+ dist=max_distance * 1.2,
57
+ network_type="walk"
58
+ )
59
+ if not graph.edges:
60
+ continue
61
+ center_node = ox.nearest_nodes(graph, row["longitude"], row["latitude"])
62
+ graph = ox.project_graph(graph)
63
+ meters_per_minute = travel_speeds_kmh * 1000 / 60
64
+ for u, v, k, data in graph.edges(keys=True, data=True):
65
+ data["time"] = data["length"] / meters_per_minute
66
+ subgraph = nx.ego_graph(graph, center_node, radius=remaining, distance="time")
67
+ if not subgraph.edges:
68
  continue
69
+ edges_gdf = ox.graph_to_gdfs(subgraph, nodes=False, edges=True)
70
+ if edges_gdf.empty:
 
71
  continue
72
+ merged_lines = edges_gdf["geometry"].unary_union
73
+ polygons = list(polygonize(merged_lines))
74
+ if not polygons:
 
 
 
 
75
  continue
76
+ polys.extend(polygons)
 
 
77
  if polys:
78
+ unified = unary_union(polys)
79
+ gdf_iso = gpd.GeoDataFrame(
80
  {"time": [total_min], "color": [colors[time_limits.index(total_min)]]},
81
+ geometry=[unified],
82
+ crs=graph.graph["crs"]
83
+ )
84
+ return gdf_iso.to_crs(epsg=4326)
 
85
  return None
86
 
87
  iso_list = [single_isochrone(t) for t in time_limits]
88
+ gdfs = [g for g in iso_list if g is not None]
89
+ if gdfs:
90
+ return gpd.GeoDataFrame(pd.concat(gdfs, ignore_index=True), crs="EPSG:4326")
91
+ return gpd.GeoDataFrame(columns=["time","color","geometry"], crs="EPSG:4326")
92
 
93
  def main():
94
+ """Streamlit application entrypoint."""
95
  st.title("Isochrone Map Demo")
96
+ station_name = st.text_input("Station name", "溜池山王")
97
  max_time = st.slider("Max travel time (min)", 10, 60, 30, 10)
98
+ step = st.selectbox("Time step (min)", [5, 10, 15], index=1)
99
+ if st.button("Generate Map"]):
100
  with st.spinner("Calculating…"):
101
+ stations = fetch_station_data(station_name, max_time)
102
  if stations.empty:
103
  st.warning("No station data.")
104
  return
105
+ time_values = list(range(step, max_time + 1, step))
106
+ iso_gdf = build_isochrone_polygons(stations, travel_speeds_kmh=4.8, time_limits=time_values)
107
+ map_center = [stations.geometry.y.mean(), stations.geometry.x.mean()]
108
+ fmap = folium.Map(location=map_center, zoom_start=12)
109
  for _, row in iso_gdf.iterrows():
110
  folium.GeoJson(
111
  row.geometry,
 
115
  "weight": 2,
116
  "fillOpacity": 0.2,
117
  },
118
+ ).add_to(fmap)
119
  for _, row in stations.iterrows():
120
  folium.Marker(
121
  [row["latitude"], row["longitude"]],
122
  popup=f"{row['station_name']} ({int(row['travel_time'])}分)"
123
+ ).add_to(fmap)
124
+ st_folium(fmap, width=700, height=500)
125
 
126
+ # Allow script to be run directly with "python app.py"
127
  if __name__ == "__main__":
128
+ import sys
129
+ from streamlit.web import cli as stcli
130
+
131
+ sys.argv = ["streamlit", "run", sys.argv[0]]
132
+ sys.exit(stcli.main())