diff --git a/app.py.bbkp b/app.py.bbkp deleted file mode 100644 index 7d1480e..0000000 --- a/app.py.bbkp +++ /dev/null @@ -1,312 +0,0 @@ -import os -import time -import uuid -import googlemaps - -# from googlemaps import LatLng -import numpy as np -import streamlit as st -from typing import List -import datetime as dt -from datetime import datetime, timedelta -from collections import namedtuple - -import firebase_admin -from firebase_admin import firestore, credentials - -from streamlit_auth0 import login_button - -AUTH0_CLIENT_ID = "ciFCosdQaze8Vwz2CQRci6Xa4Or1GTuu" -AUTH0_DOMAIN = "tk42.jp.auth0.com" - -from ortools.constraint_solver import routing_enums_pb2 -from ortools.constraint_solver import pywrapcp - -gmaps = googlemaps.Client(key=os.environ.get("GOOGLEMAP_API_KEY")) - -Location = namedtuple("Location", ["lat", "lng"]) -baseCls = namedtuple("StepPoint", ["id", "index", "name", "address", "staying_min", "start_time", "end_time"]) - - -class StepPoint(baseCls): - def to_dict(self) -> dict: - return { - "id": self.id, - "index": self.index, - "name": self.name, - "address": self.address, - "staying_min": self.staying_min, - "start_time": self.start_time.isoformat(), - "end_time": self.end_time.isoformat(), - } - - @staticmethod - def from_dict(source: dict): - return StepPoint( - source["id"], - source["index"], - source["name"], - source["address"], - source["staying_min"], - datetime.fromisoformat(source["start_time"]), - datetime.fromisoformat(source["end_time"]), - ) - - def __repr__(self) -> str: - return f"StepPoint({self.id}, {self.index}, {self.name}, {self.address}, {self.staying_min}, {self.start_time}, {self.end_time})" - - -def create_step_point(num: int, default_address: str) -> StepPoint | None: - col1, col2, col3, col4, col5 = st.columns([1, 4, 1, 1, 1]) - with col1: - step_name = st.text_input(f"Name {num}", f"餃子の王将 No.{num}", key=f"name{num}") - with col2: - step_address = st.text_input(f"Address {num}", default_address, key=f"address{num}") - with col3: - staying_min = st.number_input("Staying time [min.]", min_value=0, max_value=180, step=5, key=f"staying{num}") - with col4: - start_time = st.time_input("Available start time", dt.time(hour=8, minute=45), key=f"start{num}") - with col5: - end_time = st.time_input("Available end time", dt.time(hour=19, minute=0), key=f"end{num}") - if step_address == "": - return None - return StepPoint( - id=str(uuid.uuid4()), - index=num, - name=step_name, - address=step_address, - staying_min=int(staying_min), - start_time=start_time, - end_time=end_time, - ) - - -def create_time_matrix(*step_points: List[StepPoint]): - n = len(step_points) - arr = np.zeros((n, n)) - items = [sp.address for sp in step_points] - split_arrs = np.array_split(items, (n // 10) + 1) - split_arrs_len = [len(a) for a in split_arrs] - for i, a in enumerate(split_arrs): - for j, b in enumerate(split_arrs): - resp = gmaps.distance_matrix( - list(a), - list(b), - ) - for k, r in enumerate(resp["rows"]): - for l, c in enumerate(r["elements"]): - p, q = ( - sum(split_arrs_len[:i]) + k, - sum(split_arrs_len[:j]) + l, - ) - if p == q: - continue - arr[p][q] = step_points[p].staying_min + int(c["duration"]["value"] / 60) # sec -> min - return arr.astype(int) - - -def diff_min(end: time, start: time) -> int: - return int( - (datetime.combine(datetime.today(), end) - datetime.combine(datetime.today(), start)).total_seconds() / 60 - ) - - -def create_time_windows(*step_points: List[StepPoint]): - return [ - (diff_min(sp.start_time, step_points[0].start_time), diff_min(sp.end_time, step_points[0].start_time)) - for sp in step_points - ] - - -# Stores the data for the problem. -def create_data_model(*step_points: List[StepPoint]): - data = {} - data["name"] = [sp.name for sp in step_points] - data["start_time"] = datetime.combine(datetime.today(), step_points[0].start_time) - data["time_matrix"] = create_time_matrix(*step_points) - # https://developers.google.com/optimization/reference/python/constraint_solver/pywrapcp#intvar - data["time_windows"] = create_time_windows(*step_points) - data["num_vehicles"] = 1 - data["depot"] = 0 - return data - - -def print_solution(data, manager, routing, solution): - st.header("Timeschedule") - st.info("A ~ B : 到着時刻の解の範囲.すなわち「車両は時刻AとBの間にそこに到着していれば良い」という意味.滞在時間帯ではないので注意!") - # st.write("Objective:", solution.ObjectiveValue()) - time_dimension = routing.GetDimensionOrDie("Time") - total_time = 0 - for vehicle_id in range(data["num_vehicles"]): - index = routing.Start(vehicle_id) - st.write("Route for vehicle", vehicle_id) - while not routing.IsEnd(index): - time_var = time_dimension.CumulVar(index) - st.write( - data["start_time"] + timedelta(minutes=solution.Min(time_var)), - " ~ ", - data["start_time"] + timedelta(minutes=solution.Max(time_var)), - " @ ", - data["name"][manager.IndexToNode(index)], - " ➡ ", - ) - index = solution.Value(routing.NextVar(index)) - time_var = time_dimension.CumulVar(index) - st.write( - "From ", - data["start_time"] + timedelta(minutes=solution.Min(time_var)), - " To ", - data["start_time"] + timedelta(minutes=solution.Max(time_var)), - " @ ", - data["name"][manager.IndexToNode(index)], - ) - st.write("Time of the route: ", solution.Min(time_var), "min") - total_time += solution.Min(time_var) - st.write("Total time of all routes: ", total_time, "min") - - -# Solve the VRP with time windows. -def solve_vrp(*step_points: List[StepPoint]): - assert len(step_points) > 0, "There is no step point." - - # Instantiate the data problem. - data = create_data_model(*step_points) - - # Create the routing index manager. - manager = pywrapcp.RoutingIndexManager(len(data["time_matrix"]), data["num_vehicles"], data["depot"]) - - # Create Routing Model. - routing = pywrapcp.RoutingModel(manager) - - # Create and register a transit callback. - def time_callback(from_index, to_index): - """Returns the travel time between the two nodes.""" - # Convert from routing variable Index to time matrix NodeIndex. - from_node = manager.IndexToNode(from_index) - to_node = manager.IndexToNode(to_index) - return data["time_matrix"][from_node][to_node] - - transit_callback_index = routing.RegisterTransitCallback(time_callback) - - # Define cost of each arc. - routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) - - # Add Time Windows constraint. - depot_opening_time = diff_min(step_points[0].end_time, step_points[0].start_time) - dimension_name = "Time" - routing.AddDimension( - transit_callback_index, - depot_opening_time, # allow waiting time [min] - depot_opening_time, # maximum time [min] per vehicle until return - False, # Don't force start cumul to zero. - dimension_name, - ) - time_dimension = routing.GetDimensionOrDie(dimension_name) - # Add time window constraints for each location except depot. - for location_idx, time_window in enumerate(data["time_windows"]): - if location_idx == data["depot"]: - continue - index = manager.NodeToIndex(location_idx) - time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1]) - # Add time window constraints for each vehicle start node. - depot_idx = data["depot"] - for vehicle_id in range(data["num_vehicles"]): - index = routing.Start(vehicle_id) - time_dimension.CumulVar(index).SetRange(data["time_windows"][depot_idx][0], data["time_windows"][depot_idx][1]) - - # Instantiate route start and end times to produce feasible times. - for i in range(data["num_vehicles"]): - routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(i))) - routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i))) - - # Setting first solution heuristic. - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC - - # Solve the problem. - solution = routing.SolveWithParameters(search_parameters) - - # Print solution on console. - if solution: - print_solution(data, manager, routing, solution) - else: - st.error("Not found the solution") - st.warning(data["time_matrix"]) # for debug - return solution - - -def logined(sid: str): - db = firestore.client() - - hist_ref = db.collection(sid).document("history") - doc = hist_ref.get() - if doc.exists: - st.write(doc.to_dict()) - - cont_ref = db.collection(sid).document("contact") - - st.info("No. 0 is the depot. Skipped if the address is empty.") - - step_points = [] - # depot - # step_points += [create_step_point(len(step_points), "650-0011 兵庫県神戸市中央区下山手通2丁目11-1")] - # step_points += [create_step_point(len(step_points), "658-0072 兵庫県神戸市東灘区岡本1-5-5 ダイソービル 2階")] - # step_points += [create_step_point(len(step_points), "651-0078 兵庫県神戸市中央区八雲通1-4-35")] - # step_points += [create_step_point(len(step_points), "652-0811 兵庫県神戸市兵庫区新開地3-4-22")] - # step_points += [create_step_point(len(step_points), "651-1101 兵庫県神戸市北区山田町小部 字広苅14-8")] - # step_points += [create_step_point(len(step_points), "654-0111 兵庫県神戸市須磨区車道谷山1-2")] - # step_points += [create_step_point(len(step_points), "655-0852 兵庫県神戸市垂水区名谷町686-2")] - # step_points += [create_step_point(len(step_points), "650-0022 兵庫県神戸市中央区元町通1-10-7")] - # step_points += [create_step_point(len(step_points), "653-0015 兵庫県神戸市長田区菅原通6-2")] - # step_points += [create_step_point(len(step_points), "654-0055 兵庫県神戸市須磨区須磨浦通4-6-20")] - - # step_points += [create_step_point(len(step_points), "651-1131 兵庫県神戸市北区北五葉1-1-3")] - # step_points += [create_step_point(len(step_points), "655-0893 兵庫県神戸市垂水区日向1-4")] - # step_points += [create_step_point(len(step_points), "652-0804 兵庫県神戸市兵庫区塚本通6-1-18")] - # step_points += [create_step_point(len(step_points), "657-0027 兵庫県神戸市灘区永手町5-4-16")] - # step_points += [create_step_point(len(step_points), "658-0023 兵庫県神戸市東灘区深江浜町82")] - # step_points += [create_step_point(len(step_points), "658-0054 兵庫県神戸市東灘区御影中町1-13-8")] - # step_points += [create_step_point(len(step_points), "659-0065 兵庫県芦屋市公光町10-14")] - # step_points += [create_step_point(len(step_points), "662-0947 兵庫県西宮市宮前町1-14")] - # step_points += [create_step_point(len(step_points), "662-0043 兵庫県西宮市常磐町1-2")] - # step_points += [create_step_point(len(step_points), "662-0832 兵庫県西宮市甲風園1-5-16")] - # step_points += [create_step_point(len(step_points), "")] - # step_points += [create_step_point(len(step_points), "")] - # step_points += [create_step_point(len(step_points), "")] - # step_points += [create_step_point(len(step_points), "")] - # step_points += [create_step_point(len(step_points), "")] - - send_data = {sp.id: sp.to_dict() for sp in step_points if sp is not None} - if send_data: - cont_ref.set(send_data) - - if st.button("FIND ROUTE 🔍"): - if solve_vrp(*[stp for stp in step_points if stp is not None]): - hist_ref.set( - {"timestamp": int(time.time()), "step_points": [sp.to_dict() for sp in step_points if sp is not None]} - ) - - -def main(): - st.set_page_config(page_icon="🗺️", page_title="TSPTW with Streamlit", layout="wide") - st.title("Traveling Salesman Problem with Time Windows and Steps on Streamlit") - st.caption("This is a webapp with streamlit to solve the traveling salesman problem with time windows and steps") - - if firebase_admin._DEFAULT_APP_NAME not in firebase_admin._apps: - if os.environ.get("CLOUD_RUN") and os.environ.get("CLOUD_RUN").title() == "True": - cred = credentials.ApplicationDefault() - else: - cred = credentials.Certificate("./serviceAccount.json") - firebase_admin.initialize_app(cred) - - user_info = login_button(client_id=AUTH0_CLIENT_ID, domain=AUTH0_DOMAIN) - if user_info: - with st.sidebar: - st.write("test") - logined(user_info["sid"]) - else: - st.warning("Please login to continue") - - -if __name__ == "__main__": - main() diff --git a/app.py.bkp b/app.py.bkp deleted file mode 100644 index 104fec0..0000000 --- a/app.py.bkp +++ /dev/null @@ -1,70 +0,0 @@ -import os -import threading -import googlemaps -import folium -import numpy as np -import streamlit as st -from datetime import datetime -from streamlit_folium import st_folium - - -gmaps = googlemaps.Client(key=os.environ.get("GOOGLEMAP_API_KEY")) - - -def main(): - st.set_page_config(page_icon="🗺️", page_title="TSPTW with Streamlit") - st.title("Traveling Salesman Problem with Time Windows and Steps on Streamlit") - st.write("This is a webapp with streamlit to solve the traveling salesman problem with time windows and steps") - - m = folium.Map(location=[39.949610, -75.150282], zoom_start=16) - - # marker = folium.Marker( - # [init_marker["lat"], init_marker["lng"]], popup=init_marker["popup"], tooltip=init_marker["tooltip"] - # ) - # marker.add_to(m) - - # call to render Folium map in Streamlit - st_data = st_folium(m, width=725) - st.write(f"st_data: {st_data}") - - if "last_clicked" not in st.session_state: - st.session_state.last_clicked = { - "lat": 39.949610, - "lng": -75.150282, - "popup": "Liberty Bell", - "tooltip": "Liberty Bell", - } - print(st.session_state.last_clicked) - marker = folium.Marker( - [st.session_state.last_clicked["lat"], st.session_state.last_clicked["lng"]], - popup=st.session_state.last_clicked["popup"], - tooltip=st.session_state.last_clicked["tooltip"], - ) - marker.add_to(m) - - last_clicked = st_data["last_clicked"] - if last_clicked: - st.session_state.last_clicked = { - "lat": last_clicked["lat"], - "lng": last_clicked["lng"], - "popup": "last clicked", - "tooltip": "last clicked", - } - - st.write(st.session_state.last_clicked) - - marker = folium.Marker( - [st.session_state.last_clicked["lat"], st.session_state.last_clicked["lng"]], - popup=st.session_state.last_clicked["popup"], - tooltip=st.session_state.last_clicked["tooltip"], - ) - marker.add_to(m) - - # now = datetime.now() - # directions_result = gmaps.directions("Sydney Town Hall", "Parramatta, NSW", mode="transit", departure_time=now) - # st.write(directions_result) - # directions_result[0]["legs"][0]["duration"]["value"] => 2704 [sec] => almost 45min - - -if __name__ == "__main__": - main() diff --git a/requirements.txt b/requirements.txt index 117fac9..a462a2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ ortools -streamlit==1.14.1 -#streamlit_folium -#folium +streamlit==1.15.0 +pydeck +pandas googlemaps firebase-admin streamlit-auth0 diff --git a/tsptw/app.py b/tsptw/app.py index df73f04..51b27f1 100644 --- a/tsptw/app.py +++ b/tsptw/app.py @@ -13,7 +13,6 @@ def __init__(self, pages: list[BasePage], nav_label: str = "ページ一覧") -> self.pages = {page.page_id: page for page in pages} self.nav_label = nav_label - @st.cache(hash_funcs={Client: hash_client}, allow_output_mutation=True) def connect_to_database(self, key: str): db = firestore.client() return db.collection(key).document("user_info") diff --git a/tsptw/const.py b/tsptw/const.py index 23ab625..73fe398 100644 --- a/tsptw/const.py +++ b/tsptw/const.py @@ -1,6 +1,7 @@ import os import googlemaps import datetime as dt +from typing import List from enum import Enum, IntEnum, auto from datetime import datetime, date from collections import namedtuple @@ -12,17 +13,32 @@ gmaps = googlemaps.Client(key=os.environ.get("GOOGLEMAP_API_KEY")) Location = namedtuple("Location", ["lat", "lng"]) -baseCls = namedtuple("StepPoint", ["id", "timestamp", "name", "address", "staying_min", "start_time", "end_time"]) +baseCls = namedtuple( + "StepPoint", + ["id", "timestamp", "name", "address", "lat", "lng", "staying_min", "start_time", "end_time"] +) today = datetime.now().date() +def hex_to_rgb(h): + h = h.lstrip("#") + return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4)) + + def create_datetime(t: dt.time or str, fromisoformat=False): if fromisoformat: t = dt.time.fromisoformat(t) return datetime.combine(today, t) +def geocode(address: str) -> Location: + results = gmaps.geocode(address) + print(results) + location = results[0]["geometry"]["location"] + return Location(location["lat"], location["lng"]) + + class StepPoint(baseCls): def to_dict(self) -> dict: return { @@ -30,6 +46,8 @@ def to_dict(self) -> dict: "timestamp": self.timestamp, "name": self.name, "address": self.address, + "lat": self.lat, + "lng": self.lng, "staying_min": self.staying_min, "start_time": self.start_time.isoformat(), "end_time": self.end_time.isoformat(), @@ -42,6 +60,8 @@ def from_dict(source: dict): source["timestamp"], source["name"], source["address"], + source["lat"], + source["lng"], source["staying_min"], datetime.fromisoformat(str(date.today()) + "T" + source["start_time"]), datetime.fromisoformat(str(date.today()) + "T" + source["end_time"]), @@ -49,11 +69,25 @@ def from_dict(source: dict): def __repr__(self) -> str: return ( - f"StepPoint({self.id}, {self.timestamp}, {self.name}, {self.address}," + f"StepPoint({self.id}, {self.timestamp}, {self.name}, {self.address}, {self.lat}, {self.lng}" + f" {self.staying_min}, {self.start_time}, {self.end_time})" ) +def get_route(step_points: List[StepPoint]): + result = gmaps.directions( + step_points[0].address, + step_points[-1].address, + waypoints=[sp.address for sp in step_points[1:-1]], + avoid="highways", + ) + print(result) + return [ + [step["start_location"]["lng"], step["start_location"]["lat"]] + for step in result[0]["legs"][0]["steps"] + ] + + def hash_client(ref): return hash(ref) diff --git a/tsptw/pages/edit.py b/tsptw/pages/edit.py index 6d6ac55..37e4d51 100644 --- a/tsptw/pages/edit.py +++ b/tsptw/pages/edit.py @@ -2,10 +2,11 @@ import time import datetime as dt import streamlit as st + from .base import BasePage from firebase_admin import firestore from google.cloud.firestore import DELETE_FIELD -from tsptw.const import StepPoint, ActorId, PageId, create_datetime +from tsptw.const import StepPoint, ActorId, create_datetime, geocode class EditPage(BasePage): @@ -20,46 +21,53 @@ def submit( last_delete: bool = False, ): email = st.session_state["user_info"]["email"] + cont_ref = self.connect_to_database(email) + if actor == ActorId.DELETE: + if last_delete: + cont_ref.delete() + else: + cont_ref.update({sp_id: DELETE_FIELD}) + return + step_name = st.session_state["step_name"] step_address = st.session_state["step_address"] staying_min = st.session_state["staying_min"] start_time = st.session_state["start_time"] end_time = st.session_state["end_time"] + loc = geocode(step_address) - cont_ref = self.connect_to_database(email) - # TODO: Remove ValueError logs somewhere around here. - # ValueError: {'start_time': ,,,, 'staying_min': 0} is not in iterable - # See: https://github.com/streamlit/streamlit/issues/3598 if actor == ActorId.ADD: sp = StepPoint( uuid.uuid4().hex, int(time.time()), step_name, step_address, + loc.lat, + loc.lng, staying_min, start_time, end_time, ) cont_ref.set({sp.id: sp.to_dict()}, merge=True) - elif actor == ActorId.UPDATE: + + if actor == ActorId.UPDATE: sp = StepPoint( sp_id, timestamp, step_name, step_address, + loc.lat, + loc.lng, staying_min, start_time, end_time, ) + # TODO: Remove ValueError logs somewhere around here. + # ValueError: {'start_time': ,,,, 'staying_min': 0} is not in iterable + # See: https://github.com/streamlit/streamlit/issues/3598 + # st.session_state["selected"] = sp.to_dict() cont_ref.update({sp.id: sp.to_dict()}) - elif actor == ActorId.DELETE: - if last_delete: - cont_ref.delete() - else: - cont_ref.update({sp_id: DELETE_FIELD}) - return - @st.cache(allow_output_mutation=True) def connect_to_database(self, key: str): db = firestore.client() return db.collection(key).document("contact") @@ -77,18 +85,18 @@ def render(self): st.markdown( """ ### 説明 - - 「名称」:経由地点の名称.訪問場所の覚えやすい名称を付けることが可能です. - - 「住所」:経由地点の住所.都道府県から番地まで入力してください.郵便番号は不要です.挙動不審の場合はGoogleMapで住所検索をおこない,想定の場所にドロップピンが指されることを確認してください. - - 「見積診察時間」:車を停車してから発車するまでのおおよその時間を5分刻みで入力してください - - 「訪問可能時間帯(始)」:経由地点に到着してもよい最も早い時刻を入力してください - - 「訪問可能時間帯(終)」:経由地点に滞在してもよい最も遅い時刻を入力してください + - 「名称」:経由地点の名称.訪問場所の覚えやすい名称を付けることが可能です. + - 「住所」:経由地点の住所.都道府県から番地まで入力してください.郵便番号は不要です.挙動不審の場合はGoogleMapで住所検索をおこない,想定の場所にドロップピンが指されることを確認してください. + - 「見積診察時間」:車を停車してから発車するまでのおおよその時間を5分刻みで入力してください + - 「訪問可能時間帯(始)」:経由地点に到着してもよい最も早い時刻を入力してください + - 「訪問可能時間帯(終)」:経由地点に滞在してもよい最も遅い時刻を入力してください """ ) st.markdown( """ ### 使い方 - - 追加する場合:「名称」「住所」「見積診察時間」「訪問可能時間(始)」「訪問可能時間(終)」を入力し,「追加」ボタンを押してください - - 編集/削除する場合:上段の「編集/削除対象」で対象の経由地点を選択し,下段で「更新」/「削除」ボタンを押してください + - 追加する場合:「名称」「住所」「見積診察時間」「訪問可能時間(始)」「訪問可能時間(終)」を入力し,「追加」ボタンを押してください + - 編集/削除する場合:上段の「編集/削除対象」で対象の経由地点を選択し,下段で「更新」/「削除」ボタンを押してください """ ) diff --git a/tsptw/pages/findroute.py b/tsptw/pages/findroute.py index ed9d90f..ebd37fc 100644 --- a/tsptw/pages/findroute.py +++ b/tsptw/pages/findroute.py @@ -1,23 +1,22 @@ import numpy as np +# import pandas as pd +# import pydeck as pdk import datetime as dt from datetime import timedelta import streamlit as st from typing import List +from tsptw.const import StepPoint, gmaps, PageId, create_datetime #, get_route, hex_to_rgb from .base import BasePage -from tsptw.const import hash_client, StepPoint, gmaps, PageId, create_datetime from firebase_admin import firestore -from google.cloud.firestore_v1.client import Client - -from ortools.constraint_solver import routing_enums_pb2 -from ortools.constraint_solver import pywrapcp +from ortools.constraint_solver import pywrapcp #, routing_enums_pb2 class FindRoutePage(BasePage): def __init__(self, page_id: PageId, title: str) -> None: super().__init__(page_id, title) - self.step_points = [] + self.step_points_id = [] def create_time_matrix(self, *step_points: List[StepPoint]): n = len(step_points) @@ -58,34 +57,39 @@ def create_time_windows(self, start_time: dt.time, *step_points: List[StepPoint] # Stores the data for the problem. def create_data_model(self, start_time: dt.datetime, end_time: dt.datetime, *step_points: List[StepPoint]): data = {} - data["name"] = [sp.name for sp in step_points] + data["sp"] = step_points data["start_time"] = start_time data["depot_opening_time"] = self.diff_min(end_time, start_time) data["time_matrix"] = self.create_time_matrix(*step_points) # https://developers.google.com/optimization/reference/python/constraint_solver/pywrapcp#intvar data["time_windows"] = self.create_time_windows(start_time, *step_points) - data["num_vehicles"] = 1 + data["vehicles"] = [ + {"path_color": "#ed1c24"} + ] data["depot"] = 0 return data def print_solution(self, data, manager, routing, solution): time_dimension = routing.GetDimensionOrDie("Time") total_time = 0 - for vehicle_id in range(data["num_vehicles"]): + for vehicle_id in range(len(data["vehicles"])): index = routing.Start(vehicle_id) + step_points = [] # st.write("Route for vehicle", vehicle_id) while not routing.IsEnd(index): + sp = data["sp"][manager.IndexToNode(index)] + step_points += [sp] time_var = time_dimension.CumulVar(index) if solution.Min(time_var) == solution.Max(time_var): st.write( - data["name"][manager.IndexToNode(index)], + sp.name, "さん宅.最短で", data["start_time"] + timedelta(minutes=solution.Min(time_var)), "に到着することができます", ) else: st.write( - data["name"][manager.IndexToNode(index)], + sp.name, "さん宅.最短で", data["start_time"] + timedelta(minutes=solution.Min(time_var)), "に到着することができますが,遅くとも", @@ -96,13 +100,51 @@ def print_solution(self, data, manager, routing, solution): time_var = time_dimension.CumulVar(index) st.write( "最終地点(=出発地点) ", - data["name"][manager.IndexToNode(index)], + data["sp"][0].name, ".最短で", data["start_time"] + timedelta(minutes=solution.Min(time_var)), "に到着することができます", ) st.write("この経路の所要時間: ", solution.Min(time_var), "分") total_time += solution.Min(time_var) + + # chunk_num = (len(step_points) // 10) + 1 + # per_chunk = len(step_points) // chunk_num + # path = [] + # for chunk_index in range(chunk_num): + # path += get_route(step_points[per_chunk*chunk_index: per_chunk*(chunk_index+1)]) + + # df = pd.DataFrame([{ + # "color": data["vehicles"][vehicle_id]["path_color"], + # "path": path + # }]) + # df["color"] = df["color"].apply(hex_to_rgb) + + # layer = pdk.Layer( + # type="PathLayer", + # data=df, + # pickable=True, + # get_color="color", + # width_scale=20, + # width_min_pixels=2, + # get_path="path", + # get_width=5, + # ) + + # view_state = pdk.ViewState( + # latitude=data["sp"][0].lat, + # longitude=data["sp"][0].lng, + # zoom=12, + # # pitch=50, + # ) + + # st.pydeck_chart( + # pdk.Deck( + # layers=[layer], + # initial_view_state=view_state, + # ) + # ) + # st.write("全経路の所要時間: ", total_time, "分") # Solve the VRP with time windows. @@ -116,7 +158,7 @@ def solve_vrp(self, *step_points: List[StepPoint]): data = self.create_data_model(start_time, end_time, *step_points) # Create the routing index manager. - manager = pywrapcp.RoutingIndexManager(len(data["time_matrix"]), data["num_vehicles"], data["depot"]) + manager = pywrapcp.RoutingIndexManager(len(data["time_matrix"]), len(data["vehicles"]), data["depot"]) # Create Routing Model. routing = pywrapcp.RoutingModel(manager) @@ -152,14 +194,14 @@ def time_callback(from_index, to_index): time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1]) # Add time window constraints for each vehicle start node. depot_idx = data["depot"] - for vehicle_id in range(data["num_vehicles"]): + for vehicle_id in range(len(data["vehicles"])): index = routing.Start(vehicle_id) time_dimension.CumulVar(index).SetRange( data["time_windows"][depot_idx][0], data["time_windows"][depot_idx][1] ) # Instantiate route start and end times to produce feasible times. - for i in range(data["num_vehicles"]): + for i in range(len(data["vehicles"])): routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(i))) routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i))) @@ -168,7 +210,7 @@ def time_callback(from_index, to_index): # Setting first solution heuristic. search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC + # search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC # Solve the problem. solution = routing.SolveWithParameters(search_parameters) @@ -181,7 +223,6 @@ def time_callback(from_index, to_index): st.warning(data["time_matrix"]) # for debug return solution - @st.cache(hash_funcs={Client: hash_client}, allow_output_mutation=True) def connect_to_database(self, key: str): db = firestore.client() return db.collection(key).document("contact") @@ -215,7 +256,7 @@ def render(self): return if "step_points" in st.session_state and st.session_state["step_points"]: - self.step_points = st.session_state["step_points"] + self.step_points_id = [pt['id'] for pt in st.session_state["step_points"]] contacts = self.sort_data(self.connect_to_database(st.session_state["user_info"]["email"])) @@ -229,13 +270,13 @@ def render(self): ) col2.time_input("出発時刻", dt.time.fromisoformat("09:00:00"), key="start_time") - self.step_points = st.multiselect( + step_points = st.multiselect( "経由地点", contacts.values(), - default=self.step_points, + default=[contacts[id] for id in self.step_points_id], format_func=lambda contact: contact["name"], key="step_points", - disabled=len(self.step_points) > 25, + disabled=len(self.step_points_id) > 25, ) if st.session_state.depot["id"] in [pt["id"] for pt in st.session_state.step_points]: @@ -248,7 +289,7 @@ def render(self): f"出発地点の訪問可能時間帯(始){st.session_state.depot['start_time']}よりも出発時刻{st.session_state.start_time}が早いです.出発時刻を見直すか出発地点を見直してください." ) - all_points = [StepPoint.from_dict(p) for p in [depot] + self.step_points] + all_points = [StepPoint.from_dict(p) for p in [depot] + step_points] if st.button("ルート探索 🔍"): self.solve_vrp(*all_points)