<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Daniel Reis</title>
    <description>The latest articles on Forem by Daniel Reis (@danielscoffee).</description>
    <link>https://forem.com/danielscoffee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1162421%2F049ef346-8d59-4b69-9027-e11ff05eef1a.jpg</url>
      <title>Forem: Daniel Reis</title>
      <link>https://forem.com/danielscoffee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danielscoffee"/>
    <language>en</language>
    <item>
      <title>Building PathCraft: An Open-Source Routing Engine in Go</title>
      <dc:creator>Daniel Reis</dc:creator>
      <pubDate>Sun, 11 Jan 2026 00:43:12 +0000</pubDate>
      <link>https://forem.com/danielscoffee/building-pathcraft-an-open-source-routing-engine-in-go-4had</link>
      <guid>https://forem.com/danielscoffee/building-pathcraft-an-open-source-routing-engine-in-go-4had</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I started building &lt;strong&gt;PathCraft&lt;/strong&gt;, I had a simple goal: create a routing engine that I could actually understand, extend, and deploy without vendor lock-in. What began as an experimental side project has evolved into a modular, multimodal routing engine that handles everything from pedestrian navigation to public transit routing.&lt;/p&gt;

&lt;p&gt;In this post, I'll share the journey of building PathCraft. Architectural decisions, the algorithms powering it, and the lessons learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is PathCraft?
&lt;/h2&gt;

&lt;p&gt;PathCraft is an experimental walking and transit routing engine written in Go. It's designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;reusable Go library (SDK)&lt;/strong&gt; that developers can embed in their applications&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CLI application&lt;/strong&gt; for quick routing queries&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;HTTP server&lt;/strong&gt; for production deployments&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;embeddable engine&lt;/strong&gt; for integration into larger systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core philosophy? &lt;strong&gt;Correctness first, performance second.&lt;/strong&gt; Too many routing engines sacrifice readability and maintainability for marginal performance gains. PathCraft takes a different approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Go?
&lt;/h3&gt;

&lt;p&gt;Go was a natural choice for this project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Go's compiled nature gives us near-native performance without sacrificing developer productivity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: The language's minimalist design aligns with PathCraft's philosophy of clarity over cleverness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: Built-in goroutines and channels make parallel routing a future possibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Binary&lt;/strong&gt;: Easy deployment—just ship a binary, no runtime dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Core Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom A* Implementation&lt;/strong&gt;: Hand-rolled for maximum control and educational value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAPTOR Algorithm&lt;/strong&gt;: For efficient public transit routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OSM Parser&lt;/strong&gt;: Ingests OpenStreetMap data to build walkable graphs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTFS Parser&lt;/strong&gt;: Handles General Transit Feed Specification data for transit schedules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Haversine Calculations&lt;/strong&gt;: Geographic distance computations for heuristics&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture: The Modular Monolith
&lt;/h2&gt;

&lt;p&gt;One of the most important decisions was the architecture. I initially considered a Hexagonal (Ports and Adapters) architecture, but ultimately chose a &lt;strong&gt;Modular Monolith&lt;/strong&gt; approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Hexagonal?
&lt;/h3&gt;

&lt;p&gt;For a project focused on algorithmic correctness and rapid iteration, Hexagonal architecture introduces unnecessary overhead. The indirection layers and abstraction boundaries, while valuable for large enterprise systems, would slow down development without providing proportional benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Modular Monolith Advantage
&lt;/h3&gt;

&lt;p&gt;The codebase is organized into well-defined modules with explicit boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/internal         → Private core logic
  /graph          → Graph data structures
  /geo            → Geographic calculations
  /routing        → A*, RAPTOR algorithms
  /osm            → OpenStreetMap parsing
  /gtfs           → Transit data parsing

/pkg/pathcraft/engine  → Public API (the only thing users import)
/cmd/pathcraft         → CLI entrypoint
/web                   → Visualization tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure maintains low coupling and high cohesion while keeping the system easy to reason about. The dependency flow is strict: &lt;strong&gt;core logic never depends on infrastructure&lt;/strong&gt; (HTTP, CLI, file I/O).&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive: The A* Algorithm
&lt;/h2&gt;

&lt;p&gt;At the heart of PathCraft's walking router is the A* algorithm. Here's a simplified view of how it works:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Idea
&lt;/h3&gt;

&lt;p&gt;A* combines the best of Dijkstra's algorithm (guaranteed shortest path) with heuristic-guided search (faster exploration). For each node, we calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f(n) = g(n) + h(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;g(n)&lt;/strong&gt;: Actual cost from start to node n&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;h(n)&lt;/strong&gt;: Estimated cost from n to the goal (our heuristic)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Heuristic: Haversine Distance
&lt;/h3&gt;

&lt;p&gt;For geographic routing, we use the Haversine formula to calculate the great-circle distance between two points on Earth. This gives us an admissible heuristic—it never overestimates the actual distance, guaranteeing we find the optimal path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Priority Queue Implementation
&lt;/h3&gt;

&lt;p&gt;The algorithm maintains an open set as a priority queue, always exploring the most promising node first. Go's &lt;code&gt;container/heap&lt;/code&gt; package provides the foundation, with a custom &lt;code&gt;pqItem&lt;/code&gt; struct tracking node IDs and priorities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive: RAPTOR for Transit Routing
&lt;/h2&gt;

