<?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>DEV Community: Daniel Sá</title>
    <description>The latest articles on DEV Community by Daniel Sá (@danieljvsa).</description>
    <link>https://dev.to/danieljvsa</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2968130%2Fa62ea6ac-8456-47a3-956f-42df9eb16fd6.png</url>
      <title>DEV Community: Daniel Sá</title>
      <link>https://dev.to/danieljvsa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danieljvsa"/>
    <language>en</language>
    <item>
      <title>Why I Built My Own OpenStreetMap Routing Engine — and How I Use It in a Real Application</title>
      <dc:creator>Daniel Sá</dc:creator>
      <pubDate>Tue, 27 Oct 2026 16:32:31 +0000</pubDate>
      <link>https://dev.to/danieljvsa/why-i-built-my-own-openstreetmap-routing-engine-and-how-i-use-it-in-a-real-application-23c1</link>
      <guid>https://dev.to/danieljvsa/why-i-built-my-own-openstreetmap-routing-engine-and-how-i-use-it-in-a-real-application-23c1</guid>
      <description>&lt;p&gt;&lt;em&gt;Building roadgraph-router-v2: from a practical mapping problem to a lightweight, browser-based routing engine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, the best way to understand a technology is to build something that depends on it.&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;roadgraph-router-v2&lt;/strong&gt; started.&lt;/p&gt;

&lt;p&gt;I was developing &lt;strong&gt;Energy&amp;amp;Fuel&lt;/strong&gt;, a web application for exploring electric vehicle charging stations and fuel stations across Portugal.&lt;/p&gt;

&lt;p&gt;The idea was relatively straightforward: bring together information about EV charging infrastructure, fuel prices, and station locations in one interactive map.&lt;/p&gt;

&lt;p&gt;But as the project evolved, I wanted to go beyond displaying markers.&lt;/p&gt;

&lt;p&gt;I wanted users to calculate actual road routes, compare alternatives, and understand how to reach a particular station.&lt;/p&gt;

&lt;p&gt;That led me to an interesting problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could I implement routing directly in the browser without relying on an external routing API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result was &lt;a href="https://www.npmjs.com/package/roadgraph-router-v2" rel="noopener noreferrer"&gt;roadgraph-router-v2&lt;/a&gt;, an open-source TypeScript routing engine built around OpenStreetMap data, compact road graphs, and A* pathfinding.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain why I built it, how it works, and how I'm using it in Energy&amp;amp;Fuel.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Project That Started Everything
&lt;/h2&gt;

&lt;p&gt;Energy&amp;amp;Fuel is an interactive map focused on Portugal's energy and mobility infrastructure.&lt;/p&gt;

&lt;p&gt;It brings together two major datasets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 3,000 EV charging stations from the MOBI.E network.&lt;/li&gt;
&lt;li&gt;More than 6,000 fuel stations using data from Portugal's Direção-Geral de Energia e Geologia (DGEG).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application uses Next.js, React, TypeScript, and mapping libraries to display stations, prices, and geographic information.&lt;/p&gt;

&lt;p&gt;Users can explore nearby stations, filter by fuel type, compare prices, and look for charging infrastructure.&lt;/p&gt;

&lt;p&gt;You can explore the application here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://energy-fuel.vercel.app" rel="noopener noreferrer"&gt;Energy&amp;amp;Fuel — Live Application&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The routing problem
&lt;/h3&gt;

&lt;p&gt;Finding the nearest station is relatively simple when considering geographic distance.&lt;/p&gt;

&lt;p&gt;However, geographic distance and driving distance are not the same thing.&lt;/p&gt;

&lt;p&gt;Imagine two fuel stations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Station A is 2 km away in a straight line.&lt;/li&gt;
&lt;li&gt;Station B is 3 km away in a straight line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Station A might appear closer, but reaching it could require driving around a river, following a one-way system, or using a distant motorway exit.&lt;/p&gt;

&lt;p&gt;In practice, Station B might be the better destination.&lt;/p&gt;

&lt;p&gt;To address this, I needed a routing engine capable of understanding an actual road network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seeing the routing engine in action
&lt;/h3&gt;

&lt;p&gt;Here's an example of roadgraph-router-v2 running inside Energy&amp;amp;Fuel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0uxq01rejwrcz9uctaih.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0uxq01rejwrcz9uctaih.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the application calculates a journey of approximately 664 km across Portugal, with an estimated driving time of 5 hours and 48 minutes.&lt;/p&gt;

&lt;p&gt;The interface displays three alternative routes and allows users to switch between cars, motorcycles, and trucks.&lt;/p&gt;

&lt;p&gt;What's interesting isn't necessarily the route itself, but where the calculation happens.&lt;/p&gt;

&lt;p&gt;Instead of sending the coordinates to an external directions API, Energy&amp;amp;Fuel uses roadgraph-router-v2 to calculate the route locally in the browser.&lt;/p&gt;

&lt;p&gt;The routing engine operates inside a Web Worker, reading the necessary sections of a precompiled OpenStreetMap graph.&lt;/p&gt;

&lt;p&gt;The application can therefore provide road routing without operating its own routing backend or depending on a third-party directions service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use an existing routing API?
&lt;/h3&gt;

&lt;p&gt;There are already excellent routing solutions available.&lt;/p&gt;

&lt;p&gt;Projects such as OSRM, GraphHopper, and Valhalla demonstrate how sophisticated open-source routing infrastructure has become.&lt;/p&gt;

&lt;p&gt;I wasn't trying to replace them.&lt;/p&gt;

&lt;p&gt;However, for my application, I wanted to explore a different architecture.&lt;/p&gt;

&lt;p&gt;My objectives were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Avoid requiring a separately hosted routing service.&lt;/li&gt;
&lt;li&gt;Keep the routing logic independent of a commercial API.&lt;/li&gt;
&lt;li&gt;Use OpenStreetMap as the underlying geographic dataset.&lt;/li&gt;
&lt;li&gt;Support route calculations inside the browser.&lt;/li&gt;
&lt;li&gt;Minimize the amount of geographic data downloaded by users.&lt;/li&gt;
&lt;li&gt;Make the routing engine reusable in other projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also wanted to understand the underlying algorithms rather than simply integrating another API.&lt;/p&gt;

&lt;p&gt;This became the foundation of roadgraph-router-v2.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Introducing roadgraph-router-v2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;roadgraph-router-v2&lt;/strong&gt; is a TypeScript library that transforms OpenStreetMap extracts into compact routing graphs and uses them to calculate routes.&lt;/p&gt;

&lt;p&gt;The project is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/danieljvsa/roadgraph-router" rel="noopener noreferrer"&gt;GitHub — roadgraph-router&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And on npm:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/roadgraph-router-v2" rel="noopener noreferrer"&gt;npm — roadgraph-router-v2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The package handles two main responsibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing geographic data
&lt;/h3&gt;

&lt;p&gt;OpenStreetMap provides detailed information about roads, intersections, access restrictions, and other geographic features.&lt;/p&gt;

&lt;p&gt;However, raw &lt;code&gt;.osm.pbf&lt;/code&gt; files aren't optimized for direct browser-based routing.&lt;/p&gt;

