Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -3,14 +3,10 @@ import pandas as pd
|
|
| 3 |
import numpy as np
|
| 4 |
import folium
|
| 5 |
from streamlit_folium import st_folium
|
| 6 |
-
import math
|
| 7 |
import requests
|
| 8 |
|
| 9 |
-
st.set_page_config(page_title="์ฌ์ผ ๋์ค๊ตํต ์ต์ ํ ๋์๋ณด๋
|
| 10 |
|
| 11 |
-
# ==========================================
|
| 12 |
-
# 1. Fetch Real Data from OSM (Overpass API)
|
| 13 |
-
# ==========================================
|
| 14 |
@st.cache_data
|
| 15 |
def fetch_osm_data_v4():
|
| 16 |
bbox = "37.491,127.020,37.505,127.035"
|
|
@@ -37,32 +33,27 @@ def fetch_osm_data_v4():
|
|
| 37 |
st.error(f"๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
|
| 38 |
data = {'elements': []}
|
| 39 |
|
| 40 |
-
bus_stops = []
|
| 41 |
-
amenities = []
|
| 42 |
-
safety_infra = []
|
| 43 |
-
real_bus_routes = []
|
| 44 |
-
|
| 45 |
for element in data.get('elements', []):
|
| 46 |
if element['type'] == 'node':
|
| 47 |
-
lat = element['lat']
|
| 48 |
-
lon = element['lon']
|
| 49 |
tags = element.get('tags', {})
|
| 50 |
-
|
| 51 |
if 'highway' in tags and tags['highway'] == 'bus_stop':
|
| 52 |
-
bus_stops.append({'lat': lat, 'lon': lon, 'stop_id': element['id'], 'name': tags.get('name', '์ ๋ฅ์ฅ')})
|
| 53 |
elif 'amenity' in tags:
|
| 54 |
amenities.append({'lat': lat, 'lon': lon})
|
| 55 |
elif 'highway' in tags or 'man_made' in tags:
|
| 56 |
safety_infra.append({'lat': lat, 'lon': lon})
|
| 57 |
elif element['type'] == 'relation':
|
| 58 |
tags = element.get('tags', {})
|
| 59 |
-
ref = tags.get('ref', 'N๋ฒ์ค')
|
| 60 |
-
name = tags.get('name', ref)
|
| 61 |
coords = []
|
| 62 |
for member in element.get('members', []):
|
| 63 |
if member['type'] == 'way' and 'geometry' in member:
|
| 64 |
for pt in member['geometry']:
|
| 65 |
-
coords.append([pt['lat'], pt['lon']])
|
| 66 |
if coords:
|
| 67 |
real_bus_routes.append({'name': name, 'ref': ref, 'coords': coords})
|
| 68 |
|
|
@@ -70,9 +61,6 @@ def fetch_osm_data_v4():
|
|
| 70 |
|
| 71 |
stops_df, amenities_df, safety_df, real_bus_routes = fetch_osm_data_v4()
|
| 72 |
|
| 73 |
-
# ==========================================
|
| 74 |
-
# 2. Grid Generation & Feature Calculation
|
| 75 |
-
# ==========================================
|
| 76 |
@st.cache_data
|
| 77 |
def create_grid_features(_stops, _amenities, _safety):
|
| 78 |
lats = np.linspace(37.492, 37.504, 20)
|
|
@@ -86,26 +74,22 @@ def create_grid_features(_stops, _amenities, _safety):
|
|
| 86 |
for lat in lats:
|
| 87 |
for lon in lons:
|
| 88 |
point = np.array([lat, lon])
|
| 89 |
-
demand = np.sum(np.sqrt(np.sum((amenity_coords - point)**2, axis=1)) < 0.002) if len(amenity_coords) > 0 else 0
|
| 90 |
-
deficit = np.min(np.sqrt(np.sum((stop_coords - point)**2, axis=1))) if len(stop_coords) > 0 else 0
|
| 91 |
-
safety_count = np.sum(np.sqrt(np.sum((safety_coords - point)**2, axis=1)) < 0.002) if len(safety_coords) > 0 else 0
|
| 92 |
-
grid_data.append({'lat': lat, 'lon': lon, 'raw_demand': demand, 'raw_deficit': deficit, 'raw_safety_count': safety_count})
|
| 93 |
|
| 94 |
df = pd.DataFrame(grid_data)
|
| 95 |
-
df['base_demand'] = df['raw_demand'] / df['raw_demand'].max() if df['raw_demand'].max() > 0 else 0
|
| 96 |
-
df['base_deficit'] = df['raw_deficit'] / df['raw_deficit'].max() if df['raw_deficit'].max() > 0 else 0
|
| 97 |
-
df['base_risk'] = 1 - (df['raw_safety_count'] / df['raw_safety_count'].max()) if df['raw_safety_count'].max() > 0 else 1.0
|
| 98 |
-
|
| 99 |
return df
|
| 100 |
|
| 101 |
grids_df = create_grid_features(stops_df, amenities_df, safety_df)
|
| 102 |
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
nbus_coords = np.array([pt for route in real_bus_routes for pt in route['coords']])
|
| 107 |
-
if not stops_df.empty and len(nbus_coords) > 0:
|
| 108 |
-
stops_df['min_dist_to_nbus'] = stops_df.apply(lambda r: np.min(np.sqrt((nbus_coords[:,0]-r['lat'])**2 + (nbus_coords[:,1]-r['lon'])**2)), axis=1)
|
| 109 |
stops_df['is_nbus_stop'] = stops_df['min_dist_to_nbus'] < 0.001
|
| 110 |
else:
|
| 111 |
stops_df['is_nbus_stop'] = False
|
|
@@ -113,61 +97,33 @@ else:
|
|
| 113 |
nbus_stops = stops_df[stops_df['is_nbus_stop']]
|
| 114 |
blind_stops = stops_df[~stops_df['is_nbus_stop']]
|
| 115 |
|
| 116 |
-
|
| 117 |
-
# 4. Sidebar & Interactivity (Dynamic Time Logic)
|
| 118 |
-
# ==========================================
|
| 119 |
-
st.sidebar.header("โ๏ธ ์๋ฎฌ๋ ์ด์
์ค์ (OSM Data)")
|
| 120 |
selected_time = st.sidebar.select_slider("์๊ฐ๋ ์ ํ", options=[f"{str(h).zfill(2)}:{str(m).zfill(2)}" for h in [22, 23, 0, 1, 2, 3, 4] for m in [0, 30]] + ["05:00"], value="23:30")
|
| 121 |
time_idx = [f"{str(h).zfill(2)}:{str(m).zfill(2)}" for h in [22, 23, 0, 1, 2, 3, 4] for m in [0, 30]].index(selected_time) if selected_time != "05:00" else 14
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
dyn_gamma = 0.0
|
| 127 |
-
|
| 128 |
-
dyn_alpha = 0.
|
| 129 |
-
dyn_gamma = 0.4
|
| 130 |
-
else: # 02:00 ~ 05:00 (์ฌ์ผ ์ฃผ๊ฑฐ์ง๊ตฌ ์ค์ฌ)
|
| 131 |
-
dyn_alpha = 0.1
|
| 132 |
-
dyn_gamma = 0.8
|
| 133 |
|
| 134 |
dyn_beta = 0.2
|
| 135 |
|
| 136 |
-
st.sidebar.markdown(f""
|
| 137 |
-
**โฐ ์๊ฐ๋ ์๋ ๋ฐ์ ๊ฐ์ค์น**
|
| 138 |
-
- ๐บ ์์
/์ ํฅ(ฮฑ): **{dyn_alpha:.1f}**
|
| 139 |
-
- ๐ถโโ๏ธ ์ ๋ฅ์ฅ๊ฒฐํ(ฮฒ): **{dyn_beta:.1f}**
|
| 140 |
-
- ๐ ์ฃผ๊ฑฐ/์์ (ฮณ): **{dyn_gamma:.1f}**
|
| 141 |
-
""")
|
| 142 |
-
|
| 143 |
-
st.sidebar.caption("์๊ฐ์ด ๋ฆ์ด์ง์๋ก ๋ฒํ๊ฐ(์์
์ง)์์ ์ด๋์ด ๊ณจ๋ชฉ(์ฃผ๊ฑฐ์ง)์ผ๋ก ํ๊ฒํ
์ด ์๋ ์ด๋ํ๋ฉฐ ๋ฃจํ ํํ๊ฐ ๋ฌ๋ผ์ง๋๋ค.")
|
| 144 |
-
|
| 145 |
-
alpha = dyn_alpha
|
| 146 |
-
beta = dyn_beta
|
| 147 |
-
gamma = dyn_gamma
|
| 148 |
-
|
| 149 |
drt_budget = st.sidebar.number_input("ํฌ์
๊ฐ๋ฅ DRT ์ฐจ๋ ์", min_value=1, max_value=20, value=5)
|
| 150 |
|
| 151 |
-
# ==========================================
|
| 152 |
-
# 5. Risk Score & DRT Routing Calculation
|
| 153 |
-
# ==========================================
|
| 154 |
current_grids = grids_df.copy()
|
| 155 |
-
|
| 156 |
-
current_grids['demand'] = current_grids['base_demand']
|
| 157 |
-
current_grids['deficit'] = current_grids['base_deficit']
|
| 158 |
-
current_grids['risk'] = current_grids['base_risk']
|
| 159 |
-
current_grids['risk_score'] = alpha * current_grids['demand'] + beta * current_grids['deficit'] + gamma * current_grids['risk']
|
| 160 |
|
| 161 |
-
threshold = current_grids['risk_score'].quantile(0.85)
|
| 162 |
-
drt_targets = current_grids.nlargest(50, 'risk_score')
|
| 163 |
|
| 164 |
-
# N๋ฒ์ค๊ฐ ์๋ ์ฌ๊ฐ์ง๋ ์ ๋ฅ์ฅ ๋งค์นญ
|
| 165 |
drt_assignments = []
|
| 166 |
for idx, grid_row in drt_targets.iterrows():
|
| 167 |
if not blind_stops.empty:
|
| 168 |
distances = np.sqrt((blind_stops['lat'] - grid_row['lat'])**2 + (blind_stops['lon'] - grid_row['lon'])**2)
|
| 169 |
-
|
| 170 |
-
drt_assignments.append(nearest_stop)
|
| 171 |
|
| 172 |
unique_blind_stops = pd.DataFrame(drt_assignments).drop_duplicates('stop_id')
|
| 173 |
|
|
@@ -180,7 +136,7 @@ def ccw(p1, p2, p3):
|
|
| 180 |
|
| 181 |
def get_convex_hull(points):
|
| 182 |
if len(points) <= 3: return points
|
| 183 |
-
points = sorted(points, key=lambda p: (p[0], p[1]))
|
| 184 |
lower = []
|
| 185 |
for p in points:
|
| 186 |
while len(lower) >= 2 and ccw(lower[-2], lower[-1], p) <= 0: lower.pop()
|
|
@@ -191,118 +147,83 @@ def get_convex_hull(points):
|
|
| 191 |
upper.append(p)
|
| 192 |
return lower[:-1] + upper[:-1]
|
| 193 |
|
| 194 |
-
# ์ฌ๊ฐ์ง๋ ์ ๋ฅ์ฅ + [ํ์น ๊ณต์ ๊ตฌ๊ฐ 3๊ฐ]๋ก DRT Loop ์์ฑ
|
| 195 |
if not unique_blind_stops.empty and not nbus_stops.empty:
|
| 196 |
-
centroid_lat = unique_blind_stops['lat'].mean()
|
| 197 |
-
centroid_lon = unique_blind_stops['lon'].mean()
|
| 198 |
|
| 199 |
-
# ์ฌ๊ฐ์ง๋ ์ค์ฌ์์ ๊ฐ์ฅ ๊ฐ๊น์ด N๋ฒ์ค ์ ๋ฅ์ฅ์ 3๊ฐ ์ฐพ์ 'ํ์น ๊ณต์ ๊ตฌ๊ฐ'๏ฟฝ๏ฟฝ๋ก ์ค์
|
| 200 |
dist_to_hub = np.sqrt((nbus_stops['lat'] - centroid_lat)**2 + (nbus_stops['lon'] - centroid_lon)**2)
|
| 201 |
closest_hubs = nbus_stops.loc[dist_to_hub.nsmallest(3).index]
|
| 202 |
-
|
| 203 |
-
# ํ์น ๊ตฌ๊ฐ ์๊ฐํ๋ฅผ ์ํด ๊ฒฝ๋ ์์ผ๋ก ์ ๋ ฌ (์ผ์ง์ ํ์ฑ)
|
| 204 |
-
transfer_pts = closest_hubs[['lat', 'lon']].values.tolist()
|
| 205 |
-
transfer_coords = sorted(transfer_pts, key=lambda x: x[1])
|
| 206 |
|
| 207 |
loop_stops = pd.concat([unique_blind_stops, closest_hubs]).drop_duplicates('stop_id')
|
| 208 |
-
|
| 209 |
-
# Convex Hull(์ธ๊ณฝ์ ) ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ํฌ๊ณ ๊น๋ํ ๋ฃจํ ๋์ถ
|
| 210 |
coords = loop_stops[['lat', 'lon']].values.tolist()
|
| 211 |
hull_coords = get_convex_hull(coords)
|
| 212 |
|
| 213 |
-
hull_lats = [pt[0] for pt in hull_coords]
|
| 214 |
-
hull_lons = [pt[1] for pt in hull_coords]
|
| 215 |
unique_blind_stops = unique_blind_stops[unique_blind_stops.apply(lambda r: any(abs(r['lat'] - hl) < 1e-6 and abs(r['lon'] - hlon) < 1e-6 for hl, hlon in zip(hull_lats, hull_lons)), axis=1)]
|
| 216 |
|
| 217 |
-
hull_coords.append(hull_coords[0])
|
| 218 |
-
loop_coords = hull_coords
|
| 219 |
|
| 220 |
-
# ==========================================
|
| 221 |
-
# 6. Main Dashboard UI
|
| 222 |
-
# ==========================================
|
| 223 |
st.title("๐ ์ฌ์ผ ๋์ค๊ตํต N๋ฒ์ค-DRT ํตํฉ ๋์๋ณด๋")
|
| 224 |
-
st.markdown("์๊ฐ๋์ ๋ฐ๋ผ ๋ณํํ๋ ํซ์คํ(์์
์ง โ ์ฃผ๊ฑฐ์ง)์ ๋ฐ๋ผ ์ํ ๋
ธ์ ์ด ์๋ ๋ณ๊ฒฝ๋๋ฉฐ, N๋ฒ์ค์ **ํ์น ๊ตฌ๊ฐ(Transfer Zone)** ์ ๊ณต์ ํฉ๋๋ค.")
|
| 225 |
|
| 226 |
col1, col2 = st.columns([2, 1])
|
| 227 |
|
| 228 |
with col1:
|
| 229 |
m = folium.Map(location=[37.498, 127.027], zoom_start=15, tiles="CartoDB dark_matter")
|
| 230 |
|
| 231 |
-
# ๊ฒฉ์
|
| 232 |
for idx, row in current_grids.iterrows():
|
| 233 |
-
|
|
|
|
| 234 |
folium.CircleMarker(
|
| 235 |
-
location=[row['lat'], row['lon']], radius=4,
|
| 236 |
-
color=
|
| 237 |
-
|
|
|
|
| 238 |
).add_to(m)
|
| 239 |
|
| 240 |
-
# ์ฌ๊ฐ์ง๋ ์ ๋ฅ์ฅ (ํฐ์)
|
| 241 |
for idx, row in blind_stops.iterrows():
|
| 242 |
folium.CircleMarker(
|
| 243 |
-
location=[row['lat'], row['lon']], radius=2, color="gray", fill=True, fill_color="gray", tooltip="์ผ๋ฐ ์ ๋ฅ์ฅ
|
| 244 |
).add_to(m)
|
| 245 |
|
| 246 |
-
# ์ค์ N๋ฒ์ค ๋
ธ์ (PolyLine)
|
| 247 |
colors = ['cyan', 'lime', 'yellow']
|
| 248 |
for i, route in enumerate(real_bus_routes):
|
| 249 |
if route['coords']:
|
| 250 |
folium.PolyLine(
|
| 251 |
-
locations=route['coords'], dash_array="15, 20",
|
| 252 |
-
color=colors[i % len(colors)], weight=4, opacity=0.6,
|
| 253 |
-
tooltip=f"์ค์ ์ฌ์ผ๋ฒ์ค ๋
ธ์ : {route['name']}"
|
| 254 |
).add_to(m)
|
| 255 |
|
| 256 |
-
# DRT ๋ฃจํ ๋
ธ์ (PolyLine - ๋ณด๋ผ์)
|
| 257 |
if loop_coords:
|
| 258 |
folium.PolyLine(
|
| 259 |
locations=loop_coords, dash_array="10, 15",
|
| 260 |
-
color='purple', weight=5, opacity=0.9,
|
| 261 |
-
tooltip="๐ฅ ์ฌ๊ฐ์ง๋ ํ๊ฒ DRT ์ํ ๋
ธ์ "
|
| 262 |
).add_to(m)
|
| 263 |
|
| 264 |
-
# [NEW] ํ์น ๊ณต์ ๊ตฌ๊ฐ (Transfer Zone - ์ค๋ ์ง์ ๊ตต์ ์ )
|
| 265 |
if len(transfer_coords) >= 2:
|
| 266 |
folium.PolyLine(
|
| 267 |
-
locations=transfer_coords, dash_array="1, 10",
|
| 268 |
-
color='orange', weight=8, opacity=1.0,
|
| 269 |
-
tooltip="๐ N๋ฒ์ค โ DRT ํ์น ๊ตฌ์ญ (Transfer Zone)"
|
| 270 |
).add_to(m)
|
| 271 |
|
| 272 |
-
# DRT ์ ๋ฅ์ฅ ๋ง์ปค
|
| 273 |
for idx, row in unique_blind_stops.iterrows():
|
| 274 |
folium.Marker(
|
| 275 |
-
location=[row['lat'], row['lon']], popup=f"
|
| 276 |
icon=folium.Icon(color="purple", icon="bus", prefix="fa")
|
| 277 |
).add_to(m)
|
| 278 |
|
| 279 |
-
# ํ์น ๊ฑฐ์ (N๋ฒ์ค ์ ๋ฅ์ฅ 3๊ณณ)
|
| 280 |
if not closest_hubs.empty:
|
| 281 |
for idx, row in closest_hubs.iterrows():
|
| 282 |
folium.CircleMarker(
|
| 283 |
-
location=[row['lat'], row['lon']], radius=7, color="gold", fill=True, fill_color="orange", fill_opacity=0.8,
|
| 284 |
-
tooltip=f"
|
| 285 |
).add_to(m)
|
| 286 |
|
| 287 |
-
st_folium
|
|
|
|
| 288 |
|
| 289 |
with col2:
|
| 290 |
-
st.subheader("๐ก
|
| 291 |
-
st.info(f"
|
| 292 |
-
|
| 293 |
-
if not closest_hubs.empty:
|
| 294 |
-
hub_names = ", ".join(closest_hubs['name'].tolist())
|
| 295 |
-
st.warning(f"**๐ฅ ํ์น ๊ณต์ ๊ตฌ์ญ (Transfer Zone)**\n\nN๋ฒ์ค ๋
ธ์ ์ **'{hub_names}'** ์ ๋ฅ์ฅ๋ค์ DRT ๋ฃจํ๊ฐ ๊ทธ๋๋ก ๋ฐ๋ผ ์ฃผํํ๋ฉฐ ๊ฒน์นฉ๋๋ค. (์ง๋ ์ **๋น๋๋ ๊ธ์/์ค๋ ์ง์ ์ **)\n\n์น๊ฐ์ ์ด ๊ตฌ์ญ ๋ด ์๋ฌด ๊ณณ์์๋ ๋ด๋ ค N๋ฒ์ค๋ก ํธํ๊ฒ ๊ฐ์ํ ์ ์์ต๋๋ค.")
|
| 296 |
-
|
| 297 |
-
st.divider()
|
| 298 |
-
st.subheader("๐ ๋ฐฐ์ฐจ๊ฐ๊ฒฉ(๋๊ธฐ ์๊ฐ) ๊ฐ์ ํจ๊ณผ")
|
| 299 |
-
col_b, col_a, col_diff = st.columns(3)
|
| 300 |
-
|
| 301 |
-
base_n_interval = 40
|
| 302 |
-
drt_interval = max(5, 30 - drt_budget * 4)
|
| 303 |
-
|
| 304 |
-
col_b.metric("์ฌ๊ฐ์ง๋ ๊ธฐ์กด ๋ฐฐ์ฐจ๊ฐ๊ฒฉ", "์ดํ ์์ (โ)", delta=None)
|
| 305 |
-
col_a.metric("DRT ๋์
ํ ์ฌ๊ฐ์ง๋", f"์ฝ {drt_interval} ๋ถ", delta="-โ ๋ถ (์ ๊ท ์๋น์ค ์ฐฝ์ถ)", delta_color="normal")
|
| 306 |
-
col_diff.metric("๊ธฐ์กด N๋ฒ์ค ๋ฐฐ์ฐจ๊ฐ๊ฒฉ (๊ฐ์ )", f"์ฝ {base_n_interval} ๋ถ", delta="์ ์ง", delta_color="off")
|
| 307 |
-
|
| 308 |
-
st.markdown("> **๊ฒฐ๊ณผ**: ์ง๋ ์ ๋น๋๋ ๊ธ์ ์ ๊ตฌ๊ฐ(ํ์น ๊ตฌ์ญ)์์ ๋๊ธฐ์๊ฐ ์์ด ๋ถ๋๋ฝ๊ฒ N๋ฒ์ค๋ก ๊ฐ์ํ ์ ์์ผ๋ฉฐ, ์๊ฐ๋์ ๋ง์ถฐ ์ต์ ์ ๊ฒฝ๋ก๋ก ๊ตฝ์ด์น๋ ๋๋ํ ๋ฃจํ๋ฅผ ํ์ฑํฉ๋๋ค.")
|
|
|
|
| 3 |
import numpy as np
|
| 4 |
import folium
|
| 5 |
from streamlit_folium import st_folium
|
|
|
|
| 6 |
import requests
|
| 7 |
|
| 8 |
+
st.set_page_config(page_title="์ฌ์ผ ๋์ค๊ตํต ์ต์ ํ ๋์๋ณด๋", layout="wide")
|
| 9 |
|
|
|
|
|
|
|
|
|
|
| 10 |
@st.cache_data
|
| 11 |
def fetch_osm_data_v4():
|
| 12 |
bbox = "37.491,127.020,37.505,127.035"
|
|
|
|
| 33 |
st.error(f"๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
|
| 34 |
data = {'elements': []}
|
| 35 |
|
| 36 |
+
bus_stops, amenities, safety_infra, real_bus_routes = [], [], [], []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
for element in data.get('elements', []):
|
| 38 |
if element['type'] == 'node':
|
| 39 |
+
lat = float(element['lat'])
|
| 40 |
+
lon = float(element['lon'])
|
| 41 |
tags = element.get('tags', {})
|
|
|
|
| 42 |
if 'highway' in tags and tags['highway'] == 'bus_stop':
|
| 43 |
+
bus_stops.append({'lat': lat, 'lon': lon, 'stop_id': str(element['id']), 'name': str(tags.get('name', '์ ๋ฅ์ฅ'))})
|
| 44 |
elif 'amenity' in tags:
|
| 45 |
amenities.append({'lat': lat, 'lon': lon})
|
| 46 |
elif 'highway' in tags or 'man_made' in tags:
|
| 47 |
safety_infra.append({'lat': lat, 'lon': lon})
|
| 48 |
elif element['type'] == 'relation':
|
| 49 |
tags = element.get('tags', {})
|
| 50 |
+
ref = str(tags.get('ref', 'N๋ฒ์ค'))
|
| 51 |
+
name = str(tags.get('name', ref))
|
| 52 |
coords = []
|
| 53 |
for member in element.get('members', []):
|
| 54 |
if member['type'] == 'way' and 'geometry' in member:
|
| 55 |
for pt in member['geometry']:
|
| 56 |
+
coords.append([float(pt['lat']), float(pt['lon'])])
|
| 57 |
if coords:
|
| 58 |
real_bus_routes.append({'name': name, 'ref': ref, 'coords': coords})
|
| 59 |
|
|
|
|
| 61 |
|
| 62 |
stops_df, amenities_df, safety_df, real_bus_routes = fetch_osm_data_v4()
|
| 63 |
|
|
|
|
|
|
|
|
|
|
| 64 |
@st.cache_data
|
| 65 |
def create_grid_features(_stops, _amenities, _safety):
|
| 66 |
lats = np.linspace(37.492, 37.504, 20)
|
|
|
|
| 74 |
for lat in lats:
|
| 75 |
for lon in lons:
|
| 76 |
point = np.array([lat, lon])
|
| 77 |
+
demand = int(np.sum(np.sqrt(np.sum((amenity_coords - point)**2, axis=1)) < 0.002)) if len(amenity_coords) > 0 else 0
|
| 78 |
+
deficit = float(np.min(np.sqrt(np.sum((stop_coords - point)**2, axis=1)))) if len(stop_coords) > 0 else 0.0
|
| 79 |
+
safety_count = int(np.sum(np.sqrt(np.sum((safety_coords - point)**2, axis=1)) < 0.002)) if len(safety_coords) > 0 else 0
|
| 80 |
+
grid_data.append({'lat': float(lat), 'lon': float(lon), 'raw_demand': demand, 'raw_deficit': deficit, 'raw_safety_count': safety_count})
|
| 81 |
|
| 82 |
df = pd.DataFrame(grid_data)
|
| 83 |
+
df['base_demand'] = df['raw_demand'] / df['raw_demand'].max() if df['raw_demand'].max() > 0 else 0.0
|
| 84 |
+
df['base_deficit'] = df['raw_deficit'] / df['raw_deficit'].max() if df['raw_deficit'].max() > 0 else 0.0
|
| 85 |
+
df['base_risk'] = 1.0 - (df['raw_safety_count'] / df['raw_safety_count'].max()) if df['raw_safety_count'].max() > 0 else 1.0
|
|
|
|
| 86 |
return df
|
| 87 |
|
| 88 |
grids_df = create_grid_features(stops_df, amenities_df, safety_df)
|
| 89 |
|
| 90 |
+
if not stops_df.empty and len(real_bus_routes) > 0:
|
| 91 |
+
nbus_coords = np.array([pt for route in real_bus_routes for pt in route['coords']])
|
| 92 |
+
stops_df['min_dist_to_nbus'] = stops_df.apply(lambda r: float(np.min(np.sqrt((nbus_coords[:,0]-r['lat'])**2 + (nbus_coords[:,1]-r['lon'])**2))), axis=1)
|
|
|
|
|
|
|
|
|
|
| 93 |
stops_df['is_nbus_stop'] = stops_df['min_dist_to_nbus'] < 0.001
|
| 94 |
else:
|
| 95 |
stops_df['is_nbus_stop'] = False
|
|
|
|
| 97 |
nbus_stops = stops_df[stops_df['is_nbus_stop']]
|
| 98 |
blind_stops = stops_df[~stops_df['is_nbus_stop']]
|
| 99 |
|
| 100 |
+
st.sidebar.header("โ๏ธ ์๋ฎฌ๋ ์ด์
์ค์ ")
|
|
|
|
|
|
|
|
|
|
| 101 |
selected_time = st.sidebar.select_slider("์๊ฐ๋ ์ ํ", options=[f"{str(h).zfill(2)}:{str(m).zfill(2)}" for h in [22, 23, 0, 1, 2, 3, 4] for m in [0, 30]] + ["05:00"], value="23:30")
|
| 102 |
time_idx = [f"{str(h).zfill(2)}:{str(m).zfill(2)}" for h in [22, 23, 0, 1, 2, 3, 4] for m in [0, 30]].index(selected_time) if selected_time != "05:00" else 14
|
| 103 |
|
| 104 |
+
if time_idx <= 3:
|
| 105 |
+
dyn_alpha, dyn_gamma = 0.8, 0.0
|
| 106 |
+
elif time_idx <= 7:
|
| 107 |
+
dyn_alpha, dyn_gamma = 0.4, 0.4
|
| 108 |
+
else:
|
| 109 |
+
dyn_alpha, dyn_gamma = 0.1, 0.8
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
dyn_beta = 0.2
|
| 112 |
|
| 113 |
+
st.sidebar.markdown(f"**โฐ ๊ฐ์ค์น**\n- ์์
/์ ํฅ(ฮฑ): {dyn_alpha:.1f}\n- ์ ๋ฅ์ฅ๊ฒฐํ(ฮฒ): {dyn_beta:.1f}\n- ์ฃผ๊ฑฐ/์์ (ฮณ): {dyn_gamma:.1f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
drt_budget = st.sidebar.number_input("ํฌ์
๊ฐ๋ฅ DRT ์ฐจ๋ ์", min_value=1, max_value=20, value=5)
|
| 115 |
|
|
|
|
|
|
|
|
|
|
| 116 |
current_grids = grids_df.copy()
|
| 117 |
+
current_grids['risk_score'] = dyn_alpha * current_grids['base_demand'] + dyn_beta * current_grids['base_deficit'] + dyn_gamma * current_grids['base_risk']
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
threshold = float(current_grids['risk_score'].quantile(0.85))
|
| 120 |
+
drt_targets = current_grids.nlargest(50, 'risk_score')
|
| 121 |
|
|
|
|
| 122 |
drt_assignments = []
|
| 123 |
for idx, grid_row in drt_targets.iterrows():
|
| 124 |
if not blind_stops.empty:
|
| 125 |
distances = np.sqrt((blind_stops['lat'] - grid_row['lat'])**2 + (blind_stops['lon'] - grid_row['lon'])**2)
|
| 126 |
+
drt_assignments.append(blind_stops.loc[distances.idxmin()])
|
|
|
|
| 127 |
|
| 128 |
unique_blind_stops = pd.DataFrame(drt_assignments).drop_duplicates('stop_id')
|
| 129 |
|
|
|
|
| 136 |
|
| 137 |
def get_convex_hull(points):
|
| 138 |
if len(points) <= 3: return points
|
| 139 |
+
points = sorted(points, key=lambda p: (float(p[0]), float(p[1])))
|
| 140 |
lower = []
|
| 141 |
for p in points:
|
| 142 |
while len(lower) >= 2 and ccw(lower[-2], lower[-1], p) <= 0: lower.pop()
|
|
|
|
| 147 |
upper.append(p)
|
| 148 |
return lower[:-1] + upper[:-1]
|
| 149 |
|
|
|
|
| 150 |
if not unique_blind_stops.empty and not nbus_stops.empty:
|
| 151 |
+
centroid_lat = float(unique_blind_stops['lat'].mean())
|
| 152 |
+
centroid_lon = float(unique_blind_stops['lon'].mean())
|
| 153 |
|
|
|
|
| 154 |
dist_to_hub = np.sqrt((nbus_stops['lat'] - centroid_lat)**2 + (nbus_stops['lon'] - centroid_lon)**2)
|
| 155 |
closest_hubs = nbus_stops.loc[dist_to_hub.nsmallest(3).index]
|
| 156 |
+
transfer_coords = sorted(closest_hubs[['lat', 'lon']].values.tolist(), key=lambda x: x[1])
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
loop_stops = pd.concat([unique_blind_stops, closest_hubs]).drop_duplicates('stop_id')
|
|
|
|
|
|
|
| 159 |
coords = loop_stops[['lat', 'lon']].values.tolist()
|
| 160 |
hull_coords = get_convex_hull(coords)
|
| 161 |
|
| 162 |
+
hull_lats = [float(pt[0]) for pt in hull_coords]
|
| 163 |
+
hull_lons = [float(pt[1]) for pt in hull_coords]
|
| 164 |
unique_blind_stops = unique_blind_stops[unique_blind_stops.apply(lambda r: any(abs(r['lat'] - hl) < 1e-6 and abs(r['lon'] - hlon) < 1e-6 for hl, hlon in zip(hull_lats, hull_lons)), axis=1)]
|
| 165 |
|
| 166 |
+
hull_coords.append(hull_coords[0])
|
| 167 |
+
loop_coords = [[float(pt[0]), float(pt[1])] for pt in hull_coords]
|
| 168 |
|
|
|
|
|
|
|
|
|
|
| 169 |
st.title("๐ ์ฌ์ผ ๋์ค๊ตํต N๋ฒ์ค-DRT ํตํฉ ๋์๋ณด๋")
|
|
|
|
| 170 |
|
| 171 |
col1, col2 = st.columns([2, 1])
|
| 172 |
|
| 173 |
with col1:
|
| 174 |
m = folium.Map(location=[37.498, 127.027], zoom_start=15, tiles="CartoDB dark_matter")
|
| 175 |
|
|
|
|
| 176 |
for idx, row in current_grids.iterrows():
|
| 177 |
+
rs = float(row['risk_score'])
|
| 178 |
+
if rs > 0.3:
|
| 179 |
folium.CircleMarker(
|
| 180 |
+
location=[float(row['lat']), float(row['lon'])], radius=4,
|
| 181 |
+
color="red" if rs > threshold else "orange", weight=0, fill=True,
|
| 182 |
+
fill_color="red" if rs > threshold else "orange", fill_opacity=float(rs),
|
| 183 |
+
popup=str(f"Risk: {rs:.2f}")
|
| 184 |
).add_to(m)
|
| 185 |
|
|
|
|
| 186 |
for idx, row in blind_stops.iterrows():
|
| 187 |
folium.CircleMarker(
|
| 188 |
+
location=[float(row['lat']), float(row['lon'])], radius=2, color="gray", weight=0, fill=True, fill_color="gray", tooltip="์ผ๋ฐ ์ ๋ฅ์ฅ"
|
| 189 |
).add_to(m)
|
| 190 |
|
|
|
|
| 191 |
colors = ['cyan', 'lime', 'yellow']
|
| 192 |
for i, route in enumerate(real_bus_routes):
|
| 193 |
if route['coords']:
|
| 194 |
folium.PolyLine(
|
| 195 |
+
locations=[[float(pt[0]), float(pt[1])] for pt in route['coords']], dash_array="15, 20",
|
| 196 |
+
color=str(colors[i % len(colors)]), weight=4, opacity=0.6, tooltip=str(f"N๋ฒ์ค: {route['name']}")
|
|
|
|
| 197 |
).add_to(m)
|
| 198 |
|
|
|
|
| 199 |
if loop_coords:
|
| 200 |
folium.PolyLine(
|
| 201 |
locations=loop_coords, dash_array="10, 15",
|
| 202 |
+
color='purple', weight=5, opacity=0.9, tooltip="DRT ๋ฃจํ"
|
|
|
|
| 203 |
).add_to(m)
|
| 204 |
|
|
|
|
| 205 |
if len(transfer_coords) >= 2:
|
| 206 |
folium.PolyLine(
|
| 207 |
+
locations=[[float(pt[0]), float(pt[1])] for pt in transfer_coords], dash_array="1, 10",
|
| 208 |
+
color='orange', weight=8, opacity=1.0, tooltip="ํ์น ๊ตฌ์ญ"
|
|
|
|
| 209 |
).add_to(m)
|
| 210 |
|
|
|
|
| 211 |
for idx, row in unique_blind_stops.iterrows():
|
| 212 |
folium.Marker(
|
| 213 |
+
location=[float(row['lat']), float(row['lon'])], popup=str(f"์นํ์ฐจ: {row['name']}"),
|
| 214 |
icon=folium.Icon(color="purple", icon="bus", prefix="fa")
|
| 215 |
).add_to(m)
|
| 216 |
|
|
|
|
| 217 |
if not closest_hubs.empty:
|
| 218 |
for idx, row in closest_hubs.iterrows():
|
| 219 |
folium.CircleMarker(
|
| 220 |
+
location=[float(row['lat']), float(row['lon'])], radius=7, color="gold", weight=2, fill=True, fill_color="orange", fill_opacity=0.8,
|
| 221 |
+
tooltip=str(f"ํ์น: {row['name']}")
|
| 222 |
).add_to(m)
|
| 223 |
|
| 224 |
+
# st_folium ํธ์ถ ์ ์ง๋ ฌํ ์๋ฌ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด returned_objects=[] ์ต์
์ถ๊ฐ
|
| 225 |
+
st_folium(m, width=800, height=500, returned_objects=[])
|
| 226 |
|
| 227 |
with col2:
|
| 228 |
+
st.subheader("๐ก ์๋๋ฆฌ์ค")
|
| 229 |
+
st.info(f"์ ํ ์๊ฐ: **{selected_time}**")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|