What I Learned Building ADV Agent: An AI Motorcycle Route Community
Honestly, I didn't expect building a motorcycle route sharing community to be this complicated. When I started ADV Agent three months ago after finishing Trip Agent, I thought "hey, it's basically the same thing but for ADV riding - how hard can it be?"
Spoiler: It's way harder. And I learned more in these three months than I did in the previous six months working on my first project. Let me walk you through what I built, what went wrong, and what I learned the hard way.
What is ADV Agent Anyway?
If you're into adventure motorcycling (that's what ADV stands for), you know the problem: the best routes are hidden in ride reports, scattered across Facebook groups, buried in forums, or just in some guy's GPS device that never gets shared. Everyone wants new roads to explore, but nobody has a good way to discover or share them.
ADV Agent is my attempt to fix that. It's an AI-powered community where riders can share their favorite ADV routes, discover new ones based on their skill level and bike, and get AI recommendations for the next adventure.
Check it out on GitHub: https://github.com/kevinten10/ADV-Agent ⭐
The core idea is simple:
- Riders upload their GPS tracks from rides
- AI processes the track to extract interesting roads (twisties, elevation, scenery)
- The community adds descriptions, photos, and points of interest
- Other riders can search for routes near them that match their preferences
- AI recommends similar routes you might like
Sounds straightforward, right? Yeah, that's what I thought too. Let me show you where the hidden complexity lives.
The First Big Surprise: GPS Data Is a Mess
I came into this project thinking "GPS is just latitude and longitude - how hard can it be to process?" After all, I'd already dealt with GPS in Trip Agent. How much harder could ADV be?
Oh boy. The difference between street riding GPS and ADV riding GPS is night and day.
Here's what nobody tells you: In the mountains, where most ADV riding happens, GPS is just straight-up wrong sometimes. You'll get jumps in coordinates where your signal dropped, points scattered across the landscape because of tree cover, and sometimes entire segments just go missing.
Let me show you the dirty little filtering function I ended up writing to clean this mess:
func CleanGPSPoints(points []GPSPoint, maxJump float64, intervalSeconds float64) []GPSPoint {
if len(points) == 0 {
return nil
}
cleaned := make([]GPSPoint, 0, len(points))
lastPoint := points[0]
cleaned = append(cleaned, lastPoint)
for i := 1; i < len(points); i++ {
current := points[i]
// Calculate distance from last point
dist := haversine(lastPoint.Lat, lastPoint.Lng, current.Lat, current.Lng)
timeDiff := current.Timestamp.Sub(lastPoint.Timestamp).Seconds()
// Skip impossible jumps (GPS error)
if dist > maxJump && timeDiff < 60 {
continue // this is probably a GPS glitch
}
// Throttle points to avoid too much data
if timeDiff < intervalSeconds {
continue
}
cleaned = append(cleaned, current)
lastPoint = current
}
// If we ended up with too few points, it's probably a bad track
if len(cleaned) < 10 {
return nil
}
return cleaned
}
This is the humble beginning of my GPS cleaning pipeline. And it's still not enough. I've had to add:
- Outlier detection - Remove points that are 1+km away from the previous/next point
- Elevation smoothing - GPS elevation is garbage, so we use a terrain API to correct it
- Gap filling - If there's a big gap in the track, we try to interpolate or at least warn the user
The lesson I learned the hard way: Never trust GPS data from the wild, especially in the mountains. Expect everything to be broken, and build your cleaning pipeline accordingly.
The AI Recommendation Paradox
Here's something that's been eating at me: In a community of explorers, do you really want personalized AI recommendations that only show you what you already like?
Think about it. ADV riding is all about discovery. The whole point is to find that hidden road you never knew existed. If your AI just gives you more of what you've already ridden, you end up in a filter bubble - you never discover that amazing new region outside your comfort zone.
I actually fought with this for weeks. My first approach was the standard collaborative filtering: "Riders who liked this route also liked..." But that creates bubbles. Riders who stick to paved roads never see dirt tracks, and vice versa.
So what did I do? I intentionally inject 30% exploration into every recommendation.
Here's the simplified version of the current algorithm:
func GetRecommendations(user User, limit int) []Route {
// 70% from matching user preferences
matches := findMatchingRoutes(user.Preferences, int(float64(limit)*0.7))
// 30% completely random from outside their comfort zone
exploration := findRandomExplorationRoutes(user, int(float64(limit)*0.3))
// Shuffle them together so users don't know which is which
result := append(matches, exploration...)
shuffle(result)
return result
}
It sounds crazy, right? Throwing randomness into your recommendations on purpose. But here's what happened: engagement went up. People actually click on the "weird" recommendations, try them out, and often find new favorite roads they never would have discovered otherwise.
Pros: More discovery, less filter bubble, happy explorers.
Cons: Sometimes you get a recommendation that's completely wrong for you. The user has to deal with it.
Is this the perfect solution? I don't know. But it feels right for this community. Sometimes the best discoveries are the ones you weren't looking for.
Trust Through Transparency, Not Perfection
Another thing that surprised me: In a community like this, trust doesn't come from perfect moderation. It comes from transparency.
When I started, I thought I needed to heavily moderate routes to make sure everything was "quality." But then I realized something: Riders have different definitions of quality. What's "too technical" for me is "perfect" for someone else. And what's "boring" on a Sunday cruise might be exactly what someone wants for a training ride.
So instead of removing "bad" routes, I add time-decay transparency:
Every route shows when it was last ridden. If a route hasn't been ridden in five years, we tell you that upfront. Roads change, forests burn, landslides happen - it's not that the route is bad, it's just that the information might be out of date.
func routeFreshness(lastRidden time.Time) string {
yearsSince := time.Since(lastRidden).Hours() / 24 / 365
switch {
case yearsSince < 1:
return "Recently ridden - information likely up to date"
case yearsSince < 3:
return "Several years old - conditions may have changed"
default:
return "Older route - expect possible changes"
}
}
This does two things:
- It manages user expectations - nobody expects a 10-year-old route to be exactly as described
- It encourages riders to update the route if they've ridden it recently
- It removes the need for me (the maintainer) to play gatekeeper
This has worked way better than I expected. The community self-corrects, and I don't have to spend all day moderateing. Trust me, that's a win for everyone.
Attribution Matters: How to Get People to Share
Here's a sociological problem I never thought about: If people put in the work to find and share an amazing route, they want credit for it. If you don't give them permanent credit, they won't share.
It seems obvious now, but I missed it in the beginning. My first version just had "contributor" listed as a username without any permanent link to their contributions. Guess what? People didn't bother sharing much.
So I completely redesigned it:
- Every route permanently links to the original submitter
- Riders can see all routes a person has shared on their profile
- The community can "thank" a route, which goes to the submitter's score
- Top contributors get featured on the community page
It's crazy what a difference this made. Sharing went up 3x overnight. People want their work recognized, even in a hobby community. If you're building any kind of content-sharing community, don't make this mistake - attribution needs to be front and center from day one.
Privacy Considerations: Protecting the Trails
This one is near and dear to my heart. One of the reasons great ADV routes stay great is that they don't get hammered with hundreds of riders every weekend. Too many riders can destroy a sensitive trail, cause erosion, and create conflict with landowners.
So I built in a simple but effective privacy feature: route submitters can intentionally blur the starting coordinates.
When you share a route, you can choose to:
- Share exact starting point (if it's a public trailhead with plenty of parking)
- Blur starting point by 1-3 kilometers (to keep the exact location secret but still give people the general area)
- Keep the entire route private (just for you)
This way, people can still share the description and the experience without opening up a fragile trail to thousands of riders. It's a small feature, but it's important for long-term sustainability.
func blurCoordinates(lat float64, lng float64, blurKm float64) (float64, float64) {
// Blur within roughly blurKm kilometers
// This is approximate - good enough for privacy, not for navigation
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// Convert km to degrees (very approximate)
latDelta := blurKm / 111.0
lngDelta := blurKm / 111.0 * math.Cos(lat * math.Pi / 180.0)
// Random offset between -blurDelta and +blurDelta
latOffset := (rng.Float64() * 2 - 1) * latDelta
lngOffset := (rng.Float64() * 2 - 1) * lngDelta
return lat + latOffset, lng + lngOffset
}
It's not military-grade cryptography or anything - but it doesn't need to be. It just needs to keep out the casual crowds that would love the trail to death. If you really want to find it, you can still put in the work. That's exactly the balance we want.
Tech Stack: What I Built It With
I chose a fairly simple stack for this project - nothing too fancy, but everything gets the job done:
- Backend: Go with Gin framework - I like Go for API services, it's fast, compiles to a single binary, easy to deploy
- Database: PostgreSQL with PostGIS for spatial queries - because when you need to find "routes within 50km of me," PostGIS does all the heavy lifting
- Storage: Cloudflare R2 for storing photos and GPX files - no egress fees, which is perfect for a side project
- AI: Currently using GPT-4 to process route descriptions and generate recommendations - nothing fancy, just good enough for now
- Frontend: React with TypeScript - I'm not a frontend expert, but it gets the job done
Wait, you want to see the actual route search query? Here's how you find all routes within a certain distance of a point in PostGIS:
SELECT
r.id,
r.name,
r.description,
ST_Distance(r.start_location, ST_SetSRID(ST_MakePoint($1, $2), 4326)) AS distance
FROM routes r
WHERE
ST_DWithin(r.start_location, ST_SetSRID(ST_MakePoint($1, $2), 4326), $3)
AND r.public = true
ORDER BY distance
LIMIT $4;
See? That's it. PostGIS does all the hard work. I love when good tools just work.
Pros & Cons: Let's Be Honest
Okay, let's cut the marketing crap. Here's what's good about what I've built so far, and what's still missing:
Pros ✅
- Intentional exploration - 30% random recommendations mean you actually discover new things, not just more of the same
- Transparency over moderation - Time-decay labels instead of deleting old routes means more content and less work for maintainers
- Privacy for trails - Blurred starting coordinates protect sensitive areas from overuse 4 Proper attribution - Contributors get permanent credit, which encourages sharing
- Simple tech stack - Easy to deploy, easy to maintain, no fancy Kubernetes cluster needed for a side project
Cons ❌
- Mobile app is still in progress - Right now it's just web, which isn't ideal when you're actually on the bike
- Offline maps need work - When you're in the mountains with no cell service, you need offline maps downloaded ahead of time - this works but isn't super smooth yet
- AI recommendations can still be hit or miss - The 30% exploration approach is good, but the matching algorithm still needs improvement
- Battery life considerations - GPS tracking kills your phone battery on long rides, we need better background optimization
- Small community right now - It's brand new, so not many routes shared yet - obviously that takes time
So yeah, it's not perfect. What side project is? But it's solving a real problem for me, and I'm enjoying working on it.
What I've Learned Building This
Looking back at three months of work, what are the big takeaways?
Vertical communities have unique problems you can't anticipate - Even though ADV Agent is similar to Trip Agent on the surface, the specific use case completely changes the problem. Don't assume "it's just another X" - expect surprises.
Sociological problems are harder than technical problems - Getting attribution right, building trust, dealing with privacy - these aren't algorithm problems, they're human problems. And they're harder.
Sometimes the counterintuitive approach works better - Injecting randomness into recommendations, transparency instead of moderation - these went against my initial instincts but worked better.
Privacy isn't just about user data - it can also be about protecting places - I never thought about this before starting, but it's become one of the most important features.
You don't need a huge team or fancy infrastructure to build a useful community product - This is just me working on weekends, and we already have something usable. Don't be afraid to start small.
Wrapping Up
Building ADV Agent has been a wild ride. I went into it thinking "it's just another route sharing app" and ended up learning about GPS data cleaning, recommendation paradoxes, community trust, trail privacy, and attribution economics.
If you're a motorcyclist, I'd love for you to check it out and share a route. If you're building a community project, I hope some of these lessons I learned the hard way help you avoid the mistakes I made.
What do you think? Have you ever built a community project where the hidden sociological problems ended up being harder than the technical ones? Did you ever intentionally add randomness to recommendations? Drop a comment below - I'd love to hear your experiences.
This post is part of my series documenting side project development. Every few months I build something, write about what I learned, and put the code on GitHub. Follow me if you're into that kind of thing!
Top comments (0)