diff --git a/ExoMy_Software-master/gui/admin.css b/ExoMy_Software-master/gui/admin.css index 963b49f..7ad6c6d 100644 --- a/ExoMy_Software-master/gui/admin.css +++ b/ExoMy_Software-master/gui/admin.css @@ -172,6 +172,152 @@ header { .ctrl-select:focus { border-color:rgba(0,255,145,.5); } .ctrl-status { font-size:7px; letter-spacing:1.5px; color:rgba(0,255,145,.45); white-space:nowrap; min-width:50px; text-align:right; } +.act-btn.is_active { + background: rgba(0,136,255,.16); + color: #55aaff; + border-color: rgba(0,136,255,.55); + box-shadow: 0 0 14px rgba(0,136,255,.18); +} + +.admin-section-grid_map { + grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.85fr); +} + +.admin-col_map_main, +.admin-col_map_side { + min-width: 0; +} + +.map-panel { + display: flex; + flex-direction: column; + min-height: 620px; +} + +.map-toolbar { + display: flex; + gap: 8px; + padding: 10px 12px 0; + flex-wrap: wrap; +} + +.map-copy { + padding: 10px 12px; +} + +.map-stage { + position: relative; + flex: 1; + min-height: 460px; + margin: 0 12px 12px; + border: 1px solid rgba(0,255,145,.12); + background: + radial-gradient(circle at 20% 20%, rgba(0,136,255,.12), transparent 34%), + radial-gradient(circle at 78% 24%, rgba(0,255,145,.08), transparent 28%), + #04101a; + overflow: hidden; +} + +.rover-map, +.rover-map-fallback { + position: absolute; + inset: 0; +} + +.rover-map-fallback { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 20px; + color: rgba(0,255,145,.65); + font-size: 12px; + letter-spacing: .6px; + background: rgba(4, 16, 26, .92); +} + +.map-waypoint-list { + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px 12px 12px; +} + +.map-waypoint-empty { + padding: 12px; + border: 1px dashed rgba(0,255,145,.14); + color: rgba(0,255,145,.42); + font-size: 8px; + letter-spacing: 1px; + text-transform: uppercase; + text-align: center; +} + +.map-waypoint-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px; + border: 1px solid rgba(0,255,145,.1); + background: rgba(0,255,145,.03); +} + +.map-waypoint-index { + min-width: 42px; + font-size: 8px; + color: rgba(0,255,145,.5); + letter-spacing: 1.4px; + text-transform: uppercase; +} + +.map-waypoint-coords { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + +.map-waypoint-coords strong, +.map-waypoint-coords span { + font-size: 10px; + letter-spacing: .5px; + color: rgba(0,255,145,.82); +} + +.leaflet-container { + font-family: var(--mono); + background: #04101a; +} + +.leaflet-control-attribution { + background: rgba(4, 16, 26, .82); + color: rgba(0,255,145,.58); +} + +.leaflet-control-attribution a { + color: rgba(85,170,255,.85); +} + +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: #07111c; + color: rgba(0,255,145,.82); +} + +@media (max-width: 1024px) { + .admin-section-grid_map { + grid-template-columns: 1fr; + } + + .map-panel { + min-height: 0; + } + + .map-stage { + min-height: 360px; + } +} + /* ── AUTH OVERLAY ───────────────────────────────── */ .auth-overlay { position:fixed; inset:0; background:rgba(5,8,15,.93); diff --git a/ExoMy_Software-master/gui/admin.html b/ExoMy_Software-master/gui/admin.html index 378bb6f..8feeb6b 100644 --- a/ExoMy_Software-master/gui/admin.html +++ b/ExoMy_Software-master/gui/admin.html @@ -5,7 +5,9 @@ CUNO // Admin + + @@ -40,6 +42,7 @@
+
@@ -245,6 +248,115 @@ +
+
+
+
+
+
+ Rover-Karte + Live Position +
+
+ + + + +
+
+ Die Karte zeigt den aktuellen GPS-Standort des Rovers. Ein Klick in die Karte setzt sofort einen Wegpunkt, solange Route planen aktiv ist. Die eigentliche Routenausführung binden wir später darauf auf. +
+
+
+ +
+
+
+ +
+
+
+
+ GPS + Telemetry +
+
+ + Status + Offline +
+
+ + Sat + -- +
+
+ + Breite + -- +
+
+ + Länge + -- +
+
+ + Höhe + -- +
+
+ + Genauigk. + -- +
+
+ + Letztes Fix + -- +
+
+ +
+
+
+ Route + Planning Scaffold +
+
+ + Planung + aus +
+
+ + Wegpunkte + 0 +
+
+ Diese Wegpunkte sind aktuell nur lokal in der Admin-Seite. Sie dienen als Vorbereitung für die spätere echte Routenprogrammierung. +
+
+ + + +
+
+ + + + +
+
+
+
+
+
+
@@ -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 = ['']; + routeLibrary.forEach(function(route) { + options.push(''); + }); + 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 = '
Noch keine Wegpunkte gesetzt.
'; + updateMapTelemetryPanel(); + return; + } + + var html = roverMapState.waypoints.map(function(point, index) { + return ( + '
' + + '
WP ' + (index + 1) + '
' + + '
' + + '' + formatCoordinate(point.lat) + '' + + '' + formatCoordinate(point.lon) + '' + + '
' + + '
' + ); + }); + list.innerHTML = html.join(''); + updateMapTelemetryPanel(); + } + + function createWaypointIcon(index) { + return L.divIcon({ + className: 'map-waypoint-icon', + html: + '
' + + '
WP ' + (index + 1) + '
' + + '
' + + '
', + 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 ( + '
' + + '
Wegpunkt ' + (index + 1) + '
' + + '
' + formatCoordinate(lat) + ', ' + formatCoordinate(lon) + '
' + + '' + + '
' + ); + } + + 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
' + + 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); diff --git a/ExoMy_Software-master/gui/app.css b/ExoMy_Software-master/gui/app.css index 1ee71b2..53639f4 100644 --- a/ExoMy_Software-master/gui/app.css +++ b/ExoMy_Software-master/gui/app.css @@ -1073,6 +1073,12 @@ body.page-admin .act-btn.danger:hover{ background:rgba(255,51,51,.12); color:#FF body.page-admin .act-btn.full{ grid-column:span 2; } body.page-admin .act-btn.subtle{ border-color:rgba(0,255,145,.1); color:rgba(0,255,145,.3); } body.page-admin .act-btn.subtle:hover{ color:rgba(0,255,145,.6); border-color:rgba(0,255,145,.35); } +body.page-admin .act-btn.is_active{ + background:rgba(0,136,255,.16); + color:#55aaff; + border-color:rgba(0,136,255,.55); + box-shadow:0 0 14px rgba(0,136,255,.18); +} /* ── LINK CARD ──────────────────────────────────── */ body.page-admin .link-card{ display:flex; align-items:center; gap:12px; padding:10px 12px; } @@ -1177,6 +1183,217 @@ body.page-admin .drive-calibration-actions{ body.page-admin .drive-calibration-button{ width:100%; } +body.page-admin .admin-section-grid_map{ + grid-template-columns:minmax(0, 1.7fr) minmax(280px, .85fr); +} +body.page-admin .admin-col_map_main, +body.page-admin .admin-col_map_side{ min-width:0; } +body.page-admin .map-panel{ + display:flex; + flex-direction:column; + min-height:620px; +} +body.page-admin .map-toolbar{ + display:flex; + gap:8px; + padding:10px 12px 0; + flex-wrap:wrap; +} +body.page-admin .map-copy{ padding:10px 12px; } +body.page-admin .map-route-storage{ + display:grid; + grid-template-columns:auto 1fr auto; + gap:8px; + align-items:center; + padding:0 12px 10px; +} +body.page-admin .map-route-storage_secondary{ + grid-template-columns:auto 1fr auto auto; +} +body.page-admin .map-route-storage-label{ + font-size:9px; + letter-spacing:1.4px; + color:rgba(210,255,235,.9); + text-transform:uppercase; +} +body.page-admin .map-route-storage-input{ + font-family:var(--mono); + font-size:10px; + letter-spacing:1px; + color:rgba(245,255,250,.96); + background:#050A14; + border:1px solid rgba(120,255,205,.36); + padding:7px 10px; + outline:none; + min-width:0; +} +body.page-admin .map-route-storage-input:focus{ + border-color:rgba(0,255,145,.5); +} +body.page-admin .map-stage{ + position:relative; + flex:1; + min-height:460px; + margin:0 12px 12px; + border:1px solid rgba(0,255,145,.12); + background: + radial-gradient(circle at 20% 20%, rgba(0,136,255,.12), transparent 34%), + radial-gradient(circle at 78% 24%, rgba(0,255,145,.08), transparent 28%), + #04101a; + overflow:hidden; +} +body.page-admin .rover-map, +body.page-admin .rover-map-fallback{ + position:absolute; + inset:0; +} +body.page-admin .rover-map.is_planning{ + cursor: crosshair; +} +body.page-admin .rover-map-fallback{ + display:flex; + align-items:center; + justify-content:center; + text-align:center; + padding:20px; + color:rgba(205,255,235,.86); + font-size:12px; + letter-spacing:.6px; + background:rgba(4,16,26,.92); +} +body.page-admin .map-hidden{ + display:none !important; +} +body.page-admin .map-waypoint-list{ + display:flex; + flex-direction:column; + gap:8px; + padding:10px 12px 12px; +} +body.page-admin .map-waypoint-empty{ + padding:12px; + border:1px dashed rgba(0,255,145,.14); + color:rgba(180,255,225,.66); + font-size:8px; + letter-spacing:1px; + text-transform:uppercase; + text-align:center; +} +body.page-admin .map-waypoint-item{ + display:flex; + align-items:center; + gap:10px; + padding:10px; + border:1px solid rgba(0,255,145,.1); + background:rgba(0,255,145,.03); +} +body.page-admin .map-waypoint-index{ + min-width:42px; + font-size:8px; + color:rgba(180,255,225,.72); + letter-spacing:1.4px; + text-transform:uppercase; +} +body.page-admin .map-waypoint-coords{ + display:flex; + flex-direction:column; + gap:3px; + min-width:0; +} +body.page-admin .map-waypoint-coords strong, +body.page-admin .map-waypoint-coords span{ + font-size:10px; + letter-spacing:.5px; + color:rgba(245,255,250,.98); +} +body.page-admin .map-waypoint-icon{ + background:transparent; + border:none; +} +body.page-admin .map-waypoint-stack{ + display:flex; + flex-direction:column; + align-items:center; + gap:6px; +} +body.page-admin .map-waypoint-label{ + display:flex; + align-items:center; + justify-content:center; + min-width:46px; + height:24px; + padding:0 8px; + border-radius:999px; + border:1px solid rgba(0,136,255,.55); + background:rgba(3,19,31,.94); + color:#dff7ff; + font-size:10px; + font-weight:700; + letter-spacing:.6px; + box-shadow:0 0 14px rgba(0,136,255,.16); + white-space:nowrap; + user-select:none; +} +body.page-admin .map-waypoint-dot{ + width:14px; + height:14px; + border-radius:50%; + border:2px solid #03131f; + background:#0088FF; + box-shadow:0 0 14px rgba(0,136,255,.24); + cursor:grab; + user-select:none; +} +body.page-admin .map-waypoint-label:active, +body.page-admin .map-waypoint-dot:active{ + cursor:grabbing; +} +body.page-admin .map-waypoint-popup{ + display:flex; + flex-direction:column; + gap:8px; + min-width:160px; +} +body.page-admin .map-waypoint-popup-title{ + font-size:11px; + font-weight:700; + color:#eefcff; + letter-spacing:.5px; +} +body.page-admin .map-waypoint-popup-coords{ + font-size:10px; + color:rgba(205,255,235,.86); + letter-spacing:.4px; +} +body.page-admin .map-waypoint-delete{ + font-family:var(--mono); + font-size:10px; + font-weight:700; + letter-spacing:.5px; + border:1px solid rgba(255,51,51,.45); + background:rgba(255,51,51,.08); + color:#ffd7d7; + padding:8px 10px; + cursor:pointer; +} +body.page-admin .map-waypoint-delete:hover{ + background:rgba(255,51,51,.16); + color:#fff2f2; +} +body.page-admin .leaflet-container{ + font-family:var(--mono); + background:#04101a; +} +body.page-admin .leaflet-control-attribution{ + background:rgba(4,16,26,.82); + color:rgba(180,255,225,.72); +} +body.page-admin .leaflet-control-attribution a{ color:rgba(85,170,255,.9); } +body.page-admin .leaflet-popup-content-wrapper, +body.page-admin .leaflet-popup-tip{ + background:#07111c; + color:rgba(235,255,245,.96); +} @media (max-width:479px){ body.page-admin .drive-estimator-toolbar{ flex-direction:column; align-items:stretch; } body.page-admin .drive-estimator-input{ grid-template-columns:1fr; } @@ -1184,6 +1401,11 @@ body.page-admin .drive-calibration-button{ body.page-admin .drive-estimator-value{ font-size:28px; } body.page-admin .drive-calibration-toolbar{ flex-direction:column; align-items:stretch; } body.page-admin .drive-calibration-grid{ grid-template-columns:1fr; } + body.page-admin .admin-section-grid_map{ grid-template-columns:1fr; } + body.page-admin .map-panel{ min-height:0; } + body.page-admin .map-stage{ min-height:360px; } + body.page-admin .map-route-storage, + body.page-admin .map-route-storage_secondary{ grid-template-columns:1fr; } } /* ── AUTH OVERLAY ───────────────────────────────── */ diff --git a/ExoMy_Software-master/gui/vendor/leaflet/leaflet.css b/ExoMy_Software-master/gui/vendor/leaflet/leaflet.css new file mode 100644 index 0000000..9ade8dc --- /dev/null +++ b/ExoMy_Software-master/gui/vendor/leaflet/leaflet.css @@ -0,0 +1,661 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg { + max-width: none !important; + max-height: none !important; + } +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + width: auto; + padding: 0; + } + +.leaflet-container img.leaflet-tile { + /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ + mix-blend-mode: plus-lighter; +} + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +svg.leaflet-zoom-animated { + will-change: transform; +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline-offset: 1px; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; + font-size: 12px; + font-size: 0.75rem; + line-height: 1.5; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover, +.leaflet-bar a:focus { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + font-size: 13px; + font-size: 1.08333em; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.8); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + line-height: 1.4; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover, +.leaflet-control-attribution a:focus { + text-decoration: underline; + } +.leaflet-attribution-flag { + display: inline !important; + vertical-align: baseline !important; + width: 1em; + height: 0.6669em; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + white-space: nowrap; + -moz-box-sizing: border-box; + box-sizing: border-box; + background: rgba(255, 255, 255, 0.8); + text-shadow: 1px 1px #fff; + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 24px 13px 20px; + line-height: 1.3; + font-size: 13px; + font-size: 1.08333em; + min-height: 1px; + } +.leaflet-popup-content p { + margin: 17px 0; + margin: 1.3em 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-top: -1px; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + pointer-events: auto; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + border: none; + text-align: center; + width: 24px; + height: 24px; + font: 16px/24px Tahoma, Verdana, sans-serif; + color: #757575; + text-decoration: none; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover, +.leaflet-container a.leaflet-popup-close-button:focus { + color: #585858; + } +.leaflet-popup-scrolled { + overflow: auto; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-interactive { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } + +/* Printing */ + +@media print { + /* Prevent printers from removing background-images of controls. */ + .leaflet-control { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + } diff --git a/ExoMy_Software-master/gui/vendor/leaflet/leaflet.js b/ExoMy_Software-master/gui/vendor/leaflet/leaflet.js new file mode 100644 index 0000000..a3bf693 --- /dev/null +++ b/ExoMy_Software-master/gui/vendor/leaflet/leaflet.js @@ -0,0 +1,6 @@ +/* @preserve + * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com + * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var e,i,n=1,o=arguments.length;n=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=_(t);var e=this.min,i=this.max,n=t.min,t=t.max,o=t.x>=e.x&&n.x<=i.x,t=t.y>=e.y&&n.y<=i.y;return o&&t},overlaps:function(t){t=_(t);var e=this.min,i=this.max,n=t.min,t=t.max,o=t.x>e.x&&n.xe.y&&n.y=n.lat&&i.lat<=o.lat&&e.lng>=n.lng&&i.lng<=o.lng},intersects:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=e.lat&&n.lat<=i.lat,t=t.lng>=e.lng&&n.lng<=i.lng;return o&&t},overlaps:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>e.lat&&n.late.lng&&n.lng","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var b={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:bt,safari:Pt,phantom:Lt,opera12:o,win:Tt,ie3d:Mt,webkit3d:zt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:Et,msPointer:kt,pointer:Ot,touch:Bt,touchNative:At,mobileOpera:It,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),e=(t.innerHTML='',t.firstChild);return e.style.behavior="url(#default#VML)",e&&"object"==typeof e.adj}catch(t){return!1}}(),inlineSvg:Wt,mac:0===navigator.platform.indexOf("Mac"),linux:0===navigator.platform.indexOf("Linux")},Ft=b.msPointer?"MSPointerDown":"pointerdown",Ut=b.msPointer?"MSPointerMove":"pointermove",Vt=b.msPointer?"MSPointerUp":"pointerup",qt=b.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,e){e.MSPOINTER_TYPE_TOUCH&&e.pointerType===e.MSPOINTER_TYPE_TOUCH&&O(e);ee(t,e)},touchmove:ee,touchend:ee,touchcancel:ee},Yt={},Xt=!1;function Jt(t,e,i){return"touchstart"!==e||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,te,!0),document.addEventListener(qt,te,!0),Xt=!0),Kt[e]?(i=Kt[e].bind(this,i),t.addEventListener(Gt[e],i,!1),i):(console.warn("wrong event specified:",e),u)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function te(t){delete Yt[t.pointerId]}function ee(t,e){if(e.pointerType!==(e.MSPOINTER_TYPE_MOUSE||"mouse")){for(var i in e.touches=[],Yt)e.touches.push(Yt[i]);e.changedTouches=[e],t(e)}}var ie=200;function ne(t,i){t.addEventListener("dblclick",i);var n,o=0;function e(t){var e;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((e=Ne(t)).some(function(t){return t instanceof HTMLLabelElement&&t.attributes.for})&&!e.some(function(t){return t instanceof HTMLInputElement||t instanceof HTMLSelectElement})||((e=Date.now())-o<=ie?2===++n&&i(function(t){var e,i,n={};for(i in t)e=t[i],n[i]=e&&e.bind?e.bind(t):e;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=e))}return t.addEventListener("click",e),{dblclick:i,simDblclick:e}}var oe,se,re,ae,he,le,ue=we(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ce=we(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),de="webkitTransition"===ce||"OTransition"===ce?ce+"End":"transitionend";function _e(t){return"string"==typeof t?document.getElementById(t):t}function pe(t,e){var i=t.style[e]||t.currentStyle&&t.currentStyle[e];return"auto"===(i=i&&"auto"!==i||!document.defaultView?i:(t=document.defaultView.getComputedStyle(t,null))?t[e]:null)?null:i}function P(t,e,i){t=document.createElement(t);return t.className=e||"",i&&i.appendChild(t),t}function T(t){var e=t.parentNode;e&&e.removeChild(t)}function me(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fe(t){var e=t.parentNode;e&&e.lastChild!==t&&e.appendChild(t)}function ge(t){var e=t.parentNode;e&&e.firstChild!==t&&e.insertBefore(t,e.firstChild)}function ve(t,e){return void 0!==t.classList?t.classList.contains(e):0<(t=xe(t)).length&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(t)}function M(t,e){var i;if(void 0!==t.classList)for(var n=F(e),o=0,s=n.length;othis.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var i=this.getCenter(),t=this._limitCenter(i,this._zoom,g(t));return i.equals(t)||this.panTo(t,e),this._enforcingBounds=!1,this},panInside:function(t,e){var i=m((e=e||{}).paddingTopLeft||e.padding||[0,0]),n=m(e.paddingBottomRight||e.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),i=_([s.min.add(i),s.max.subtract(n)]),s=i.getSize();return i.contains(t)||(this._enforcingBounds=!0,n=t.subtract(i.getCenter()),i=i.extend(t).getSize().subtract(s),o.x+=n.x<0?-i.x:i.x,o.y+=n.y<0?-i.y:i.y,this.panTo(this.unproject(o),e),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var e=this.getSize(),i=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=e.divideBy(2).round(),o=i.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){var e,i;return t=this._locateOptions=l({timeout:1e4,watch:!1},t),"geolocation"in navigator?(e=a(this._handleGeolocationResponse,this),i=a(this._handleGeolocationError,this),t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t)):this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e;this._container._leaflet_id&&(e=t.code,t=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var e,i,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(e=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(e,s.maxZoom):e)),{latlng:n,bounds:o,timestamp:t.timestamp});for(i in t.coords)"number"==typeof t.coords[i]&&(r[i]=t.coords[i]);this.fire("locationfound",r)}},addHandler:function(t,e){return e&&(e=this[t]=new e(this),this._handlers.push(e),this.options[t]&&e.enable()),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,e){e=P("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),e||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter.clone():this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=g(t),i=m(i||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),i=this.getSize().subtract(i),t=_(this.project(t,n),this.project(r,n)).getSize(),r=b.any3d?this.options.zoomSnap:1,a=i.x/t.x,i=i.y/t.y,t=e?Math.max(a,i):Math.min(a,i),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=e?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){t=this._getTopLeftPoint(t,e);return new f(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var i=this.options.crs;return e=void 0===e?this._zoom:e,i.scale(t)/i.scale(e)},getScaleZoom:function(t,e){var i=this.options.crs,t=(e=void 0===e?this._zoom:e,i.zoom(t*i.scale(e)));return isNaN(t)?1/0:t},project:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.latLngToPoint(w(t),e)},unproject:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.pointToLatLng(m(t),e)},layerPointToLatLng:function(t){t=m(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,e){return this.options.crs.distance(w(t),w(e))},containerPointToLayerPoint:function(t){return m(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return m(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(m(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return De(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_e(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,e=(this._fadeAnimated=this.options.fadeAnimation&&b.any3d,M(t,"leaflet-container"+(b.touch?" leaflet-touch":"")+(b.retina?" leaflet-retina":"")+(b.ielt9?" leaflet-oldie":"")+(b.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pe(t,"position"));"absolute"!==e&&"relative"!==e&&"fixed"!==e&&"sticky"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(M(t.markerPane,"leaflet-zoom-hide"),M(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e,i){Z(this._mapPane,new p(0,0));var n=!this._loaded,o=(this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset"),this._zoom!==e);this._moveStart(o,i)._move(t,e)._moveEnd(o),this.fire("viewreset"),n&&this.fire("load")},_moveStart:function(t,e){return t&&this.fire("zoomstart"),e||this.fire("movestart"),this},_move:function(t,e,i,n){void 0===e&&(e=this._zoom);var o=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?i&&i.pinch&&this.fire("zoom",i):((o||i&&i.pinch)&&this.fire("zoom",i),this.fire("move",i)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var e=t?k:S;e((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&e(window,"resize",this._onResize,this),b.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,n=[],o="mouseout"===e||"mouseover"===e,s=t.target||t.srcElement,r=!1;s;){if((i=this._targets[h(s)])&&("click"===e||"preclick"===e)&&this._draggableMoved(i)){r=!0;break}if(i&&i.listens(e,!0)){if(o&&!We(s,t))break;if(n.push(i),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(e,!0)?n:[this]},_isClickDisabled:function(t){for(;t&&t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var e,i=t.target||t.srcElement;!this._loaded||i._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(i)||("mousedown"===(e=t.type)&&Me(i),this._fireDOMEvent(t,e))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,i){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,i));var n=this._findEventTargets(t,e);if(i){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),n=this._getCenterOffset(t)._divideBy(1-1/n);if(!0!==i.animate&&!this.getSize().contains(n))return!1;x(function(){this._moveStart(!0,i.noMoveStart||!1)._animateZoom(t,e,!0)},this)}return!0},_animateZoom:function(t,e,i,n){this._mapPane&&(i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,M(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&z(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Ue(t){return new B(t)}var B=et.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),t=t._controlCorners[i];return M(e,"leaflet-control"),-1!==i.indexOf("bottom")?t.insertBefore(e,t.firstChild):t.appendChild(e),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",e=document.createElement("div");return e.innerHTML=t,e.firstChild},_addItem:function(t){var e,i=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((e=document.createElement("input")).type="checkbox",e.className="leaflet-control-layers-selector",e.defaultChecked=n):e=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(e),e.layerId=h(t.layer),S(e,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return i.appendChild(o),o.appendChild(e),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(i),this._checkDisabledLayers(),i},_onInputClick:function(){if(!this._preventClick){var t,e,i=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=i.length-1;0<=s;s--)t=i[s],e=this._getLayer(t.layerId).layer,t.checked?n.push(e):t.checked||o.push(e);for(s=0;se.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expandSafely:function(){var t=this._section,e=(this._preventClick=!0,S(t,"click",O),this.expand(),this);setTimeout(function(){k(t,"click",O),e._preventClick=!1})}})),qe=B.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=P("div",e+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,n,o){i=P("a",i,n);return i.innerHTML=t,i.href="#",i.title=e,i.setAttribute("role","button"),i.setAttribute("aria-label",e),Ie(i),S(i,"click",Re),S(i,"click",o,this),S(i,"click",this._refocusOnMap,this),i},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";z(this._zoomInButton,e),z(this._zoomOutButton,e),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(M(this._zoomOutButton,e),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(M(this._zoomInButton,e),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Ge=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qe,this.addControl(this.zoomControl))}),B.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=P("div",e),n=this.options;return this._addScales(n,e+"-line",i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=P("div",e,i)),t.imperial&&(this._iScale=P("div",e,i))},_update:function(){var t=this._map,e=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t);this._updateScale(this._mScale,e<1e3?e+" m":e/1e3+" km",e/t)},_updateImperial:function(t){var e,i,t=3.2808399*t;5280'+(b.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var e in(t.attributionControl=this)._container=P("div","leaflet-control-attribution"),Ie(this._container),t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,e=[];for(t in this._attributions)this._attributions[t]&&e.push(t);var i=[];this.options.prefix&&i.push(this.options.prefix),e.length&&i.push(e.join(", ")),this._container.innerHTML=i.join(' ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ke).addTo(this)}),B.Layers=Ve,B.Zoom=qe,B.Scale=Ge,B.Attribution=Ke,Ue.layers=function(t,e,i){return new Ve(t,e,i)},Ue.zoom=function(t){return new qe(t)},Ue.scale=function(t){return new Ge(t)},Ue.attribution=function(t){return new Ke(t)},et.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,e){return t.addHandler(e,this),this},{Events:e}),Ye=b.touch?"touchstart mousedown":"mousedown",Xe=it.extend({options:{clickTolerance:3},initialize:function(t,e,i,n){c(this,n),this._element=t,this._dragStartTarget=e||t,this._preventOutline=i},enable:function(){this._enabled||(S(this._dragStartTarget,Ye,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xe._dragging===this&&this.finishDrag(!0),k(this._dragStartTarget,Ye,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var e,i;this._enabled&&(this._moved=!1,ve(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xe._dragging===this&&this.finishDrag():Xe._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xe._dragging=this)._preventOutline&&Me(this._element),Le(),re(),this._moving||(this.fire("down"),i=t.touches?t.touches[0]:t,e=Ce(this._element),this._startPoint=new p(i.clientX,i.clientY),this._startPos=Pe(this._element),this._parentScale=Ze(e),i="mousedown"===t.type,S(document,i?"mousemove":"touchmove",this._onMove,this),S(document,i?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var e;this._enabled&&(t.touches&&1e&&(i.push(t[n]),o=n);oe.max.x&&(i|=2),t.ye.max.y&&(i|=8),i}function ri(t,e,i,n){var o=e.x,e=e.y,s=i.x-o,r=i.y-e,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(l=!l);return l||yi.prototype._containsPoint.call(this,t,!0)}});var wi=ci.extend({initialize:function(t,e){c(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,o=d(t)?t:t.features;if(o){for(e=0,i=o.length;es.x&&(r=i.x+a-s.x+o.x),i.x-r-n.x<(a=0)&&(r=i.x-n.x),i.y+e+o.y>s.y&&(a=i.y+e-s.y+o.y),i.y-a-n.y<0&&(a=i.y-n.y),(r||a)&&(this.options.keepInView&&(this._autopanning=!0),t.fire("autopanstart").panBy([r,a]))))},_getAnchor:function(){return m(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ii=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,e,i){return this._initOverlay(Bi,t,e,i).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,e){return this._popup=this._initOverlay(Bi,this._popup,t,e),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&(this instanceof ci||(this._popup._source=this),this._popup._prepareOpen(t||this._latlng)&&this._popup.openOn(this._map)),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e;this._popup&&this._map&&(Re(t),e=t.layer||t.target,this._popup._source!==e||e instanceof fi?(this._popup._source=e,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),Ai.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){Ai.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){Ai.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=Ai.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=P("div",t),this._container.setAttribute("role","tooltip"),this._container.setAttribute("id","leaflet-tooltip-"+h(this))},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e,i=this._map,n=this._container,o=i.latLngToContainerPoint(i.getCenter()),i=i.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=m(this.options.offset),l=this._getAnchor(),i="top"===s?(e=r/2,a):"bottom"===s?(e=r/2,0):(e="center"===s?r/2:"right"===s?0:"left"===s?r:i.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oi.max.x)||!e.wrapLat&&(t.yi.max.y))return!1}return!this.options.bounds||(e=this._tileCoordsToBounds(t),g(this.options.bounds).overlaps(e))},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var e=this._map,i=this.getTileSize(),n=t.scaleBy(i),i=n.add(i);return[e.unproject(n,t.z),e.unproject(i,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),e=new p(+t[0],+t[1]);return e.z=+t[2],e},_removeTile:function(t){var e=this._tiles[t];e&&(T(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){M(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=u,t.onmousemove=u,b.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,e){var i=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,i),this._tiles[n]={el:o,coords:t,current:!0},e.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,e,i){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var n=this._tileCoordsToKey(t);(i=this._tiles[n])&&(i.loaded=+new Date,this._map._fadeAnimated?(C(i.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(M(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),b.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new f(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Di=Ni.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,e){this._url=t,(e=c(this,e)).detectRetina&&b.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),zt={_initContainer:function(){this._container=P("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Wi.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=Vi("shape");M(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=Vi("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;T(e),t.removeInteractiveTarget(e),delete this._layers[h(t)]},_updateStyle:function(t){var e=t._stroke,i=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(e=e||(t._stroke=Vi("stroke")),o.appendChild(e),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=n.lineCap.replace("butt","flat"),e.joinstyle=n.lineJoin):e&&(o.removeChild(e),t._stroke=null),n.fill?(i=i||(t._fill=Vi("fill")),o.appendChild(i),i.color=n.fillColor||n.color,i.opacity=n.fillOpacity):i&&(o.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){fe(t._container)},_bringToBack:function(t){ge(t._container)}},qi=b.vml?Vi:ct,Gi=Wi.extend({_initContainer:function(){this._container=qi("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=qi("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),k(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,e,i;this._map._animatingZoom&&this._bounds||(Wi.prototype._update.call(this),e=(t=this._bounds).getSize(),i=this._container,this._svgSize&&this._svgSize.equals(e)||(this._svgSize=e,i.setAttribute("width",e.x),i.setAttribute("height",e.y)),Z(i,t.min),i.setAttribute("viewBox",[t.min.x,t.min.y,e.x,e.y].join(" ")),this.fire("update"))},_initPath:function(t){var e=t._path=qi("path");t.options.className&&M(e,t.options.className),t.options.interactive&&M(e,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var e=t._path,t=t.options;e&&(t.stroke?(e.setAttribute("stroke",t.color),e.setAttribute("stroke-opacity",t.opacity),e.setAttribute("stroke-width",t.weight),e.setAttribute("stroke-linecap",t.lineCap),e.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?e.setAttribute("stroke-dasharray",t.dashArray):e.removeAttribute("stroke-dasharray"),t.dashOffset?e.setAttribute("stroke-dashoffset",t.dashOffset):e.removeAttribute("stroke-dashoffset")):e.setAttribute("stroke","none"),t.fill?(e.setAttribute("fill",t.fillColor||t.color),e.setAttribute("fill-opacity",t.fillOpacity),e.setAttribute("fill-rule",t.fillRule||"evenodd")):e.setAttribute("fill","none"))},_updatePoly:function(t,e){this._setPath(t,dt(t._parts,e))},_updateCircle:function(t){var e=t._point,i=Math.max(Math.round(t._radius),1),n="a"+i+","+(Math.max(Math.round(t._radiusY),1)||i)+" 0 1,0 ",e=t._empty()?"M0 0":"M"+(e.x-i)+","+e.y+n+2*i+",0 "+n+2*-i+",0 ";this._setPath(t,e)},_setPath:function(t,e){t._path.setAttribute("d",e)},_bringToFront:function(t){fe(t._path)},_bringToBack:function(t){ge(t._path)}});function Ki(t){return b.svg||b.vml?new Gi(t):null}b.vml&&Gi.include(zt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){var e;return"overlayPane"!==t&&void 0!==t&&(void 0===(e=this._paneRenderers[t])&&(e=this._createRenderer({pane:t}),this._paneRenderers[t]=e),e)},_createRenderer:function(t){return this.options.preferCanvas&&Ui(t)||Ki(t)}});var Yi=xi.extend({initialize:function(t,e){xi.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Gi.create=qi,Gi.pointsToPath=dt,wi.geometryToLayer=bi,wi.coordsToLatLng=Li,wi.coordsToLatLngs=Ti,wi.latLngToCoords=Mi,wi.latLngsToCoords=zi,wi.getFeature=Ci,wi.asFeature=Zi,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){k(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),re(),Le(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Re,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=P("div","leaflet-zoom-box",this._container),M(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new f(this._point,this._startPoint),e=t.getSize();Z(this._box,t.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(T(this._box),z(this._container,"leaflet-crosshair")),ae(),Te(),k(document,{contextmenu:Re,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom(),n=e.options.zoomDelta,i=t.originalEvent.shiftKey?i-n:i+n;"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xe(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),M(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){z(this._map._container,"leaflet-grab"),z(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,e=this._map;e._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=_(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,e.fire("movestart").fire("dragstart"),e.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var e,i;this._map.options.inertia&&(e=this._lastTime=+new Date,i=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(i),this._times.push(e),this._prunePositions(e)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1e.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,n=(n+e+i)%t-e-i,t=Math.abs(o+i)e.getMaxZoom()&&1 0 else b'{}' + if self.path == '/api/delay': try: content_length = int(self.headers.get('Content-Length', '0')) @@ -782,6 +947,31 @@ class Handler(http.server.BaseHTTPRequestHandler): self.write_json(status_code, response) return + if self.path == '/api/routes/save': + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = save_named_route( + payload.get('name'), + payload.get('waypoints'), + ) + self.write_json(status_code, response) + return + + if self.path == '/api/routes/delete': + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = delete_named_route(payload.get('name')) + self.write_json(status_code, response) + return + if self.path == '/api/motor-test/drive-neutral/preview': try: content_length = int(self.headers.get('Content-Length', '0'))