blackopsrepl's picture
Upload 36 files
08e15f1 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Vehicle Routing - SolverForge for Python</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vis-timeline@7.7.2/styles/vis-timeline-graph2d.min.css"
integrity="sha256-svzNasPg1yR5gvEaRei2jg+n4Pc3sVyMUWeS6xRAh6U=" crossorigin="anonymous">
<link rel="stylesheet" href="/webjars/solverforge/css/solverforge-webui.css"/>
<link rel="icon" href="/webjars/solverforge/img/solverforge-favicon.svg" type="image/svg+xml">
<style>
/* Customer marker icons */
.customer-marker, .vehicle-home-marker, .temp-vehicle-marker {
background: transparent !important;
border: none !important;
}
/* Pulse animation for new vehicle placement */
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 2px 4px rgba(0,0,0,0.4); }
50% { transform: scale(1.1); box-shadow: 0 4px 8px rgba(99, 102, 241, 0.6); }
100% { transform: scale(1); box-shadow: 0 2px 4px rgba(0,0,0,0.4); }
}
/* Customer type buttons in modal */
.customer-type-btn {
transition: all 0.2s ease;
padding: 0.75rem 0.5rem;
}
.customer-type-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Vehicle table styling */
#vehicles tr.vehicle-row {
transition: background-color 0.2s ease;
}
#vehicles tr.vehicle-row:hover {
background-color: rgba(99, 102, 241, 0.1);
}
#vehicles tr.vehicle-row.table-active {
background-color: rgba(99, 102, 241, 0.15) !important;
}
/* Route number markers */
.route-number-marker {
background: transparent !important;
border: none !important;
}
/* Notification panel */
#notificationPanel {
z-index: 1050;
}
/* Click hint for vehicle rows */
#vehicles tr.vehicle-row td:not(:last-child) {
cursor: pointer;
}
/* Solving spinner */
#solvingSpinner {
display: none;
width: 1.25rem;
height: 1.25rem;
border: 2px solid #10b981;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.75s linear infinite;
vertical-align: middle;
}
#solvingSpinner.active {
display: inline-block;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Progress bar text should stay horizontal and inside */
.progress-bar {
overflow: visible;
white-space: nowrap;
}
/* Map hint overlay */
.map-hint {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 255, 255, 0.95);
padding: 8px 16px;
border-radius: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
font-size: 0.9rem;
color: #374151;
z-index: 1000;
pointer-events: none;
transition: opacity 0.3s ease;
}
.map-hint i {
color: #10b981;
margin-right: 6px;
}
.map-hint.hidden {
opacity: 0;
}
/* Timeline stop badges */
.timeline-stop-badge {
background-color: #6366f1;
color: white;
padding: 1px 6px;
border-radius: 10px;
font-size: 0.7rem;
font-weight: bold;
margin-right: 4px;
}
.timeline-status-icon {
margin-left: 4px;
font-size: 0.85rem;
}
.timeline-status-ontime { color: #10b981; }
.timeline-status-late { color: #ef4444; }
.timeline-status-early { color: #3b82f6; }
.vis-item .vis-item-content {
font-size: 0.85rem;
padding: 2px 4px;
}
.vis-labelset .vis-label {
padding: 4px 8px;
}
/* Loading overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
transition: opacity 0.3s ease;
}
.loading-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.loading-content {
text-align: center;
padding: 2rem;
}
.loading-spinner {
width: 60px;
height: 60px;
border: 4px solid #e5e7eb;
border-top-color: #10b981;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1.5rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Real Roads toggle styling */
#realRoadRouting:checked {
background-color: #10b981;
border-color: #10b981;
}
</style>
</head>
<body>
<header id="solverforge-auto-header">
<!-- Filled in by app.js -->
</header>
<div class="tab-content">
<div id="demo" class="tab-pane fade show active container-fluid">
<div class="sticky-top d-flex justify-content-center align-items-center">
<div id="notificationPanel" style="position: absolute; top: .5rem;"></div>
</div>
<h1>Vehicle routing with capacity and time windows</h1>
<p>Generate optimal route plan of a vehicle fleet with limited vehicle capacity and time windows.</p>
<div class="container-fluid mb-2">
<div class="row justify-content-start">
<div class="col-9">
<ul class="nav nav-pills col" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="mapTab" data-bs-toggle="tab" data-bs-target="#mapPanel"
type="button"
role="tab" aria-controls="mapPanel" aria-selected="false">Map
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="byVehicleTab" data-bs-toggle="tab" data-bs-target="#byVehiclePanel"
type="button" role="tab" aria-controls="byVehiclePanel" aria-selected="false">By vehicle
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="byVisitTab" data-bs-toggle="tab" data-bs-target="#byVisitPanel"
type="button" role="tab" aria-controls="byVisitPanel" aria-selected="false">By visit
</button>
</li>
</ul>
</div>
<div class="col-3">
<button id="solveButton" type="button" class="btn btn-success">
<i class="fas fa-play"></i> Solve
</button>
<button id="stopSolvingButton" type="button" class="btn btn-danger p-2">
<i class="fas fa-stop"></i> Stop solving
</button>
<span id="solvingSpinner" class="ms-2"></span>
<span id="score" class="score ms-2 align-middle fw-bold">Score: ?</span>
<button id="analyzeButton" type="button" class="ms-2 btn btn-secondary">
<span class="fas fa-question"></span>
</button>
</div>
</div>
</div>
<div class="tab-content">
<div class="tab-pane fade show active" id="mapPanel" role="tabpanel" aria-labelledby="mapTab">
<div class="row">
<div class="col-7 col-lg-8 col-xl-9 position-relative">
<div id="map" style="width: 100%; height: 100vh;"></div>
<div id="mapHint" class="map-hint">
<i class="fas fa-mouse-pointer"></i> Click on the map to add a new visit
</div>
</div>
<div class="col-5 col-lg-4 col-xl-3" style="height: 100vh; overflow-y: scroll;">
<div class="row pt-2 row-cols-1">
<div class="col">
<h5>
Solution summary
</h5>
<table class="table">
<tr>
<td>Total driving time:</td>
<td><span id="drivingTime">unknown</span></td>
</tr>
</table>
</div>
<div class="col mb-3">
<h5>Time Windows</h5>
<div class="d-flex flex-column gap-1">
<div><i class="fas fa-utensils" style="color: #f59e0b; width: 20px;"></i> <strong>Restaurant</strong> <small class="text-muted">06:00-10:00 · 20-40 min</small></div>
<div><i class="fas fa-building" style="color: #3b82f6; width: 20px;"></i> <strong>Business</strong> <small class="text-muted">09:00-17:00 · 15-30 min</small></div>
<div><i class="fas fa-home" style="color: #10b981; width: 20px;"></i> <strong>Residential</strong> <small class="text-muted">17:00-20:00 · 5-10 min</small></div>
</div>
</div>
<div class="col">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<h5 class="mb-0">Vehicles</h5>
<small class="text-muted"><i class="fas fa-hand-pointer"></i> Click to highlight route</small>
</div>
<div class="btn-group btn-group-sm" role="group" aria-label="Vehicle management">
<button type="button" class="btn btn-outline-danger" id="removeVehicleBtn" title="Remove last vehicle">
<i class="fas fa-minus"></i>
</button>
<button type="button" class="btn btn-outline-success" id="addVehicleBtn" title="Add new vehicle">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<table class="table-sm w-100">
<thead>
<tr>
<th class="col-1"></th>
<th class="col-3">Name</th>
<th class="col-3">
Cargo
<i class="fas fa-info-circle" data-bs-toggle="tooltip" data-bs-placement="top"
data-html="true"
title="Units to deliver on this route. Each customer requires cargo units (e.g., packages, crates). Bar shows current load vs. vehicle capacity."></i>
</th>
<th class="col-2">Drive</th>
<th class="col-1"></th>
</tr>
</thead>
<tbody id="vehicles"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="byVehiclePanel" role="tabpanel" aria-labelledby="byVehicleTab">
</div>
<div class="tab-pane fade" id="byVisitPanel" role="tabpanel" aria-labelledby="byVisitTab">
</div>
</div>
</div>
<div id="rest" class="tab-pane fade container-fluid">
<h1>REST API Guide</h1>
<h2>Vehicle routing with vehicle capacity and time windows - integration via cURL</h2>
<h3>1. Download demo data</h3>
<pre>
<button class="btn btn-outline-dark btn-sm float-end"
onclick="copyTextToClipboard('curl1')">Copy</button>
<code id="curl1">curl -X GET -H 'Accept:application/json' http://localhost:8080/demo-data/FIRENZE -o sample.json</code>
</pre>
<h3>2. Post the sample data for solving</h3>
<p>The POST operation returns a <code>jobId</code> that should be used in subsequent commands.</p>
<pre>
<button class="btn btn-outline-dark btn-sm float-end"
onclick="copyTextToClipboard('curl2')">Copy</button>
<code id="curl2">curl -X POST -H 'Content-Type:application/json' http://localhost:8080/route-plans -d@sample.json</code>
</pre>
<h3>3. Get the current status and score</h3>
<pre>
<button class="btn btn-outline-dark btn-sm float-end"
onclick="copyTextToClipboard('curl3')">Copy</button>
<code id="curl3">curl -X GET -H 'Accept:application/json' http://localhost:8080/route-plans/{jobId}/status</code>
</pre>
<h3>4. Get the complete route plan</h3>
<pre>
<button class="btn btn-outline-dark btn-sm float-end"
onclick="copyTextToClipboard('curl4')">Copy</button>
<code id="curl4">curl -X GET -H 'Accept:application/json' http://localhost:8080/route-plans/{jobId}</code>
</pre>
<h3>5. Terminate solving early</h3>
<pre>
<button class="btn btn-outline-dark btn-sm float-end"
onclick="copyTextToClipboard('curl5')">Copy</button>
<code id="curl5">curl -X DELETE -H 'Accept:application/json' http://localhost:8080/route-plans/{jobId}</code>
</pre>
</div>
<div id="openapi" class="tab-pane fade container-fluid">
<h1>REST API Reference</h1>
<div class="ratio ratio-1x1">
<!-- "scrolling" attribute is obsolete, but e.g. Chrome does not support "overflow:hidden" -->
<iframe src="/q/swagger-ui" style="overflow:hidden;" scrolling="no"></iframe>
</div>
</div>
</div>
<div class="modal fadebd-example-modal-lg" id="scoreAnalysisModal" tabindex="-1" aria-labelledby="scoreAnalysisModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="scoreAnalysisModalLabel">Score analysis <span id="scoreAnalysisScoreLabel"></span></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="scoreAnalysisModalContent">
<!-- Filled in by app.js -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<form id='visitForm' class='needs-validation' novalidate>
<div class="modal fadebd-example-modal-lg" id="newVisitModal" tabindex="-1"
aria-labelledby="newVisitModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="newVisitModalLabel">Add New Visit</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body" id="newVisitModalContent">
<!-- Filled in by app.js -->
</div>
<div class="modal-footer" id="newVisitModalFooter">
</div>
</div>
</div>
</div>
</form>
<!-- Add Vehicle Modal -->
<div class="modal fade" id="addVehicleModal" tabindex="-1" aria-labelledby="addVehicleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addVehicleModalLabel"><i class="fas fa-truck"></i> Add New Vehicle</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="vehicleName" class="form-label">Name</label>
<input type="text" class="form-control" id="vehicleName" placeholder="e.g., Kilo">
<div class="form-text">Unique name for the vehicle</div>
</div>
<div class="mb-3">
<label for="vehicleCapacity" class="form-label">Capacity</label>
<input type="number" class="form-control" id="vehicleCapacity" value="25" min="1">
<div class="form-text">Maximum cargo the vehicle can carry</div>
</div>
<div class="mb-3">
<label for="vehicleDepartureTime" class="form-label">Departure Time</label>
<input type="text" class="form-control" id="vehicleDepartureTime">
</div>
<div class="mb-3">
<label class="form-label">Home Location</label>
<div class="d-flex gap-2 mb-2">
<button type="button" class="btn btn-outline-primary btn-sm" id="pickLocationBtn">
<i class="fas fa-map-marker-alt"></i> Pick on Map
</button>
<span class="text-muted small align-self-center">or enter coordinates:</span>
</div>
<div class="row g-2">
<div class="col-6">
<input type="number" step="any" class="form-control" id="vehicleHomeLat" placeholder="Latitude">
</div>
<div class="col-6">
<input type="number" step="any" class="form-control" id="vehicleHomeLng" placeholder="Longitude">
</div>
</div>
<div id="vehicleLocationPreview" class="mt-2 text-muted small"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" id="confirmAddVehicle"><i class="fas fa-plus"></i> Add Vehicle</button>
</div>
</div>
</div>
</div>
<!-- Loading/Progress Overlay -->
<div id="loadingOverlay" class="loading-overlay hidden">
<div class="loading-content">
<div class="loading-spinner"></div>
<h5 id="loadingTitle">Loading Demo Data</h5>
<p id="loadingMessage" class="text-muted mb-2">Initializing...</p>
<div class="progress" style="width: 300px; height: 8px;">
<div id="loadingProgress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
<small id="loadingDetail" class="text-muted mt-2 d-block"></small>
</div>
</div>
<footer id="solverforge-auto-footer"></footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-joda/1.11.0/js-joda.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vis-timeline@7.7.2/standalone/umd/vis-timeline-graph2d.min.js"
integrity="sha256-Jy2+UO7rZ2Dgik50z3XrrNpnc5+2PAx9MhL2CicodME=" crossorigin="anonymous"></script>
<script src="/webjars/solverforge/js/solverforge-webui.js"></script>
<script src="/score-analysis.js"></script>
<script src="/recommended-fit.js"></script>
<script src="/app.js"></script>
</body>
</html>