Roadtrip Companion

TLDR: Check out my cool roadtrip app; it’s perfect for a Memorial Day road trip!

A truism of the startup world is that there are no new ideas; everything you have ever thought of has been tried before. If you’re lucky, your timing is right, or you’ve figured out that one insight that gets it over the hump, or you just have more money — but you ain’t the first. Which is why I’ve always been surprised that, twenty-plus years after first thinking about this roadtrip companion app, nobody has made it happen. But then again I didn’t build it either, because the confluence of tech was never quite right for a side project. Until now!

Roadtrip Companion

Here’s the pitch. When you’re on a long drive — even on our supposedly personality-free interstate highway system — you’re driving through amazing history and culture and science. What’s growing on that field? What’s with the big tower over there? How did that mountain get its weird shape? Who plays at that baseball field? What’s with the big canal next to the road? Why is this town here, in the middle of nowhere? It never ends and the answers are awesome.

For years, I’ve wanted to have an app — not a routing tool or something to help me find a bathroom (both important things) — that continuously feeds me fun facts about wherever I am. And it has to work without a bunch of interaction, because my Rivian already bitches like a backseat driver, constantly nagging me to “look at the road.” So judgy.

Try it yourself at https://seanno.github.io/points/ … or maybe more interestingly, these simulated (as the crow flies) trips from Blaine, WA to Vancouver, BC or Bath, ME to East Boothbay, ME. it’s optimized for a mobile device in landscape mode, but it’s just a web app, so your desktop browser is fine too.

A quick feature tour

When you open the app, it requests location permission and plots that on top of a road map, courtesy of OpenStreetMap and Leaflet — more amazing open source technology that generous people have caused to exist in the world. Standard zoom and pan stuff works as you’d expect; use the “Recenter” button to focus the map back on you.

As your location updates, it keeps track of speed and heading, and queries (the grand-daddy of open source information) Wikidata to identify points of interest near and ideally in front of you. Every minute, a new point is plotted on the map and shown on the right-side pane, with a picture and (very) short description if available. As an aid to keeping your eyes on the road, clicking the “bell” enables a chime each time a new one is shown. Click the “FS” button to enter full-screen mode which looks way better, and if you want to advance through points more quickly, use the “Next” button.

This is neat, but the really cool part is hiding behind the “More” button. This prompts Claude AI to act as a local tour guide, giving you a couple of quick paragraphs about that location. It’s shown on screen and, much better for driving, is read aloud automatically.

One caveat on the AI integration — the app runs entirely client-side including the calls to Claude. This is great for lots of reasons but does mean that if I included my own Claude API key, anyone could grab it and use it for anything. Since this app was just for entertainment purposes, I avoid this by prompting for a key the first time the “More” button is clicked, and persisting it in browser local storage. You can get one at https://platform.claude.com and the cost is truly trivial, pennies per day of active use. If you just want to give it a try and aren’t a jerk, let me know and I can hook you up temporarily; drop me a note.

That’s it — simple, single purpose, and at least IMNSHO incredibly rewarding. I’ve spent drives the last few months fine-tuning the behavior, and it seems pretty dialed in.

Some interesting implementation details

Most of the interesting things about this app come down to managing the queue of locations so that things stay interesting and available.

Querying “ahead”

First of all, Wikidata is a truly huge RDF data store, and it responds to potentially really expensive SPARQL queries, including geolocation, for free. Not surprisingly, it can be a bit slow to respond and nobody damn well better complain about that. But it does require some care and feeding — the site makes its queries on a background thread, and tries to identify key points at which you’re almost out of good stuff so there’s time to find new ones, i.e.:

  • When the queue length gets too small (duh),
  • When you’ve travelled a certain distance, or
  • When all the remaining points are behind your direction of travel.

The app also tries to look “ahead” in space — using your heading and speed to target searches not just where you are right now, but where you’ll be over the next few minutes. There’s a lot of angular math going on here, and I’m thankful to have had Claude Code helping me out with all that. Yeesh.

What is “interesting” anyways?

I could keep tweaking this forever. RDF is really powerful, but it’s also kind of a pain in my a**. Everything is very general and hierarchical, without a lot of defined structure. You can see the basic query in the code; pretty much put together by trial and error.

The points returned are filtered and sorted using a few different heuristics:

This trickiest part of all this is the failure case. We prefer all of these rules, but the bottom line is that we always want to show something, so the code has to fall back if necessary.

The local tour guide

It is truly amazing how good Claude is at generating fun facts about just about any random location I’ve happened to drive by. We’re talking really obscure stuff, like drainage ditches and little pocket parks way out in the boonies.  My prompt isn’t even very sophisticated; it’s just magic.

But it does have a style, and that style can become really grating over time. “If you’re the kind of person who enjoys the history of community water systems” … “It’s the kind of place most people just drive by” … you’ll see what I mean.

My first idea for fixing this was to add more context — feed the model its last X descriptions and say “make it sound different.” But Claude itself came up with a better idea — we created a set of “prompt angles” that emphasize different approaches or styles, and randomly pick a new one with each request. A much cheaper option, and one that works very well. Nice.

