DroneAgent / waypoint_editor.html
zok213
Here's a summary of the changes:
6740714
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚁 DroneAgent - Waypoint Editor</title>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
display: flex;
height: 100vh;
}
.sidebar {
width: 350px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 20px;
overflow-y: auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}
.map-container {
flex: 1;
position: relative;
}
#map {
height: 100%;
width: 100%;
}
.header {
background: rgba(255, 255, 255, 0.2);
padding: 15px;
border-radius: 10px;
margin-bottom: 20px;
text-align: center;
}
.waypoint-item {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
margin: 8px 0;
border-radius: 8px;
border-left: 4px solid #4CAF50;
cursor: pointer;
transition: all 0.3s ease;
}
.waypoint-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(5px);
}
.waypoint-item.active {
background: rgba(76, 175, 80, 0.3);
border-left-color: #4CAF50;
}
.btn {
background: linear-gradient(45deg, #4CAF50, #45a049);
color: white;
border: none;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
width: 100%;
margin: 8px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
}
.btn:disabled {
background: #666;
cursor: not-allowed;
transform: none;
}
.status {
padding: 10px;
border-radius: 8px;
margin: 10px 0;
font-weight: bold;
}
.status.success {
background: rgba(76, 175, 80, 0.2);
border-left: 4px solid #4CAF50;
}
.status.error {
background: rgba(244, 67, 54, 0.2);
border-left: 4px solid #f44336;
}
.status.info {
background: rgba(33, 150, 243, 0.2);
border-left: 4px solid #2196F3;
}
.coordinate-display {
font-family: 'Courier New', monospace;
background: rgba(0, 0, 0, 0.2);
padding: 8px;
border-radius: 4px;
margin: 5px 0;
font-size: 12px;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="header">
<h2>🚁 Waypoint Editor</h2>
<p>Edit mission waypoints on the map</p>
</div>
<div id="status" class="status info">
Click "Load Mission" to get started
</div>
<button id="loadBtn" class="btn">📥 Load Mission Waypoints</button>
<button id="saveBtn" class="btn" disabled>💾 Confirm Flight with Edited Waypoints</button>
<button id="resetBtn" class="btn" disabled>🔄 Reset Changes</button>
<div id="waypointsList">
<!-- Waypoints will be loaded here -->
</div>
<div id="coordinatesDisplay" style="margin-top: 20px;">
<!-- Coordinate details will be shown here -->
</div>
</div>
<div class="map-container">
<div id="map"></div>
</div>
</div>
<script>
// Initialize map
const map = L.map('map').setView([16.047, 108.206], 15);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Store waypoints and markers
let waypoints = [];
let markers = [];
let originalWaypoints = [];
let selectedWaypointIndex = -1;
// API functions
async function loadWaypoints() {
try {
updateStatus('Loading waypoints...', 'info');
showLoading(true, 'loadBtn');
const response = await fetch('/mission/waypoints');
const data = await response.json();
if (data.status === 'success') {
waypoints = data.waypoints;
originalWaypoints = JSON.parse(JSON.stringify(waypoints)); // Deep copy
displayWaypoints();
updateMapMarkers();
updateStatus(`✅ Loaded ${waypoints.length} waypoints`, 'success');
document.getElementById('saveBtn').disabled = false;
document.getElementById('resetBtn').disabled = false;
} else {
updateStatus(`❌ ${data.message}`, 'error');
}
} catch (error) {
updateStatus(`❌ Error loading waypoints: ${error.message}`, 'error');
} finally {
showLoading(false, 'loadBtn');
}
}
async function confirmFlight() {
try {
updateStatus('Confirming flight...', 'info');
showLoading(true, 'saveBtn');
const confirmData = {
mission_id: `edited_mission_${Date.now()}`,
drone_id: 'drone_001',
waypoints: waypoints,
altitude: 70.0
};
const response = await fetch('/flight/confirm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(confirmData)
});
const data = await response.json();
if (data.status === 'confirmed' || response.ok) {
updateStatus('✅ Flight confirmed with edited waypoints!', 'success');
alert('🎉 Flight confirmed successfully!\n\nYour edited waypoints have been saved and the flight mission is ready.');
} else {
updateStatus(`❌ Confirmation failed: ${data.message}`, 'error');
}
} catch (error) {
updateStatus(`❌ Error confirming flight: ${error.message}`, 'error');
} finally {
showLoading(false, 'saveBtn');
}
}
function resetChanges() {
waypoints = JSON.parse(JSON.stringify(originalWaypoints));
displayWaypoints();
updateMapMarkers();
updateStatus('🔄 Changes reset to original waypoints', 'info');
}
function displayWaypoints() {
const list = document.getElementById('waypointsList');
list.innerHTML = '<h3>📍 Waypoints</h3>';
waypoints.forEach((wp, index) => {
const item = document.createElement('div');
item.className = 'waypoint-item';
item.onclick = () => selectWaypoint(index);
const latDiff = originalWaypoints[index] ?
(wp.lat - originalWaypoints[index].lat).toFixed(6) : '0.000000';
const lngDiff = originalWaypoints[index] ?
(wp.lng - originalWaypoints[index].lng).toFixed(6) : '0.000000';
const hasChanged = Math.abs(latDiff) > 0.000001 || Math.abs(lngDiff) > 0.000001;
item.innerHTML = `
<strong>Waypoint ${index + 1}</strong><br>
<div class="coordinate-display">
Lat: ${wp.lat.toFixed(8)}<br>
Lng: ${wp.lng.toFixed(8)}
</div>
${hasChanged ? '<small style="color: #FFC107;">✏️ Modified</small>' : '<small style="color: #4CAF50;">✅ Original</small>'}
`;
list.appendChild(item);
});
}
function selectWaypoint(index) {
selectedWaypointIndex = index;
// Update UI
document.querySelectorAll('.waypoint-item').forEach((item, i) => {
item.classList.toggle('active', i === index);
});
// Pan map to selected waypoint
if (waypoints[index]) {
map.setView([waypoints[index].lat, waypoints[index].lng], 18);
}
updateCoordinateDisplay();
}
function updateCoordinateDisplay() {
const display = document.getElementById('coordinatesDisplay');
if (selectedWaypointIndex >= 0 && waypoints[selectedWaypointIndex]) {
const wp = waypoints[selectedWaypointIndex];
const original = originalWaypoints[selectedWaypointIndex];
display.innerHTML = `
<h4>🎯 Selected Waypoint ${selectedWaypointIndex + 1}</h4>
<div class="coordinate-display">
<strong>Current:</strong><br>
Latitude: ${wp.lat}<br>
Longitude: ${wp.lng}
</div>
${original ? `
<div class="coordinate-display">
<strong>Original:</strong><br>
Latitude: ${original.lat}<br>
Longitude: ${original.lng}
</div>
<div class="coordinate-display">
<strong>Difference:</strong><br>
Lat: ${(wp.lat - original.lat).toFixed(8)}<br>
Lng: ${(wp.lng - original.lng).toFixed(8)}
</div>
` : ''}
`;
} else {
display.innerHTML = '<p style="color: #999;">Click a waypoint to see details</p>';
}
}
function updateMapMarkers() {
// Clear existing markers
markers.forEach(marker => map.removeLayer(marker));
markers = [];
// Add new markers
waypoints.forEach((wp, index) => {
const marker = L.marker([wp.lat, wp.lng], {
draggable: true,
title: `Waypoint ${index + 1}`
});
// Create custom icon with waypoint number
const customIcon = L.divIcon({
className: 'waypoint-marker',
html: `<div style="
background: #4CAF50;
color: white;
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
border: 2px solid white;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
">${index + 1}</div>`,
iconSize: [30, 30],
iconAnchor: [15, 15]
});
marker.setIcon(customIcon);
// Handle drag events
marker.on('dragend', function(event) {
const newPos = event.target.getLatLng();
waypoints[index] = {
lat: newPos.lat,
lng: newPos.lng
};
displayWaypoints();
updateCoordinateDisplay();
updateStatus('📍 Waypoint moved - click Save to confirm', 'info');
});
marker.addTo(map);
markers.push(marker);
});
// Fit map to show all waypoints
if (waypoints.length > 0) {
const bounds = L.latLngBounds(waypoints.map(wp => [wp.lat, wp.lng]));
map.fitBounds(bounds, { padding: [20, 20] });
}
}
function updateStatus(message, type) {
const status = document.getElementById('status');
status.textContent = message;
status.className = `status ${type}`;
}
function showLoading(show, buttonId) {
const button = document.getElementById(buttonId);
const loading = button.querySelector('.loading');
if (show) {
if (!loading) {
button.innerHTML = '<div class="loading"></div> Loading...';
}
button.disabled = true;
} else {
if (loading) {
button.innerHTML = button.innerHTML.replace('<div class="loading"></div> Loading...', button.textContent);
}
button.disabled = false;
}
}
// Event listeners
document.getElementById('loadBtn').addEventListener('click', loadWaypoints);
document.getElementById('saveBtn').addEventListener('click', confirmFlight);
document.getElementById('resetBtn').addEventListener('click', resetChanges);
// Initialize
updateStatus('🚁 Welcome to Waypoint Editor! Click "Load Mission" to get started.', 'info');
</script>
</body>
</html>