&lt;p&gt;For public transit, we implemented the &lt;strong&gt;RAPTOR&lt;/strong&gt; (Round-bAsed Public Transit Optimized Router) algorithm. Unlike traditional graph-based approaches, RAPTOR works directly on timetable data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How RAPTOR Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rounds&lt;/strong&gt;: Each round represents one additional transit leg (transfer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route Scanning&lt;/strong&gt;: For each marked stop, scan all routes serving that stop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transfer Processing&lt;/strong&gt;: After route scanning, process footpath transfers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pareto Optimality&lt;/strong&gt;: Track both arrival time and number of transfers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why RAPTOR?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Typically faster than graph-based approaches for transit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: The algorithm is elegant and easy to understand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Easy to add constraints (max transfers, walking distance)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Engine Facade
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Engine&lt;/code&gt; struct in &lt;code&gt;/pkg/pathcraft/engine&lt;/code&gt; serves as the public API—the only package external users should import.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Graph&lt;/span&gt;
    &lt;span class="n"&gt;gtfsIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gtfs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopTimeIndex&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Core methods&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;RouteRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RouteResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LoadOSM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LoadGTFS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopTimesPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tripsPath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine &lt;strong&gt;orchestrates&lt;/strong&gt;, it doesn't compute. This separation keeps HTTP/CLI concerns from leaking into algorithms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling GTFS Time: The &amp;gt;24:00:00 Problem
&lt;/h2&gt;

&lt;p&gt;One interesting challenge was handling GTFS time formats. In GTFS, times can exceed 24:00:00 to represent service that runs past midnight. A trip departing at &lt;code&gt;25:30:00&lt;/code&gt; means 1:30 AM the next day.&lt;/p&gt;

&lt;p&gt;We built a custom &lt;code&gt;time&lt;/code&gt; package in &lt;code&gt;/internal/time&lt;/code&gt; that handles this edge case transparently, keeping the rest of the codebase clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  Development Principles
&lt;/h2&gt;

&lt;p&gt;Throughout development, I've adhered to several principles:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Pure and Deterministic Core
&lt;/h3&gt;

&lt;p&gt;The routing algorithms are pure functions. Given the same graph and query, they always produce the same result. No hidden state, no randomness, no side effects.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Testability First
&lt;/h3&gt;

&lt;p&gt;Every routing algorithm has comprehensive tests. Edge cases are mandatory. This isn't just good practice—it's essential when you're implementing algorithms where bugs can be subtle and hard to detect.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No Premature Abstraction
&lt;/h3&gt;

&lt;p&gt;I resisted the urge to abstract too early. Interfaces are introduced only when there's a concrete need, not based on hypothetical future requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Comments Explain Why, Not What
&lt;/h3&gt;

&lt;p&gt;The code should be self-documenting for &lt;em&gt;what&lt;/em&gt; it does. Comments are reserved for explaining &lt;em&gt;why&lt;/em&gt; certain decisions were made.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Status &amp;amp; Roadmap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Completed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;OSM parsing and graph construction&lt;/li&gt;
&lt;li&gt;A* routing for walking&lt;/li&gt;
&lt;li&gt;CLI interface&lt;/li&gt;
&lt;li&gt;GeoJSON export&lt;/li&gt;
&lt;li&gt;HTTP server mode with &lt;code&gt;/route&lt;/code&gt; and &lt;code&gt;/health&lt;/code&gt; endpoints&lt;/li&gt;
&lt;li&gt;GTFS ingestion&lt;/li&gt;
&lt;li&gt;RAPTOR algorithm implementation&lt;/li&gt;
&lt;li&gt;Basic map visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  In Progress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Walk + Transit integration&lt;/li&gt;
&lt;li&gt;Time-dependent routing&lt;/li&gt;
&lt;li&gt;Performance benchmarking&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Future Plans
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graph contraction for faster queries&lt;/li&gt;
&lt;li&gt;Caching strategies&lt;/li&gt;
&lt;li&gt;WASM/JavaScript bindings&lt;/li&gt;
&lt;li&gt;gRPC API&lt;/li&gt;
&lt;li&gt;Plugin system&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start Simple, Iterate Fast
&lt;/h3&gt;

&lt;p&gt;The first version of PathCraft was embarrassingly simple. That was intentional. Get something working, then improve it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Invest in Tooling Early
&lt;/h3&gt;

&lt;p&gt;A good Makefile, consistent formatting, and automated testing pay dividends. Every hour spent on tooling saves days of debugging later.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Documentation as Design
&lt;/h3&gt;

&lt;p&gt;Writing documentation forces you to think through your design. If you can't explain it simply, you probably don't understand it well enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Algorithms Are the Easy Part
&lt;/h3&gt;

&lt;p&gt;The A* and RAPTOR implementations were straightforward. The hard parts? Data parsing, edge cases, and making everything work together seamlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;PathCraft is open source and available on GitHub. To get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/danielscoffee/pathcraft

&lt;span class="c"&gt;# Build the CLI&lt;/span&gt;
make build

&lt;span class="c"&gt;# See available commands&lt;/span&gt;
./bin/pathcraft &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need an OSM file for your area of interest. The &lt;code&gt;examples/&lt;/code&gt; directory contains sample data to get you started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building PathCraft has been an incredible learning experience. From implementing classic algorithms to designing clean interfaces, every challenge has taught me something new.&lt;/p&gt;

&lt;p&gt;The goal remains the same as day one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The open-source routing engine you deploy when you don't want vendor lock-in."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're interested in routing algorithms, Go development, or just want to understand how navigation apps work under the hood, I hope PathCraft can serve as both a useful tool and an educational resource.&lt;/p&gt;

&lt;p&gt;Contributions are welcome. Happy routing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/A*_search_algorithm" rel="noopener noreferrer"&gt;A* Search Algorithm - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf" rel="noopener noreferrer"&gt;RAPTOR Paper - Microsoft Research&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gtfs.org/" rel="noopener noreferrer"&gt;GTFS Specification&lt;/a&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1zq0wdthhczfr63558h.png" alt=" " width="800" height="426"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2sez6du6twig0c5go808.png" alt=" " width="800" height="369"&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>algorithms</category>
      <category>programming</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