&lt;p&gt;The first step is converting the relevant data into a graph that a routing algorithm can efficiently traverse.&lt;/p&gt;

&lt;p&gt;roadgraph-router-v2 includes a compiler that produces a compact &lt;code&gt;.efrb&lt;/code&gt; bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calculating routes
&lt;/h3&gt;

&lt;p&gt;Once the graph is prepared, the routing engine can calculate paths between geographic coordinates.&lt;/p&gt;

&lt;p&gt;The engine uses the &lt;strong&gt;A* algorithm&lt;/strong&gt;, which explores a road network while using a geographic heuristic to guide the search toward the destination.&lt;/p&gt;

&lt;p&gt;This makes it possible to calculate routes without contacting a remote routing server.&lt;/p&gt;

&lt;p&gt;The package supports both Node.js and modern browsers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported routing profiles
&lt;/h3&gt;

&lt;p&gt;At the time of writing, version 0.4.0 supports seven profiles.&lt;/p&gt;

&lt;p&gt;Road networks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Car&lt;/li&gt;
&lt;li&gt;Motorcycle&lt;/li&gt;
&lt;li&gt;Truck&lt;/li&gt;
&lt;li&gt;Bicycle&lt;/li&gt;
&lt;li&gt;Pedestrian&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Railway infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Train&lt;/li&gt;
&lt;li&gt;Metro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Railway routing is focused on mapped physical infrastructure, not passenger timetables or live train services.&lt;/p&gt;

&lt;p&gt;Different transport networks can require separate compiled bundles.&lt;/p&gt;

&lt;p&gt;For Energy&amp;amp;Fuel, the important profiles are car, truck, and motorcycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Compiling OpenStreetMap Data
&lt;/h2&gt;

&lt;p&gt;Before calculating routes, we need road network data.&lt;/p&gt;

&lt;p&gt;For Energy&amp;amp;Fuel, I use an OpenStreetMap extract of Portugal.&lt;/p&gt;

&lt;p&gt;The first step is installing the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;roadgraph-router-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The routing compiler requires Node.js 22.5 or newer.&lt;/p&gt;

&lt;p&gt;Once installed, we can generate a routing bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx roadgraph-compile portugal.osm.pbf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; portugal.efrb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; car,truck,motorcycle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a file containing the compiled road network.&lt;/p&gt;

&lt;p&gt;Instead of processing raw OpenStreetMap data every time the application starts, the preprocessing work is performed beforehand.&lt;/p&gt;

&lt;p&gt;The browser only needs to read the compiled bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the compiled format matters
&lt;/h3&gt;

&lt;p&gt;One of my main concerns was the size of the geographic data.&lt;/p&gt;

&lt;p&gt;A national road network contains a considerable amount of information.&lt;/p&gt;

&lt;p&gt;Downloading and loading the entire graph before calculating a simple journey would be inefficient.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.efrb&lt;/code&gt; format addresses this by supporting range-based access.&lt;/p&gt;

&lt;p&gt;Rather than downloading everything, the router can retrieve the portions of the bundle needed for a particular route.&lt;/p&gt;

&lt;p&gt;For example, the package's documented Portugal benchmark reports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original Portugal OSM extract&lt;/td&gt;
&lt;td&gt;425 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled routing bundle&lt;/td&gt;
&lt;td&gt;38 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data downloaded for a city route&lt;/td&gt;
&lt;td&gt;4.3 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end city route calculation&lt;/td&gt;
&lt;td&gt;~270 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser library size&lt;/td&gt;
&lt;td&gt;24.5 kB gzipped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are measurements from the package's documented benchmark, not guarantees for every machine, network, or route.&lt;/p&gt;

&lt;p&gt;Nevertheless, they demonstrate why selective graph loading can make browser-based routing practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compiling the Energy&amp;amp;Fuel dataset
&lt;/h3&gt;

&lt;p&gt;In the actual Energy&amp;amp;Fuel project, the compilation process is integrated into npm scripts.&lt;/p&gt;

&lt;p&gt;The command follows this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run routing:compile &lt;span class="nt"&gt;--&lt;/span&gt; portugal-261003.osm.pbf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; public/routing/portugal-v10.efrb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; car,truck,motorcycle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated file is placed in the application's public directory.&lt;/p&gt;

&lt;p&gt;That means the browser can request the routing bundle as a static resource.&lt;/p&gt;

&lt;p&gt;No separate routing API is required.&lt;/p&gt;

&lt;p&gt;One important deployment detail: the server must preserve HTTP Range request behavior for &lt;code&gt;.efrb&lt;/code&gt; files. Whole-file HTTP content encoding can interfere with accessing individual byte ranges.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Calculating Our First Route
&lt;/h2&gt;

&lt;p&gt;The public API is designed to be straightforward.&lt;/p&gt;

&lt;p&gt;Let's calculate a route between two coordinates in Monaco.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roadgraph-router-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/routing/monaco.efrb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4246&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7325&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4189&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distanceMeters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durationSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result contains the calculated road distance, an estimated travel duration, and the geometry of the route.&lt;/p&gt;

&lt;p&gt;The geometry is particularly useful because it can be rendered directly on an interactive map using GeoJSON-compatible mapping tools.&lt;/p&gt;

&lt;p&gt;The routing calculation happens inside the library, using the compiled graph.&lt;/p&gt;

&lt;p&gt;At this point, we've implemented routing without making a request to an external directions API.&lt;/p&gt;

&lt;p&gt;However, this is only a simple example.&lt;/p&gt;

&lt;p&gt;In a real web application, there is another problem to solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Keeping the Interface Responsive with Web Workers
&lt;/h2&gt;

&lt;p&gt;One of the challenges I encountered was deciding where routing calculations should execute.&lt;/p&gt;

&lt;p&gt;Even if a routing engine is efficient, decoding road network data and searching a graph requires CPU work.&lt;/p&gt;

&lt;p&gt;Running this directly on the browser's main thread can affect responsiveness.&lt;/p&gt;

&lt;p&gt;For a map-based application, that's particularly noticeable.&lt;/p&gt;

&lt;p&gt;Users expect to be able to zoom, pan, and interact with markers while calculations are taking place.&lt;/p&gt;

&lt;p&gt;In Energy&amp;amp;Fuel, I decided to move the routing engine into a &lt;strong&gt;Web Worker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Energy&amp;amp;Fuel
                 React / Next.js
                       |
                       |
                Routing Client
                       |
                       | postMessage()
                       |
                 Web Worker
                       |
                       v
              roadgraph-router-v2
                       |
                       v
              Compiled OSM Graph
                       |
                       v
                A* Pathfinding
                       |
                       |
                Route Result
                       |
                       v
                 Map Interface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main thread sends a request to the worker.&lt;/p&gt;

&lt;p&gt;The worker calculates the route and returns the result.&lt;/p&gt;

&lt;p&gt;This separation allows route calculations to run without occupying the UI thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual worker implementation
&lt;/h3&gt;

&lt;p&gt;Energy&amp;amp;Fuel uses the following pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Coordinate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RoutePreference&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roadgraph-router-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Routing graph not loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preference&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Routing failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified version of the worker used in Energy&amp;amp;Fuel.&lt;/p&gt;

