Blog
Aug 7, 2021 - 5 MIN READ
Update on GPX Project

Update on GPX Project

Building an Electron App

Steve Furches

Steve Furches

Brushing up on my Vanilla JS skills and building an Electron app

Who Needs Vue?

I decided that building this app would be an excellent chance to brush up on my Vanilla JavaScript skills. I've also been wanting to give Electron a go. The Electron documentation is nice, and it has been a fun process.

EJS and Bulma

While I didn't want to use Vue, I did decide to use EJS to make it easier split up my pages into components and reuse my banner and navigation. I'm still keeping all of my logic in my JS files, rather than taking full advantage of EJS features. I realize this can technically be done in plain JS, but it gets to be a bit clunky. On a related note - I'm amazed that in 2021 there still isn't an easy way to do this in plain HTML, CSS, and JavaScript. Every solution I've found involves adding a framework or writing a bunch of HTML inside a JavaScript function or file.

For my styling I decided to use Bulma. Bulma was a nice fit, because it simply provides CSS classes and nothing else. I wanted to write all of the JS myself, and didn't want something bulky like Bootstrap.

Uploading and Processing GPX Files

The first step was to get data from a GPX file. In my last post I described the basic total distance and total time calculations. In addition to these, I wanted to get the following information:

  • pace
  • segement calculations for 1 km, 1 mile, 5 km, 10 km, 10 miles, half marathon, and full marathon
    • distance
    • time
    • pace
    • starting point
    • ending point
  • real language starting location (not just lat/lon)

First, I got the basic information and set up a set of arrays to track the time and distance.

const eventTime = gpxFileData.gpx.metadata[0].time[0];
const trkpts = gpxFileData.gpx.trk[0].trkseg[0].trkpt;
const trkptLength = trkpts.length;
const startTime = new Date(trkpts[0].time[0]);
const finishTime = new Date(trkpts[trkptLength - 1].time[0]);
const totalTime = (finishTime - startTime) / 1000;
const timeArray = [];
const totalTimeArray = [];
const latLongArray = [];
const distanceArray = [];
let totalDistance = 0;
const totalDistanceArray = [];
let timeCounter = 0;

Then I processed the GPX data and built the arrays

// build time and total time arrays
for (let i = 1; i < trkptLength; i++) {
    let segmentTime =
        new Date(trkpts[i].time[0]) - new Date(trkpts[i - 1].time[0]);
    timeArray.push(segmentTime);
    timeCounter += segmentTime;
    totalTimeArray.push(timeCounter);
}

// loop through trackpoints
for (let i = 1; i < trkptLength; i++) {
    let lat1 = trkpts[i - 1].$.lat;
    let lon1 = trkpts[i - 1].$.lon;
    let lat2 = trkpts[i].$.lat;
    let lon2 = trkpts[i].$.lon;
    let segmentDistance = distance(lat1, lon1, lat2, lon2);

    // distanceArray includes distance for each segment
    distanceArray.push(segmentDistance);

    // totalDistanceArray includes running total
    totalDistance += segmentDistance;
    totalDistanceArray.push(totalDistance);

    // latLongArray includes pairs of coordinates
    latLongArray.push([trkpts[0].$.lat, trkpts[0].$.lon]);
}

Next I used the initial coordinates for my location information

// add the initial coordinates
latLongArray.push([trkpts[0].$.lat, trkpts[0].$.lon]);
let startingLocation = latLongArray[0];

Finally, I built a function that would create a sliding window of a given length (1 km, 5 km, etc) and look for the fastest segment:

const getFastestSegmentData = (segmentName, distanceMin, distanceMax) => {
    let fastestSegmentData = { rawTime: 9999999999 };
    let trackPointValues = parseInt(distanceMin * 50); // skip points <<< desired length
    for (let i = 0; i < trkptLength; i++) {
        for (let j = i + trackPointValues; j < trkptLength; j++) {
            let distanceSeg = totalDistanceArray[j] - totalDistanceArray[i];
            if (distanceSeg > distanceMin && distanceSeg < distanceMax) {
                let timeSeg = totalTimeArray[j] - totalTimeArray[i];
                let newPace = timeSeg / distanceSeg;
                if (newPace < fastestSegmentData.rawTime) {
                    fastestSegmentData = {
                        segmentMin: segmentName,
                        startTime: totalTimeArray[i] / 1000,
                        endTime: totalTimeArray[j] / 1000,
                        startPoint: totalDistanceArray[i],
                        endPoint: totalDistanceArray[j],
                        time: timeSeg / 1000,
                        pace: newPace / 1000,
                        distance: distanceSeg.toFixed(3),
                        timeArray: totalTimeArray.slice(i, j + 1),
                        distanceArray: totalDistanceArray.slice(i, j + 1),
                        rawTime: newPace,
                    };
                }
            }
        }
    }
    return fastestSegmentData;
};

I added a call to the PositionStack API to get a string of the starting location, then added this to each event.

I then added the ability to add data to a SQLite database. I intially went with Firebase when I was considering a web version of the app. After I settled on Electron, I thought a local SQLite database would be better.

Accessing Data

So far I've added functionality for searching the database based on event type (run, walk, or mix), starting and ending dates, and a total distance range.

What's Next?

It's been fun working with Electron! I know I didn't cover much of the actual Electron work here, but honestly that is because it is so easy. I'm sure I'll get deeper into Electron settings as I go, but so far it has been quite easy and straightforward.

In the meantime, I'm still working on Flutter and Dart, and I still plan on doing more with that in the future. I'm a huge fan of Flutter, but I think continuing my JavaScript related work and improving my C# skills are best for now.

Built with Nuxt UI • © 2026 Steve Furches