Added track visualization

- Render GPX tracks on map
- Statistics dashboard with total distance, duration, elevation
- Reactive updates when tracks are added
This commit is contained in:
Atharva Sawant
2024-11-08 15:44:29 +05:30
parent be5ff4992d
commit f6cd4da529
2 changed files with 98 additions and 3 deletions

View File

@@ -8,12 +8,39 @@
<main class="container mx-auto p-4">
<div class="mb-6">
<h2 class="text-xl mb-4">Your Cycling Tracks</h2>
<MapView />
<MapView :tracks="tracks" />
</div>
<div class="mb-6">
<FileUpload @files-uploaded="handleFilesUploaded" />
</div>
<!-- Track Statistics -->
<div v-if="tracks.length > 0" class="mb-6">
<h3 class="text-lg font-medium mb-3">Statistics</h3>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
<div class="bg-gray-50 p-4 rounded-lg text-center">
<div class="text-2xl font-bold text-blue-600">{{ totalStats.totalTracks }}</div>
<div class="text-sm text-gray-600">Total Tracks</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg text-center">
<div class="text-2xl font-bold text-green-600">{{ totalStats.totalDistance }}</div>
<div class="text-sm text-gray-600">Total Distance (km)</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg text-center">
<div class="text-2xl font-bold text-purple-600">{{ totalStats.totalDuration }}</div>
<div class="text-sm text-gray-600">Total Hours</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg text-center">
<div class="text-2xl font-bold text-orange-600">{{ totalStats.avgSpeed }}</div>
<div class="text-sm text-gray-600">Avg Speed (km/h)</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg text-center">
<div class="text-2xl font-bold text-red-600">{{ totalStats.totalElevationGain }}</div>
<div class="text-sm text-gray-600">Elevation Gain (m)</div>
</div>
</div>
</div>
</main>
</div>
</template>

View File

@@ -3,13 +3,20 @@
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import L from 'leaflet'
export default {
name: 'MapView',
setup() {
props: {
tracks: {
type: Array,
default: () => []
}
},
setup(props) {
const map = ref(null)
const trackLayers = ref([])
const getLocationFromIP = async () => {
try {
@@ -23,6 +30,61 @@ export default {
}
}
const getTrackColor = (index) => {
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
]
return colors[index % colors.length]
}
const renderTracks = () => {
// Clear existing track layers
trackLayers.value.forEach(layer => {
map.value.removeLayer(layer)
})
trackLayers.value = []
if (!props.tracks || props.tracks.length === 0) return
const allBounds = []
props.tracks.forEach((track, trackIndex) => {
const color = getTrackColor(trackIndex)
track.segments.forEach(segment => {
if (segment.points && segment.points.length > 0) {
const latLngs = segment.points.map(point => [point.lat, point.lng])
const polyline = L.polyline(latLngs, {
color: color,
weight: 3,
opacity: 0.8
}).bindPopup(`
<div class="font-medium">${track.name}</div>
<div class="text-sm text-gray-600 mt-1">
Distance: ${(track.stats?.distance / 1000 || 0).toFixed(1)} km<br>
Duration: ${Math.floor((track.stats?.duration || 0) / 3600)}:${Math.floor(((track.stats?.duration || 0) % 3600) / 60).toString().padStart(2, '0')}<br>
Avg Speed: ${track.stats?.avgSpeed || 0} km/h
</div>
`)
polyline.addTo(map.value)
trackLayers.value.push(polyline)
// Add bounds for this segment
allBounds.push(...latLngs)
}
})
})
// Fit map to show all tracks
if (allBounds.length > 0) {
const group = new L.featureGroup(trackLayers.value)
map.value.fitBounds(group.getBounds(), { padding: [20, 20] })
}
}
const initMap = async () => {
const coords = await getLocationFromIP()
@@ -31,8 +93,14 @@ export default {
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map.value)
// Initial track rendering
renderTracks()
}
// Watch for track changes
watch(() => props.tracks, renderTracks, { deep: true })
onMounted(() => {
initMap()
})