&lt;p&gt;The actual implementation also handles multiple vehicle profiles, alternative routes, departure times, and router caching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this architecture matters
&lt;/h3&gt;

&lt;p&gt;The result is a clean separation between the application and the routing engine.&lt;/p&gt;

&lt;p&gt;The React components don't need to know how the graph is loaded or how A* works.&lt;/p&gt;

&lt;p&gt;They only need to request a route and display the result.&lt;/p&gt;

&lt;p&gt;For me, this was an important architectural improvement.&lt;/p&gt;

&lt;p&gt;It also means the routing library can evolve independently from the interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Multiple Vehicle Profiles Without Duplicating the Entire Graph
&lt;/h2&gt;

&lt;p&gt;Another interesting challenge was supporting multiple types of vehicles.&lt;/p&gt;

&lt;p&gt;A road that's accessible to a motorcycle might have different restrictions for a truck.&lt;/p&gt;

&lt;p&gt;Even when two vehicles can use the same road, they might have different speed assumptions.&lt;/p&gt;

&lt;p&gt;roadgraph-router-v2 handles these differences through routing profiles.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roadgraph-router-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/routing/portugal.efrb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ["car", "truck", "motorcycle"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then select another vehicle profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;truckRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;truck&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;truckRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distanceMeters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that multiple motor-vehicle profiles can share road geometry and topology inside the compiled bundle.&lt;/p&gt;

&lt;p&gt;Each profile adds the information needed to evaluate its own routing rules.&lt;/p&gt;

&lt;p&gt;This avoids storing an entirely separate road network for every supported vehicle.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Energy&amp;amp;Fuel handles this
&lt;/h3&gt;

&lt;p&gt;In Energy&amp;amp;Fuel, I cache the routers associated with different profiles.&lt;/p&gt;

&lt;p&gt;When a user changes vehicle type, the application can reuse a previously initialized router instead of recreating it unnecessarily.&lt;/p&gt;

&lt;p&gt;The worker maintains a cache using a &lt;code&gt;Map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;routerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Router not initialized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;byProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly useful when users switch between vehicle types while comparing routes.&lt;/p&gt;

&lt;p&gt;It also illustrates one of the benefits of designing the library around reusable graph data.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Alternative Routes and Route Preferences
&lt;/h2&gt;

&lt;p&gt;Not every journey has one ideal route.&lt;/p&gt;

&lt;p&gt;Sometimes users want the fastest option.&lt;/p&gt;

&lt;p&gt;Other times, they might prefer the shortest distance.&lt;/p&gt;

&lt;p&gt;roadgraph-router-v2 exposes route preferences through its API.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shortest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shortest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine also supports alternative-route calculation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alternatives&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routeAlternatives&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;alternatives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distanceMeters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durationSeconds&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives applications the ability to present multiple journey options.&lt;/p&gt;

&lt;p&gt;In Energy&amp;amp;Fuel, the routing client supports both normal routes and alternative routes through the same worker interface.&lt;/p&gt;

&lt;p&gt;An important distinction is that these are routes based on the compiled network and available routing rules.&lt;/p&gt;

&lt;p&gt;They do not represent live traffic predictions.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. What About Performance?
&lt;/h2&gt;

&lt;p&gt;Performance was one of the reasons I decided to investigate compact routing graphs.&lt;/p&gt;

&lt;p&gt;A routing engine that works well on a small city graph might behave very differently when processing a national road network.&lt;/p&gt;

&lt;p&gt;The library therefore needs to consider more than just the pathfinding algorithm.&lt;/p&gt;

&lt;p&gt;Three areas are particularly important.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph representation
&lt;/h3&gt;

&lt;p&gt;The compiler transforms OpenStreetMap data into a more suitable representation for routing.&lt;/p&gt;

&lt;p&gt;This means the browser doesn't need to parse raw OSM data whenever a route is requested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selective loading
&lt;/h3&gt;

&lt;p&gt;The routing bundle supports HTTP Range requests.&lt;/p&gt;

&lt;p&gt;This allows the browser to retrieve relevant sections instead of transferring the entire file.&lt;/p&gt;

&lt;p&gt;It reduces initial data transfer, although the amount downloaded depends on the route and the graph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusing loaded data
&lt;/h3&gt;

&lt;p&gt;Previously downloaded and decoded graph data can be reused.&lt;/p&gt;

&lt;p&gt;This matters when users calculate multiple routes or change vehicle profiles.&lt;/p&gt;

&lt;p&gt;In Energy&amp;amp;Fuel, the worker architecture allows the router to remain initialized between requests.&lt;/p&gt;

&lt;p&gt;That reduces unnecessary repeated setup work.&lt;/p&gt;

&lt;p&gt;It's also worth distinguishing calculation time from user-perceived latency.&lt;/p&gt;

&lt;p&gt;A route may require network transfers, graph decoding, and pathfinding. Measuring only the A* search doesn't tell the full story.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Limitations and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building a routing engine has been an opportunity to learn more about geographic data structures, graph algorithms, and browser performance.&lt;/p&gt;

&lt;p&gt;But an equally important part of the project has been understanding its limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenStreetMap data isn't perfect
&lt;/h3&gt;

&lt;p&gt;The quality of calculated routes depends heavily on the underlying geographic data.&lt;/p&gt;

&lt;p&gt;Some access restrictions, vehicle limitations, and road attributes may be missing or outdated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Truck routing requires special care
&lt;/h3&gt;

&lt;p&gt;Although roadgraph-router-v2 supports truck profiles and certain vehicle dimensions, a calculated route isn't a guarantee that a heavy vehicle can legally or physically travel along it.&lt;/p&gt;

&lt;p&gt;Not all bridge clearances, weight restrictions, or local regulations are fully represented in OpenStreetMap.&lt;/p&gt;

&lt;p&gt;The library shouldn't be the sole routing system used for safety-critical freight operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The graph needs updating
&lt;/h3&gt;

&lt;p&gt;A compiled routing bundle represents the OpenStreetMap data available when it was generated.&lt;/p&gt;

&lt;p&gt;It doesn't automatically know about newly opened roads, roadworks, or temporary closures.&lt;/p&gt;

&lt;p&gt;Updating the geographic dataset requires compiling a new bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline routing requires preparation
&lt;/h3&gt;

&lt;p&gt;The engine doesn't require a routing API, but it still needs access to the compiled graph.&lt;/p&gt;

&lt;p&gt;In a normal web deployment, that graph can be downloaded from a static server.&lt;/p&gt;

&lt;p&gt;Fully offline operation requires the necessary data to be available locally or cached beforehand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing routing engines remain valuable
&lt;/h3&gt;

&lt;p&gt;OSRM, GraphHopper, and Valhalla are mature routing solutions with capabilities that roadgraph-router-v2 doesn't attempt to reproduce.&lt;/p&gt;

&lt;p&gt;For applications requiring large-scale routing services, live data integration, or advanced routing functionality, those solutions may be more appropriate.&lt;/p&gt;

&lt;p&gt;My goal was different: build a lightweight, reusable engine that could operate directly over compact OpenStreetMap graphs in JavaScript environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. From a Project Requirement to an Open-Source Library
&lt;/h2&gt;

&lt;p&gt;One of the most rewarding aspects of this project is that roadgraph-router-v2 didn't begin as an isolated experiment.&lt;/p&gt;

&lt;p&gt;It came from a practical requirement in another application.&lt;/p&gt;

&lt;p&gt;Energy&amp;amp;Fuel needed routing.&lt;/p&gt;

&lt;p&gt;Instead of making the routing logic specific to Energy&amp;amp;Fuel, I separated it into a reusable package.&lt;/p&gt;

&lt;p&gt;That decision made it possible to develop and test the routing engine independently.&lt;/p&gt;

&lt;p&gt;It also opened the door to features that go beyond the original application.&lt;/p&gt;

&lt;p&gt;The package now supports multiple road vehicle profiles, pedestrian and bicycle routing, alternative routes, turn restrictions, and routing over railway infrastructure.&lt;/p&gt;

&lt;p&gt;Not every capability is used by Energy&amp;amp;Fuel.&lt;/p&gt;

&lt;p&gt;And that's fine.&lt;/p&gt;

&lt;p&gt;The application benefits from the routing engine, while the routing engine can continue evolving as a separate open-source project.&lt;/p&gt;




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

&lt;p&gt;If you're interested in experimenting with OpenStreetMap routing in TypeScript, you can install roadgraph-router-v2 directly from npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;roadgraph-router-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile an OSM extract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx roadgraph-compile monaco.osm.pbf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; monaco.efrb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then calculate a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roadgraph-router-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./monaco.efrb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4246&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7325&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4189&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distanceMeters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durationSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more advanced functionality, the GitHub repository contains documentation covering vehicle profiles, graph compilation, alternative routes, and additional routing options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project links
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Routing engine&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/danieljvsa/roadgraph-router" rel="noopener noreferrer"&gt;GitHub — roadgraph-router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/roadgraph-router-v2" rel="noopener noreferrer"&gt;npm — roadgraph-router-v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Application&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://energy-fuel.vercel.app" rel="noopener noreferrer"&gt;Energy&amp;amp;Fuel — Live Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building Energy&amp;amp;Fuel reminded me that seemingly simple application features can involve interesting engineering challenges.&lt;/p&gt;

&lt;p&gt;Displaying a location on a map is relatively straightforward.&lt;/p&gt;

&lt;p&gt;Calculating a route through thousands of interconnected roads, while minimizing data transfer and keeping a browser responsive, is a different problem.&lt;/p&gt;

&lt;p&gt;Working on roadgraph-router-v2 gave me the opportunity to explore graph algorithms, geographic data processing, binary data formats, caching strategies, and browser threading.&lt;/p&gt;

&lt;p&gt;It also reinforced an architectural principle I value:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A solution developed for one application can become a useful&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>algorithms</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built an Open-Source Routing Engine for JavaScript Using OpenStreetMap 🚗</title>
      <dc:creator>Daniel Sá</dc:creator>
      <pubDate>Mon, 12 Oct 2026 18:32:03 +0000</pubDate>
      <link>https://dev.to/danieljvsa/i-built-an-open-source-routing-engine-for-javascript-using-openstreetmap-4akl</link>
      <guid>https://dev.to/danieljvsa/i-built-an-open-source-routing-engine-for-javascript-using-openstreetmap-4akl</guid>
      <description>&lt;p&gt;&lt;strong&gt;What if you could calculate routes directly in your JavaScript application, without relying on Google Maps, external routing APIs, or a running routing server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;strong&gt;roadgraph-router&lt;/strong&gt;, an open-source npm package I've been working on.&lt;/p&gt;

&lt;p&gt;After spending time experimenting with OpenStreetMap data, graph structures, and routing algorithms, I wanted to build something that could process real road networks while remaining lightweight enough to run in Node.js or directly in a browser.&lt;/p&gt;

&lt;p&gt;Today, I'm releasing the project publicly.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Project Links
&lt;/h3&gt;

&lt;p&gt;🐙 &lt;strong&gt;&lt;a href="https://github.com/danieljvsa/roadgraph-router" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/roadgraph-router-v2" rel="noopener noreferrer"&gt;npm — Latest Version&lt;/a&gt;&lt;/strong&gt; ⭐ Recommended&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/roadgraph-router" rel="noopener noreferrer"&gt;npm — Legacy Version&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(No longer maintained)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Why I Built It
&lt;/h2&gt;

&lt;p&gt;OpenStreetMap provides an incredible amount of geographical information, including road networks, intersections, speed limits, and turn restrictions.&lt;/p&gt;

&lt;p&gt;But transforming all that information into something you can efficiently use inside a JavaScript application isn't necessarily straightforward.&lt;/p&gt;

&lt;p&gt;When working with larger geographical datasets, such as Portugal's road network, I encountered several challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenStreetMap files can be hundreds of megabytes.&lt;/li&gt;
&lt;li&gt;Processing large road networks can require significant memory.&lt;/li&gt;
&lt;li&gt;Loading an entire road network into a browser isn't practical.&lt;/li&gt;
&lt;li&gt;Routing needs to respect one-way streets and turn restrictions.&lt;/li&gt;
&lt;li&gt;I wanted the routing engine to work without depending on an external API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started experimenting with ways to compile OSM data into smaller, more efficient graph structures.&lt;/p&gt;

&lt;p&gt;That experimentation eventually became &lt;strong&gt;roadgraph-router&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Introducing roadgraph-router
&lt;/h2&gt;

&lt;p&gt;roadgraph-router is a TypeScript routing library that converts OpenStreetMap &lt;code&gt;.osm.pbf&lt;/code&gt; extracts into compact routing bundles and calculates routes using the A* search algorithm.&lt;/p&gt;

&lt;p&gt;The goal is simple: make road routing accessible to JavaScript developers without requiring a dedicated routing service.&lt;/p&gt;

&lt;p&gt;Some of its current capabilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenStreetMap integration:&lt;/strong&gt; Compile &lt;code&gt;.osm.pbf&lt;/code&gt; extracts from different geographical regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A* pathfinding:&lt;/strong&gt; Calculate road routes between geographical coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compact binary storage:&lt;/strong&gt; Store road networks in optimized, compressed bundles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial network loading:&lt;/strong&gt; Download only the parts of the road network needed for a route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser and Node.js support:&lt;/strong&gt; Run routing calculations locally without an external routing API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Road snapping:&lt;/strong&gt; Match geographical coordinates to actual road segments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic rule awareness:&lt;/strong&gt; Respect supported one-way streets, access rules, speed limits, and turn restrictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GeoJSON output:&lt;/strong&gt; Integrate resulting route geometries into mapping applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial release focuses on car routing, with opportunities to expand its capabilities over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Getting Started
&lt;/h2&gt;

&lt;p&gt;Install the package using npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;roadgraph-router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before calculating routes, you'll need to compile an OpenStreetMap extract into a routing bundle.&lt;/p&gt;

&lt;p&gt;For example, using a Monaco OSM extract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx roadgraph-compile monaco.osm.pbf &lt;span class="nt"&gt;--output&lt;/span&gt; monaco.efrb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler requires Node.js 22.5 or newer.&lt;/p&gt;

&lt;p&gt;Once compiled, you can load the routing bundle and calculate a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roadgraph-router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RoadRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/routing/monaco.efrb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4246&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.7325&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4189&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distanceMeters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durationSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result contains the calculated distance, estimated duration, and road-following GeoJSON geometry.&lt;/p&gt;

&lt;p&gt;This means you can use the output with libraries such as Leaflet or MapLibre to visualize routes on a map.&lt;/p&gt;

&lt;p&gt;And because routing happens locally, there's no need to send every route request to an external routing service.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Making Large OSM Files Smaller
&lt;/h2&gt;

&lt;p&gt;One of the most interesting challenges was working with larger OSM extracts.&lt;/p&gt;

&lt;p&gt;For example, the Portugal dataset I used for testing was approximately &lt;strong&gt;425 MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than loading and maintaining that entire dataset in memory, roadgraph-router compiles the information into a compact, range-addressable binary format.&lt;/p&gt;

&lt;p&gt;The results were encouraging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Portugal compilation benchmark
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original OSM PBF&lt;/td&gt;
&lt;td&gt;425 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled routing bundle&lt;/td&gt;
&lt;td&gt;38.03 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing edges&lt;/td&gt;
&lt;td&gt;2,757,265&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compilation time&lt;/td&gt;
&lt;td&gt;7 min 46 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak memory usage&lt;/td&gt;
&lt;td&gt;1.11 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's approximately a &lt;strong&gt;91% reduction in file size&lt;/strong&gt; compared with the original PBF extract.&lt;/p&gt;

&lt;p&gt;Of course, the compiled bundle is a purpose-built routing representation, not a lossless replacement for the original OSM dataset.&lt;/p&gt;

&lt;p&gt;The important part is that it retains the road-network information the routing engine needs.&lt;/p&gt;

&lt;p&gt;The measurements above were collected on one desktop machine, so actual performance will depend on hardware, geographical data, and compilation options.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌍 You Don't Need to Download the Entire Map
&lt;/h2&gt;

&lt;p&gt;Another important design decision was to avoid downloading the complete routing bundle whenever someone requests a route.&lt;/p&gt;

&lt;p&gt;Instead, the package uses HTTP range requests to retrieve the relevant sections of the road network.&lt;/p&gt;

&lt;p&gt;For example, in my Portugal benchmark, a short urban route in Lisbon required approximately &lt;strong&gt;4.93 MB&lt;/strong&gt; of downloaded routing data, despite the complete bundle being 38 MB.&lt;/p&gt;

&lt;p&gt;That route took around 118 ms to load its graph data and 86 ms for the A* search, excluding additional overhead.&lt;/p&gt;

&lt;p&gt;This approach makes it possible to work with fairly large road networks without loading everything at once.&lt;/p&gt;

&lt;p&gt;It is particularly interesting for web applications where bandwidth and memory matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 More Than Just Finding the Shortest Path
&lt;/h2&gt;

&lt;p&gt;Finding a path through a graph is one thing.&lt;/p&gt;

&lt;p&gt;Finding a usable driving route is another.&lt;/p&gt;

&lt;p&gt;Real road networks contain rules that a routing engine needs to understand.&lt;/p&gt;

&lt;p&gt;A driver cannot always travel in both directions along a road or turn wherever they want.&lt;/p&gt;

&lt;p&gt;roadgraph-router currently handles several important aspects of this problem, including one-way streets, supported turn restrictions, and speed information from OSM.&lt;/p&gt;

&lt;p&gt;It also snaps coordinates to road segments rather than simply selecting the closest intersection.&lt;/p&gt;

&lt;p&gt;This becomes especially important in rural areas, where junctions can be far apart.&lt;/p&gt;

&lt;p&gt;Internally, the routing engine uses A* and can switch to an edge-based search when turn restrictions must be enforced.&lt;/p&gt;

&lt;p&gt;There are still limitations. For example, conditional access restrictions, live traffic, and traffic signal delays aren't currently modelled.&lt;/p&gt;

&lt;p&gt;I'm treating those as areas for future development rather than pretending the library is already a complete navigation platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔮 What's Next?
&lt;/h2&gt;

&lt;p&gt;The package is still in its early stages, and there are several improvements I'd like to work on.&lt;/p&gt;

&lt;p&gt;Two of my main priorities are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Different vehicle profiles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now, the bundled routing profile focuses on cars.&lt;/p&gt;

&lt;p&gt;I'd like to expand this to support other types of vehicles and transportation, each with its own routing rules.&lt;/p&gt;

&lt;p&gt;For example, bicycles and pedestrians shouldn't necessarily follow the same road network constraints as cars.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Alternative routes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding one route is useful, but there are situations where having multiple options is better.&lt;/p&gt;

&lt;p&gt;I'd like the engine to eventually return alternative routes when meaningful alternatives exist, allowing applications to compare options by distance or estimated travel time.&lt;/p&gt;

&lt;p&gt;Beyond those features, I want to keep improving processing efficiency, bundle sizes, memory consumption, and developer experience.&lt;/p&gt;

&lt;p&gt;The objective is to make the package increasingly useful while keeping its architecture understandable and lightweight.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤝 Open Source and Contributions
&lt;/h2&gt;

&lt;p&gt;roadgraph-router is available on GitHub under the MIT license.&lt;/p&gt;

&lt;p&gt;It's a project I'm actively developing, and I'd genuinely appreciate feedback from other developers.&lt;/p&gt;

&lt;p&gt;Whether you work with mapping applications, OpenStreetMap, transportation software, routing algorithms, or simply enjoy exploring graph-based problems, I'd be interested in hearing your thoughts.&lt;/p&gt;

&lt;p&gt;If you find something that doesn't work as expected, have an idea for an improvement, or want to contribute, feel free to open an issue or pull request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/danieljvsa/roadgraph-router" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/roadgraph-router&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/roadgraph-router" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/roadgraph-router&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;roadgraph-router &lt;span class="c"&gt;#legacy&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;roadgraph-router-v2 &lt;span class="c"&gt;#current-valid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💬 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this package has been an interesting exercise in working with large datasets, binary formats, graph algorithms, and geographical information.&lt;/p&gt;

&lt;p&gt;There's still a lot I want to improve, but I felt it was time to make it public rather than continue developing it in isolation.&lt;/p&gt;

&lt;p&gt;I'm interested in seeing whether other developers find it useful, especially those who want to experiment with routing without operating a dedicated backend service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you were building a routing engine in JavaScript, what feature would you prioritize next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading! 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #javascript #typescript #opensource #showdev&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Teltonika-Go Update: From Initial Release to Full Codec Support</title>
      <dc:creator>Daniel Sá</dc:creator>
      <pubDate>Fri, 29 May 2026 20:17:41 +0000</pubDate>
      <link>https://dev.to/danieljvsa/teltonika-go-update-from-initial-release-to-full-codec-support-5b03</link>
      <guid>https://dev.to/danieljvsa/teltonika-go-update-from-initial-release-to-full-codec-support-5b03</guid>
      <description>&lt;p&gt;A few months ago, I published my first article introducing Teltonika-Go:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/danieljvsa/introducing-teltonika-go-a-go-package-for-parsing-and-communicating-with-teltonika-devices-4oof"&gt;&lt;strong&gt;Introducing Teltonika-Go: A Go Package for Parsing and Communicating with Teltonika Devices&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time, the goal was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build an open-source Go library to simplify communication with Teltonika GPS tracking devices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The project started as a side project inspired by my experience working in vehicle tracking and telemetry systems. I wanted a reusable package that developers could easily integrate into their own fleet management, IoT, and GPS tracking platforms.&lt;/p&gt;

&lt;p&gt;Since then, the project has grown significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Has Changed?
&lt;/h2&gt;

&lt;p&gt;The initial version focused primarily on AVL packet parsing and the most commonly used codecs.&lt;/p&gt;

&lt;p&gt;Today, Teltonika-Go supports:&lt;/p&gt;

&lt;h3&gt;
  
  
  AVL Data Codecs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Codec 08&lt;/li&gt;
&lt;li&gt;Codec 8E&lt;/li&gt;
&lt;li&gt;Codec 16&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Command &amp;amp; Response Codecs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Codec 12&lt;/li&gt;
&lt;li&gt;Codec 13&lt;/li&gt;
&lt;li&gt;Codec 14&lt;/li&gt;
&lt;li&gt;Codec 15&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AVL packet decoding&lt;/li&gt;
&lt;li&gt;AVL packet encoding&lt;/li&gt;
&lt;li&gt;Command packet encoding&lt;/li&gt;
&lt;li&gt;Command response decoding&lt;/li&gt;
&lt;li&gt;TCP support&lt;/li&gt;
&lt;li&gt;UDP support&lt;/li&gt;
&lt;li&gt;IMEI handling&lt;/li&gt;
&lt;li&gt;CRC validation&lt;/li&gt;
&lt;li&gt;Timestamp utilities&lt;/li&gt;
&lt;li&gt;Extensive unit testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/danieljvsa/teltonika-go" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/teltonika-go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the biggest milestones was expanding support beyond AVL data packets and implementing command-related codecs.&lt;/p&gt;

&lt;p&gt;This transformed the library from a parser into a more complete communication toolkit for Teltonika devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Many GPS tracking systems eventually need more than just location data.&lt;/p&gt;

&lt;p&gt;Real-world deployments often require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending commands to devices&lt;/li&gt;
&lt;li&gt;Receiving command responses&lt;/li&gt;
&lt;li&gt;Managing remote configurations&lt;/li&gt;
&lt;li&gt;Supporting multiple Teltonika device generations&lt;/li&gt;
&lt;li&gt;Handling both TCP and UDP communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting these additional codecs makes it easier to build complete integrations without having to implement protocol handling from scratch.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Protocol Specifications Matter
&lt;/h3&gt;

&lt;p&gt;The most challenging aspect of the project was never the Go code itself.&lt;/p&gt;

&lt;p&gt;The difficult part was ensuring protocol correctness.&lt;/p&gt;

&lt;p&gt;When working with binary protocols, small mistakes can break communication completely.&lt;/p&gt;

&lt;p&gt;Issues such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect packet lengths&lt;/li&gt;
&lt;li&gt;CRC calculation errors&lt;/li&gt;
&lt;li&gt;Wrong byte ordering&lt;/li&gt;
&lt;li&gt;Timestamp conversion mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can result in devices rejecting packets without providing much information about what went wrong.&lt;/p&gt;

&lt;p&gt;Most development time was spent validating behavior against protocol documentation and testing edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Became Essential
&lt;/h3&gt;

&lt;p&gt;As support for more codecs was added, the importance of testing increased dramatically.&lt;/p&gt;

&lt;p&gt;Adding new functionality without introducing regressions became a priority.&lt;/p&gt;

&lt;p&gt;Today, the project includes a growing suite of tests that helps ensure stability when adding features or improving existing implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Source Is About Iteration
&lt;/h3&gt;

&lt;p&gt;One thing I enjoy about open source is that projects rarely remain in their original form.&lt;/p&gt;

&lt;p&gt;The first release is simply the starting point.&lt;/p&gt;

&lt;p&gt;Since publishing the original article, I've received feedback, discovered new use cases, and identified areas where the library could be improved.&lt;/p&gt;

&lt;p&gt;Each iteration has made the project more complete and more useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with Modern AI Tools
&lt;/h2&gt;

&lt;p&gt;This project was also an interesting opportunity to explore modern AI-assisted development.&lt;/p&gt;

&lt;p&gt;I used tools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude&lt;/li&gt;
&lt;li&gt;OpenCode&lt;/li&gt;
&lt;li&gt;DeepSeek&lt;/li&gt;
&lt;li&gt;Qwen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to accelerate development tasks including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boilerplate generation&lt;/li&gt;
&lt;li&gt;Test creation&lt;/li&gt;
&lt;li&gt;Documentation improvements&lt;/li&gt;
&lt;li&gt;Code reviews&lt;/li&gt;
&lt;li&gt;Refactoring suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest lesson was that AI works best as an accelerator rather than a replacement for domain knowledge.&lt;/p&gt;

&lt;p&gt;Understanding GPS tracking systems, binary protocols, and Teltonika specifications remained essential for validating the generated code and ensuring correct behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;There are still several improvements planned for future releases.&lt;/p&gt;

&lt;p&gt;Some areas I'd like to focus on include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More real-world examples&lt;/li&gt;
&lt;li&gt;Complete TCP server examples&lt;/li&gt;
&lt;li&gt;Improved documentation&lt;/li&gt;
&lt;li&gt;Better pkg.go.dev support&lt;/li&gt;
&lt;li&gt;Additional protocol utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal remains the same as when the project started:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make Teltonika device integration easier for Go developers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Looking back at the original article and comparing it with the current state of the project, it's satisfying to see how much it has evolved.&lt;/p&gt;

&lt;p&gt;What started as a simple parser has grown into a much more complete Teltonika communication library supporting multiple codecs, command handling, and packet generation.&lt;/p&gt;

&lt;p&gt;If you're working with Teltonika devices, GPS tracking systems, or telemetry platforms, I'd love to hear your feedback.&lt;/p&gt;

&lt;p&gt;Feel free to check out the repository, open an issue, or contribute to the project.&lt;/p&gt;

&lt;p&gt;Happy coding 🚀&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/danieljvsa/teltonika-go" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/teltonika-go&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>iot</category>
      <category>gps</category>
    </item>
    <item>
      <title>Building Two Web Apps Using Claude AI + OpenCode</title>
      <dc:creator>Daniel Sá</dc:creator>
      <pubDate>Sun, 24 May 2026 12:11:22 +0000</pubDate>
      <link>https://dev.to/danieljvsa/building-two-small-web-apps-using-claude-ai-opencode-1hhk</link>
      <guid>https://dev.to/danieljvsa/building-two-small-web-apps-using-claude-ai-opencode-1hhk</guid>
      <description>&lt;p&gt;Over the last weeks I decided to experiment with AI-assisted development workflows to see how much faster I could go from idea to working MVP.&lt;/p&gt;

&lt;p&gt;Instead of treating AI as a “code generator”, I approached it more like an engineering accelerator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;brainstorming partner&lt;/li&gt;
&lt;li&gt;rapid prototyping assistant&lt;/li&gt;
&lt;li&gt;UI iteration helper&lt;/li&gt;
&lt;li&gt;debugging companion&lt;/li&gt;
&lt;li&gt;architecture sounding board&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result was two small projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Altivo — a lightweight portfolio tracker&lt;/li&gt;
&lt;li&gt;Hopway — a visual route planning app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both projects are intentionally lightweight, but they helped me explore a modern development workflow using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude AI&lt;/li&gt;
&lt;li&gt;OpenCode AI&lt;/li&gt;
&lt;li&gt;DeepSeek V4 Flash&lt;/li&gt;
&lt;li&gt;Qwen 3.6 Plus&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Projects
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Altivo
&lt;/h2&gt;

&lt;p&gt;Altivo is a lightweight and privacy-friendly portfolio tracker focused on simplicity and static deployment.&lt;/p&gt;

&lt;p&gt;Live Demo:&lt;br&gt;
&lt;a href="https://danieljvsa.github.io/altivo/" rel="noopener noreferrer"&gt;https://danieljvsa.github.io/altivo/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/danieljvsa/altivo" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/altivo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea was to build something lightweight that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;track ETFs&lt;/li&gt;
&lt;li&gt;support crypto&lt;/li&gt;
&lt;li&gt;run entirely as a static frontend&lt;/li&gt;
&lt;li&gt;avoid unnecessary complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a cleaner and simpler experience than many traditional portfolio platforms.&lt;/p&gt;

&lt;p&gt;The project also became an interesting exercise in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend architecture&lt;/li&gt;
&lt;li&gt;UI simplification&lt;/li&gt;
&lt;li&gt;API integration&lt;/li&gt;
&lt;li&gt;local-first persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;li&gt;Financial Market APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hopway
&lt;/h2&gt;

&lt;p&gt;Hopway is a lightweight route planning and travel exploration tool.&lt;/p&gt;

&lt;p&gt;Live Demo:&lt;br&gt;
&lt;a href="https://danieljvsa.github.io/hopway/" rel="noopener noreferrer"&gt;https://danieljvsa.github.io/hopway/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/danieljvsa/hopway" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/hopway&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The concept behind Hopway was creating a map-first experience where users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create routes&lt;/li&gt;
&lt;li&gt;visually explore destinations&lt;/li&gt;
&lt;li&gt;share travel plans&lt;/li&gt;
&lt;li&gt;experiment with AI-generated route ideas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app uses CARTO maps and focuses on keeping the UX lightweight and visual.&lt;/p&gt;

&lt;p&gt;I wanted it to feel more like an exploration tool than a traditional route planner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;CARTO Maps&lt;/li&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The AI Workflow
&lt;/h1&gt;

&lt;p&gt;One of the most interesting parts of these projects was experimenting with different AI tools together.&lt;/p&gt;

&lt;p&gt;Each model had different strengths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude AI
&lt;/h2&gt;

&lt;p&gt;Claude was the strongest tool for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture discussions&lt;/li&gt;
&lt;li&gt;reasoning&lt;/li&gt;
&lt;li&gt;debugging&lt;/li&gt;
&lt;li&gt;planning features&lt;/li&gt;
&lt;li&gt;refining ideas&lt;/li&gt;
&lt;li&gt;writing structured prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was especially useful when thinking through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application structure&lt;/li&gt;
&lt;li&gt;user experience&lt;/li&gt;
&lt;li&gt;roadmap decisions&lt;/li&gt;
&lt;li&gt;feature tradeoffs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  OpenCode AI
&lt;/h2&gt;

&lt;p&gt;OpenCode became the main execution layer for applying changes directly inside the repositories.&lt;/p&gt;

&lt;p&gt;This made iteration significantly faster because I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define tasks&lt;/li&gt;
&lt;li&gt;refine prompts&lt;/li&gt;
&lt;li&gt;generate implementations&lt;/li&gt;
&lt;li&gt;review results quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow started feeling much closer to collaborating with a junior developer that could iterate extremely fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  DeepSeek V4 Flash
&lt;/h2&gt;

&lt;p&gt;DeepSeek V4 Flash was surprisingly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quick fixes&lt;/li&gt;
&lt;li&gt;smaller implementations&lt;/li&gt;
&lt;li&gt;fast iterations&lt;/li&gt;
&lt;li&gt;UI experimentation&lt;/li&gt;
&lt;li&gt;alternative code suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because it was fast and free, it became useful for high-frequency iteration loops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Qwen 3.6 Plus
&lt;/h2&gt;

&lt;p&gt;Qwen 3.6 Plus was particularly good for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generating alternative approaches&lt;/li&gt;
&lt;li&gt;refining frontend ideas&lt;/li&gt;
&lt;li&gt;brainstorming UX improvements&lt;/li&gt;
&lt;li&gt;comparing implementation styles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes asking multiple models the same question produced surprisingly useful contrasts.&lt;/p&gt;




&lt;h1&gt;
  
  
  What AI Was Actually Good At
&lt;/h1&gt;

&lt;p&gt;The biggest productivity gain was not “AI writing code”.&lt;/p&gt;

&lt;p&gt;It was reducing friction.&lt;/p&gt;

&lt;p&gt;AI was extremely useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generating boilerplate&lt;/li&gt;
&lt;li&gt;accelerating iteration&lt;/li&gt;
&lt;li&gt;refining UI ideas&lt;/li&gt;
&lt;li&gt;debugging faster&lt;/li&gt;
&lt;li&gt;exploring alternatives&lt;/li&gt;
&lt;li&gt;reducing context-switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small MVPs, that becomes incredibly powerful.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Still Required Human Control
&lt;/h1&gt;

&lt;p&gt;The important engineering decisions still required human oversight.&lt;/p&gt;

&lt;p&gt;Things that still needed strong manual control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture decisions&lt;/li&gt;
&lt;li&gt;consistency&lt;/li&gt;
&lt;li&gt;product direction&lt;/li&gt;
&lt;li&gt;deployment&lt;/li&gt;
&lt;li&gt;performance considerations&lt;/li&gt;
&lt;li&gt;long-term maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI accelerated execution, but it did not replace engineering judgment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Lessons Learned
&lt;/h1&gt;

&lt;p&gt;A few things became very clear during these projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller focused prompts worked better than giant prompts&lt;/li&gt;
&lt;li&gt;AI performs best when architecture is already defined&lt;/li&gt;
&lt;li&gt;Iteration speed becomes one of the biggest advantages&lt;/li&gt;
&lt;li&gt;Reviewing generated code remains critical&lt;/li&gt;
&lt;li&gt;Combining multiple models produced better results than relying on a single one&lt;/li&gt;
&lt;li&gt;AI is especially powerful for reducing setup and boilerplate friction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the most interesting outcomes was how quickly experimentation became possible.&lt;/p&gt;

&lt;p&gt;Normally many side projects die during the setup phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;too much boilerplate&lt;/li&gt;
&lt;li&gt;too much context switching&lt;/li&gt;
&lt;li&gt;too much repetitive work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI dramatically reduced that friction.&lt;/p&gt;

&lt;p&gt;That allowed me to spend more time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product thinking&lt;/li&gt;
&lt;li&gt;UX refinement&lt;/li&gt;
&lt;li&gt;architecture&lt;/li&gt;
&lt;li&gt;experimentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of repetitive setup work.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;These projects are small, but they represent something bigger:&lt;br&gt;
a shift in how rapid MVP development is evolving.&lt;/p&gt;

&lt;p&gt;AI-assisted workflows are becoming extremely valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;side projects&lt;/li&gt;
&lt;li&gt;prototypes&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;li&gt;experimentation&lt;/li&gt;
&lt;li&gt;product validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI did not replace development.&lt;/p&gt;

&lt;p&gt;But it significantly reduced the time between:&lt;br&gt;
idea → prototype → working application.&lt;/p&gt;

&lt;p&gt;As a backend developer, these projects also helped me improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend awareness&lt;/li&gt;
&lt;li&gt;UX thinking&lt;/li&gt;
&lt;li&gt;rapid delivery skills&lt;/li&gt;
&lt;li&gt;product-focused development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, the workflow was fun.&lt;/p&gt;

&lt;p&gt;If you're experimenting with AI-assisted development workflows too, I'd love to hear how you're using them.&lt;/p&gt;




&lt;h1&gt;
  
  
  Links
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Altivo
&lt;/h2&gt;

&lt;p&gt;Live Demo:&lt;br&gt;
&lt;a href="https://danieljvsa.github.io/altivo/" rel="noopener noreferrer"&gt;https://danieljvsa.github.io/altivo/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/danieljvsa/altivo" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/altivo&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Hopway
&lt;/h2&gt;

&lt;p&gt;Live Demo:&lt;br&gt;
&lt;a href="https://danieljvsa.github.io/hopway/" rel="noopener noreferrer"&gt;https://danieljvsa.github.io/hopway/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/danieljvsa/hopway" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/hopway&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;Portfolio:&lt;br&gt;
&lt;a href="https://danieljvsa.vercel.app/" rel="noopener noreferrer"&gt;https://danieljvsa.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/danieljvsa" rel="noopener noreferrer"&gt;https://github.com/danieljvsa&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing teltonika-go: A Go Package for Parsing and Communicating with Teltonika Devices</title>
      <dc:creator>Daniel Sá</dc:creator>
      <pubDate>Thu, 12 Jun 2025 13:40:53 +0000</pubDate>
      <link>https://dev.to/danieljvsa/introducing-teltonika-go-a-go-package-for-parsing-and-communicating-with-teltonika-devices-4oof</link>
      <guid>https://dev.to/danieljvsa/introducing-teltonika-go-a-go-package-for-parsing-and-communicating-with-teltonika-devices-4oof</guid>
      <description>&lt;p&gt;If you've ever worked with Teltonika GPS tracking devices, you know that parsing their proprietary protocol can be a bit of a challenge. Whether you're building a fleet management system, a custom IoT platform, or just tinkering with real-time vehicle telemetry, understanding and communicating with these devices is critical.&lt;/p&gt;

&lt;p&gt;That's why I created teltonika-go — an open-source Go package that simplifies parsing Teltonika messages and enables communication with their devices over TCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 What is teltonika-go?
&lt;/h3&gt;

&lt;p&gt;teltonika-go is a lightweight, idiomatic Go library designed to help developers decode, parse, and interpret the binary protocol used by Teltonika GPS trackers like the FMB series. It provides building blocks for server-side communication with these devices, which typically send AVL (Automatic Vehicle Location) data over TCP.&lt;/p&gt;

&lt;p&gt;With teltonika-go, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read and parse AVL packets sent by devices&lt;/li&gt;
&lt;li&gt;Understand GPS, IO, and timestamp data&lt;/li&gt;
&lt;li&gt;Handle device handshakes and codec types (including Codec8, Codec8 Extended and Codec16)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build your own custom server or integrate Teltonika device communication into an existing Go application&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Why Use It?
&lt;/h3&gt;

&lt;p&gt;Teltonika devices are widely used in the GPS tracking industry, but documentation is limited and most of it is not tailored to developers. If you're a Go developer, teltonika-go provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clean and idiomatic API&lt;/li&gt;
&lt;li&gt;No external dependencies (other than the Go standard library)&lt;/li&gt;
&lt;li&gt;Open-source flexibility (MIT License)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔧 Getting Started
&lt;/h3&gt;

&lt;p&gt;Install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go get github.com/danieljvsa/teltonika-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Parse a TCP packet from a Teltonika FMB device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    pkg "github.com/danieljvsa/teltonika-go/pkg" //for general functions
    tools "github.com/danieljvsa/teltonika-go/tools" //for functions that used in package to help in case only want to decode specific tram code
)

