From b758be1b76011abebb897a3e3ab9f808e4da1631 Mon Sep 17 00:00:00 2001 From: awenjb <126257927+awenjb@users.noreply.github.com> Date: Fri, 7 Mar 2025 15:04:33 +0100 Subject: [PATCH] add output and simple visualisation - add simple solution exporter (to json) - add jupyter visualisation (map only) --- CMakeLists.txt | 1 + python/solution_reader.py | 129 +++++++++ python/visualisation.ipynb | 447 +++++++++++++++++++++++++++++++ src/input/json_parser.cpp | 68 ++--- src/input/json_parser.h | 2 +- src/input/pdptw_data.cpp | 10 +- src/input/pdptw_data.h | 5 +- src/mains/main.cpp | 3 + src/output/solution_exporter.cpp | 67 +++++ src/output/solution_exporter.h | 30 +++ 10 files changed, 726 insertions(+), 36 deletions(-) create mode 100644 python/solution_reader.py create mode 100644 python/visualisation.ipynb create mode 100644 src/output/solution_exporter.cpp create mode 100644 src/output/solution_exporter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f6cae39..9f1351b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(pdptw src/mains/main.cpp src/lns/modification/route/insert_route.cpp src/lns/modification/route/remove_route.cpp src/output/solution_checker.cpp + src/output/solution_exporter.cpp src/lns/operators/sorting_strategy.cpp src/lns/operators/destruction/random_destroy.cpp src/lns/operators/generators/enumerate.cpp diff --git a/python/solution_reader.py b/python/solution_reader.py new file mode 100644 index 0000000..790695e --- /dev/null +++ b/python/solution_reader.py @@ -0,0 +1,129 @@ +import json +import datetime +import enum + +import folium +import numpy as np +import pandas as pd +import osmnx as ox +import networkx as nx + +def read_json(file): + try: + with open(file, 'r') as file: + data = json.load(file) + return data + except FileNotFoundError: + print(f"Error : The file {file} does not exist.") + except json.JSONDecodeError: + print(f"Error : The file {file} is not a valid JSON file.") + except Exception as e: + print(f"Error : {e}") + + +G_drive = ox.graph_from_point((47.213811, -1.553168), dist=10000, + network_type='bike') + +class PDPTWSolution: + def __init__(self, filename: str): + with open(filename, mode="r", encoding="utf-8") as f: + self._json_file = json.load(f) + self._parse_json() + + def _parse_json(self): + if self._json_file is None: + raise "Trying to parse json, but was not loaded" + self._routes: list[dict] = self._json_file["routes"] + + def get_routes(self): + return self._routes + + def get_json(self): + return self._json_file + + +class PDPTWData: + def __init__(self, filename: str): + with open(filename, mode="r", encoding="utf-8") as f: + self._json_file = json.load(f) + self._parse_json() + + def _parse_json(self): + if self._json_file is None: + raise "Trying to parse json, but was not loaded" + self._locations: list[dict] = self._json_file["locations"] + self._depot: list[dict] = self._json_file["depot"] + self._capacity = self._json_file["capacity"] + + def get_depot(self): + return self._depot + + def get_locations(self): + return self._locations + + def get_capacity(self): + return self._capacity + + def get_json(self): + return self._json_file + + +def get_route_as_dataframe(data: PDPTWData, solution: PDPTWSolution, route_index:int) -> pd.DataFrame: + route_df=pd.DataFrame() + route_ids = solution.get_routes()[route_index]["locationIDs"] + for id in route_ids: + line = {"route": route_index, + "location": id, + "type": data.get_locations()[id - 1]["locType"], + "latitude": data.get_locations()[id - 1]["latitude"], + "longitude": data.get_locations()[id - 1]["longitude"]} + route_df = pd.concat([route_df, pd.DataFrame.from_dict([line])], ignore_index=True) + return route_df + +def display_points_of_interest(map: folium.Map, data: PDPTWData): + folium.Marker(location=[data.get_depot()["latitude"], data.get_depot()["longitude"]], + icon=folium.Icon(color="red", popup="DEPOT")).add_to(map) + for location in data.get_locations(): + if location["locType"] == "PICKUP": + color = "darkblue" + text = "pickup" + elif location["locType"] == "DELIVERY": + color = "lightblue" + text = "delivery" + else: + continue + folium.Marker(location=[location["latitude"], location["longitude"]], + icon=folium.Icon(color=color, popup=text)).add_to(map) + +def display_route(map: folium.Map, data: PDPTWData, solution: PDPTWSolution, route_index: int, color: str="red"): + df = get_route_as_dataframe(data, solution, route_index) + # add depot at the begining and the end + types = ["DEPOT"] + list(df["type"]) + ["DEPOT"] + depot_coord = (data.get_depot()["latitude"], data.get_depot()["longitude"]) + locations = [depot_coord] + list(zip(df["latitude"], df["longitude"])) + [depot_coord] + real_path_locations = [] + network_locations = ox.nearest_nodes(G_drive, + [data.get_depot()["longitude"]] + list(df["longitude"]) + [data.get_depot()["longitude"]], + [data.get_depot()["latitude"]] + list(df["latitude"]) + [data.get_depot()["latitude"]]) + + for i in range(1, len(locations)): + edge = [network_locations[i - 1], network_locations[i]] + real_nodes = ox.shortest_path(G_drive, edge[0], edge[1], weight="length") + if real_nodes is not None: + long_lat_edges = [] + for node in real_nodes: + g_drive_node = G_drive.nodes[node] + if "x" in g_drive_node and "y" in g_drive_node: + long_lat_edges.append((g_drive_node["y"], g_drive_node["x"])) + real_path_locations.extend(long_lat_edges) + else: + real_path_locations.append(locations[i]) + + folium.PolyLine(real_path_locations, color=color, weight=5, opacity=0.5, smooth_factor=0).add_to(map) + for index, row in df.iterrows(): + if row["type"] == "REQUEST": + text = f'{index} - {row["latitude"], row["longitude"]} : times ({row["arrival"]},{row["service"]},{row["departure"]}), time window [{row["Hmin"]},{row["Hmax"]}]' + folium.Circle(location=(row["latitude"], row["longitude"]), fill_color="orange", radius=4, + tooltip=text).add_to(map) + + diff --git a/python/visualisation.ipynb b/python/visualisation.ipynb new file mode 100644 index 0000000..1c250ee --- /dev/null +++ b/python/visualisation.ipynb @@ -0,0 +1,447 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: folium in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (0.19.4)\n", + "Requirement already satisfied: pandas in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (2.2.3)\n", + "Requirement already satisfied: OSMnx in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (2.0.1)\n", + "Requirement already satisfied: networkx in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (3.4.2)\n", + "Requirement already satisfied: scikit-learn in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (1.6.1)\n", + "Requirement already satisfied: branca>=0.6.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (0.8.1)\n", + "Requirement already satisfied: jinja2>=2.9 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (3.1.5)\n", + "Requirement already satisfied: numpy in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2.2.3)\n", + "Requirement already satisfied: requests in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2.32.3)\n", + "Requirement already satisfied: xyzservices in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2025.1.0)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2025.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2025.1)\n", + "Requirement already satisfied: geopandas>=1.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from OSMnx) (1.0.1)\n", + "Requirement already satisfied: shapely>=2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from OSMnx) (2.0.7)\n", + "Requirement already satisfied: scipy>=1.6.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (1.15.2)\n", + "Requirement already satisfied: joblib>=1.2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (1.4.2)\n", + "Requirement already satisfied: threadpoolctl>=3.1.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (3.5.0)\n", + "Requirement already satisfied: pyogrio>=0.7.2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (0.10.0)\n", + "Requirement already satisfied: packaging in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (24.2)\n", + "Requirement already satisfied: pyproj>=3.3.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (3.7.1)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from jinja2>=2.9->folium) (3.0.2)\n", + "Requirement already satisfied: six>=1.5 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (3.4.1)\n", + "Requirement already satisfied: idna<4,>=2.5 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (2.3.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (2025.1.31)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install folium pandas OSMnx networkx scikit-learn" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import solution_reader\n", + "import datetime as dt\n", + "import folium" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Files\n", + "solutionFile = \"./../output/Nantes_1_sol.json\"\n", + "dataFile = \"./../data_in/Nantes_1.json\"" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "solution = solution_reader.PDPTWSolution(solutionFile)\n", + "data = solution_reader.PDPTWData(dataFile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Map" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<iframe srcdoc=\"<!DOCTYPE html>\n", + "<html>\n", + "<head>\n", + " \n", + " <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n", + " \n", + " <script>\n", + " L_NO_TOUCH = false;\n", + " L_DISABLE_3D = false;\n", + " </script>\n", + " \n", + " <style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>\n", + " <style>#map {position:absolute;top:0;bottom:0;right:0;left:0;}</style>\n", + " <script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>\n", + " <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>\n", + " <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>\n", + " <script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>\n", + " <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.0/css/all.min.css"/>\n", + " <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>\n", + " \n", + " <meta name="viewport" content="width=device-width,\n", + " initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n", + " <style>\n", + " #map_c353624f2ef6701bef2dfdb918ec18f1 {\n", + " position: relative;\n", + " width: 100.0%;\n", + " height: 100.0%;\n", + " left: 0.0%;\n", + " top: 0.0%;\n", + " }\n", + " .leaflet-container { font-size: 1rem; }\n", + " </style>\n", + " \n", + "</head>\n", + "<body>\n", + " \n", + " \n", + " <div class="folium-map" id="map_c353624f2ef6701bef2dfdb918ec18f1" ></div>\n", + " \n", + "</body>\n", + "<script>\n", + " \n", + " \n", + " var map_c353624f2ef6701bef2dfdb918ec18f1 = L.map(\n", + " "map_c353624f2ef6701bef2dfdb918ec18f1",\n", + " {\n", + " center: [47.213811, -1.553168],\n", + " crs: L.CRS.EPSG3857,\n", + " ...{\n", + " "zoom": 15,\n", + " "zoomControl": true,\n", + " "preferCanvas": false,\n", + "}\n", + "\n", + " }\n", + " );\n", + "\n", + " \n", + "\n", + " \n", + " \n", + " var tile_layer_8895f6ed2b442e297dc430f0081dcb8f = L.tileLayer(\n", + " "https://tile.openstreetmap.org/{z}/{x}/{y}.png",\n", + " {\n", + " "minZoom": 0,\n", + " "maxZoom": 19,\n", + " "maxNativeZoom": 19,\n", + " "noWrap": false,\n", + " "attribution": "\\u0026copy; \\u003ca href=\\"https://www.openstreetmap.org/copyright\\"\\u003eOpenStreetMap\\u003c/a\\u003e contributors",\n", + " "subdomains": "abc",\n", + " "detectRetina": false,\n", + " "tms": false,\n", + " "opacity": 1,\n", + "}\n", + "\n", + " );\n", + " \n", + " \n", + " tile_layer_8895f6ed2b442e297dc430f0081dcb8f.addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var marker_20cbbc5fa19a011514524b35d837b38b = L.marker(\n", + " [47.218371, -1.553621],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_4caaf078bbfddb1dbb7303167daf370d = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "red",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "DEPOT",\n", + "}\n", + " );\n", + " marker_20cbbc5fa19a011514524b35d837b38b.setIcon(icon_4caaf078bbfddb1dbb7303167daf370d);\n", + " \n", + " \n", + " var marker_e1ec6cd694e9c61c45f08fde56983ef3 = L.marker(\n", + " [47.213568, -1.555056],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_f2cfa910989fb46a0591df6585bf4253 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "darkblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "pickup",\n", + "}\n", + " );\n", + " marker_e1ec6cd694e9c61c45f08fde56983ef3.setIcon(icon_f2cfa910989fb46a0591df6585bf4253);\n", + " \n", + " \n", + " var marker_f80d5165e7d392cb682a9c8d15512f07 = L.marker(\n", + " [47.220253, -1.550774],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_1d3d3f82ab67ef25193e1c1db5bc81c0 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "darkblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "pickup",\n", + "}\n", + " );\n", + " marker_f80d5165e7d392cb682a9c8d15512f07.setIcon(icon_1d3d3f82ab67ef25193e1c1db5bc81c0);\n", + " \n", + " \n", + " var marker_8853de7a8d8a2cc515cdf1cb3193f948 = L.marker(\n", + " [47.209292, -1.560482],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_8ee68172cbeb64dde52575bb3c0070e8 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "darkblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "pickup",\n", + "}\n", + " );\n", + " marker_8853de7a8d8a2cc515cdf1cb3193f948.setIcon(icon_8ee68172cbeb64dde52575bb3c0070e8);\n", + " \n", + " \n", + " var marker_e63ff96c3c8263e9bab166a1c1e224e3 = L.marker(\n", + " [47.216312, -1.548712],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_86c688a45ab441ffe9688a93aa0447d5 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "darkblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "pickup",\n", + "}\n", + " );\n", + " marker_e63ff96c3c8263e9bab166a1c1e224e3.setIcon(icon_86c688a45ab441ffe9688a93aa0447d5);\n", + " \n", + " \n", + " var marker_615cd366e4f967cdeceb9b1cb84e9002 = L.marker(\n", + " [47.223158, -1.557308],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_7547818df1dfe54f69c51fc646080dad = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "darkblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "pickup",\n", + "}\n", + " );\n", + " marker_615cd366e4f967cdeceb9b1cb84e9002.setIcon(icon_7547818df1dfe54f69c51fc646080dad);\n", + " \n", + " \n", + " var marker_4b1c02fe9b43add497d10ce2a87606c3 = L.marker(\n", + " [47.219011, -1.563922],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_bdfc7560b738c96e5e5639a0a1851278 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "lightblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "delivery",\n", + "}\n", + " );\n", + " marker_4b1c02fe9b43add497d10ce2a87606c3.setIcon(icon_bdfc7560b738c96e5e5639a0a1851278);\n", + " \n", + " \n", + " var marker_f3d5f6fa59ad8c59dffb574c5a80e698 = L.marker(\n", + " [47.214682, -1.554387],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_4ecafabf83aceb11746c6346d174202b = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "lightblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "delivery",\n", + "}\n", + " );\n", + " marker_f3d5f6fa59ad8c59dffb574c5a80e698.setIcon(icon_4ecafabf83aceb11746c6346d174202b);\n", + " \n", + " \n", + " var marker_0f8d08e8869e5939f234b80dfb3cc480 = L.marker(\n", + " [47.217748, -1.552834],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_0c04360857e24a27af8f3606098ec5c1 = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "lightblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "delivery",\n", + "}\n", + " );\n", + " marker_0f8d08e8869e5939f234b80dfb3cc480.setIcon(icon_0c04360857e24a27af8f3606098ec5c1);\n", + " \n", + " \n", + " var marker_0f14f775c0140a6f3d2ab6a7c0bf13de = L.marker(\n", + " [47.210421, -1.561234],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_c3e9638f3929bda329b5a4f5507e22aa = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "lightblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "delivery",\n", + "}\n", + " );\n", + " marker_0f14f775c0140a6f3d2ab6a7c0bf13de.setIcon(icon_c3e9638f3929bda329b5a4f5507e22aa);\n", + " \n", + " \n", + " var marker_6decad1d5222cc076ea596bc00ce4797 = L.marker(\n", + " [47.213824, -1.558907],\n", + " {\n", + "}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + " \n", + " var icon_51f0648f6226ca2b848827e94b6857df = L.AwesomeMarkers.icon(\n", + " {\n", + " "markerColor": "lightblue",\n", + " "iconColor": "white",\n", + " "icon": "info-sign",\n", + " "prefix": "glyphicon",\n", + " "extraClasses": "fa-rotate-0",\n", + " "popup": "delivery",\n", + "}\n", + " );\n", + " marker_6decad1d5222cc076ea596bc00ce4797.setIcon(icon_51f0648f6226ca2b848827e94b6857df);\n", + " \n", + " \n", + " var poly_line_527a754ef0df57c187022733d15c2bb3 = L.polyline(\n", + " [[47.2183453, -1.5534755], [47.2180456, -1.5534249], [47.2179026, -1.5534213], [47.2173096, -1.5533652], [47.2163273, -1.552867], [47.2160168, -1.5534298], [47.2158832, -1.5537904], [47.2156671, -1.5543686], [47.2155362, -1.5547237], [47.2154727, -1.5548879], [47.2154154, -1.5550336], [47.2153044, -1.555331], [47.2150828, -1.5559266], [47.2150388, -1.5560491], [47.2150223, -1.5560952], [47.2150106, -1.5561331], [47.2149286, -1.5563563], [47.2148674, -1.5565232], [47.2144755, -1.5576091], [47.2143928, -1.5578413], [47.2143668, -1.5579057], [47.2141806, -1.5584372], [47.2141855, -1.5586343], [47.2139823, -1.5587194], [47.2138656, -1.558768], [47.213595, -1.5588636], [47.2133024, -1.5589669], [47.2132707, -1.5589781], [47.2126378, -1.5591137], [47.2121042, -1.5593879], [47.2117136, -1.559891], [47.2116749, -1.5597488], [47.2113887, -1.5605287], [47.2112181, -1.5609503], [47.2110607, -1.5613661], [47.2101237, -1.5616227], [47.2100646, -1.5616045], [47.2100053, -1.5616541], [47.2099268, -1.5616842], [47.2098593, -1.5615066], [47.209841, -1.5614154], [47.2098043, -1.5612944], [47.2097481, -1.5610591], [47.2097481, -1.5610591], [47.2098572, -1.5612284], [47.2098934, -1.5611817], [47.210006, -1.5612451], [47.2104866, -1.5600564], [47.2105383, -1.5599477], [47.2106919, -1.5595986], [47.210698, -1.5595018], [47.210765, -1.559475], [47.2112648, -1.5589293], [47.2113092, -1.5587873], [47.2114439, -1.5587814], [47.2114847, -1.5588541], [47.2115747, -1.5588769], [47.2119118, -1.5588963], [47.2124823, -1.5576001], [47.2127522, -1.5569902], [47.2130621, -1.5562719], [47.2133885, -1.5554467], [47.2137115, -1.5552366], [47.2137115, -1.5552366], [47.2133885, -1.5554467], [47.2139676, -1.5558336], [47.214047, -1.5555956], [47.214322, -1.5558496], [47.2149286, -1.5563563], [47.2152704, -1.5565161], [47.2156942, -1.5566909], [47.2160719, -1.5568626], [47.2161023, -1.5566372], [47.2160668, -1.556594], [47.216062, -1.55652], [47.2160888, -1.5564689], [47.2161627, -1.5564754], [47.2162459, -1.5565005], [47.2162987, -1.5563239], [47.2163182, -1.5562613], [47.2166103, -1.5553853], [47.2166552, -1.5552407], [47.2167391, -1.5549874], [47.2168734, -1.5545202], [47.2170421, -1.5545425], [47.2171995, -1.5538062], [47.2173096, -1.5533652], [47.2179026, -1.5534213], [47.2179732, -1.5529077], [47.2179732, -1.5529077], [47.2179026, -1.5534213], [47.2178056, -1.5542105], [47.2177651, -1.5545826], [47.2177545, -1.554897], [47.2176684, -1.5548815], [47.2173841, -1.5547654], [47.2167391, -1.5549874], [47.2166552, -1.5552407], [47.2166103, -1.5553853], [47.2163182, -1.5562613], [47.2162987, -1.5563239], [47.2164163, -1.5564274], [47.2163872, -1.5565937], [47.2163782, -1.5566448], [47.2163685, -1.5566999], [47.2163262, -1.5569411], [47.2162653, -1.5569158], [47.2163908, -1.5570911], [47.2168916, -1.5577689], [47.2178606, -1.5591488], [47.2177585, -1.5592958], [47.2174626, -1.5595293], [47.2173537, -1.5596481], [47.2173628, -1.5597536], [47.217422, -1.5601737], [47.2176085, -1.5613937], [47.2176974, -1.5620016], [47.217875, -1.5623626], [47.2180253, -1.5626025], [47.2184235, -1.5632157], [47.2184899, -1.5633144], [47.2185675, -1.5632755], [47.2186651, -1.563122], [47.2187473, -1.5632832], [47.2186498, -1.56339], [47.2186631, -1.5636429], [47.219081, -1.5643201], [47.219081, -1.5643201], [47.2195174, -1.5638054], [47.2192922, -1.5634829], [47.2202091, -1.5623961], [47.2202406, -1.5620657], [47.2201827, -1.561978], [47.2202337, -1.5618217], [47.2202964, -1.5618185], [47.2208756, -1.5619707], [47.2209515, -1.5616614], [47.2208425, -1.5614354], [47.2205302, -1.5607196], [47.2204977, -1.5606348], [47.2204344, -1.560452], [47.2200804, -1.5592354], [47.2200109, -1.5589945], [47.2199823, -1.558947], [47.2199731, -1.5589098], [47.2200193, -1.5586361], [47.2200296, -1.5586236], [47.2201608, -1.558607], [47.2203358, -1.558474], [47.2207131, -1.557133], [47.2209603, -1.5563172], [47.2209331, -1.5562339], [47.2209355, -1.5562025], [47.220957, -1.5561485], [47.221036, -1.5557407], [47.2210876, -1.5556392], [47.2211358, -1.5556193], [47.2211711, -1.5555632], [47.2211884, -1.5555797], [47.2211918, -1.5556247], [47.2215321, -1.5559809], [47.2218819, -1.5562763], [47.2225196, -1.556822], [47.2227481, -1.5570086], [47.2227481, -1.5570086], [47.2225196, -1.556822], [47.2218819, -1.5562763], [47.2215321, -1.5559809], [47.2211643, -1.5556505], [47.2211358, -1.5556193], [47.2210876, -1.5556392], [47.2205384, -1.5551774], [47.220372, -1.5554198], [47.2192285, -1.5569368], [47.218466, -1.5575159], [47.2177308, -1.5576904], [47.2175953, -1.5575156], [47.2173674, -1.5579742], [47.2155435, -1.558366], [47.2150354, -1.558371], [47.2148049, -1.5582289], [47.2146123, -1.5587577], [47.214493, -1.5588765], [47.2143149, -1.5588703], [47.2141855, -1.5586343], [47.2139823, -1.5587194], [47.2138656, -1.558768], [47.2138656, -1.558768], [47.2139823, -1.5587194], [47.2141855, -1.5586343], [47.2143149, -1.5588703], [47.214493, -1.5588765], [47.2146123, -1.5587577], [47.2148049, -1.5582289], [47.214887, -1.5579725], [47.2148938, -1.5575822], [47.2155597, -1.557499], [47.2159756, -1.5575279], [47.2160405, -1.5570888], [47.2160719, -1.5568626], [47.2161023, -1.5566372], [47.2160668, -1.556594], [47.216062, -1.55652], [47.2160888, -1.5564689], [47.2161627, -1.5564754], [47.2162459, -1.5565005], [47.2162987, -1.5563239], [47.2163182, -1.5562613], [47.2166103, -1.5553853], [47.2166552, -1.5552407], [47.2167391, -1.5549874], [47.2168734, -1.5545202], [47.2170421, -1.5545425], [47.2171995, -1.5538062], [47.2173096, -1.5533652], [47.2179026, -1.5534213], [47.2179732, -1.5529077], [47.218073, -1.5520648], [47.2182157, -1.5515226], [47.2182438, -1.5515083], [47.218417, -1.5510646], [47.2185079, -1.5509195], [47.2190188, -1.5503685], [47.2191832, -1.5500264], [47.2195042, -1.5493542], [47.2198147, -1.5496147], [47.2200423, -1.5497925], [47.2204434, -1.5501244], [47.2204434, -1.5501244], [47.220934, -1.5505257], [47.2212358, -1.5507737], [47.2214556, -1.5509554], [47.221637, -1.5511152], [47.2216744, -1.551218], [47.2216325, -1.5513221], [47.2214653, -1.5517728], [47.2213479, -1.5518279], [47.221019, -1.5515593], [47.2209031, -1.5514647], [47.2195467, -1.550357], [47.219292, -1.5501333], [47.2190188, -1.5503685], [47.2185079, -1.5509195], [47.218417, -1.5510646], [47.2183259, -1.5510647], [47.2181201, -1.550995], [47.2180341, -1.5509182], [47.2178395, -1.5507793], [47.2177663, -1.5507359], [47.2168008, -1.5502658], [47.2163767, -1.5505283], [47.2161313, -1.550635], [47.2158726, -1.5506172], [47.2152464, -1.5503023], [47.2164709, -1.5469394], [47.216637, -1.5470663], [47.2169383, -1.5473231], [47.2168447, -1.5475416], [47.2167752, -1.5477259], [47.2167752, -1.5477259], [47.2168447, -1.5475416], [47.2169383, -1.5473231], [47.2171505, -1.5467129], [47.2167226, -1.5464322], [47.2165366, -1.5468755], [47.216637, -1.5470663], [47.2164709, -1.5469394], [47.2152464, -1.5503023], [47.2151655, -1.5505279], [47.2149818, -1.5512832], [47.214797, -1.5521377], [47.2145791, -1.5531581], [47.2147045, -1.5531551], [47.214902, -1.5541819], [47.2147737, -1.5544478], [47.2147737, -1.5544478], [47.2146992, -1.5547583], [47.214458, -1.5554], [47.2147699, -1.5556873], [47.2147364, -1.5557976], [47.2150388, -1.5560491], [47.2150223, -1.5560952], [47.2150106, -1.5561331], [47.2149286, -1.5563563], [47.2148674, -1.5565232], [47.2144755, -1.5576091], [47.2143928, -1.5578413], [47.2143668, -1.5579057], [47.2141806, -1.5584372], [47.2141855, -1.5586343], [47.2139823, -1.5587194], [47.2138656, -1.558768], [47.213595, -1.5588636], [47.2133024, -1.5589669], [47.2132707, -1.5589781], [47.2126378, -1.5591137], [47.2121042, -1.5593879], [47.2117136, -1.559891], [47.2116749, -1.5597488], [47.2113887, -1.5605287], [47.2111879, -1.5603633], [47.2109976, -1.5604866], [47.210882, -1.5603657], [47.2108289, -1.5605005], [47.2105292, -1.5612745], [47.2105292, -1.5612745], [47.2108289, -1.5605005], [47.210882, -1.5603657], [47.2107742, -1.560242], [47.2106689, -1.5601512], [47.2106179, -1.5601071], [47.2105539, -1.5601544], [47.2104866, -1.5600564], [47.2105383, -1.5599477], [47.2106919, -1.5595986], [47.210698, -1.5595018], [47.210765, -1.559475], [47.2112648, -1.5589293], [47.2113092, -1.5587873], [47.2114439, -1.5587814], [47.2114847, -1.5588541], [47.2115747, -1.5588769], [47.2119118, -1.5588963], [47.2120003, -1.5591216], [47.2121042, -1.5593879], [47.2126378, -1.5591137], [47.2132707, -1.5589781], [47.2133024, -1.5589669], [47.213595, -1.5588636], [47.2138656, -1.558768], [47.2139823, -1.5587194], [47.2141855, -1.5586343], [47.2143149, -1.5588703], [47.214493, -1.5588765], [47.2146123, -1.5587577], [47.2148049, -1.5582289], [47.214887, -1.5579725], [47.2148938, -1.5575822], [47.2155597, -1.557499], [47.2159756, -1.5575279], [47.2160405, -1.5570888], [47.2160719, -1.5568626], [47.2161023, -1.5566372], [47.2160668, -1.556594], [47.216062, -1.55652], [47.2160888, -1.5564689], [47.2161627, -1.5564754], [47.2162459, -1.5565005], [47.2162987, -1.5563239], [47.2163182, -1.5562613], [47.2166103, -1.5553853], [47.2166552, -1.5552407], [47.2167391, -1.5549874], [47.2173841, -1.5547654], [47.2176684, -1.5548815], [47.2177545, -1.554897], [47.2177651, -1.5545826], [47.2178056, -1.5542105], [47.2179086, -1.5542302], [47.2179566, -1.5542666], [47.2181433, -1.5539675], [47.2183453, -1.5534755]],\n", + " {"bubblingMouseEvents": true, "color": "red", "dashArray": null, "dashOffset": null, "fill": false, "fillColor": "red", "fillOpacity": 0.2, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "noClip": false, "opacity": 0.5, "smoothFactor": 0, "stroke": true, "weight": 5}\n", + " ).addTo(map_c353624f2ef6701bef2dfdb918ec18f1);\n", + " \n", + "</script>\n", + "</html>\" width=\"1000\" height=\"1000\"style=\"border:none !important;\" \"allowfullscreen\" \"webkitallowfullscreen\" \"mozallowfullscreen\"></iframe>" + ], + "text/plain": [ + "<branca.element.Figure at 0x7fadee233080>" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f = folium.Figure(width=1000, height=1000)\n", + "map = folium.Map(location=(47.213811, -1.553168), zoom_start=15)\n", + "solution_reader.display_points_of_interest(map, data)\n", + "solution_reader.display_route(map, data, solution, 0)\n", + "f.add_child(map)\n", + "f" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/input/json_parser.cpp b/src/input/json_parser.cpp index 31d221c..b5b0094 100644 --- a/src/input/json_parser.cpp +++ b/src/input/json_parser.cpp @@ -4,19 +4,18 @@ #include <filesystem> #include <fstream> #include <nlohmann/json.hpp> +#include <utility> // spdlog repris du code lns-framework mais pas utilisé ici -> TODO namespace fs = std::filesystem; using json = nlohmann::json; - bool checkFilePresence(std::string &filepath) { return fs::is_regular_file(filepath); } - PDPTWData parsing::parseJson(std::string filepath) { if (!checkFilePresence(filepath)) @@ -24,6 +23,7 @@ PDPTWData parsing::parseJson(std::string filepath) spdlog::error("Data file \"{}\" does not exist", filepath); exit(1); } + std::ifstream jsonFile(filepath); if (!jsonFile.is_open()) @@ -35,11 +35,22 @@ PDPTWData parsing::parseJson(std::string filepath) try { + // extract filename (data name) + size_t pos = filepath.find_last_of("/\\"); + std::string filename = (pos != std::string::npos) ? filepath.substr(pos + 1) : filepath; + + size_t dotPos = filename.find_last_of('.'); + if (dotPos != std::string::npos) + { + filename = filename.substr(0, dotPos); + } + + // generate PDPTWData json j; jsonFile >> j; - return json_to_data(j); - } - catch (std::exception const &e)// catching all exceptions to properly log the error and quit gracefully + return json_to_data(filename, j); + + } catch (std::exception const &e)// catching all exceptions to properly log the error and quit gracefully { spdlog::error("Error while parsing the input json:"); spdlog::error("{}", e.what()); @@ -48,46 +59,41 @@ PDPTWData parsing::parseJson(std::string filepath) } } -PDPTWData json_to_data(const json& j) +PDPTWData json_to_data(std::string dataName, json const &j) { - int size = j.at("size").get<int>(); int capacity = j.at("capacity").get<int>(); - + auto depot_json = j.at("depot"); TimeWindow tw(depot_json.at("timeWindow").at(0), depot_json.at("timeWindow").at(1)); - Location depot( - 0, - depot_json.at("longitude"), - depot_json.at("latitude"), - 0, - tw, - depot_json.at("serviceDuration"), - 0, - LocType::DEPOT - ); - + Location depot(0, + depot_json.at("longitude"), + depot_json.at("latitude"), + 0, + tw, + depot_json.at("serviceDuration"), + 0, + LocType::DEPOT); + std::vector<Location> locations; - for (const auto& loc : j.at("locations")) + for (auto const &loc: j.at("locations")) { LocType loc_type = (loc.at("locType") == "PICKUP") ? LocType::PICKUP : LocType::DELIVERY; TimeWindow loc_tw(loc.at("timeWindow").at(0), loc.at("timeWindow").at(1)); - locations.emplace_back( - loc.at("id"), - loc.at("longitude"), - loc.at("latitude"), - loc.at("demand"), - loc_tw, - loc.at("serviceDuration"), - loc.at("pairedLocation"), - loc_type - ); + locations.emplace_back(loc.at("id"), + loc.at("longitude"), + loc.at("latitude"), + loc.at("demand"), + loc_tw, + loc.at("serviceDuration"), + loc.at("pairedLocation"), + loc_type); } Matrix distance_matrix = j.at("distance_matrix").get<Matrix>(); - return {size, capacity, depot, locations, distance_matrix}; + return {std::move(dataName), size, capacity, depot, locations, distance_matrix}; } diff --git a/src/input/json_parser.h b/src/input/json_parser.h index 56acdbd..72de0a8 100644 --- a/src/input/json_parser.h +++ b/src/input/json_parser.h @@ -10,4 +10,4 @@ namespace parsing PDPTWData parseJson(std::string filepath); } -PDPTWData json_to_data(const json& j); \ No newline at end of file +PDPTWData json_to_data(std::string dataName, const json& j); \ No newline at end of file diff --git a/src/input/pdptw_data.cpp b/src/input/pdptw_data.cpp index 5682449..a21d8e6 100644 --- a/src/input/pdptw_data.cpp +++ b/src/input/pdptw_data.cpp @@ -30,6 +30,11 @@ Location const &PDPTWData::getDepot() const return depot; } +std::string PDPTWData::getDataName() const +{ + return dataName; +} + Location const &PDPTWData::getLocation(int id) const { if (id==0) @@ -45,8 +50,8 @@ Matrix const &PDPTWData::getMatrix() const return distanceMatrix; } -PDPTWData::PDPTWData(int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix) - : size(size), capacity(capacity), depot(depot), locations(std::move(locations)), distanceMatrix(std::move(distanceMatrix)) +PDPTWData::PDPTWData(std::string dataName, int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix) + : dataName(dataName), size(size), capacity(capacity), depot(depot), locations(std::move(locations)), distanceMatrix(std::move(distanceMatrix)) { // Associate pair of locations pairs.clear(); @@ -75,6 +80,7 @@ const Pair &PDPTWData::getPair(int id) const void PDPTWData::print() const { + std::cout << "Instance name : " << dataName << "\n"; std::cout << "Instance size: " << size << "\n"; std::cout << "Capacity: " << capacity << "\n"; std::cout << "Depot:\n"; diff --git a/src/input/pdptw_data.h b/src/input/pdptw_data.h index 3cfb829..a0a7ae5 100644 --- a/src/input/pdptw_data.h +++ b/src/input/pdptw_data.h @@ -23,6 +23,7 @@ public: class PDPTWData { + std::string dataName; int size; int capacity; Location depot; @@ -42,7 +43,7 @@ public: * Constructs an empty PDPTWData. * @see parsing::parseJson */ - PDPTWData(int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix); + PDPTWData(std::string dataName, int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix); /** * Checks some data coherence */ @@ -67,6 +68,6 @@ public: int getSize() const; int getCapacity() const; - + std::string getDataName() const; void print() const; }; diff --git a/src/mains/main.cpp b/src/mains/main.cpp index 4f306b8..fb240d1 100644 --- a/src/mains/main.cpp +++ b/src/mains/main.cpp @@ -25,6 +25,7 @@ #include "lns/operators/generators/modification_generator.h" #include "lns/operators/sorting_strategy.h" +#include "output/solution_exporter.h" using json = nlohmann::json; @@ -82,5 +83,7 @@ int main(int argc, char const *argv[]) operatorInstance.reconstructSolution(solution, blinkRate); solution.print(); + + output::exportToJson(solution); return 0; } diff --git a/src/output/solution_exporter.cpp b/src/output/solution_exporter.cpp new file mode 100644 index 0000000..a537cdf --- /dev/null +++ b/src/output/solution_exporter.cpp @@ -0,0 +1,67 @@ +#include "solution_exporter.h" + +std::string getCurrentDate() +{ + std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + char buffer[11];// "DD-MM-YYYY" + null terminator + std::strftime(buffer, sizeof(buffer), "%d-%m-%Y", &tm); + return std::string(buffer); +} + +nlohmann::ordered_json output::getMinimalJson(Solution const &solution) +{ + nlohmann::ordered_json jsonSolution; + + nlohmann::ordered_json jsonRoutes = nlohmann::ordered_json::array(); + int routeID = 0; + for (auto const &route: solution.getRoutes()) + { + jsonRoutes.push_back(routeToJson(routeID, route)); + ++routeID; + } + + jsonSolution["InstanceName"] = solution.getData().getDataName(); + jsonSolution["Authors"] = "..."; + jsonSolution["Date"] = getCurrentDate(); + jsonSolution["Reference"] = "..."; + jsonSolution["routes"] = jsonRoutes; + + return jsonSolution; +} + +nlohmann::ordered_json output::getCompleteJson(Solution const &solution) +{ + nlohmann::ordered_json jsonSolution; + // TO DO + return jsonSolution; +} + +void output::exportToJson(Solution const &solution) +{ + std::string directory = "./../../output"; + std::string filename = directory + "/" + solution.getData().getDataName() + "_sol.json"; + + if (!std::filesystem::exists(directory)) + { + std::filesystem::create_directory(directory); + } + + std::ofstream file(filename); + if (!file) + { + spdlog::error("Error when opening the file {}", filename); + exit(1); + return; + } + + nlohmann::ordered_json jsonData = output::getMinimalJson(solution); + file << jsonData.dump(); + file.close(); + std::cout << "Solution exported" << std::endl; +} + +nlohmann::ordered_json output::routeToJson(int routeID, Route const &route) +{ + return nlohmann::ordered_json{{"routeID", routeID}, {"locationIDs", route.getRoute()}}; +} diff --git a/src/output/solution_exporter.h b/src/output/solution_exporter.h new file mode 100644 index 0000000..62a6f09 --- /dev/null +++ b/src/output/solution_exporter.h @@ -0,0 +1,30 @@ +#pragma once + +#include "lns/solution/solution.h" + +#include <nlohmann/json.hpp> +#include <ctime> +#include <fstream> + + +namespace output +{ + /** + * Get a json representation of a solution with the same info as in the benchmarks results (Li&Lim, Sartori&Buriol) + */ + nlohmann::ordered_json getMinimalJson(Solution const &solution); + + /** + * Get a complete json representation of a solution (heavier) + */ + nlohmann::ordered_json getCompleteJson(Solution const &solution); + + + nlohmann::ordered_json routeToJson(int routeID, const Route& route); + + + void exportToJson(Solution const &solution); + +}// namespace output + +std::string getCurrentDate(); \ No newline at end of file -- GitLab