Spaces:
Running
Running
| ```javascript | |
| // Initialize map | |
| const map = L.map('map').setView([37.5665, 126.9780], 13); // Default to Seoul | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | |
| }).addTo(map); | |
| // Tracking variables | |
| let tracking = false; | |
| let startTime = null; | |
| let path = []; | |
| let polyline = null; | |
| let totalDistance = 0; | |
| let watchId = null; | |
| // DOM elements | |
| const startBtn = document.getElementById('startBtn'); | |
| const stopBtn = document.getElementById('stopBtn'); | |
| const saveBtn = document.getElementById('saveBtn'); | |
| const distanceEl = document.getElementById('distance'); | |
| const durationEl = document.getElementById('duration'); | |
| const speedEl = document.getElementById('speed'); | |
| const caloriesEl = document.getElementById('calories'); | |
| const historyTable = document.getElementById('historyTable'); | |
| // Event listeners | |
| startBtn.addEventListener('click', startTracking); | |
| stopBtn.addEventListener('click', stopTracking); | |
| saveBtn.addEventListener('click', saveSession); | |
| function startTracking() { | |
| tracking = true; | |
| startTime = new Date(); | |
| path = []; | |
| totalDistance = 0; | |
| if (polyline) { | |
| map.removeLayer(polyline); | |
| } | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| saveBtn.disabled = true; | |
| watchId = navigator.geolocation.watchPosition( | |
| updatePosition, | |
| handleError, | |
| { enableHighAccuracy: true, maximumAge: 10000, timeout: 5000 } | |
| ); | |
| updateTimer(); | |
| timerInterval = setInterval(updateTimer, 1000); | |
| } | |
| function stopTracking() { | |
| tracking = false; | |
| if (watchId) { | |
| navigator.geolocation.clearWatch(watchId); | |
| } | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| saveBtn.disabled = false; | |
| clearInterval(timerInterval); | |
| } | |
| function saveSession() { | |
| // Save to local storage | |
| const sessions = JSON.parse(localStorage.getItem('trackingSessions') || '[]'); | |
| const duration = (new Date() - startTime) / 1000; | |
| sessions.push({ | |
| date: new Date().toLocaleString(), | |
| distance: totalDistance, | |
| duration: formatTime(duration), | |
| path: path | |
| }); | |
| localStorage.setItem('trackingSessions', JSON.stringify(sessions)); | |
| updateHistoryTable(); | |
| saveBtn.disabled = true; | |
| } | |
| function updatePosition(position) { | |
| const { latitude, longitude, speed, altitude } = position.coords; | |
| const newPoint = [latitude, longitude]; | |
| // Add to path | |
| path.push(newPoint); | |
| // Update polyline | |
| if (polyline) { | |
| map.removeLayer(polyline); | |
| } | |
| polyline = L.polyline(path, {color: 'blue'}).addTo(map); | |
| // Calculate distance | |
| if (path.length > 1) { | |
| const lastPoint = path[path.length - 2]; | |
| const distance = calculateDistance(lastPoint[0], lastPoint[1], latitude, longitude); | |
| totalDistance += distance; | |
| distanceEl.textContent = (totalDistance / 1000).toFixed(2); | |
| } | |
| // Update speed | |
| const speedKmh = speed ? (speed * 3.6).toFixed(1) : 0; | |
| speedEl.textContent = speedKmh; | |
| // Estimate calories (very rough estimate) | |
| const calories = (totalDistance / 1000) * 60; // 60kcal per km | |
| caloriesEl.textContent = Math.round(calories); | |
| // Center map on current position | |
| map.setView(newPoint, map.getZoom()); | |
| } | |
| function handleError(error) { | |
| console.error('Geolocation error:', error.message); | |
| if (tracking) { | |
| stopTracking(); | |
| alert('Geolocation error: ' + error.message); | |
| } | |
| } | |
| function updateTimer() { | |
| if (!startTime) return; | |
| const elapsed = (new Date() - startTime) / 1000; | |
| durationEl.textContent = formatTime(elapsed); | |
| } | |
| function formatTime(seconds) { | |
| const hrs = Math.floor(seconds / 3600); | |
| const mins = Math.floor((seconds % 3600) / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; | |
| } | |
| function calculateDistance(lat1, lon1, lat2, lon2) { | |
| const R = 6371e3; // Earth radius in meters | |
| const φ1 = lat1 * Math.PI/180; | |
| const φ2 = lat2 * Math.PI/180; | |
| const Δφ = (lat2-lat1) * Math.PI/180; | |
| const Δλ = (lon2-lon1) * Math.PI/180; | |
| const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + | |
| Math.cos(φ1) * Math.cos(φ2) * | |
| Math.sin(Δλ/2) * Math.sin(Δλ/2); | |
| const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); | |
| return R * c; | |
| } | |
| function updateHistoryTable() { | |
| const sessions = JSON.parse(localStorage.getItem('trackingSessions') || '[]'); | |
| historyTable.innerHTML = sessions.map(session => ` | |
| <tr> | |
| <td>${session.date}</td> | |
| <td>${(session.distance / 1000).toFixed(2)} km</td> | |
| <td>${session.duration}</td> | |
| <td><button class="btn btn-sm btn-outline-primary">View</button></td> | |
| </tr> | |
| `).join(''); | |
| } | |
| // Initialize | |
| updateHistoryTable(); | |
| ``` |