func main() {
    // example binary data from a Teltonika device
    rawLogin := []byte{ /* login packet */ }
    rawTram := []byte{ /* AVL packet */ }

    // Decode login packet
    login := pkg.LoginDecoder(rawLogin)
    if login.Error != nil {
        fmt.Println("Login decode error:", login.Error)
    } else {
        fmt.Printf("Login decoded: %+v\n", login.Response)
    }

    // Decode AVL/tram packet
    tram := pkg.TramDecoder(rawTram)
    if tram.Error != nil {
        fmt.Println("Tram decode error:", tram.Error)
    } else {
        fmt.Printf("Tram decoded: %+v\n", tram.Response)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧱 Project Structure
&lt;/h3&gt;

&lt;p&gt;The project is still in its early stages, but currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decode login packets&lt;/li&gt;
&lt;li&gt;Parse AVL records using Codecs 08, 8E and 16&lt;/li&gt;
&lt;li&gt;Validate and interpret Teltonika TCP/UDP headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to check out the repo: &lt;a href="https://github.com/danieljvsa/teltonika-go" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/teltonika-go&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🚧 Roadmap
&lt;/h3&gt;

&lt;p&gt;The project is under active development, and here are some features on the horizon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full support for Codec 12 and Codec 14&lt;/li&gt;
&lt;li&gt;Full support for encoding for commands with codecs 12, 13, 14, 15.&lt;/li&gt;
&lt;li&gt;Tools to send commands to devices (e.g., engine cut-off, configuration updates)&lt;/li&gt;
&lt;li&gt;Improved error handling and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're working with Teltonika devices and using Go, I'd love your feedback and contributions!&lt;/p&gt;

&lt;h4&gt;
  
  
  🤝 Contributing
&lt;/h4&gt;

&lt;p&gt;Contributions are welcome! If you find a bug, have a feature request, or just want to improve the code, feel free to open an issue or a pull request.&lt;/p&gt;

&lt;h4&gt;
  
  
  🙌 Final Thoughts
&lt;/h4&gt;

&lt;p&gt;Parsing Teltonika’s binary protocol doesn’t need to be a pain. With teltonika-go, you can start building robust applications that speak Teltonika's language — all in idiomatic Go.&lt;/p&gt;

&lt;h4&gt;
  
  
  👉 Check out the project on GitHub: danieljvsa/teltonika-go
&lt;/h4&gt;

&lt;p&gt;⭐ Star it if you find it useful, and let's make working with Teltonika devices easier together!&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/danieljvsa/teltonika-go" rel="noopener noreferrer"&gt;https://github.com/danieljvsa/teltonika-go&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>gps</category>
      <category>teltonika</category>
    </item>
  </channel>
</rss>
