Day 5 - A Journey

November 8, 2024 ยท 4 min read

There and back again

My partner and I decided to do a trip to Victor, Idaho this last June to visit a friend named Duncan. Having just read the first two Dune books I get a good hoot out of visiting Duncan Idaho. One of the days out there, he and I decided to do a mountain bike ride that went up Teton Pass and through the hills, starting from the Idaho side. The climb started out a bit hairy since we were riding on the highway shoulder for the first 6 miles, but as soon as we reached the trailhead we were deep in the wilderness. The views were amazing and the singletrack was world class. We even saw a moose within the first few minutes of being on trail.

Another few, very hard miles of climbing eventually got us to a ridge that overlooked the many hills surrounding Teton Pass. There were a number of places we ended up needing to hike a bike but it was totally worth it. The descent at the end back to the car was unreal, my favorite kind of chundery, loose terrain. Needless to say, I can't wait to get back there. In the meantime, here is a little map animating the ride that we did. The play/pause button doesn't do a whole lot besides what you'd expect.

How it was built

I record all my rides using an old Garmin 510 bike computer so I was able to download the GPX file of this ride from the Garmin Connect website that stores all my rides. Using this useful togeojson utility built by Mapbox, I was able to just use the CLI to convert my GPX file to a GeoJSON without any effort, nice. Now for animating the line.

I start with following the Mapbox guide on animating line. There were some small changes I needed to make like splicing parts of my geojson file during the animation rather than generating points like shown in the tutorial. The resulting loop of my animation looks something like this.

if (progressRef.current >= totalPoints) {
  progressRef.current = 0;
}

const coords = route.features[0].geometry.coordinates.slice(
  0,
  progressRef.current
);

const animatedLine = {
  ...route,
  features: [
    {
      ...route.features[0],
      geometry: {
        ...route.features[0].geometry,
        coordinates: coords,
      },
    },
  ],
};

(mapRef.current?.getSource('bikeRoute') as GeoJSONSource)?.setData(
  animatedLine
);

progressRef.current += 1;

animationRef.current = requestAnimationFrame(frame);

I wanted to add a small play/pause button to the map like shown in the guide but as soon as I started adding state to it I came across a number of issues with my existing Map component. The first was related to switching the theme which was causing the map to be re-rendered and the data to be re-fetched. This was related to how I was changing the style based on the active theme. Turns out using setStyle is the proper way to do it rather than in the map initializer. This then caused another issue of the map data disappearing since switching a style in mapbox causes it to clear out the sources and layers. I added some logic to re-add both whenever the styledata event is triggered. Another unexpected issue was related to the pause/play button which would also cause the map child component to rerender whenever the playback state changed which was fixed with some useCallbacks. The parent/child rendering issues ended up being fixed using a combination of React.memo, useCallback, and useMemo between the Map component and its parent component. I posted the latest version of the map component here. I ended up also needing to rewrite the maps for the first few days of this blog to fit in with the new component. There are no visual changes to the user, but it should be faster now and fetch a lot less data. There are still some issues that need to get fixed, but for now it's fine, just don't toggle the theme too many times. ๐Ÿ˜