|
<!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> |