File size: 6,348 Bytes
fe91c4b dc1fe70 1a11bb9 4c486ab fe91c4b bfe6cc2 1a11bb9 d3c3011 30a096f d3c3011 dc1fe70 d3c3011 a8d1596 bfe6cc2 a8d1596 1a11bb9 d3c3011 30a096f d3c3011 a8d1596 d3c3011 30a096f a8d1596 30a096f d3c3011 30a096f d3c3011 30a096f d3c3011 a8d1596 bfe6cc2 d3c3011 dc1fe70 d3c3011 a8d1596 d3c3011 bfe6cc2 d3c3011 bfe6cc2 a8d1596 1a11bb9 d3c3011 bfe6cc2 d3c3011 a8d1596 30a096f bfe6cc2 d3c3011 30a096f bfe6cc2 d3c3011 bfe6cc2 30a096f bfe6cc2 d3c3011 30a096f d3c3011 bfe6cc2 a8d1596 30a096f a8d1596 d3c3011 30a096f a8d1596 30a096f d3c3011 dc1fe70 d3c3011 a8d1596 d3c3011 30a096f d3c3011 bfe6cc2 dc1fe70 bfe6cc2 30a096f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
import os
from pathlib import Path
import streamlit as st
import pandas as pd
import networkx as nx
import folium
from streamlit_folium import st_folium
from geopy.distance import geodesic
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings("ignore")
# ββ SUPPRESS PERMISSION & TELEMETRY ERRORS ββββββββββββββββββββββββββββββββββ
os.environ["HOME"] = "/tmp"
os.environ["XDG_CONFIG_HOME"] = "/tmp"
os.environ["XDG_CACHE_HOME"] = "/tmp"
os.environ["STREAMLIT_HOME"] = "/tmp"
os.environ["STREAMLIT_GATHER_USAGE_STATS"] = "false"
# ββ STREAMLIT PAGE CONFIG ββββββββββββββββββββββββββββββββββββββββββββββββββ
st.set_page_config(
page_title="MODA Multimodal Routing",
layout="wide",
initial_sidebar_state="expanded"
)
# ββ PATH SETUP ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR.parent / "data"
# ββ DATA LOADING ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@st.cache_data
def load_csv_from_local(filename):
"""Load a single CSV from data directory with caching"""
full_path = DATA_DIR / filename
if not full_path.exists():
st.error(f"File tidak ditemukan: {full_path}")
return pd.DataFrame()
df = pd.read_csv(full_path)
df.columns = [col.lower() for col in df.columns]
return df
@st.cache_data
def load_all_data():
"""Load all required datasets at once"""
files = {
'buildings': 'poi_places.csv',
'road_nodes': 'road_nodes.csv',
'road_edges': 'road_edges.csv'
}
data = {key: load_csv_from_local(fname) for key, fname in files.items()}
return data
# ββ BUILD GRAPH βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@st.cache_data
def build_graph(gdf_nodes, gdf_edges, gdf_poi):
G = nx.Graph()
# add road nodes
for _, r in gdf_nodes.iterrows():
G.add_node(r.osmid, x=r.x, y=r.y)
# add road edges
for _, r in gdf_edges.iterrows():
if pd.notna(r.length):
G.add_edge(r.u, r.v, length=r.length)
# add POI nodes
for idx, r in gdf_poi.iterrows():
nid = f"poi_{idx}"
G.add_node(nid, x=r.longitude, y=r.latitude, name=r.name)
# connect POI to nearest road
roads = [(n, d['y'], d['x']) for n, d in G.nodes(data=True) if isinstance(n, (int, float))]
for idx, r in gdf_poi.iterrows():
nid = f"poi_{idx}"
lat, lon = r.latitude, r.longitude
nearest = min(roads, key=lambda x: geodesic((lat, lon), (x[1], x[2])).meters)[0]
dist = geodesic((lat, lon), (G.nodes[nearest]['y'], G.nodes[nearest]['x'])).meters
G.add_edge(nid, nearest, length=dist)
G.add_edge(nearest, nid, length=dist)
return G
# ββ ROUTING HELPERS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def get_poi_id(name, gdf_poi):
match = gdf_poi[gdf_poi['name'].str.lower().str.strip() == name.lower().strip()]
return f"poi_{match.index[0]}" if not match.empty else None
def compute_route(G, start, end, speed_kmh):
path = nx.shortest_path(G, start, end, weight='length')
dist_m = nx.shortest_path_length(G, start, end, weight='length')
km = dist_m / 1000
time_min = km / speed_kmh * 60
return path, km, time_min
# ββ MAIN APP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Load datasets
data = load_all_data()
gdf_poi = data['buildings']
gdf_nodes = data['road_nodes']
gdf_edges = data['road_edges']
# Sidebar for user input
st.sidebar.header("π Input Rute")
if 'name' not in gdf_poi.columns:
st.sidebar.error("Kolom 'name' tidak ditemukan di poi_places.csv")
st.stop()
poi_list = gdf_poi['name'].dropna().tolist()
start = st.sidebar.selectbox("Dari:", poi_list)
end = st.sidebar.selectbox("Ke:", poi_list, index=1)
time_input = st.sidebar.time_input("Waktu Berangkat", value=datetime.now().time())
# Display selection
st.write(f"**Start:** {start} β’ **End:** {end} β’ **Depart:** {time_input.strftime('%H:%M')}")
# Trigger routing
if st.sidebar.button("π Cari Rute"):
G = build_graph(gdf_nodes, gdf_edges, gdf_poi)
sid = get_poi_id(start, gdf_poi)
eid = get_poi_id(end, gdf_poi)
if not sid or not eid:
st.error("Lokasi tidak valid.")
else:
# Drive Mode
path_d, km_d, t_d = compute_route(G, sid, eid, speed_kmh=30)
cost_d = int(km_d * 3000)
st.subheader("π Drive Mode")
st.write(f"Jarak: **{km_d:.2f} km**, Waktu: **{t_d:.1f} menit**, Biaya: **{cost_d:,} IDR**")
m1 = folium.Map(location=[gdf_poi['latitude'].mean(), gdf_poi['longitude'].mean()], zoom_start=13)
coords1 = [(G.nodes[n]['y'], G.nodes[n]['x']) for n in path_d]
folium.PolyLine(coords1, color='blue', weight=4).add_to(m1)
st_folium(m1, width=700, height=400)
# Bus Mode
path_b, km_b, t_b = compute_route(G, sid, eid, speed_kmh=25)
arrival = (datetime.combine(datetime.today(), time_input) + timedelta(minutes=t_b)).time()
st.subheader("π Bus Mode")
st.write(f"Jarak: **{km_b:.2f} km**, Waktu: **{t_b:.1f} menit**, Tiba: **{arrival.strftime('%H:%M')}**, Biaya: **3,500 IDR**")
m2 = folium.Map(location=[gdf_poi['latitude'].mean(), gdf_poi['longitude'].mean()], zoom_start=13)
coords2 = [(G.nodes[n]['y'], G.nodes[n]['x']) for n in path_b]
folium.PolyLine(coords2, color='green', weight=4).add_to(m2)
st_folium(m2, width=700, height=400)
else:
st.info("Pilih rute dan klik Cari Rute di sidebar.")
|