import numpy as np import pandas as pd from scipy.spatial import distance_matrix import streamlit as st from streamlit_folium import folium_static import folium from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp # マップ及びマップの初期座標、初期ズームの指定 m = folium.Map(location=[35.4122, 137.4130], zoom_start=6) # 緯度、経度のDataFrameを作成 data = pd.DataFrame( [ ["東京", 35.4122, 139.4130], ["千葉", 35.6047, 140.1233], ["山梨", 35.6638, 138.5683], ["宮城", 38.2688, 140.8719], ["新潟", 37.9022, 139.0236], ["長野", 36.6513, 138.1811], ["福島", 37.7500, 140.4677], ["栃木", 36.5658, 139.8836], ["石川", 36.5944, 136.6255], ["福井", 36.0652, 136.2219], ["静岡", 34.9769, 138.3830], ["岐阜", 35.3911, 136.7222], ["三重", 34.7302, 136.5086], ["愛知", 35.1802, 136.9066], ["京都", 35.0213, 135.7555], ["和歌山", 34.2261,135.1675], ["鳥取", 35.5036, 134.2383], ["島根", 35.4722, 133.0505], ["隠岐の島", 36.1200, 133.1000], #["", , ], ["広島", 34.3963, 132.4594], ["高知", 33.5597, 133.5311], ["福岡", 33.6063, 130.4180], ["大島", 34.7570, 139.3574], ["八丈島", 33.1024, 139.7990], ["鹿児島", 31.5602, 130.5580], ] ) data.columns = ["place", "latitude", "longitude"] ### サイドバー # ヘリの台数 vehicle_num = st.sidebar.selectbox('ヘリの台数',(1, 2, 3, 4)) # 県を選択するマルチラベル(出発点の東京除く) prefecture = st.sidebar.multiselect('出発点の東京除くマーカーを表示する県を選択してください',list(data[data.place!="東京"].place) , list(data[data.place!="東京"].place) ) #例 # 東京を追加 prefecture = pd.concat( [ data[data.place=="東京"].place, data.place[data.place.isin(prefecture)] ] ) data = data[data["place"].isin(prefecture)] ### タイトル st.title(f"出発点東京 {len(data)}拠点 ヘリ{vehicle_num}機のルート最適化") # 距離行列 data_matrix = pd.DataFrame( distance_matrix(data[["latitude", "longitude"]].values, data[["latitude", "longitude"]].values), index=data["place"], columns=data["place"] ) ### 以下、数理最適化 manager = pywrapcp.RoutingIndexManager( len(data), vehicle_num, # 車両台数 0 # 出発点のindex ) routing = pywrapcp.RoutingModel(manager) def distance_callback(from_index, to_index): """Returns the distance between the two nodes.""" # Convert from routing variable Index to distance matrix NodeIndex. from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return data_matrix.values.tolist()[from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) dimension_name = 'Distance' routing.AddDimension( transit_callback_index, 0, # no slack 3000, # vehicle maximum travel distance True, # start cumul to zero dimension_name) distance_dimension = routing.GetDimensionOrDie(dimension_name) distance_dimension.SetGlobalSpanCostCoefficient(100) search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = ( routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) solution = routing.SolveWithParameters(search_parameters) def get_routes(solution, routing, manager): """Get vehicle routes from a solution and store them in an array.""" # Get vehicle routes and store them in a two dimensional array whose # i,j entry is the jth location visited by vehicle i along its route. routes = [] for route_nbr in range(routing.vehicles()): index = routing.Start(route_nbr) route = [manager.IndexToNode(index)] while not routing.IsEnd(index): index = solution.Value(routing.NextVar(index)) route.append(manager.IndexToNode(index)) routes.append(route) return routes routes = get_routes(solution, routing, manager) ### 以上、数理最適化 # 線で結ぶ sq = [] # 座標を格納するリスト color_list = [] # 色指定用のリスト # sq、color_list の入力 for i in range(len(routes)): sq = sq + data.iloc[routes[i], 1:3].values.tolist() color_list = color_list + [i] * len(routes[i]) # order 経路順の作成 place_index = [0] order_num = [0] for i in range(len(routes)): for j in range(1, len(routes[i]) -1): place_index = place_index + [routes[i][j]] order_num = order_num + [j] order = pd.DataFrame( [ order_num, place_index ] ).T order.columns = ["order_num", "place_index"] order = order.sort_values("place_index").reset_index()["order_num"] # 経路の作成 folium.ColorLine( # 色付きの線をマップに表示 positions=sq, # 座標 colors= color_list, # 色 weight=3 # 線の太さ ).add_to(m) # マーカーの表示 for i in range(len(data)): popup = folium.Popup( html= f"{order[i]}", max_width=1000, show=False ) folium.Circle( location = data[["latitude", "longitude"]].values.tolist()[i], popup=popup, parse_html=True, color = "red", radius = 10000.0, fill = True ).add_to(m) # 地図をブラウザに表示 folium_static(m) st.write("注)各拠点にヘリが停まれるかどうかは無視")