Testing is hard

I have to admit that, especially in retirement, I’m pretty lazy about automated testing. At the risk of seeming (and maybe being) a bit arrogant, I’m just a pretty good coder. I walk through my code by hand, implement failure cases the first time, and am not afraid to throw away spaghetti and start over. Especially for projects where I’m a solo developer, the cost of a bunch of automation rarely pencils out.

In most ways, this holds true for this app as well. But the tough thing is — you can’t really see how all of these heuristics perform without actually getting in the car and driving around a lot, in a lot of places. And while I adore a good trip, it’s sadly not realistic to hit the road every time I tweak this code.

The obvious fix was to create a mock geolocation service that exposes the same basic geolocation API as the browser but using a synthetic route. This not only proves to be incredibly useful but also quite entertaining. I linked a few mock routes at the top of this piece; here are a few more just for kicks:

I’m looking forward to giving this a try next month when we’re on our amazing narrowboat canal trip in the UK. And as always, I’d love to hear your ideas and critiques — or just steal the code for your own purposes, it’s license-free!

Image of Government House in Saint John's, Antigua, showcasing a colonial-style building with a green roof surrounded by tropical landscaping.

Map-engraved / heart-inlaid coasters

As a present for my wife this year, I made a set of heart-shaped coasters to commemorate key times/places in our past — where we met, were married, adopted pets and had kids. Each coaster has a map, a heart inset at the key location, and a description on the back. It was a fun project using a few techniques I thought others might find useful, so just a quick post to walk through it.

I used 1/4″ MDF with maple veneer but the engraving covers the full front of the coaster so you could really use any light-colored wood with minimal grain pattern (you kind of want a blank canvas). The heart insets are translucent red 1/8” acrylic, actually part of a pack I actually got for Valentine’s last year! I was worried that insetting the half-thickness acrylic into the coaster might be an awkward fit, but it worked great. The backs are 2mm adhesive cork that I use a lot for tabletop projects.

The Hearts

I started with a simple vector heart shape and scaled it to the target size of the coaster (4.5” square works for most mugs and glasses). I then scaled copies to three additional sizes:

  1. One inset 1/8” for the cork backing.
  2. A small one 1/2″ square for cutting the hole for the inlay.
  3. The small one outset .007” for the acrylic, which (when flipped over) made a snug fit into the hole.

I use Inkscape for most of my designs. Inset/outset from the “Path” menu is the freaking best feature ever — the only trick is that the size of each step is a global setting, so be sure to double-check under Edit / Preferences / Steps / “Inset/Outset by” to be sure it’s what you want. Seven thousandths of an inch is about perfect for the kerf I get on most 1/8” and 1/4″ wood and acrylic. I’m sure it varies a little but not enough to worry about. Remember to flip the insert over before pressing it in, which takes care of the every-so-slightly-conical cut you get from the laser.

The Maps

OpenStreetMap is a fantastic resource — community-produced and openly licensed, even for commercial distribution (attribution is required; see their guidelines for details). There are a bunch of ways to use the data; this is the process I finally worked out for my purposes:

  1. Navigate to the area you want to capture and zoom in/out as needed.
  2. Export the map as a PDF:
    1. Click the “share” button on the right side of the screen.
    1. Check the “Set custom dimensions” checkbox and select the desired area. Select more area than you need; it provides some wiggle room and we’ll clip it out later.
    1. Set the format to “PDF”.
    1. Play with the “Scale” setting to get a final image that works for you. I found it easiest to start with 1:5000 and adjust from there.
    1. Click Download.
  3. Open the PDF in Inkscape and make edits (remove landmarks, reposition street names, etc.) if needed.
  4. Paste your shape (in my case the 4.5” heart) and position it over the map.
  5. “Select All” and choose Object / Clip / Set to clip the map to your shape.
  6. Optional: I pasted in another copy of the 4.5” heart with a wide stroke, which made a nice outline.
  7. Under the File menu, choose “Export PNG Image”. Make sure “Drawing” is selected at the top and then export.
  8. Finally, open the new PNG file in Inkscape, add additional elements (i.e., the cut lines for the heart and inlay hole) and save as an SVG ready for the Glowforge.

All that work to massage the map into a bitmap (PNG) is worth it — the Glowforge handles the engraving super well.

Printing and Assembling

Printing requires three Glowforge runs, one for each material. For the wood, I used the “Thick Maple Plywood” settings and they worked great, engraving with Draft Photo / Convert to Dots with default settings except two passes instead of just one. The acrylic worked fine as “Medium Red Acrylic”. For the cork I configured “uncertified” material with a height of 2mm; engrave at speed 80 / power 10% and cut at 400 / 100%.

After pressing in the inlays, I poured on two coats of TableTop Epoxy, sanded off the drips, stuck on the cork backs, and that’s a wrap.

I really love working with the maps — such a neat way to personalize stuff. Hope folks will get some use out of the technique, and if you give it a try, let me know if I can help out. Kachow!