| <!DOCTYPE html> |
| <html lang="ru"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Инерциальный трекер</title> |
| |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> |
| <style> |
| body, html { margin: 0; padding: 0; height: 100%; font-family: sans-serif; } |
| #map { height: 60%; width: 100%; } |
| #info { padding: 10px; height: 35%; overflow-y: auto; background-color: #f0f0f0; } |
| #info p { margin: 5px 0; } |
| #startButton { |
| position: absolute; |
| top: 70px; |
| right: 10px; |
| z-index: 1000; |
| padding: 10px 15px; |
| font-size: 16px; |
| background-color: #4CAF50; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| } |
| #startButton.active { |
| background-color: #f44336; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div id="map"></div> |
| <button id="startButton">Старт</button> |
|
|
| <div id="info"> |
| <h3>Панель данных</h3> |
| <p><strong>Статус:</strong> Ожидание старта...</p> |
| <p><strong>Текущие координаты (вычисленные):</strong> <span id="lat">N/A</span>, <span id="lon">N/A</span></p> |
| <p><strong>Начальные GPS координаты:</strong> <span id="startLat">N/A</span>, <span id="startLon">N/A</span></p> |
| <p><strong>Скорость (м/с):</strong> X: <span id="velocityX">0</span>, Y: <span id="velocityY">0</span></p> |
| <p><strong>Ускорение (м/с²):</strong> X: <span id="accelX">0</span>, Y: <span id="accelY">0</span>, Z: <span id="accelZ">0</span></p> |
| <p><strong>Ориентация:</strong> α (yaw): <span id="alpha">0</span>, β (pitch): <span id="beta">0</span>, γ (roll): <span id="gamma">0</span></p> |
| <p style="color: red; font-weight: bold;">ВНИМАНИЕ: Позиция будет быстро накапливать ошибку и не будет соответствовать реальности!</p> |
| </div> |
|
|
| <script> |
| |
| const map = L.map('map').setView([55.751244, 37.618423], 13); |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { |
| maxZoom: 19, |
| attribution: '© OpenStreetMap' |
| }).addTo(map); |
| |
| let startMarker, currentMarker; |
| let isTracking = false; |
| |
| |
| let lastTimestamp = null; |
| let velocity = { x: 0, y: 0 }; |
| let currentPosition = { lat: 0, lon: 0 }; |
| let orientation = { alpha: 0, beta: 0, gamma: 0 }; |
| |
| const startButton = document.getElementById('startButton'); |
| |
| startButton.addEventListener('click', () => { |
| if (!isTracking) { |
| startTracking(); |
| } else { |
| stopTracking(); |
| } |
| }); |
| |
| function startTracking() { |
| |
| if (!navigator.geolocation) { |
| alert('Геолокация не поддерживается вашим браузером'); |
| return; |
| } |
| |
| document.querySelector('#info p:nth-child(1)').innerHTML = '<strong>Статус:</strong> Получение начальных GPS координат...'; |
| |
| navigator.geolocation.getCurrentPosition(position => { |
| currentPosition.lat = position.coords.latitude; |
| currentPosition.lon = position.coords.longitude; |
| |
| document.getElementById('startLat').textContent = currentPosition.lat.toFixed(6); |
| document.getElementById('startLon').textContent = currentPosition.lon.toFixed(6); |
| |
| |
| if (startMarker) map.removeLayer(startMarker); |
| if (currentMarker) map.removeLayer(currentMarker); |
| |
| startMarker = L.marker([currentPosition.lat, currentPosition.lon]).addTo(map) |
| .bindPopup('Начальная точка (GPS)').openPopup(); |
| currentMarker = L.marker([currentPosition.lat, currentPosition.lon], { |
| icon: L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }) |
| }).addTo(map) |
| .bindPopup('Текущая точка (ИНС)'); |
| |
| map.setView([currentPosition.lat, currentPosition.lon], 18); |
| |
| |
| lastTimestamp = performance.now(); |
| window.addEventListener('deviceorientation', handleOrientation); |
| window.addEventListener('devicemotion', handleMotion); |
| |
| isTracking = true; |
| startButton.textContent = 'Стоп'; |
| startButton.classList.add('active'); |
| document.querySelector('#info p:nth-child(1)').innerHTML = '<strong>Статус:</strong> Отслеживание активно...'; |
| |
| }, error => { |
| alert(`Ошибка получения GPS: ${error.message}`); |
| document.querySelector('#info p:nth-child(1)').innerHTML = '<strong>Статус:</strong> Ошибка GPS.'; |
| }); |
| } |
| |
| function stopTracking() { |
| window.removeEventListener('deviceorientation', handleOrientation); |
| window.removeEventListener('devicemotion', handleMotion); |
| isTracking = false; |
| startButton.textContent = 'Старт'; |
| startButton.classList.remove('active'); |
| document.querySelector('#info p:nth-child(1)').innerHTML = '<strong>Статус:</strong> Остановлено.'; |
| |
| velocity = { x: 0, y: 0 }; |
| } |
| |
| |
| function handleOrientation(event) { |
| |
| orientation.alpha = event.alpha; |
| orientation.beta = event.beta; |
| orientation.gamma = event.gamma; |
| document.getElementById('alpha').textContent = orientation.alpha.toFixed(2); |
| document.getElementById('beta').textContent = orientation.beta.toFixed(2); |
| document.getElementById('gamma').textContent = orientation.gamma.toFixed(2); |
| } |
| |
| function handleMotion(event) { |
| if (lastTimestamp === null) { |
| lastTimestamp = performance.now(); |
| return; |
| } |
| |
| const now = performance.now(); |
| const dt = (now - lastTimestamp) / 1000.0; |
| lastTimestamp = now; |
| |
| |
| const accel = event.accelerationIncludingGravity; |
| |
| |
| |
| |
| const linearAccel = event.acceleration; |
| |
| |
| |
| let ax = linearAccel.x || 0; |
| let ay = linearAccel.y || 0; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| let worldAccel = { |
| x: ax, |
| y: ay |
| }; |
| |
| |
| |
| velocity.x += worldAccel.x * dt; |
| velocity.y += worldAccel.y * dt; |
| |
| |
| const deltaX = velocity.x * dt; |
| const deltaY = velocity.y * dt; |
| |
| |
| const R = 6378137; |
| const dLat = deltaY / R; |
| const dLon = deltaX / (R * Math.cos(Math.PI * currentPosition.lat / 180)); |
| |
| currentPosition.lat += dLat * 180 / Math.PI; |
| currentPosition.lon += dLon * 180 / Math.PI; |
| |
| |
| document.getElementById('accelX').textContent = (ax).toFixed(2); |
| document.getElementById('accelY').textContent = (ay).toFixed(2); |
| document.getElementById('accelZ').textContent = (linearAccel.z || 0).toFixed(2); |
| document.getElementById('velocityX').textContent = velocity.x.toFixed(2); |
| document.getElementById('velocityY').textContent = velocity.y.toFixed(2); |
| document.getElementById('lat').textContent = currentPosition.lat.toFixed(6); |
| document.getElementById('lon').textContent = currentPosition.lon.toFixed(6); |
| |
| |
| currentMarker.setLatLng([currentPosition.lat, currentPosition.lon]); |
| |
| } |
| |
| </script> |
|
|
| </body> |
| </html> |