| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <script src="https://unpkg.com/mavlink-mappings@2.0.0/mavlink_ardupilotmega_v2.0.js"></script> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>PV Drone Control - Surveillance des centrales photovoltaïques</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| #map { height: 100%; width: 100%; } |
| .mission-waypoint { |
| background-color: #3b82f6; |
| color: white; |
| border-radius: 50%; |
| width: 24px; |
| height: 24px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: bold; |
| cursor: move; |
| } |
| .mission-path { |
| stroke: #3b82f6; |
| stroke-width: 3; |
| stroke-dasharray: 10, 5; |
| fill: none; |
| } |
| .sidebar { |
| transition: all 0.3s ease; |
| } |
| .sidebar.collapsed { |
| width: 60px !important; |
| } |
| .sidebar.collapsed .sidebar-content { |
| display: none; |
| } |
| .drone-status-indicator { |
| width: 12px; |
| height: 12px; |
| border-radius: 50%; |
| display: inline-block; |
| margin-right: 8px; |
| } |
| .status-connected { |
| background-color: #10B981; |
| box-shadow: 0 0 10px #10B981; |
| } |
| .status-disconnected { |
| background-color: #EF4444; |
| } |
| .status-warning { |
| background-color: #F59E0B; |
| box-shadow: 0 0 10px #F59E0B; |
| } |
| .gauge { |
| position: relative; |
| width: 100%; |
| height: 8px; |
| background-color: #e5e7eb; |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| .gauge-fill { |
| height: 100%; |
| border-radius: 4px; |
| transition: width 0.3s ease; |
| } |
| .battery-high { |
| background-color: #10B981; |
| } |
| .battery-medium { |
| background-color: #F59E0B; |
| } |
| .battery-low { |
| background-color: #EF4444; |
| } |
| .custom-scrollbar::-webkit-scrollbar { |
| width: 6px; |
| } |
| .custom-scrollbar::-webkit-scrollbar-track { |
| background: #f1f1f1; |
| border-radius: 10px; |
| } |
| .custom-scrollbar::-webkit-scrollbar-thumb { |
| background: #cbd5e1; |
| border-radius: 10px; |
| } |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { |
| background: #94a3b8; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 h-screen flex overflow-hidden"> |
| |
| <div class="sidebar bg-white w-64 h-full flex flex-col border-r border-gray-200 shadow-sm relative"> |
| <div class="p-4 border-b border-gray-200 flex items-center justify-between"> |
| <div class="flex items-center"> |
| <i class="fas fa-solar-panel text-blue-500 text-xl mr-2"></i> |
| <h1 class="text-lg font-semibold text-gray-800">PV Drone Control</h1> |
| </div> |
| <button id="toggle-sidebar" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-chevron-left"></i> |
| </button> |
| </div> |
| |
| <div class="sidebar-content flex-1 flex flex-col overflow-hidden"> |
| |
| <div class="p-4 border-b border-gray-200"> |
| <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Statut du drone</h2> |
| <div class="flex items-center mb-2"> |
| <span class="drone-status-indicator status-connected"></span> |
| <span class="text-sm font-medium">DJI Mavic 3 Enterprise</span> |
| </div> |
| <div class="grid grid-cols-2 gap-2 text-xs"> |
| <div> |
| <div class="text-gray-500">Batterie</div> |
| <div class="flex items-center"> |
| <div class="gauge w-full mr-2"> |
| <div class="gauge-fill battery-high" style="width: 78%"></div> |
| </div> |
| <span>78%</span> |
| </div> |
| </div> |
| <div> |
| <div class="text-gray-500">Altitude</div> |
| <div>120 m</div> |
| </div> |
| <div> |
| <div class="text-gray-500">Distance</div> |
| <div>450 m</div> |
| </div> |
| <div> |
| <div class="text-gray-500">Vitesse</div> |
| <div>8 m/s</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="flex-1 overflow-y-auto custom-scrollbar"> |
| <div class="p-4 border-b border-gray-200"> |
| <div class="flex items-center justify-between"> |
| <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider">Missions</h2> |
| <button class="text-blue-500 hover:text-blue-700 text-sm"> |
| <i class="fas fa-plus mr-1"></i> |
| <span>Nouvelle</span> |
| </button> |
| </div> |
| |
| <div class="mt-3 space-y-2"> |
| <div class="p-2 bg-blue-50 rounded-md border border-blue-100 cursor-pointer"> |
| <div class="flex justify-between items-center"> |
| <span class="text-sm font-medium">Centrale PV Nord</span> |
| <span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">En cours</span> |
| </div> |
| <div class="text-xs text-gray-500 mt-1">12/06/2023 - Inspection thermique</div> |
| </div> |
| |
| <div class="p-2 hover:bg-gray-50 rounded-md cursor-pointer"> |
| <div class="flex justify-between items-center"> |
| <span class="text-sm font-medium">Centrale PV Sud</span> |
| <span class="text-xs bg-gray-100 text-gray-800 px-2 py-1 rounded-full">Planifiée</span> |
| </div> |
| <div class="text-xs text-gray-500 mt-1">15/06/2023 - Contrôle visuel</div> |
| </div> |
| |
| <div class="p-2 hover:bg-gray-50 rounded-md cursor-pointer"> |
| <div class="text-sm font-medium">Centrale PV Est</div> |
| <div class="text-xs text-gray-500 mt-1">10/06/2023 - Inspection complète</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="p-4"> |
| <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Centrales PV</h2> |
| <div class="space-y-2"> |
| <div class="flex items-center p-2 hover:bg-gray-50 rounded-md cursor-pointer"> |
| <div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div> |
| <span class="text-sm">Centrale Nord (12 MW)</span> |
| </div> |
| <div class="flex items-center p-2 hover:bg-gray-50 rounded-md cursor-pointer"> |
| <div class="w-3 h-3 bg-yellow-500 rounded-full mr-2"></div> |
| <span class="text-sm">Centrale Sud (8 MW)</span> |
| </div> |
| <div class="flex items-center p-2 hover:bg-gray-50 rounded-md cursor-pointer"> |
| <div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div> |
| <span class="text-sm">Centrale Est (5 MW)</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="p-4 border-t border-gray-200"> |
| <div class="flex items-center"> |
| <div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold mr-2">JD</div> |
| <div class="text-sm"> |
| <div class="font-medium">John Doe</div> |
| <div class="text-gray-500 text-xs">Technicien PV</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="flex-1 flex flex-col overflow-hidden"> |
| |
| <div class="bg-white border-b border-gray-200 p-4 flex items-center justify-between"> |
| <div class="flex items-center space-x-4"> |
| <h2 class="text-lg font-semibold text-gray-800">Mission: Centrale PV Nord</h2> |
| <div class="flex items-center text-sm"> |
| <span class="text-gray-500 mr-2">Progression:</span> |
| <div class="gauge w-32"> |
| <div class="gauge-fill battery-medium" style="width: 45%"></div> |
| </div> |
| <span class="ml-2">45%</span> |
| </div> |
| </div> |
| |
| <div class="flex items-center space-x-3"> |
| <button class="px-3 py-1 bg-blue-50 text-blue-600 rounded-md text-sm hover:bg-blue-100 flex items-center"> |
| <i class="fas fa-play mr-1"></i> |
| <span>Démarrer</span> |
| </button> |
| <button class="px-3 py-1 bg-gray-50 text-gray-600 rounded-md text-sm hover:bg-gray-100 flex items-center"> |
| <i class="fas fa-pause mr-1"></i> |
| <span>Pause</span> |
| </button> |
| <button class="px-3 py-1 bg-red-50 text-red-600 rounded-md text-sm hover:bg-red-100 flex items-center"> |
| <i class="fas fa-stop mr-1"></i> |
| <span>Arrêter</span> |
| </button> |
| <button class="px-3 py-1 bg-green-50 text-green-600 rounded-md text-sm hover:bg-green-100 flex items-center"> |
| <i class="fas fa-upload mr-1"></i> |
| <span>Exporter</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="flex-1 flex overflow-hidden"> |
| |
| <div id="map" class="flex-1"></div> |
| |
| |
| <div class="w-80 bg-white border-l border-gray-200 flex flex-col"> |
| <div class="p-4 border-b border-gray-200"> |
| <h3 class="font-medium text-gray-800 mb-2">Paramètres de mission</h3> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm text-gray-500 mb-1">Nom de la mission</label> |
| <input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm" value="Centrale PV Nord"> |
| </div> |
| |
| <div> |
| <label class="block text-sm text-gray-500 mb-1">Type d'inspection</label> |
| <select class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"> |
| <option>Thermique</option> |
| <option>Visuelle</option> |
| <option>Complète</option> |
| </select> |
| </div> |
| |
| <div class="grid grid-cols-2 gap-3"> |
| <div> |
| <label class="block text-sm text-gray-500 mb-1">Altitude</label> |
| <div class="flex"> |
| <input type="number" class="w-full px-3 py-2 border border-gray-300 rounded-l-md text-sm" value="120"> |
| <span class="bg-gray-100 px-3 py-2 border-t border-b border-r border-gray-300 rounded-r-md text-sm flex items-center">m</span> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm text-gray-500 mb-1">Vitesse</label> |
| <div class="flex"> |
| <input type="number" class="w-full px-3 py-2 border border-gray-300 rounded-l-md text-sm" value="8"> |
| <span class="bg-gray-100 px-3 py-2 border-t border-b border-r border-gray-300 rounded-r-md text-sm flex items-center">m/s</span> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm text-gray-500 mb-1">Chevauchement</label> |
| <div class="flex items-center"> |
| <input type="range" class="w-full" min="0" max="100" value="70"> |
| <span class="ml-2 text-sm w-8">70%</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="p-4 border-b border-gray-200 flex-1 overflow-y-auto custom-scrollbar"> |
| <h3 class="font-medium text-gray-800 mb-2">Points de passage</h3> |
| |
| <div class="space-y-2" id="waypoints-list"> |
| <div class="flex items-center p-2 bg-blue-50 rounded-md"> |
| <div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center mr-2 text-xs">1</div> |
| <div class="text-sm flex-1"> |
| <div>45.1234, 5.6789</div> |
| <div class="text-xs text-gray-500">Alt: 120m</div> |
| </div> |
| <button class="text-gray-400 hover:text-red-500"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="flex items-center p-2 hover:bg-gray-50 rounded-md"> |
| <div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center mr-2 text-xs">2</div> |
| <div class="text-sm flex-1"> |
| <div>45.1245, 5.6801</div> |
| <div class="text-xs text-gray-500">Alt: 120m</div> |
| </div> |
| <button class="text-gray-400 hover:text-red-500"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="flex items-center p-2 hover:bg-gray-50 rounded-md"> |
| <div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center mr-2 text-xs">3</div> |
| <div class="text-sm flex-1"> |
| <div>45.1256, 5.6812</div> |
| <div class="text-xs text-gray-500">Alt: 120m</div> |
| </div> |
| <button class="text-gray-400 hover:text-red-500"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| </div> |
| |
| <div class="mt-4"> |
| <button class="w-full py-2 bg-blue-50 text-blue-600 rounded-md text-sm hover:bg-blue-100 flex items-center justify-center"> |
| <i class="fas fa-plus mr-1"></i> |
| <span>Ajouter un point</span> |
| </button> |
| </div> |
| </div> |
| |
| <div class="p-4"> |
| <h3 class="font-medium text-gray-800 mb-2">Statistiques</h3> |
| |
| <div class="grid grid-cols-2 gap-3 text-sm"> |
| <div class="bg-gray-50 p-2 rounded-md"> |
| <div class="text-gray-500">Distance</div> |
| <div class="font-medium">2.4 km</div> |
| </div> |
| <div class="bg-gray-50 p-2 rounded-md"> |
| <div class="text-gray-500">Durée estimée</div> |
| <div class="font-medium">18 min</div> |
| </div> |
| <div class="bg-gray-50 p-2 rounded-md"> |
| <div class="text-gray-500">Points</div> |
| <div class="font-medium">24</div> |
| </div> |
| <div class="bg-gray-50 p-2 rounded-md"> |
| <div class="text-gray-500">Couverture</div> |
| <div class="font-medium">5.2 ha</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const map = L.map('map').setView([45.1234, 5.6789], 17); |
| |
| |
| L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { |
| maxZoom: 20, |
| subdomains: ['mt0', 'mt1', 'mt2', 'mt3'], |
| attribution: '© Google' |
| }).addTo(map); |
| |
| |
| const pvPlant = L.polygon([ |
| [45.1235, 5.6785], |
| [45.1245, 5.6795], |
| [45.1255, 5.6785], |
| [45.1245, 5.6775] |
| ], { |
| color: '#f59e0b', |
| fillColor: '#fef3c7', |
| fillOpacity: 0.5, |
| weight: 2 |
| }).addTo(map); |
| |
| |
| const waypoints = [ |
| { lat: 45.1234, lng: 5.6789, alt: 120 }, |
| { lat: 45.1245, lng: 5.6801, alt: 120 }, |
| { lat: 45.1256, lng: 5.6812, alt: 120 } |
| ]; |
| |
| const waypointMarkers = []; |
| const waypointCoordinates = []; |
| |
| waypoints.forEach((wp, index) => { |
| const marker = L.marker([wp.lat, wp.lng], { |
| icon: L.divIcon({ |
| html: `<div class="mission-waypoint">${index + 1}</div>`, |
| className: '', |
| iconSize: [24, 24] |
| }), |
| draggable: true |
| }).addTo(map); |
| |
| marker.on('drag', function(e) { |
| const newLatLng = e.target.getLatLng(); |
| waypointCoordinates[index] = [newLatLng.lat, newLatLng.lng]; |
| updatePath(); |
| }); |
| |
| waypointMarkers.push(marker); |
| waypointCoordinates.push([wp.lat, wp.lng]); |
| }); |
| |
| |
| let missionPath = L.polyline(waypointCoordinates, { |
| color: '#3b82f6', |
| weight: 3, |
| dashArray: '10, 5', |
| className: 'mission-path' |
| }).addTo(map); |
| |
| function updatePath() { |
| missionPath.setLatLngs(waypointCoordinates); |
| } |
| |
| |
| const droneMarker = L.marker([45.1238, 5.6795], { |
| icon: L.divIcon({ |
| html: '<i class="fas fa-drone-alt text-blue-600 text-2xl"></i>', |
| className: '', |
| iconSize: [32, 32] |
| }) |
| }).addTo(map); |
| |
| |
| let currentWpIndex = 0; |
| const droneInterval = setInterval(() => { |
| if (currentWpIndex >= waypoints.length - 1) { |
| currentWpIndex = 0; |
| } else { |
| currentWpIndex++; |
| } |
| |
| const nextWp = waypoints[currentWpIndex]; |
| droneMarker.setLatLng([nextWp.lat, nextWp.lng]); |
| }, 3000); |
| |
| |
| document.getElementById('toggle-sidebar').addEventListener('click', function() { |
| document.querySelector('.sidebar').classList.toggle('collapsed'); |
| const icon = this.querySelector('i'); |
| if (icon.classList.contains('fa-chevron-left')) { |
| icon.classList.remove('fa-chevron-left'); |
| icon.classList.add('fa-chevron-right'); |
| } else { |
| icon.classList.remove('fa-chevron-right'); |
| icon.classList.add('fa-chevron-left'); |
| } |
| }); |
| |
| |
| map.on('click', function(e) { |
| const newWaypoint = { |
| lat: e.latlng.lat, |
| lng: e.latlng.lng, |
| alt: 120 |
| }; |
| |
| const index = waypoints.length; |
| |
| const marker = L.marker([newWaypoint.lat, newWaypoint.lng], { |
| icon: L.divIcon({ |
| html: `<div class="mission-waypoint">${index + 1}</div>`, |
| className: '', |
| iconSize: [24, 24] |
| }), |
| draggable: true |
| }).addTo(map); |
| |
| marker.on('drag', function(e) { |
| const newLatLng = e.target.getLatLng(); |
| waypointCoordinates[index] = [newLatLng.lat, newLatLng.lng]; |
| updatePath(); |
| }); |
| |
| waypoints.push(newWaypoint); |
| waypointMarkers.push(marker); |
| waypointCoordinates.push([newWaypoint.lat, newWaypoint.lng]); |
| updatePath(); |
| |
| |
| const waypointItem = document.createElement('div'); |
| waypointItem.className = 'flex items-center p-2 hover:bg-gray-50 rounded-md'; |
| waypointItem.innerHTML = ` |
| <div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center mr-2 text-xs">${index + 1}</div> |
| <div class="text-sm flex-1"> |
| <div>${newWaypoint.lat.toFixed(4)}, ${newWaypoint.lng.toFixed(4)}</div> |
| <div class="text-xs text-gray-500">Alt: ${newWaypoint.alt}m</div> |
| </div> |
| <button class="text-gray-400 hover:text-red-500"> |
| <i class="fas fa-times"></i> |
| </button> |
| `; |
| |
| document.getElementById('waypoints-list').appendChild(waypointItem); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=nkellil/pv-uav" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |