Behold, a flexible framework for visualizing and sharing motorcycle routes. Just drop a new .kml file into the /data/ directory and make sure its name is registered in routes.json. Your map will update accordingly—new routes will be displayed, route lengths and distance from start/fuel stops are automagically calculated, and GPX/URL buttons added or removed as needed.
This is a static site—it does not rely on any server-side language or database—just HTML, CSS, and a bit of vanilla JS. When you're ready to deploy, all you need is a barebones web server (e.g. Apache, Nginx) sans database. It’s designed to be simple and easy to deploy, maintain, and scale as needed. For example, the live demo linked below is hosted on a Synology NAS in my dining room. 🥳
-
Plan and Export Your Route
- Use any route planning tool—MyRoute-app, Google My Maps, Garmin BaseCamp, etc.
- Export your route as a
.kmlfile (required), and optionally as.gpx(for sharing and/or loading to a navigation app or device) and.url(for links to the original route in your mapping app).
-
Project Structure
/demo/: Base files for new ride route pages.index.html: Main HTML file (uses Bootstrap, loads Google Maps, references/js/main.jsand/css/main.min.css).
data/: Route-specific data files..kml,.gpx,.url: GPS tracks and route links for each segment (e.g.,01-Sample-Route-One.kml).routes.json: List of route base names (e.g.,{ "base": "01-Sample-Route-One" }).build.sh: Helper script to auto-generateroutes.json.
-
File Roles & Data Management
- KML: Generates the map (required).
- GPX: For easy download, sharing, and use in most navigation apps and devices.
- URL: Adds a button linking to the original route for editing or repurposing.
NOTE: A
.urlfile in this context is simply a plain text file containing only a URL in ASCII format, with no extra formatting, metadata, or encoding—just the URL itself on a single line. routes.json: List of route base names (e.g.,{ "base": "01-Sample-Route-One" }).build.sh: Helper script to auto-generateroutes.json.
Logo images for the demo and route pages live in the local ./img/logos/ directory (not /img at the project root). There are two main logos used in the UI:
panel-logo.png— appears to the left of the title in the info panel.map-logo.png— appears at the top right of the map.
Both are referenced with relative paths like ./img/logos/map-logo.png and can be customized by swapping out the image files or updating the HTML/CSS as needed.
By default, both logo images are wrapped in anchor tags (links) in the HTML. You can change or remove these links as desired to point to your own site, a club page, or nowhere at all.
- To remove a logo: Just delete or rename the relevant PNG in
./img/logos/. The code will automatically hide the placeholder if the image is missing—no further action required. - To use your own logo: Replace any placeholder PNG with your own image (using the same filename for instant results, or update the HTML if you use a different name).
- No server restart needed: Logo changes appear as soon as you reload the page.
This makes it easy to brand your map pages with your club, group, or event logo—or remove logos entirely for a minimalist look.
You can provide your Google Maps API key in one of two ways:
If you don't want to use Node/npm at all, you can add your API key directly to the HTML files. In demo/index.html, comment out the Node.js block and uncomment the direct API block, replacing YOUR_API_KEY_HERE with your actual API key:
<!-- <script>
window.GOOGLE_MAPS_API_KEY = "{{ GOOGLE_MAPS_API_KEY }}";
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAPS_API_KEY }}&v=beta&libraries=maps,geometry&callback=initMap">
</script> -->
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY_HERE&v=beta&libraries=maps,geometry&callback=initMap"></script>2. Node/dotenv approach: Use the Express server with dotenv to inject your API key from a .env file. This is the recommended method for development and keeps your API key secure.
Create a .env file in the project root with your Google Maps API key:
GOOGLE_MAPS_API_KEY=your-key-here
PORT=6686 # Optional, defaults to 6686- The server will automatically inject this key into any HTML files it serves.
- HTML files contain placeholder tags:
{{ GOOGLE_MAPS_API_KEY }}that get replaced at runtime.
npm install- Create a
.envfile in the project root with your Google Maps API key:
GOOGLE_MAPS_API_KEY=your-key-here
PORT=6686 # Optional, defaults to 6686-
For production:
npm start
-
For development (with live reload, see below):
npm run dev
- The Express server runs on port 6686 by default, configurable in the .env file.
- Initializes Google Map and configures appearance.
- Loads/parses
.kmlroute files, displays ride paths as colored polylines. - Calculates route mileage (Google Maps Geometry API).
- Generates route legend and download buttons for each route.
- Manages interactive features (route highlighting, marker display).
- Reads from
data/routes.jsonto determine which routes to display.
initMap(): Entry point, sets up map, loads KML routes, builds UI.loadKmlRoute(): Fetches/parses KML, draws polyline, computes mileage.addRouteDownloadButtons(): Builds download table, sets up UI interactions.updateRouteLegend(): Updates color-coded route legend.- Helpers:
getWaypointTitle(role),getColoredSvgIcon(iconPath, color, opacity),setRouteHighlight(activeIndex),hexToRgba(hex, alpha)
- Loads available routes from
data/routes.json. - For each
.kml, loads and draws on the map. - Creates download buttons for GPX/KML if available.
- Interactive features are generated based on loaded data.
- Important Notes:
initMap(): Entry point, sets up map, loads KML routes, builds UI.loadKmlRoute(): Fetches/parses KML, draws polyline, computes mileage.addRouteDownloadButtons(): Builds download table, sets up UI interactions.updateRouteLegend(): Updates color-coded route legend.
Replace your-google-maps-api-key-here with your actual API key.
If you share this project, share instructions for setting up the .env file but do not share your actual API key.
- To add new routes, update
routes.jsonand add the corresponding.kml/.gpxfiles. - Colors and UI styling can be changed in the script or via CSS.
- The script is modular, so you can extend or override helper functions for custom marker icons, legends, or interactivity.
Each waypoint on the map can be assigned a type, which determines the icon used to represent it. Custom icons are chosen by prepending keywords to the waypoint name as described below. Non-named waypoints are styled with a dot, depending on if they were added manually by the user or automatically by the mapping app.
| Type | Icon | Notes |
|---|---|---|
| Manual | Waypoints defined by you manually, a human (ostensibly) in your mapping app of choice | |
| Automatic | Waypoints added automatically (e.g. using the Expand tool in MyRouteApp) |
Each waypoint can have up to four custom icons each. To display the correct icon(s) for a waypoint, name the waypoint in your mapping software with a prefix in this format:
TYPE - Waypoint Name
Where TYPE is one of the supported types below (e.g., GAS - Chevron Station). To add additional types to a single waypoint, delimit the terms with a / like:
GAS/BREAK/LUNCH - Waypoint Name
The icons in this project were designed in Figma, and can be accessed and forked by copying this Figma doc.
Note: To allow the icons to change color with the route lines, manually edit your SVG code to change the fill value from black to currentColor.
Each route polyline is assigned a color from a predefined list in main.js.
- Open
js/main.jsand search forconst colors = [to locate the color palette. - Edit the hex color values in the array to your preference. You can add, remove, or rearrange colors as needed.
- The script will automatically apply these colors to routes, cycling through the list if there are more routes than colors.
This makes it easy to control the visual identity of your maps and ensure each route is clearly distinguishable.
| Value | Context/Selector | File/Line | Notes / Purpose |
|---|---|---|---|
| 1001 | .map-logo (Map Logo) | style/main.scss:49 | Map logo overlay (always on top of map/UI elements) |
| 1000 | #info-panel | style/main.scss:176 | Info panel (fixed, bottom left) |
| 100 | .collapse-toggle | style/main.scss:245 | Collapse toggle button |
| 11 | Marker (custom, highlighted) | js/main.js:717 | Custom marker during highlight |
| 10 | Marker (custom, creation) | js/main.js:550,609 | Custom role marker creation |
| 2 | .panel-title (collapsed) | style/main.scss:311 | Panel title in collapsed state |
| 2 | Polyline (route, highlighted) | js/main.js:668 | Highlighted route line |
| 2 | (Other/legacy marker/polyline) | js/main.js:225 | Possibly legacy or unused |
| 2 | Marker (standard, highlighted) | js/main.js:717 | Standard marker during highlight |
| 1 | .route-label:hover (table) | style/main.scss:467 | Route label hover effect |
| 1 | Marker (standard, creation) | js/main.js:550,609 | Standard waypoint marker creation |
| 1 | Polyline (route, default) | js/main.js:668 | Route line stacking |
| Done | Description | Added | Completed | By |
|---|---|---|---|---|
| ☑️ | Augment the look of non-custom waypoints and/or polylines to indicate intended direction of travel along a given route (e.g. chevrons instead of dots, or perhaps polylines with pointed dashes). | 2024.07.05 | 2025.07.11 | @feralcreative |
| ⬜ | Add mailto links to email route files (kml, gpx, etc.) rather than just downloading | 2025.07.09 | ||
| ⬜ | Social sharing and/or embed code generation might be nice too | 2025.07.10 | ||
| ⬜ | Enable the ability to easily add a logo or banner of your club, riding group, etc. | 2025.07.10 | ||
| ⬜ | Alternately, a lightweight banner with top nav to allow wayfinding for a trips with multiple map pages. | 2025.07.10 |













