100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { ActivityStats } from '../types';
|
|
|
|
export const parseGpxFile = async (file: File): Promise<ActivityStats> => {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
try {
|
|
const text = e.target?.result as string;
|
|
const parser = new DOMParser();
|
|
const xmlDoc = parser.parseFromString(text, 'text/xml');
|
|
|
|
const trkpts = Array.from(xmlDoc.getElementsByTagName('trkpt'));
|
|
|
|
if (trkpts.length === 0) {
|
|
// Fallback for rtept if no trkpt
|
|
const rtepts = Array.from(xmlDoc.getElementsByTagName('rtept'));
|
|
if (rtepts.length === 0) {
|
|
throw new Error('No track points found in GPX');
|
|
}
|
|
}
|
|
|
|
let totalDistance = 0;
|
|
let totalTime = 0;
|
|
let elevationGain = 0;
|
|
let startTime: Date | null = null;
|
|
let endTime: Date | null = null;
|
|
let lastEle: number | null = null;
|
|
|
|
for (let i = 0; i < trkpts.length; i++) {
|
|
const lat1 = parseFloat(trkpts[i].getAttribute('lat') || '0');
|
|
const lon1 = parseFloat(trkpts[i].getAttribute('lon') || '0');
|
|
const ele = parseFloat(trkpts[i].getElementsByTagName('ele')[0]?.textContent || '0');
|
|
const timeStr = trkpts[i].getElementsByTagName('time')[0]?.textContent;
|
|
|
|
if (i > 0) {
|
|
const lat2 = parseFloat(trkpts[i - 1].getAttribute('lat') || '0');
|
|
const lon2 = parseFloat(trkpts[i - 1].getAttribute('lon') || '0');
|
|
totalDistance += getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2);
|
|
|
|
// Elevation gain
|
|
if (lastEle !== null && ele > lastEle) {
|
|
elevationGain += (ele - lastEle);
|
|
}
|
|
}
|
|
|
|
if (timeStr) {
|
|
const time = new Date(timeStr);
|
|
if (!startTime) startTime = time;
|
|
endTime = time;
|
|
}
|
|
|
|
if (!isNaN(ele)) lastEle = ele;
|
|
}
|
|
|
|
if (startTime && endTime) {
|
|
totalTime = (endTime.getTime() - startTime.getTime()); // ms
|
|
}
|
|
|
|
resolve({
|
|
distance: `${totalDistance.toFixed(2)} km`,
|
|
duration: msToTime(totalTime),
|
|
elevation: `${Math.round(elevationGain)}m`,
|
|
});
|
|
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
reader.onerror = () => reject(new Error('Error reading file'));
|
|
reader.readAsText(file);
|
|
});
|
|
};
|
|
|
|
// Haversine formula
|
|
function getDistanceFromLatLonInKm(lat1: number, lon1: number, lat2: number, lon2: number) {
|
|
const R = 6371; // Radius of the earth in km
|
|
const dLat = deg2rad(lat2 - lat1);
|
|
const dLon = deg2rad(lon2 - lon1);
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
|
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
const d = R * c; // Distance in km
|
|
return d;
|
|
}
|
|
|
|
function deg2rad(deg: number) {
|
|
return deg * (Math.PI / 180);
|
|
}
|
|
|
|
function msToTime(duration: number) {
|
|
if (duration <= 0) return "0h 00m";
|
|
const minutes = Math.floor((duration / (1000 * 60)) % 60);
|
|
const hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
|
|
|
|
return `${hours}h ${minutes}m`;
|
|
} |