Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Maritime AI Route Generation & Optimization Platform</title> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #1e3a8a; | |
| --secondary: #3b82f6; | |
| --accent: #60a5fa; | |
| --success: #059669; | |
| --warning: #d97706; | |
| --danger: #dc2626; | |
| --dark: #0f172a; | |
| --light: #f8fafc; | |
| --background: #ffffff; | |
| --text: #1e293b; | |
| --border: #e2e8f0; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background: var(--background); | |
| color: var(--text); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| .container { | |
| max-width: 2000px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 20px; | |
| background: var(--background); | |
| border-radius: 16px; | |
| margin-bottom: 25px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| border: 1px solid var(--border); | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .logo i { | |
| font-size: 2.5rem; | |
| color: var(--primary); | |
| } | |
| .logo-text h1 { | |
| font-size: 2.2rem; | |
| margin-bottom: 5px; | |
| color: var(--primary); | |
| } | |
| .logo-text p { | |
| font-size: 1rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .app-container { | |
| display: grid; | |
| grid-template-columns: 1fr 3fr; | |
| gap: 25px; | |
| height: calc(100vh - 180px); | |
| } | |
| .control-panel { | |
| background: var(--background); | |
| border-radius: 16px; | |
| padding: 25px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| border: 1px solid var(--border); | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .map-container { | |
| background: var(--background); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| border: 1px solid var(--border); | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| #map { | |
| height: 100%; | |
| width: 100%; | |
| background: #e8f4f8; | |
| } | |
| .panel-section { | |
| margin-bottom: 30px; | |
| } | |
| .section-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| padding-bottom: 10px; | |
| border-bottom: 2px solid var(--primary); | |
| font-size: 1.3rem; | |
| color: var(--primary); | |
| } | |
| .section-title i { | |
| color: var(--primary); | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: var(--dark); | |
| } | |
| input, select, button, textarea { | |
| width: 100%; | |
| padding: 14px; | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| font-size: 16px; | |
| background: var(--background); | |
| color: var(--text); | |
| } | |
| input::placeholder { | |
| color: var(--dark); | |
| opacity: 0.6; | |
| } | |
| button { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| border: none; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| font-weight: 600; | |
| margin-top: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(37, 99, 235, 0.3); | |
| } | |
| .btn-secondary { | |
| background: linear-gradient(135deg, var(--warning) 0%, #e58e0c 100%); | |
| } | |
| .btn-danger { | |
| background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%); | |
| } | |
| .btn-success { | |
| background: linear-gradient(135deg, var(--success) 0%, #047857 100%); | |
| } | |
| .model-cards { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 15px; | |
| margin-top: 15px; | |
| } | |
| .model-card { | |
| background: var(--light); | |
| border-radius: 12px; | |
| padding: 15px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| border: 2px solid var(--border); | |
| text-align: center; | |
| } | |
| .model-card:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .model-card.active { | |
| border-color: var(--primary); | |
| background: linear-gradient(135deg, var(--light) 0%, #e0f2fe 100%); | |
| } | |
| .model-name { | |
| font-weight: bold; | |
| margin-bottom: 5px; | |
| color: var(--primary); | |
| } | |
| .model-desc { | |
| font-size: 0.8rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .coordinates-input { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .input-group label { | |
| font-size: 0.9rem; | |
| margin-bottom: 5px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 15px; | |
| margin-top: 20px; | |
| } | |
| .stat-card { | |
| background: var(--light); | |
| border-radius: 12px; | |
| padding: 15px; | |
| text-align: center; | |
| border: 1px solid var(--border); | |
| transition: all 0.3s ease; | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .stat-value { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| margin: 10px 0; | |
| color: var(--primary); | |
| } | |
| .stat-label { | |
| font-size: 0.9rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .route-item { | |
| background: var(--light); | |
| border-radius: 12px; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| border-left: 4px solid var(--primary); | |
| border: 1px solid var(--border); | |
| } | |
| .route-item:hover { | |
| background: var(--background); | |
| transform: translateX(-5px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| } | |
| .route-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| } | |
| .route-name { | |
| font-weight: bold; | |
| font-size: 1.1rem; | |
| color: var(--dark); | |
| } | |
| .route-type { | |
| background: var(--primary); | |
| color: white; | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| font-size: 0.8rem; | |
| } | |
| .route-details { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.9rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .map-overlay { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| right: 20px; | |
| z-index: 1000; | |
| display: flex; | |
| gap: 15px; | |
| } | |
| .search-box { | |
| flex: 1; | |
| background: var(--background); | |
| border-radius: 12px; | |
| padding: 12px 20px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| border: 1px solid var(--border); | |
| } | |
| .search-box input { | |
| background: transparent; | |
| border: none; | |
| flex: 1; | |
| padding: 0; | |
| } | |
| .map-controls { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .control-btn { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 12px; | |
| background: var(--background); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |
| border: 1px solid var(--border); | |
| color: var(--primary); | |
| } | |
| .control-btn:hover { | |
| background: var(--primary); | |
| color: white; | |
| transform: translateY(-3px); | |
| } | |
| .legend { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| background: var(--background); | |
| border-radius: 12px; | |
| padding: 15px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |
| z-index: 1000; | |
| border: 1px solid var(--border); | |
| } | |
| .legend-title { | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| text-align: center; | |
| color: var(--primary); | |
| } | |
| .legend-items { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 0.9rem; | |
| } | |
| .legend-color { | |
| width: 15px; | |
| height: 15px; | |
| border-radius: 50%; | |
| } | |
| .ship-marker { | |
| background: var(--primary); | |
| border: 3px solid white; | |
| border-radius: 50%; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.1); } | |
| 100% { transform: scale(1); } | |
| } | |
| .route-popup { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| min-width: 250px; | |
| } | |
| .popup-title { | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| color: var(--primary); | |
| border-bottom: 1px solid var(--border); | |
| padding-bottom: 5px; | |
| } | |
| .popup-details { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 10px; | |
| } | |
| .popup-detail { | |
| font-size: 0.9rem; | |
| } | |
| .popup-label { | |
| font-weight: bold; | |
| color: var(--primary); | |
| } | |
| /* Scrollbar improvements */ | |
| .control-panel::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .control-panel::-webkit-scrollbar-track { | |
| background: var(--light); | |
| border-radius: 10px; | |
| } | |
| .control-panel::-webkit-scrollbar-thumb { | |
| background: var(--primary); | |
| border-radius: 10px; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 1200px) { | |
| .app-container { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto 1fr; | |
| } | |
| .control-panel { | |
| max-height: 400px; | |
| } | |
| } | |
| .anomaly-indicator { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| padding: 10px; | |
| border-radius: 8px; | |
| margin-top: 10px; | |
| font-weight: bold; | |
| } | |
| .anomaly-low { | |
| background: #fef3c7; | |
| color: #d97706; | |
| border: 1px solid #f59e0b; | |
| } | |
| .anomaly-medium { | |
| background: #fed7aa; | |
| color: #ea580c; | |
| border: 1px solid #f97316; | |
| } | |
| .anomaly-high { | |
| background: #fecaca; | |
| color: #dc2626; | |
| border: 1px solid #ef4444; | |
| } | |
| .context-input { | |
| height: 100px; | |
| resize: vertical; | |
| } | |
| .generation-results { | |
| margin-top: 20px; | |
| padding: 15px; | |
| background: var(--light); | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| } | |
| .performance-metrics { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| .metric { | |
| text-align: center; | |
| padding: 10px; | |
| background: var(--background); | |
| border-radius: 8px; | |
| border: 1px solid var(--border); | |
| } | |
| .metric-value { | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| color: var(--primary); | |
| } | |
| .metric-label { | |
| font-size: 0.8rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .file-upload { | |
| border: 2px dashed var(--border); | |
| border-radius: 12px; | |
| padding: 25px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| margin-bottom: 20px; | |
| } | |
| .file-upload:hover { | |
| border-color: var(--primary); | |
| background: var(--light); | |
| } | |
| .file-upload i { | |
| font-size: 3rem; | |
| margin-bottom: 15px; | |
| color: var(--primary); | |
| } | |
| .tab-container { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .tab { | |
| flex: 1; | |
| padding: 12px; | |
| text-align: center; | |
| background: var(--light); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| font-weight: 600; | |
| } | |
| .tab.active { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .optimization-panel { | |
| background: var(--light); | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-top: 20px; | |
| border: 1px solid var(--border); | |
| } | |
| .model-comparison { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 15px; | |
| margin-top: 15px; | |
| } | |
| .comparison-card { | |
| background: var(--background); | |
| border-radius: 12px; | |
| padding: 15px; | |
| text-align: center; | |
| border: 1px solid var(--border); | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| } | |
| .comparison-card:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .comparison-card.active { | |
| border-color: var(--success); | |
| background: linear-gradient(135deg, var(--light) 0%, #d1fae5 100%); | |
| } | |
| .comparison-value { | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| margin: 10px 0; | |
| color: var(--success); | |
| } | |
| .comparison-label { | |
| font-size: 0.9rem; | |
| color: var(--dark); | |
| opacity: 0.8; | |
| } | |
| .route-controls { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| .route-control { | |
| flex: 1; | |
| padding: 10px; | |
| background: var(--light); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| text-align: center; | |
| transition: all 0.3s ease; | |
| } | |
| .route-control:hover { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| .route-control.active { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <div class="logo"> | |
| <i class="fas fa-ship"></i> | |
| <div class="logo-text"> | |
| <h1>Maritime AI Route Generation & Optimization</h1> | |
| <p>Professional platform for route prediction and optimization</p> | |
| </div> | |
| </div> | |
| <div class="header-controls"> | |
| <button class="btn-secondary" id="exportBtn"><i class="fas fa-download"></i> Export Report</button> | |
| </div> | |
| </header> | |
| <div class="app-container"> | |
| <div class="control-panel"> | |
| <div class="tab-container"> | |
| <div class="tab active" data-tab="input">Input Data</div> | |
| <div class="tab" data-tab="generation">Route Generation</div> | |
| <div class="tab" data-tab="optimization">Route Optimization</div> | |
| </div> | |
| <div class="tab-content active" id="input-tab"> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-database"></i> | |
| <h3>Data Input Methods</h3> | |
| </div> | |
| <div class="file-upload" id="fileUpload"> | |
| <i class="fas fa-file-excel"></i> | |
| <p>Click or drag Excel file here</p> | |
| <p class="file-format">Supports CSV and Excel files</p> | |
| </div> | |
| <input type="file" id="fileInput" accept=".csv,.xlsx,.xls" style="display: none;"> | |
| <div class="form-group"> | |
| <label><i class="fas fa-map-marker-alt"></i> Manual Point Selection</label> | |
| <div class="coordinates-input"> | |
| <div class="input-group"> | |
| <label>Start Latitude</label> | |
| <input type="number" id="startLat" step="0.0001" value="12.6" placeholder="12.6"> | |
| </div> | |
| <div class="input-group"> | |
| <label>Start Longitude</label> | |
| <input type="number" id="startLon" step="0.0001" value="43.0" placeholder="43.0"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label><i class="fas fa-flag-checkered"></i> End Point</label> | |
| <div class="coordinates-input"> | |
| <div class="input-group"> | |
| <label>End Latitude</label> | |
| <input type="number" id="endLat" step="0.0001" value="30.5" placeholder="30.5"> | |
| </div> | |
| <div class="input-group"> | |
| <label>End Longitude</label> | |
| <input type="number" id="endLon" step="0.0001" value="32.3" placeholder="32.3"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="contextInput"><i class="fas fa-code"></i> Context Data</label> | |
| <textarea id="contextInput" class="context-input" placeholder="Enter route context data here...">Historical route from Bab el Mandeb to Suez Canal with AIS data</textarea> | |
| </div> | |
| <button id="saveContextBtn"><i class="fas fa-save"></i> Save Context</button> | |
| </div> | |
| </div> | |
| <div class="tab-content" id="generation-tab"> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-brain"></i> | |
| <h3>AI Model Selection</h3> | |
| </div> | |
| <div class="model-cards"> | |
| <div class="model-card active" data-model="TrAISformer"> | |
| <div class="model-name">TrAISformer</div> | |
| <div class="model-desc">Transformer-based model</div> | |
| </div> | |
| <div class="model-card" data-model="EnhcTrAISformer"> | |
| <div class="model-name">EnhcTrAISformer</div> | |
| <div class="model-desc">Enhanced Transformer</div> | |
| </div> | |
| <div class="model-card" data-model="eDQTI-GRU"> | |
| <div class="model-name">eDQTI-GRU</div> | |
| <div class="model-desc">GRU with DQTI</div> | |
| </div> | |
| <div class="model-card" data-model="eDQTI-LSTM"> | |
| <div class="model-name">eDQTI-LSTM</div> | |
| <div class="model-desc">LSTM with DQTI</div> | |
| </div> | |
| <div class="model-card" data-model="eDQTI-HYPER"> | |
| <div class="model-name">eDQTI-HYPER</div> | |
| <div class="model-desc">Advanced hybrid model</div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label><i class="fas fa-layer-group"></i> Display Mode</label> | |
| <div class="route-controls"> | |
| <div class="route-control active" data-display="single">Single Model</div> | |
| <div class="route-control" data-display="compare">Compare All</div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="anomalyModel"><i class="fas fa-exclamation-triangle"></i> Anomaly Detection</label> | |
| <select id="anomalyModel"> | |
| <option value="auto">Auto Detection</option> | |
| <option value="enabled">Anomaly Detection Enabled</option> | |
| <option value="disabled">No Anomaly Detection</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-route"></i> | |
| <h3>Generation Parameters</h3> | |
| </div> | |
| <div class="form-group"> | |
| <label for="hours"><i class="fas fa-clock"></i> Hours to Generate</label> | |
| <input type="number" id="hours" min="1" max="168" value="24" placeholder="24"> | |
| </div> | |
| <div class="form-group"> | |
| <label for="pointsPerHour"><i class="fas fa-map-pin"></i> Points per Hour</label> | |
| <input type="number" id="pointsPerHour" min="1" max="10" value="2" placeholder="2"> | |
| </div> | |
| <button id="generateBtn" class="btn-success"><i class="fas fa-play"></i> Generate Routes</button> | |
| </div> | |
| </div> | |
| <div class="tab-content" id="optimization-tab"> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-magic"></i> | |
| <h3>Route Optimization</h3> | |
| </div> | |
| <div class="optimization-panel"> | |
| <p>Optimize generated routes using advanced algorithms</p> | |
| <div class="model-comparison"> | |
| <div class="comparison-card" data-model="eDQTI-GRU"> | |
| <div class="model-name">eDQTI-GRU</div> | |
| <div class="comparison-value">92%</div> | |
| <div class="comparison-label">Optimization Score</div> | |
| </div> | |
| <div class="comparison-card" data-model="eDQTI-LSTM"> | |
| <div class="model-name">eDQTI-LSTM</div> | |
| <div class="comparison-value">88%</div> | |
| <div class="comparison-label">Optimization Score</div> | |
| </div> | |
| <div class="comparison-card" data-model="eDQTI-HYPER"> | |
| <div class="model-name">eDQTI-HYPER</div> | |
| <div class="comparison-value">95%</div> | |
| <div class="comparison-label">Optimization Score</div> | |
| </div> | |
| </div> | |
| <button id="optimizeBtn" class="btn-success"><i class="fas fa-bolt"></i> Optimize Selected Routes</button> | |
| </div> | |
| </div> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-chart-bar"></i> | |
| <h3>Optimization Results</h3> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-label">Distance Saved</div> | |
| <div class="stat-value" id="distanceSaved">42</div> | |
| <div class="stat-label">km</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Time Saved</div> | |
| <div class="stat-value" id="timeSaved">3.5</div> | |
| <div class="stat-label">hours</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Fuel Efficiency</div> | |
| <div class="stat-value" id="fuelEfficiency">+12%</div> | |
| <div class="stat-label">improvement</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Safety Score</div> | |
| <div class="stat-value" id="safetyScore">94%</div> | |
| <div class="stat-label">rating</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="panel-section"> | |
| <div class="section-title"> | |
| <i class="fas fa-history"></i> | |
| <h3>Generation History</h3> | |
| </div> | |
| <div id="generationHistory"> | |
| <!-- Will be populated dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="map-container"> | |
| <div id="map"></div> | |
| <div class="map-overlay"> | |
| <div class="search-box"> | |
| <i class="fas fa-search"></i> | |
| <input type="text" placeholder="Search for location or route..."> | |
| </div> | |
| <div class="map-controls"> | |
| <div class="control-btn" id="zoomIn"> | |
| <i class="fas fa-plus"></i> | |
| </div> | |
| <div class="control-btn" id="zoomOut"> | |
| <i class="fas fa-minus"></i> | |
| </div> | |
| <div class="control-btn" id="locateRedSea"> | |
| <i class="fas fa-crosshairs"></i> | |
| </div> | |
| <div class="control-btn" id="clearRoutes"> | |
| <i class="fas fa-trash"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="legend"> | |
| <div class="legend-title">Map Legend</div> | |
| <div class="legend-items"> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #1e3a8a;"></div> | |
| <span>Context Route</span> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #dc2626;"></div> | |
| <span>Generated Route</span> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #3b82f6;"></div> | |
| <span>Optimized Route</span> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #10b981;"></div> | |
| <span>Anomaly Point</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <script> | |
| // Initialize map with clean sea layer | |
| const map = L.map('map', { | |
| zoomControl: false, | |
| attributionControl: false | |
| }).setView([20.0, 38.0], 6); | |
| // Add clean maritime map layer (no country names) | |
| L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', { | |
| maxZoom: 10 | |
| }).addTo(map); | |
| // Add Red Sea specific layer | |
| const redSeaBounds = L.rectangle([[12, 32], [31, 44]], { | |
| color: "#1e3a8a", | |
| fillColor: "#1e3a8a", | |
| fillOpacity: 0.05, | |
| weight: 2, | |
| dashArray: '5, 5' | |
| }).addTo(map); | |
| // Key points | |
| const babMandeb = [12.6, 43.0]; // Bab el Mandeb | |
| const suezCanal = [30.5, 32.3]; // Suez Canal | |
| // Add start and end points | |
| const startMarker = L.marker(babMandeb, { | |
| icon: L.divIcon({ | |
| className: 'ship-marker', | |
| html: '🚢', | |
| iconSize: [30, 30] | |
| }) | |
| }).addTo(map).bindPopup('<div class="route-popup"><div class="popup-title">Bab el Mandeb</div><div>Starting point for Red Sea navigation</div></div>').openPopup(); | |
| const endMarker = L.marker(suezCanal, { | |
| icon: L.divIcon({ | |
| className: 'ship-marker', | |
| html: '⚓', | |
| iconSize: [30, 30] | |
| }) | |
| }).addTo(map).bindPopup('<div class="route-popup"><div class="popup-title">Suez Canal</div><div>End point for Red Sea navigation</div></div>'); | |
| // Variables to store routes and state | |
| let contextRoute = []; | |
| let generatedRoutes = {}; | |
| let optimizedRoutes = {}; | |
| let routeLayers = []; | |
| let currentModel = 'TrAISformer'; | |
| let displayMode = 'single'; | |
| let savedContext = null; | |
| // UI Elements | |
| const tabs = document.querySelectorAll('.tab'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| const modelCards = document.querySelectorAll('.model-card'); | |
| const routeControls = document.querySelectorAll('.route-control'); | |
| const comparisonCards = document.querySelectorAll('.comparison-card'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const optimizeBtn = document.getElementById('optimizeBtn'); | |
| const saveContextBtn = document.getElementById('saveContextBtn'); | |
| const fileUpload = document.getElementById('fileUpload'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const contextInput = document.getElementById('contextInput'); | |
| const startLat = document.getElementById('startLat'); | |
| const startLon = document.getElementById('startLon'); | |
| const endLat = document.getElementById('endLat'); | |
| const endLon = document.getElementById('endLon'); | |
| const hours = document.getElementById('hours'); | |
| const pointsPerHour = document.getElementById('pointsPerHour'); | |
| const generationHistory = document.getElementById('generationHistory'); | |
| const zoomInBtn = document.getElementById('zoomIn'); | |
| const zoomOutBtn = document.getElementById('zoomOut'); | |
| const locateRedSeaBtn = document.getElementById('locateRedSea'); | |
| const clearRoutesBtn = document.getElementById('clearRoutes'); | |
| const distanceSaved = document.getElementById('distanceSaved'); | |
| const timeSaved = document.getElementById('timeSaved'); | |
| const fuelEfficiency = document.getElementById('fuelEfficiency'); | |
| const safetyScore = document.getElementById('safetyScore'); | |
| // Tab switching | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| const tabId = tab.getAttribute('data-tab'); | |
| tabs.forEach(t => t.classList.remove('active')); | |
| tabContents.forEach(tc => tc.classList.remove('active')); | |
| tab.classList.add('active'); | |
| document.getElementById(`${tabId}-tab`).classList.add('active'); | |
| }); | |
| }); | |
| // Model selection handling | |
| modelCards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| if (displayMode === 'single') { | |
| modelCards.forEach(c => c.classList.remove('active')); | |
| card.classList.add('active'); | |
| currentModel = card.getAttribute('data-model'); | |
| } else { | |
| card.classList.toggle('active'); | |
| } | |
| }); | |
| }); | |
| // Display mode handling | |
| routeControls.forEach(control => { | |
| control.addEventListener('click', () => { | |
| routeControls.forEach(c => c.classList.remove('active')); | |
| control.classList.add('active'); | |
| displayMode = control.getAttribute('data-display'); | |
| if (displayMode === 'single') { | |
| // Reset to single model selection | |
| modelCards.forEach(c => c.classList.remove('active')); | |
| modelCards[0].classList.add('active'); | |
| currentModel = modelCards[0].getAttribute('data-model'); | |
| } | |
| }); | |
| }); | |
| // File upload handling | |
| fileUpload.addEventListener('click', () => { | |
| fileInput.click(); | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| // Simulate file processing | |
| alert(`File "${file.name}" loaded successfully!`); | |
| // In a real application, you would process the Excel/CSV file here | |
| } | |
| }); | |
| // Save context | |
| saveContextBtn.addEventListener('click', () => { | |
| const start = [parseFloat(startLat.value), parseFloat(startLon.value)]; | |
| const end = [parseFloat(endLat.value), parseFloat(endLon.value)]; | |
| const context = contextInput.value; | |
| savedContext = { | |
| start, | |
| end, | |
| context, | |
| timestamp: new Date().toLocaleString() | |
| }; | |
| // Update markers on map | |
| startMarker.setLatLng(start); | |
| endMarker.setLatLng(end); | |
| alert('Context saved successfully!'); | |
| }); | |
| // Generate context route from coordinates | |
| function generateContextRoute() { | |
| if (!savedContext) { | |
| alert('Please save context first!'); | |
| return null; | |
| } | |
| const start = savedContext.start; | |
| const end = savedContext.end; | |
| const route = []; | |
| const numPoints = 8; | |
| // Add starting point | |
| route.push([start[0], start[1]]); | |
| // Generate intermediate points | |
| for (let i = 1; i < numPoints - 1; i++) { | |
| const progress = i / (numPoints - 1); | |
| const lat = start[0] + (end[0] - start[0]) * progress + (Math.random() - 0.5) * 0.2; | |
| const lon = start[1] + (end[1] - start[1]) * progress + (Math.random() - 0.5) * 0.3; | |
| route.push([lat, lon]); | |
| } | |
| // Add end point | |
| route.push([end[0], end[1]]); | |
| return route; | |
| } | |
| // Generate route based on selected model | |
| function generateRouteWithModel(contextRoute, model, totalHours, pointsPerHour) { | |
| const totalPoints = totalHours * pointsPerHour; | |
| const route = []; | |
| // Add starting point (same as context start) | |
| route.push([contextRoute[0][0], contextRoute[0][1]]); | |
| // Generate route based on selected model | |
| for (let i = 1; i < totalPoints - 1; i++) { | |
| const progress = i / (totalPoints - 1); | |
| const contextIndex = Math.floor(progress * (contextRoute.length - 1)); | |
| const contextPoint = contextRoute[contextIndex]; | |
| let lat, lon; | |
| // Simulate different model behaviors | |
| switch(model) { | |
| case 'TrAISformer': | |
| lat = contextPoint[0] + (Math.random() - 0.5) * 0.3; | |
| lon = contextPoint[1] + (Math.random() - 0.5) * 0.4; | |
| break; | |
| case 'EnhcTrAISformer': | |
| lat = contextPoint[0] + (Math.random() - 0.5) * 0.2; | |
| lon = contextPoint[1] + (Math.random() - 0.5) * 0.3; | |
| break; | |
| case 'eDQTI-GRU': | |
| lat = contextPoint[0] + (Math.random() - 0.5) * 0.4; | |
| lon = contextPoint[1] + (Math.random() - 0.5) * 0.5; | |
| break; | |
| case 'eDQTI-LSTM': | |
| lat = contextPoint[0] + (Math.random() - 0.5) * 0.5; | |
| lon = contextPoint[1] + (Math.random() - 0.5) * 0.6; | |
| break; | |
| case 'eDQTI-HYPER': | |
| const pattern = Math.sin(progress * Math.PI * 3) * 0.3; | |
| lat = contextPoint[0] + (Math.random() - 0.5) * 0.3 + pattern; | |
| lon = contextPoint[1] + (Math.random() - 0.5) * 0.4 + pattern; | |
| break; | |
| } | |
| route.push([lat, lon]); | |
| } | |
| // Add end point (same as context end) | |
| route.push([contextRoute[contextRoute.length - 1][0], contextRoute[contextRoute.length - 1][1]]); | |
| return route; | |
| } | |
| // Optimize route (simulated) | |
| function optimizeRoute(route, model) { | |
| const optimized = [...route]; | |
| // Simulate optimization by smoothing the route | |
| for (let i = 1; i < optimized.length - 1; i++) { | |
| // Simple smoothing algorithm | |
| optimized[i][0] = (optimized[i-1][0] + optimized[i][0] + optimized[i+1][0]) / 3; | |
| optimized[i][1] = (optimized[i-1][1] + optimized[i][1] + optimized[i+1][1]) / 3; | |
| } | |
| return optimized; | |
| } | |
| // Draw routes on map | |
| function drawRoutes() { | |
| // Clear previous routes | |
| clearRoutes(); | |
| if (!contextRoute) return; | |
| // Draw context route | |
| const contextPolyline = L.polyline(contextRoute, { | |
| color: '#1e3a8a', | |
| weight: 3, | |
| opacity: 0.7, | |
| lineJoin: 'round', | |
| dashArray: '5, 5' | |
| }).addTo(map); | |
| routeLayers.push(contextPolyline); | |
| // Draw generated routes | |
| const modelColors = { | |
| 'TrAISformer': '#dc2626', | |
| 'EnhcTrAISformer': '#ea580c', | |
| 'eDQTI-GRU': '#d97706', | |
| 'eDQTI-LSTM': '#059669', | |
| 'eDQTI-HYPER': '#7c3aed' | |
| }; | |
| Object.keys(generatedRoutes).forEach(model => { | |
| if (displayMode === 'single' && model !== currentModel) return; | |
| const route = generatedRoutes[model]; | |
| const color = modelColors[model] || '#dc2626'; | |
| const polyline = L.polyline(route, { | |
| color: color, | |
| weight: 4, | |
| opacity: 0.8, | |
| lineJoin: 'round' | |
| }).addTo(map); | |
| polyline.bindPopup(`<div class="route-popup"> | |
| <div class="popup-title">${model} Generated Route</div> | |
| <div class="popup-details"> | |
| <div class="popup-detail"><span class="popup-label">Points:</span> ${route.length}</div> | |
| <div class="popup-detail"><span class="popup-label">Model:</span> ${model}</div> | |
| <div class="popup-detail"><span class="popup-label">Status:</span> Generated</div> | |
| </div> | |
| </div>`); | |
| routeLayers.push(polyline); | |
| }); | |
| // Draw optimized routes | |
| Object.keys(optimizedRoutes).forEach(model => { | |
| const route = optimizedRoutes[model]; | |
| const color = '#3b82f6'; | |
| const polyline = L.polyline(route, { | |
| color: color, | |
| weight: 5, | |
| opacity: 0.9, | |
| lineJoin: 'round' | |
| }).addTo(map); | |
| polyline.bindPopup(`<div class="route-popup"> | |
| <div class="popup-title">${model} Optimized Route</div> | |
| <div class="popup-details"> | |
| <div class="popup-detail"><span class="popup-label">Points:</span> ${route.length}</div> | |
| <div class="popup-detail"><span class="popup-label">Model:</span> ${model}</div> | |
| <div class="popup-detail"><span class="popup-label">Status:</span> Optimized</div> | |
| </div> | |
| </div>`); | |
| routeLayers.push(polyline); | |
| }); | |
| // Adjust map bounds to fit routes | |
| const group = L.featureGroup(routeLayers); | |
| map.fitBounds(group.getBounds().pad(0.1)); | |
| } | |
| // Clear all routes | |
| function clearRoutes() { | |
| routeLayers.forEach(layer => map.removeLayer(layer)); | |
| routeLayers = []; | |
| } | |
| // Add to generation history | |
| function addToHistory(model, points, anomalyLevel) { | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'route-item'; | |
| historyItem.innerHTML = ` | |
| <div class="route-header"> | |
| <div class="route-name">${model} - ${new Date().toLocaleString()}</div> | |
| <div class="route-type">${model}</div> | |
| </div> | |
| <div class="route-details"> | |
| <span>Points: ${points}</span> | |
| <span>Anomaly: ${anomalyLevel}%</span> | |
| </div> | |
| `; | |
| historyItem.addEventListener('click', () => { | |
| // Switch to single display and show this route | |
| routeControls.forEach(c => c.classList.remove('active')); | |
| document.querySelector('[data-display="single"]').classList.add('active'); | |
| displayMode = 'single'; | |
| modelCards.forEach(c => c.classList.remove('active')); | |
| document.querySelector(`[data-model="${model}"]`).classList.add('active'); | |
| currentModel = model; | |
| drawRoutes(); | |
| }); | |
| generationHistory.insertBefore(historyItem, generationHistory.firstChild); | |
| // Keep limited items in history | |
| if (generationHistory.children.length > 5) { | |
| generationHistory.removeChild(generationHistory.lastChild); | |
| } | |
| } | |
| // Generate button handler | |
| generateBtn.addEventListener('click', () => { | |
| // Generate context route | |
| contextRoute = generateContextRoute(); | |
| if (!contextRoute) return; | |
| // Clear previous generated routes | |
| generatedRoutes = {}; | |
| // Get selected models | |
| const selectedModels = displayMode === 'single' | |
| ? [currentModel] | |
| : Array.from(document.querySelectorAll('.model-card.active')) | |
| .map(card => card.getAttribute('data-model')); | |
| if (selectedModels.length === 0) { | |
| alert('Please select at least one model!'); | |
| return; | |
| } | |
| // Generate routes for selected models | |
| selectedModels.forEach(model => { | |
| const route = generateRouteWithModel( | |
| contextRoute, | |
| model, | |
| parseInt(hours.value), | |
| parseInt(pointsPerHour.value) | |
| ); | |
| generatedRoutes[model] = route; | |
| // Simulate anomaly detection | |
| const anomalyLevel = (Math.random() * 10).toFixed(1); | |
| // Add to history | |
| addToHistory(model, route.length, anomalyLevel); | |
| }); | |
| // Draw routes | |
| drawRoutes(); | |
| alert(`Successfully generated routes for ${selectedModels.length} model(s)!`); | |
| }); | |
| // Optimize button handler | |
| optimizeBtn.addEventListener('click', () => { | |
| if (Object.keys(generatedRoutes).length === 0) { | |
| alert('Please generate routes first!'); | |
| return; | |
| } | |
| // Get selected models for optimization | |
| const selectedModels = Array.from(document.querySelectorAll('.comparison-card.active')) | |
| .map(card => card.getAttribute('data-model')); | |
| if (selectedModels.length === 0) { | |
| alert('Please select at least one model to optimize!'); | |
| return; | |
| } | |
| // Optimize selected routes | |
| selectedModels.forEach(model => { | |
| if (generatedRoutes[model]) { | |
| optimizedRoutes[model] = optimizeRoute(generatedRoutes[model], model); | |
| } | |
| }); | |
| // Update optimization metrics | |
| distanceSaved.textContent = Math.floor(Math.random() * 100); | |
| timeSaved.textContent = (Math.random() * 5).toFixed(1); | |
| fuelEfficiency.textContent = `+${Math.floor(Math.random() * 20)}%`; | |
| safetyScore.textContent = `${80 + Math.floor(Math.random() * 20)}%`; | |
| // Draw routes | |
| drawRoutes(); | |
| alert(`Successfully optimized ${selectedModels.length} route(s)!`); | |
| }); | |
| // Comparison card selection | |
| comparisonCards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| card.classList.toggle('active'); | |
| }); | |
| }); | |
| // Map control elements | |
| zoomInBtn.addEventListener('click', () => { | |
| map.zoomIn(); | |
| }); | |
| zoomOutBtn.addEventListener('click', () => { | |
| map.zoomOut(); | |
| }); | |
| locateRedSeaBtn.addEventListener('click', () => { | |
| map.setView([20.0, 38.0], 6); | |
| }); | |
| clearRoutesBtn.addEventListener('click', () => { | |
| clearRoutes(); | |
| generatedRoutes = {}; | |
| optimizedRoutes = {}; | |
| }); | |
| // Initialize with example data | |
| window.addEventListener('load', () => { | |
| // Auto-save initial context | |
| setTimeout(() => { | |
| saveContextBtn.click(); | |
| }, 500); | |
| }); | |
| </script> | |
| </body> | |
| </html> |