Routenplanung eingebaut
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CUNO // Admin</title>
|
||||
<link rel="stylesheet" href="./app.css?v=20260522104500">
|
||||
<link rel="stylesheet" href="./vendor/leaflet/leaflet.css">
|
||||
<script src="./vendor/roslib.min.js"></script>
|
||||
<script src="./vendor/leaflet/leaflet.js"></script>
|
||||
</head>
|
||||
<body class="page-admin">
|
||||
|
||||
@@ -40,6 +42,7 @@
|
||||
<div class="admin-section-tabs" role="tablist" aria-label="Admin-Bereiche">
|
||||
<button id="admin_tab_system" class="admin-section-tab is_active" role="tab" aria-selected="true" onclick="switchAdminSection('system')">Admin</button>
|
||||
<button id="admin_tab_drive" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('drive')">Geschwindigkeit / Weg</button>
|
||||
<button id="admin_tab_map" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('map')">Karte</button>
|
||||
<button id="admin_tab_motor" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('motor')">Motorentest</button>
|
||||
</div>
|
||||
|
||||
@@ -245,6 +248,115 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="admin_panel_map" class="admin-section-panel" role="tabpanel">
|
||||
<div class="admin-section-grid admin-section-grid_map">
|
||||
<div class="admin-col admin-col_map_main">
|
||||
<div class="panel map-panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">Rover-Karte</span>
|
||||
<span class="p-tag">Live Position</span>
|
||||
</div>
|
||||
<div class="map-toolbar">
|
||||
<button id="map_plan_toggle" class="act-btn subtle is_active" onclick="toggleWaypointPlanning()">Route planen: an</button>
|
||||
<button class="act-btn" onclick="addWaypointAtMapCenter()">Wegpunkt an Mitte setzen</button>
|
||||
<button class="act-btn warm" onclick="centerMapOnRover()">Auf Rover zentrieren</button>
|
||||
<button class="act-btn subtle" onclick="clearPlannedRoute()">Route löschen</button>
|
||||
</div>
|
||||
<div class="copy-text map-copy">
|
||||
Die Karte zeigt den aktuellen GPS-Standort des Rovers. Ein Klick in die Karte setzt sofort einen Wegpunkt, solange <code>Route planen</code> aktiv ist. Die eigentliche Routenausführung binden wir später darauf auf.
|
||||
</div>
|
||||
<div class="map-stage">
|
||||
<div id="rover_map" class="rover-map"></div>
|
||||
<div id="rover_map_fallback" class="rover-map-fallback map-hidden" hidden>
|
||||
Kartenbibliothek nicht geladen. GPS-Daten sind trotzdem unten sichtbar.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-col admin-col_map_side">
|
||||
<div class="panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">GPS</span>
|
||||
<span class="p-tag">Telemetry</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-ok"></span>
|
||||
<span class="sr-key">Status</span>
|
||||
<span id="map_gps_status" class="sr-val">Offline</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Sat</span>
|
||||
<span id="map_gps_satellites" class="sr-val">--</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Breite</span>
|
||||
<span id="map_gps_lat" class="sr-val">--</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Länge</span>
|
||||
<span id="map_gps_lon" class="sr-val">--</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Höhe</span>
|
||||
<span id="map_gps_alt" class="sr-val">--</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Genauigk.</span>
|
||||
<span id="map_gps_accuracy" class="sr-val">--</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Letztes Fix</span>
|
||||
<span id="map_gps_last_fix" class="sr-val">--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">Route</span>
|
||||
<span class="p-tag">Planning Scaffold</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Planung</span>
|
||||
<span id="map_route_mode" class="sr-val">aus</span>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<span class="sr-led led-blue"></span>
|
||||
<span class="sr-key">Wegpunkte</span>
|
||||
<span id="map_waypoint_count" class="sr-val">0</span>
|
||||
</div>
|
||||
<div class="copy-text map-copy">
|
||||
Diese Wegpunkte sind aktuell nur lokal in der Admin-Seite. Sie dienen als Vorbereitung für die spätere echte Routenprogrammierung.
|
||||
</div>
|
||||
<div class="map-route-storage">
|
||||
<label class="map-route-storage-label" for="map_route_name">Name</label>
|
||||
<input id="map_route_name" class="map-route-storage-input" type="text" placeholder="z. B. Hofrunde">
|
||||
<button class="act-btn" onclick="saveCurrentRoute()">Route speichern</button>
|
||||
</div>
|
||||
<div class="map-route-storage map-route-storage_secondary">
|
||||
<label class="map-route-storage-label" for="map_route_select">Gespeichert</label>
|
||||
<select id="map_route_select" class="ctrl-select" onchange="loadSelectedRoute()">
|
||||
<option value="">Route wählen</option>
|
||||
</select>
|
||||
<button class="act-btn subtle" onclick="refreshRouteLibrary()">Neu laden</button>
|
||||
<button class="act-btn warm" onclick="deleteSelectedRoute()">Route löschen</button>
|
||||
</div>
|
||||
<div id="map_waypoint_list" class="map-waypoint-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="admin_panel_motor" class="admin-section-panel motor-admin-tab" role="tabpanel">
|
||||
<div class="admin-section-grid">
|
||||
<div class="admin-col">
|
||||
@@ -365,6 +477,8 @@
|
||||
var ros = null;
|
||||
var roverCommandTopic = null;
|
||||
var motorCommandsTopic = null;
|
||||
var gpsFixTopic = null;
|
||||
var gpsStatusTopic = null;
|
||||
|
||||
var isMotorTestRunning = false;
|
||||
var isContainerActionRunning = false;
|
||||
@@ -412,6 +526,7 @@
|
||||
calibrationPoints: [],
|
||||
savedCalibrationPoints: []
|
||||
};
|
||||
var routeLibrary = [];
|
||||
var driveCalibrationMeasurement = {
|
||||
distanceM: 1.0,
|
||||
activeIndex: null,
|
||||
@@ -423,6 +538,28 @@
|
||||
startThresholdPercent: 2,
|
||||
stopThresholdPercent: 1
|
||||
};
|
||||
var roverMapState = {
|
||||
map: null,
|
||||
mapReady: false,
|
||||
roverMarker: null,
|
||||
roverAccuracyCircle: null,
|
||||
roverTrackLine: null,
|
||||
routeLine: null,
|
||||
waypointMarkers: [],
|
||||
waypointPlanning: true,
|
||||
waypoints: [],
|
||||
trackPoints: [],
|
||||
roverLat: null,
|
||||
roverLon: null,
|
||||
roverAlt: null,
|
||||
accuracyMeters: null,
|
||||
hasFix: false,
|
||||
gpsStatusText: 'Offline',
|
||||
satellites: '--',
|
||||
lastFixAt: null,
|
||||
hasCenteredOnFix: false,
|
||||
suppressMapClickUntil: 0
|
||||
};
|
||||
|
||||
function submitPassword() {
|
||||
var input = document.getElementById('auth_input');
|
||||
@@ -449,22 +586,35 @@
|
||||
activeAdminSection = sectionName;
|
||||
var systemTab = document.getElementById('admin_tab_system');
|
||||
var driveTab = document.getElementById('admin_tab_drive');
|
||||
var mapTab = document.getElementById('admin_tab_map');
|
||||
var motorTab = document.getElementById('admin_tab_motor');
|
||||
var systemPanel = document.getElementById('admin_panel_system');
|
||||
var drivePanel = document.getElementById('admin_panel_drive');
|
||||
var mapPanel = document.getElementById('admin_panel_map');
|
||||
var motorPanel = document.getElementById('admin_panel_motor');
|
||||
var isSystem = sectionName === 'system';
|
||||
var isDrive = sectionName === 'drive';
|
||||
var isMap = sectionName === 'map';
|
||||
var isMotor = sectionName === 'motor';
|
||||
systemTab.classList.toggle('is_active', isSystem);
|
||||
driveTab.classList.toggle('is_active', isDrive);
|
||||
mapTab.classList.toggle('is_active', isMap);
|
||||
motorTab.classList.toggle('is_active', isMotor);
|
||||
systemTab.setAttribute('aria-selected', isSystem ? 'true' : 'false');
|
||||
driveTab.setAttribute('aria-selected', isDrive ? 'true' : 'false');
|
||||
mapTab.setAttribute('aria-selected', isMap ? 'true' : 'false');
|
||||
motorTab.setAttribute('aria-selected', isMotor ? 'true' : 'false');
|
||||
systemPanel.classList.toggle('is_active', isSystem);
|
||||
drivePanel.classList.toggle('is_active', isDrive);
|
||||
mapPanel.classList.toggle('is_active', isMap);
|
||||
motorPanel.classList.toggle('is_active', isMotor);
|
||||
window.location.hash = isSystem ? '' : ('#' + sectionName);
|
||||
if (isMap) {
|
||||
initRoverMap();
|
||||
window.setTimeout(function() {
|
||||
if (roverMapState.map) roverMapState.map.invalidateSize();
|
||||
}, 80);
|
||||
}
|
||||
if (isMotor) fetchMotorTestStatus();
|
||||
}
|
||||
|
||||
@@ -515,6 +665,487 @@
|
||||
});
|
||||
}
|
||||
|
||||
function formatCoordinate(value) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 5);
|
||||
}
|
||||
|
||||
function formatAltitude(value) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 1) + ' m';
|
||||
}
|
||||
|
||||
function formatAccuracy(value) {
|
||||
if (typeof value !== 'number' || isNaN(value) || value <= 0) return '--';
|
||||
return formatNumber(value, 1) + ' m';
|
||||
}
|
||||
|
||||
function formatFixTime(date) {
|
||||
if (!date) return '--';
|
||||
return date.toLocaleTimeString('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function updateMapTelemetryPanel() {
|
||||
setText('map_gps_status', roverMapState.gpsStatusText || 'Offline');
|
||||
setText('map_gps_satellites', roverMapState.satellites);
|
||||
setText('map_gps_lat', formatCoordinate(roverMapState.roverLat));
|
||||
setText('map_gps_lon', formatCoordinate(roverMapState.roverLon));
|
||||
setText('map_gps_alt', formatAltitude(roverMapState.roverAlt));
|
||||
setText('map_gps_accuracy', formatAccuracy(roverMapState.accuracyMeters));
|
||||
setText('map_gps_last_fix', formatFixTime(roverMapState.lastFixAt));
|
||||
setText('map_route_mode', roverMapState.waypointPlanning ? 'aktiv' : 'aus');
|
||||
setText('map_waypoint_count', roverMapState.waypoints.length);
|
||||
}
|
||||
|
||||
function renderRouteLibrary() {
|
||||
var select = document.getElementById('map_route_select');
|
||||
if (!select) return;
|
||||
|
||||
var current = select.value;
|
||||
var options = ['<option value="">Route wählen</option>'];
|
||||
routeLibrary.forEach(function(route) {
|
||||
options.push('<option value="' + route.name.replace(/"/g, '"') + '">' + route.name + '</option>');
|
||||
});
|
||||
select.innerHTML = options.join('');
|
||||
|
||||
var stillExists = routeLibrary.some(function(route) {
|
||||
return route.name === current;
|
||||
});
|
||||
select.value = stillExists ? current : '';
|
||||
}
|
||||
|
||||
function applyWaypointPlanningState() {
|
||||
var button = document.getElementById('map_plan_toggle');
|
||||
if (button) {
|
||||
button.textContent = 'Route planen: ' + (roverMapState.waypointPlanning ? 'an' : 'aus');
|
||||
button.classList.toggle('is_active', roverMapState.waypointPlanning);
|
||||
}
|
||||
|
||||
var mapNode = document.getElementById('rover_map');
|
||||
if (mapNode) {
|
||||
mapNode.classList.toggle('is_planning', roverMapState.waypointPlanning);
|
||||
}
|
||||
|
||||
updateMapTelemetryPanel();
|
||||
}
|
||||
|
||||
function renderWaypointList() {
|
||||
var list = document.getElementById('map_waypoint_list');
|
||||
if (!list) return;
|
||||
|
||||
if (!roverMapState.waypoints.length) {
|
||||
list.innerHTML = '<div class="map-waypoint-empty">Noch keine Wegpunkte gesetzt.</div>';
|
||||
updateMapTelemetryPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
var html = roverMapState.waypoints.map(function(point, index) {
|
||||
return (
|
||||
'<div class="map-waypoint-item">' +
|
||||
'<div class="map-waypoint-index">WP ' + (index + 1) + '</div>' +
|
||||
'<div class="map-waypoint-coords">' +
|
||||
'<strong>' + formatCoordinate(point.lat) + '</strong>' +
|
||||
'<span>' + formatCoordinate(point.lon) + '</span>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
});
|
||||
list.innerHTML = html.join('');
|
||||
updateMapTelemetryPanel();
|
||||
}
|
||||
|
||||
function createWaypointIcon(index) {
|
||||
return L.divIcon({
|
||||
className: 'map-waypoint-icon',
|
||||
html:
|
||||
'<div class="map-waypoint-stack">' +
|
||||
'<div class="map-waypoint-label">WP ' + (index + 1) + '</div>' +
|
||||
'<div class="map-waypoint-dot"></div>' +
|
||||
'</div>',
|
||||
iconSize: [58, 42],
|
||||
iconAnchor: [29, 34]
|
||||
});
|
||||
}
|
||||
|
||||
function buildRouteCoordinates() {
|
||||
var coords = [];
|
||||
if (roverMapState.hasFix && typeof roverMapState.roverLat === 'number' && typeof roverMapState.roverLon === 'number') {
|
||||
coords.push([roverMapState.roverLat, roverMapState.roverLon]);
|
||||
}
|
||||
roverMapState.waypoints.forEach(function(point) {
|
||||
coords.push([point.lat, point.lon]);
|
||||
});
|
||||
return coords;
|
||||
}
|
||||
|
||||
function renderPlannedRoute() {
|
||||
if (!roverMapState.mapReady || !roverMapState.routeLine) return;
|
||||
roverMapState.routeLine.setLatLngs(buildRouteCoordinates());
|
||||
renderWaypointList();
|
||||
}
|
||||
|
||||
function getWaypointInsertIndexFromRouteClick(latlng) {
|
||||
if (!roverMapState.waypoints.length) return 0;
|
||||
|
||||
var clickLat = Number(latlng.lat);
|
||||
var clickLon = Number(latlng.lng);
|
||||
var bestIndex = roverMapState.waypoints.length;
|
||||
var bestDistance = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (var i = 0; i < roverMapState.waypoints.length - 1; i += 1) {
|
||||
var start = roverMapState.waypoints[i];
|
||||
var end = roverMapState.waypoints[i + 1];
|
||||
var ax = Number(start.lon);
|
||||
var ay = Number(start.lat);
|
||||
var bx = Number(end.lon);
|
||||
var by = Number(end.lat);
|
||||
var px = clickLon;
|
||||
var py = clickLat;
|
||||
var abx = bx - ax;
|
||||
var aby = by - ay;
|
||||
var abLen2 = abx * abx + aby * aby;
|
||||
var t = 0;
|
||||
if (abLen2 > 0) {
|
||||
t = ((px - ax) * abx + (py - ay) * aby) / abLen2;
|
||||
t = Math.max(0, Math.min(1, t));
|
||||
}
|
||||
var projX = ax + abx * t;
|
||||
var projY = ay + aby * t;
|
||||
var dx = px - projX;
|
||||
var dy = py - projY;
|
||||
var distance = dx * dx + dy * dy;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
bestIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
function clearWaypointMarkers() {
|
||||
roverMapState.waypointMarkers.forEach(function(marker) {
|
||||
roverMapState.map.removeLayer(marker);
|
||||
});
|
||||
roverMapState.waypointMarkers = [];
|
||||
}
|
||||
|
||||
function buildWaypointPopupContent(index, lat, lon) {
|
||||
return (
|
||||
'<div class="map-waypoint-popup">' +
|
||||
'<div class="map-waypoint-popup-title">Wegpunkt ' + (index + 1) + '</div>' +
|
||||
'<div class="map-waypoint-popup-coords">' + formatCoordinate(lat) + ', ' + formatCoordinate(lon) + '</div>' +
|
||||
'<button class="map-waypoint-delete" onclick="removeWaypoint(' + index + ')">Wegpunkt löschen</button>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
function renderWaypointMarkers() {
|
||||
if (!roverMapState.mapReady) return;
|
||||
clearWaypointMarkers();
|
||||
roverMapState.waypoints.forEach(function(point, index) {
|
||||
var marker = L.marker([point.lat, point.lon], {
|
||||
title: 'Wegpunkt ' + (index + 1),
|
||||
icon: createWaypointIcon(index),
|
||||
draggable: true
|
||||
}).addTo(roverMapState.map);
|
||||
marker.bindPopup(buildWaypointPopupContent(index, point.lat, point.lon));
|
||||
marker.on('drag', function(event) {
|
||||
var latlng = event.target.getLatLng();
|
||||
roverMapState.waypoints[index].lat = Number(latlng.lat);
|
||||
roverMapState.waypoints[index].lon = Number(latlng.lng);
|
||||
renderPlannedRoute();
|
||||
});
|
||||
marker.on('dragend', function(event) {
|
||||
var latlng = event.target.getLatLng();
|
||||
roverMapState.waypoints[index].lat = Number(latlng.lat);
|
||||
roverMapState.waypoints[index].lon = Number(latlng.lng);
|
||||
event.target.setPopupContent(buildWaypointPopupContent(index, latlng.lat, latlng.lng));
|
||||
renderWaypointList();
|
||||
renderPlannedRoute();
|
||||
});
|
||||
roverMapState.waypointMarkers.push(marker);
|
||||
});
|
||||
renderPlannedRoute();
|
||||
}
|
||||
|
||||
function addWaypointFromMap(latlng) {
|
||||
roverMapState.waypoints.push({
|
||||
lat: Number(latlng.lat),
|
||||
lon: Number(latlng.lng)
|
||||
});
|
||||
renderWaypointMarkers();
|
||||
}
|
||||
|
||||
function insertWaypointAtIndex(index, latlng) {
|
||||
var insertIndex = Math.max(0, Math.min(roverMapState.waypoints.length, Number(index)));
|
||||
roverMapState.waypoints.splice(insertIndex, 0, {
|
||||
lat: Number(latlng.lat),
|
||||
lon: Number(latlng.lng)
|
||||
});
|
||||
renderWaypointMarkers();
|
||||
}
|
||||
|
||||
function addWaypointAtMapCenter() {
|
||||
if (!roverMapState.mapReady || !roverMapState.map) return;
|
||||
addWaypointFromMap(roverMapState.map.getCenter());
|
||||
}
|
||||
|
||||
function removeWaypoint(index) {
|
||||
if (index < 0 || index >= roverMapState.waypoints.length) return;
|
||||
roverMapState.waypoints.splice(index, 1);
|
||||
renderWaypointMarkers();
|
||||
}
|
||||
|
||||
async function refreshRouteLibrary() {
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/routes');
|
||||
var data = await response.json();
|
||||
if (!response.ok) throw new Error(data.status || ('status ' + response.status));
|
||||
routeLibrary = Array.isArray(data.library && data.library.routes) ? data.library.routes : [];
|
||||
renderRouteLibrary();
|
||||
} catch (error) {
|
||||
setAdminStatus(error.message || 'Routen konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
function loadSelectedRoute() {
|
||||
var select = document.getElementById('map_route_select');
|
||||
if (!select || !select.value) return;
|
||||
|
||||
var route = routeLibrary.find(function(item) {
|
||||
return item.name === select.value;
|
||||
});
|
||||
if (!route) return;
|
||||
|
||||
roverMapState.waypoints = (route.waypoints || []).map(function(point) {
|
||||
return {
|
||||
lat: Number(point.lat),
|
||||
lon: Number(point.lon)
|
||||
};
|
||||
});
|
||||
var nameInput = document.getElementById('map_route_name');
|
||||
if (nameInput) nameInput.value = route.name;
|
||||
renderWaypointMarkers();
|
||||
setAdminStatus('Route geladen');
|
||||
}
|
||||
|
||||
async function saveCurrentRoute() {
|
||||
var nameInput = document.getElementById('map_route_name');
|
||||
var routeName = nameInput ? String(nameInput.value || '').trim() : '';
|
||||
if (!routeName) {
|
||||
alert('Bitte einen Routennamen eingeben.');
|
||||
return;
|
||||
}
|
||||
if (!roverMapState.waypoints.length) {
|
||||
alert('Die Route hat noch keine Wegpunkte.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/routes/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
name: routeName,
|
||||
waypoints: roverMapState.waypoints
|
||||
})
|
||||
});
|
||||
var data = await response.json();
|
||||
if (!response.ok) throw new Error(data.status || ('status ' + response.status));
|
||||
routeLibrary = Array.isArray(data.library && data.library.routes) ? data.library.routes : routeLibrary;
|
||||
renderRouteLibrary();
|
||||
var select = document.getElementById('map_route_select');
|
||||
if (select) select.value = routeName;
|
||||
setAdminStatus(data.status || 'Route gespeichert');
|
||||
} catch (error) {
|
||||
alert(error.message || 'Die Route konnte nicht gespeichert werden.');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSelectedRoute() {
|
||||
var select = document.getElementById('map_route_select');
|
||||
if (!select || !select.value) {
|
||||
alert('Bitte zuerst eine gespeicherte Route auswählen.');
|
||||
return;
|
||||
}
|
||||
if (!window.confirm('Diese gespeicherte Route wirklich löschen?')) return;
|
||||
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/routes/delete', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name: select.value})
|
||||
});
|
||||
var data = await response.json();
|
||||
if (!response.ok) throw new Error(data.status || ('status ' + response.status));
|
||||
routeLibrary = Array.isArray(data.library && data.library.routes) ? data.library.routes : [];
|
||||
renderRouteLibrary();
|
||||
setAdminStatus(data.status || 'Route gelöscht');
|
||||
} catch (error) {
|
||||
alert(error.message || 'Die Route konnte nicht gelöscht werden.');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWaypointPlanning() {
|
||||
roverMapState.waypointPlanning = !roverMapState.waypointPlanning;
|
||||
applyWaypointPlanningState();
|
||||
}
|
||||
|
||||
function clearPlannedRoute() {
|
||||
roverMapState.waypoints = [];
|
||||
renderWaypointMarkers();
|
||||
}
|
||||
|
||||
function centerMapOnRover() {
|
||||
if (!roverMapState.mapReady || !roverMapState.hasFix) return;
|
||||
roverMapState.map.setView([roverMapState.roverLat, roverMapState.roverLon], Math.max(roverMapState.map.getZoom(), 17));
|
||||
}
|
||||
|
||||
function initRoverMap() {
|
||||
if (roverMapState.mapReady) return;
|
||||
|
||||
var fallback = document.getElementById('rover_map_fallback');
|
||||
var mapNode = document.getElementById('rover_map');
|
||||
if (!mapNode) return;
|
||||
|
||||
if (typeof L === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
roverMapState.map = L.map('rover_map', {
|
||||
zoomControl: true,
|
||||
attributionControl: true,
|
||||
maxZoom: 22
|
||||
}).setView([50.2984, 11.0480], 16);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 22,
|
||||
maxNativeZoom: 19,
|
||||
attribution: '© OpenStreetMap'
|
||||
}).addTo(roverMapState.map);
|
||||
|
||||
roverMapState.roverTrackLine = L.polyline([], {
|
||||
color: '#00ff91',
|
||||
weight: 3,
|
||||
opacity: 0.7,
|
||||
interactive: false
|
||||
}).addTo(roverMapState.map);
|
||||
|
||||
roverMapState.routeLine = L.polyline([], {
|
||||
color: '#0088FF',
|
||||
weight: 3,
|
||||
opacity: 0.85,
|
||||
interactive: true
|
||||
}).addTo(roverMapState.map);
|
||||
roverMapState.routeLine.on('click', function(event) {
|
||||
roverMapState.suppressMapClickUntil = Date.now() + 250;
|
||||
if (event.originalEvent) {
|
||||
if (typeof event.originalEvent.preventDefault === 'function') event.originalEvent.preventDefault();
|
||||
if (typeof event.originalEvent.stopPropagation === 'function') event.originalEvent.stopPropagation();
|
||||
}
|
||||
if (!roverMapState.waypointPlanning) return;
|
||||
if (roverMapState.waypoints.length < 2) {
|
||||
addWaypointFromMap(event.latlng);
|
||||
return;
|
||||
}
|
||||
insertWaypointAtIndex(getWaypointInsertIndexFromRouteClick(event.latlng), event.latlng);
|
||||
});
|
||||
|
||||
roverMapState.roverAccuracyCircle = L.circle([50.2984, 11.0480], {
|
||||
radius: 0,
|
||||
color: '#00ff91',
|
||||
weight: 1,
|
||||
opacity: 0.7,
|
||||
fillColor: '#00ff91',
|
||||
fillOpacity: 0.1,
|
||||
interactive: false
|
||||
}).addTo(roverMapState.map);
|
||||
|
||||
roverMapState.roverMarker = L.circleMarker([50.2984, 11.0480], {
|
||||
radius: 7,
|
||||
color: '#05111d',
|
||||
weight: 2,
|
||||
fillColor: '#FF8C00',
|
||||
fillOpacity: 1,
|
||||
interactive: false
|
||||
}).addTo(roverMapState.map);
|
||||
roverMapState.map.on('click', function(event) {
|
||||
if (Date.now() < roverMapState.suppressMapClickUntil) return;
|
||||
if (!roverMapState.waypointPlanning) return;
|
||||
addWaypointFromMap(event.latlng);
|
||||
});
|
||||
|
||||
roverMapState.mapReady = true;
|
||||
applyWaypointPlanningState();
|
||||
renderWaypointMarkers();
|
||||
updateMapTelemetryPanel();
|
||||
}
|
||||
|
||||
function updateRoverPositionOnMap() {
|
||||
if (!roverMapState.mapReady || !roverMapState.hasFix) return;
|
||||
|
||||
var latlng = [roverMapState.roverLat, roverMapState.roverLon];
|
||||
roverMapState.roverMarker.setLatLng(latlng);
|
||||
roverMapState.roverMarker.bindPopup(
|
||||
'Rover<br>' +
|
||||
formatCoordinate(roverMapState.roverLat) + ', ' + formatCoordinate(roverMapState.roverLon)
|
||||
);
|
||||
|
||||
var accuracy = roverMapState.accuracyMeters || 0;
|
||||
roverMapState.roverAccuracyCircle.setLatLng(latlng);
|
||||
roverMapState.roverAccuracyCircle.setRadius(accuracy);
|
||||
|
||||
roverMapState.trackPoints.push(latlng);
|
||||
if (roverMapState.trackPoints.length > 60) roverMapState.trackPoints.shift();
|
||||
roverMapState.roverTrackLine.setLatLngs(roverMapState.trackPoints);
|
||||
|
||||
if (!roverMapState.hasCenteredOnFix) {
|
||||
roverMapState.map.setView(latlng, 17);
|
||||
roverMapState.hasCenteredOnFix = true;
|
||||
}
|
||||
|
||||
renderPlannedRoute();
|
||||
}
|
||||
|
||||
function updateGpsStatusSummary(text) {
|
||||
roverMapState.gpsStatusText = text || 'Offline';
|
||||
var satMatch = String(roverMapState.gpsStatusText).match(/(\d+)\s*Sat/i);
|
||||
roverMapState.satellites = satMatch ? satMatch[1] : '--';
|
||||
updateMapTelemetryPanel();
|
||||
}
|
||||
|
||||
function handleGpsFix(message) {
|
||||
if (!message || !message.status) return;
|
||||
|
||||
roverMapState.hasFix = Number(message.status.status) >= 0;
|
||||
if (!roverMapState.hasFix) {
|
||||
roverMapState.roverLat = null;
|
||||
roverMapState.roverLon = null;
|
||||
roverMapState.roverAlt = null;
|
||||
roverMapState.accuracyMeters = null;
|
||||
updateMapTelemetryPanel();
|
||||
renderPlannedRoute();
|
||||
return;
|
||||
}
|
||||
|
||||
roverMapState.roverLat = Number(message.latitude);
|
||||
roverMapState.roverLon = Number(message.longitude);
|
||||
roverMapState.roverAlt = Number(message.altitude);
|
||||
roverMapState.lastFixAt = new Date();
|
||||
|
||||
var covariance = Array.isArray(message.position_covariance) ? Number(message.position_covariance[0]) : 0;
|
||||
roverMapState.accuracyMeters = covariance > 0 ? Math.sqrt(covariance) : null;
|
||||
|
||||
updateMapTelemetryPanel();
|
||||
initRoverMap();
|
||||
updateRoverPositionOnMap();
|
||||
}
|
||||
|
||||
function formatSpeedCms(speedMps, digits) {
|
||||
return formatNumber(Number(speedMps || 0) * 100, digits);
|
||||
}
|
||||
@@ -974,6 +1605,22 @@
|
||||
driveEstimator.motorSpeeds = Array.isArray(message.motor_speeds) ? message.motor_speeds.map(Number) : [0, 0, 0, 0, 0, 0];
|
||||
renderDriveEstimator();
|
||||
});
|
||||
|
||||
gpsFixTopic = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/fix',
|
||||
messageType: 'sensor_msgs/NavSatFix'
|
||||
});
|
||||
gpsFixTopic.subscribe(handleGpsFix);
|
||||
|
||||
gpsStatusTopic = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/gps/status_text',
|
||||
messageType: 'std_msgs/String'
|
||||
});
|
||||
gpsStatusTopic.subscribe(function(message) {
|
||||
updateGpsStatusSummary(message.data);
|
||||
});
|
||||
}
|
||||
|
||||
function temperatureToWidth(value) {
|
||||
@@ -1477,6 +2124,8 @@
|
||||
switchAdminSection('motor');
|
||||
} else if (window.location.hash === '#drive') {
|
||||
switchAdminSection('drive');
|
||||
} else if (window.location.hash === '#map') {
|
||||
switchAdminSection('map');
|
||||
}
|
||||
updateClock();
|
||||
window.setInterval(updateClock, 1000);
|
||||
@@ -1486,7 +2135,10 @@
|
||||
fetchMotorTestStatus();
|
||||
fetchDriveEstimatorConfig();
|
||||
loadDriveEstimatorCalibration();
|
||||
refreshRouteLibrary();
|
||||
connectDriveEstimatorRos();
|
||||
renderWaypointList();
|
||||
applyWaypointPlanningState();
|
||||
renderDriveEstimator();
|
||||
window.setInterval(fetchStatus, 10000);
|
||||
window.setInterval(fetchDelay, 5000);
|
||||
|
||||
Reference in New Issue
Block a user