Skip to content

Instantly share code, notes, and snippets.

@sburlot
Created December 7, 2024 10:46
Show Gist options
  • Save sburlot/6a0470582a9522fa641b23255f37c373 to your computer and use it in GitHub Desktop.
Save sburlot/6a0470582a9522fa641b23255f37c373 to your computer and use it in GitHub Desktop.
Extract location from a GPX file, display in HTML or export to CSV

GPX File Converter Conversation

Initial Request

Human: Build me an artifact with no react that uses JavaScript to convert a GPX file to an html table displaying the date, location in latitude, longitude and the location in plain text. The exact location will be determined with Google Maps API. I will provide the Google API key. This artifact will allow me to upload the GPX file. Make it mobile friendly and big and obvious.

Claude: Created an HTML/JavaScript artifact for GPX file conversion with mobile-friendly design and Google Maps API integration.

First Iteration

Human: it displays an error "Error: Geocoding error: TypeError: Failed to fetch"

Claude: Explained that the error was likely due to CORS restrictions and suggested using a server-side proxy approach.

Refinement Request

Human: revert to previous version, display the date and time in human format, make the table wider. Add the possibility to export the table in CSV, with the date suitable for excel

Claude: Updated the artifact with:

  • Wider container
  • Improved date formatting
  • CSV export functionality
  • Excel-compatible date format
  • Human-readable date display

Final Request

Human: there should be a table for each day, to increase readability

Claude: Modified the artifact to:

  • Create separate tables for each day
  • Add date headers for each table
  • Improve overall readability
  • Update CSV export to include date and time

Full HTML/JavaScript Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GPX File Converter</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body { 
            background-color: #f4f4f4; 
            padding: 20px; 
        }
        .container {
            max-width: 1200px;
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .daily-table-container {
            margin-bottom: 20px;
        }
        @media (max-width: 600px) {
            .container {
                padding: 15px;
            }
        }
    </style>
</head>
<body>
    <div class="container text-center">
        <h1 class="mb-4">GPX File Converter</h1>
        <input 
            type="file" 
            id="gpxFileInput" 
            accept=".gpx" 
            class="form-control mb-3"
        >
        <button 
            id="convertBtn" 
            class="btn btn-primary btn-lg mb-3"
        >
            Convert GPX
        </button>
        <input 
            type="text" 
            id="googleApiKey" 
            placeholder="Enter Google Maps API Key" 
            class="form-control mb-3"
        >
        <div class="d-flex justify-content-end mb-2">
            <button 
                id="exportCsvBtn" 
                class="btn btn-success"
                disabled
            >
                Export to CSV
            </button>
        </div>
        <div id="locationTablesContainer"></div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
    <script>
        const gpxFileInput = document.getElementById('gpxFileInput');
        const convertBtn = document.getElementById('convertBtn');
        const exportCsvBtn = document.getElementById('exportCsvBtn');
        const googleApiKeyInput = document.getElementById('googleApiKey');
        const locationTablesContainer = document.getElementById('locationTablesContainer');

        let processedLocations = [];

        function formatDate(isoString) {
            if (!isoString) return { 
                date: 'N/A', 
                time: 'N/A', 
                excel: 'N/A',
                dateKey: 'Unknown'
            };
            const date = new Date(isoString);
            return {
                date: date.toLocaleDateString(),
                time: date.toLocaleTimeString(),
                excel: date.toISOString().replace('T', ' ').split('.')[0],
                dateKey: date.toISOString().split('T')[0]
            };
        }

        function parseGPX(gpxText) {
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(gpxText, 'text/xml');
            const trackPoints = xmlDoc.querySelectorAll('trkpt');
            return Array.from(trackPoints).map(point => ({
                lat: parseFloat(point.getAttribute('lat')),
                lon: parseFloat(point.getAttribute('lon')),
                time: point.querySelector('time')?.textContent
            }));
        }

        async function getLocation(lat, lon) {
            try {
                const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lon}&key=${googleApiKeyInput.value}`;
                const response = await fetch(url);
                const data = await response.json();
                return data.results[0]?.formatted_address || 'Location not found';
            } catch (error) {
                console.error('Geocoding error:', error);
                return 'Unable to retrieve location';
            }
        }

        convertBtn.addEventListener('click', async () => {
            const file = gpxFileInput.files[0];
            const apiKey = googleApiKeyInput.value;

            if (!file || !apiKey) {
                alert('Please select a GPX file and enter Google API Key');
                return;
            }

            const reader = new FileReader();
            reader.onload = async (e) => {
                const gpxText = e.target.result;
                const locations = parseGPX(gpxText);
                processedLocations = [];
                locationTablesContainer.innerHTML = '';

                const locationsByDay = {};

                for (const loc of locations) {
                    const formattedDate = formatDate(loc.time);
                    const address = await getLocation(loc.lat, loc.lon);
                    
                    const processedLocation = {
                        date: formattedDate.date,
                        time: formattedDate.time,
                        dateExcel: formattedDate.excel,
                        lat: loc.lat,
                        lon: loc.lon,
                        address: address,
                        dateKey: formattedDate.dateKey
                    };

                    if (!locationsByDay[processedLocation.dateKey]) {
                        locationsByDay[processedLocation.dateKey] = [];
                    }
                    locationsByDay[processedLocation.dateKey].push(processedLocation);
                    processedLocations.push(processedLocation);
                }

                Object.keys(locationsByDay).forEach(dateKey => {
                    const dayLocations = locationsByDay[dateKey];
                    const tableContainer = document.createElement('div');
                    tableContainer.className = 'daily-table-container';
                    
                    const dateHeader = document.createElement('h3');
                    dateHeader.textContent = `Locations on ${dayLocations[0].date}`;
                    tableContainer.appendChild(dateHeader);

                    const table = document.createElement('table');
                    table.className = 'table table-striped table-hover';
                    table.innerHTML = `
                        <thead>
                            <tr>
                                <th>Time</th>
                                <th>Latitude</th>
                                <th>Longitude</th>
                                <th>Location</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    `;

                    const tbody = table.querySelector('tbody');
                    dayLocations.forEach(loc => {
                        const row = document.createElement('tr');
                        row.innerHTML = `
                            <td>${loc.time}</td>
                            <td>${loc.lat.toFixed(6)}</td>
                            <td>${loc.lon.toFixed(6)}</td>
                            <td>${loc.address}</td>
                        `;
                        tbody.appendChild(row);
                    });

                    tableContainer.appendChild(table);
                    locationTablesContainer.appendChild(tableContainer);
                });

                exportCsvBtn.disabled = false;
            };
            reader.readAsText(file);
        });

        exportCsvBtn.addEventListener('click', () => {
            const csvContent = [
                'Date,Time,Latitude,Longitude,Location'
            ].concat(
                processedLocations.map(loc => 
                    `"${loc.date}","${loc.time}",${loc.lat},${loc.lon},"${loc.address}"`
                )
            ).join('\n');

            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement('a');
            const url = URL.createObjectURL(blob);
            link.setAttribute('href', url);
            link.setAttribute('download', 'gpx_locations.csv');
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        });
    </script>
</body>
</html>

Key Features

  • GPX file upload
  • Google Maps API integration
  • Daily location tables
  • CSV export
  • Mobile-friendly design
  • Detailed location information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment