{"id":20143,"date":"2026-06-25T09:06:42","date_gmt":"2026-06-25T13:06:42","guid":{"rendered":"https:\/\/nuxx.net\/blog\/?p=20143"},"modified":"2026-06-25T09:06:43","modified_gmt":"2026-06-25T13:06:43","slug":"trailmaps-app-map-generator","status":"publish","type":"post","link":"https:\/\/nuxx.net\/blog\/2026\/06\/25\/trailmaps-app-map-generator\/","title":{"rendered":"trailmaps.app + Map Generator"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-scaled.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"942\" src=\"https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-1024x942.png\" alt=\"\" class=\"wp-image-22384\" srcset=\"https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-1024x942.png 1024w, https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-300x276.png 300w, https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-768x706.png 768w, https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-1536x1412.png 1536w, https:\/\/nuxx.net\/blog\/wp-content\/uploads\/2026\/06\/image-2048x1883.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">trailmaps.app Website on 2026-Jun-25<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve been using it for a while now, so I guess it&#8217;s a good time to announce the revamp \/ relaunch \/ whatever of <a href=\"https:\/\/trailmaps.app\">trailmaps.app<\/a>. This is a personal project website which started one frosty January morning as I sat in an Ishpeming rental waiting for temps to rise before heading out on a fatbike ride and is now a hub for hosting maps I&#8217;ve generated of various trail systems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The site started out as a hand-written HTML landing page, <a href=\"https:\/\/trailmaps.app\/old\/\">hand-made Leaflet maps<\/a>, and <a href=\"https:\/\/trailmaps.app\/pdf\/\">directory indexes of my trail map PDFs<\/a> where I learned a bunch about showing <a href=\"https:\/\/openstreetmap.org\">OpenStreetMap<\/a> (OSM) data via the web, generating tiles, etc (for example, see <a href=\"https:\/\/nuxx.net\/blog\/2021\/09\/22\/making-an-online-ramba-trails-map\/\" data-type=\"post\" data-id=\"19428\"><em>Making an Online RAMBA Trails Map<\/em><\/a> from 2021). Now it&#8217;s now hosting feature-rich web-based maps that help people find their way around mountain bike trails.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The root of the idea was cooked up on a long drive; I wanted something akin to the subway-map-style&#8217;d <a href=\"http:\/\/www.singletrackmapping.com\/uploads\/4\/7\/3\/1\/47318895\/ramba-trail-map-singletrack-maps_1_orig.jpg\">official RAMBA print map<\/a> (parallel lines over a single trail to illustrate the trail&#8217;s membership in multiple routes), but web based. And maybe usable on a phone. And after the <a href=\"https:\/\/nuxx.net\/blog\/category\/mapping\/\" data-type=\"category\" data-id=\"57\">early learning<\/a> then quite a bit of work this spring&#8230; Here it is.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of just doing a single map (one-map-at-a-time coding \/ tile generation \/ etc) as I had in the past, I now have a full-on map generator that takes data from OSM and other online\/open\/free sources, combines it all, and generates static content that&#8217;s easy to host and cachable so it works if a device loses cell service. It&#8217;s basically as close to an app as one can get while still staying web-based.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By being hand-curated (that is, not just auto-generated off of all OSM data) these maps also fill a long-standing gap with other online maps (eg: <a href=\"https:\/\/www.trailforks.com\/\">Trailforks<\/a>, <a href=\"https:\/\/www.mtbproject.com\/\">MTB Project<\/a>, <a href=\"https:\/\/strava.com\">Strava<\/a>, <a href=\"https:\/\/ridewithgps.com\/\">RideWithGPS<\/a>) in that they don&#8217;t style (color) the routes the way official park maps and signage do, making what a rider sees on their phone challenging to align with what they see on a signpost next to a trail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, compare these maps of the <a href=\"https:\/\/www.metroparks.com\/sheldentrails\/\">Shelden Trails at Stony Creek Metropark<\/a> to the <a href=\"https:\/\/www.metroparks.com\/wp-content\/uploads\/2022\/04\/2022-Shelden-Trails-Map.pdf\">PDF of the official park map which is at the trailhead<\/a>, and whose color-matched signs are along all loops.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.trailforks.com\/region\/stony-creek-metropark\/\">Trailforks<\/a>: Only difficulty colored.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.mtbproject.com\/directory\/8015336\/stony-creek-metropark\">MTB Project<\/a>: Only difficulty colored.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.strava.com\/maps\/global-heatmap?sport=Ride&amp;style=dark&amp;terrain=false&amp;labels=true&amp;poi=true&amp;cPhotos=true&amp;3d=false&amp;gColor=hot&amp;gOpacity=100#14.43\/42.72237\/-83.1112\">Strava<\/a>: Only dashed lines or a heatmap, unless someone&#8217;s created a route.<\/li>\n\n\n\n<li><a href=\"https:\/\/ridewithgps.com\/explore?b=b%21-83.132504%2142.710290%21-83.081369%2142.731025&amp;length=any\">RideWithGPS<\/a>: Just dashed green lines, unless someone&#8217;s created a route.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Then look at the <a href=\"https:\/\/trailmaps.app\/shelden\">trailmaps.app map of the Shelden Trails at Stony Creek Metropark<\/a>, for example, the <a href=\"https:\/\/trailmaps.app\/shelden\/#share=16\/42.72709\/-83.10843\/r\/12627723\">Beach<\/a> trail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s a bunch more features that this brought about, which I won&#8217;t dive into as much depth, but which I&#8217;m still quite proud \/ happy with. The end results are what I want in maps, and it&#8217;s nicely reusable:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Reusable map generation engine; I write a <a href=\"https:\/\/en.wikipedia.org\/wiki\/YAML\">YAML<\/a> description of the map (title, OSM references, info that can&#8217;t be found in OSM) and it makes the map. If the engine or data gets updated, re-run the map and\/or website generation tools.<\/li>\n\n\n\n<li>PWA (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Progressive_web_app\">Progressive Web Apps<\/a>), so they are installable app-ish, but without the app store overhead.<\/li>\n\n\n\n<li>OSM data is not fetched live &#8212; it&#8217;s a snapshot taken map-generation time &#8212; meaning errant edits don&#8217;t break the map.<\/li>\n\n\n\n<li>Zero user\/usage tracking, including having all assets loaded from trailmaps.app. (I&#8217;m trying to support less and less online tracking while still providing a good tool.)<\/li>\n\n\n\n<li>Ability to generate maps custom from non-OSM data, such as race or group ride routes.<\/li>\n\n\n\n<li>Thorough, proper (read: non-shady) SEO such as <a href=\"https:\/\/www.opengraph.xyz\/\">OpenGraph<\/a> previews and metadata, making link embedding, sharing, and site discovery by search engines work well.<\/li>\n\n\n\n<li>Hostable for cheap since the only server requirements are TLS and RANGE requests. A $5\/mo Nanode from Akamai (formerly Linode) easily does it all.<\/li>\n\n\n\n<li>Stand-alone maps (each map is a self-contained site) makes it possible for them to be hosted elsewhere, such as if one was made for a trail club\/org, etc.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">And yes, I heavily used AI-assisted development for this. It was quite educational as since I knew the inputs and outputs, use the maps myself, and was able to do quite a bit of QA, the result is great. At my day job in IT there is (as typical) a huge emphasis in using AI tooling to assist us with our work. This served as a nights-and-weekends project that was quite educational and will benefit me in day-job stuff while achieving a personal goal of making something I wanted and useful for others. It also illustrated the interesting balance between what AI-generation is good at (code, bug finding) and what it&#8217;s not (wording, stylistic choices that aren&#8217;t simple clones, avoiding feature bloat).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m subsequently making the <a href=\"https:\/\/github.com\/c0nsumer\/trailmaps.app-map-generator\">map generator itself<\/a> available under the MIT license so others can use it. I do have an extensive toolchain for generating the website (takes a definition file and generates the maps, creates preview images, updates the index, rsync&#8217;s it to the server) but that part is staying closed \/ non-released because it&#8217;s very my-setup-specific.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been using it for a while now, so I guess it&#8217;s a good time to announce the revamp \/ relaunch \/ whatever of trailmaps.app.&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/nuxx.net\/blog\/2026\/06\/25\/trailmaps-app-map-generator\/\">Continue reading<span class=\"screen-reader-text\">trailmaps.app + Map Generator<\/span><\/a><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,11,57],"tags":[],"class_list":["post-20143","post","type-post","status-publish","format-standard","hentry","category-cycling","category-making-things","category-mapping","entry"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/posts\/20143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/comments?post=20143"}],"version-history":[{"count":8,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/posts\/20143\/revisions"}],"predecessor-version":[{"id":22386,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/posts\/20143\/revisions\/22386"}],"wp:attachment":[{"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/media?parent=20143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/categories?post=20143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nuxx.net\/blog\/wp-json\/wp\/v2\/tags?post=20143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}