<?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: Roman Kotenko</title>
    <description>The latest articles on DEV Community by Roman Kotenko (@road511).</description>
    <link>https://dev.to/road511</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3869750%2F9094c537-628b-474a-85f5-580d4039ba77.png</url>
      <title>DEV Community: Roman Kotenko</title>
      <link>https://dev.to/road511</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/road511"/>
    <language>en</language>
    <item>
      <title>How I Normalized 30+ Different 511 Traffic APIs Into One REST Endpoint</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 09 Apr 2026 11:50:10 +0000</pubDate>
      <link>https://dev.to/road511/how-i-normalized-30-different-511-traffic-apis-into-one-rest-endpoint-i8j</link>
      <guid>https://dev.to/road511/how-i-normalized-30-different-511-traffic-apis-into-one-rest-endpoint-i8j</guid>
      <description>&lt;p&gt;Every US state and Canadian province runs its own 511 traveler information system. They all serve the same kind of data — traffic incidents, cameras, road conditions, message signs — but every single one does it differently.&lt;/p&gt;

&lt;p&gt;I spent the past year building &lt;a href="https://road511.com" rel="noopener noreferrer"&gt;Road511&lt;/a&gt;, a unified API that normalizes data from 57 jurisdictions into one consistent REST endpoint. Here's what I learned about wrangling 30+ incompatible APIs into a single schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Let's say you want traffic camera feeds for a route from New York to Chicago. You'll need to integrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New York&lt;/strong&gt; — ibi511 REST API with custom JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pennsylvania&lt;/strong&gt; — ASP.NET map layer markers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ohio&lt;/strong&gt; — OHGO Public API with &lt;code&gt;api-key&lt;/code&gt; param auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indiana&lt;/strong&gt; — CARS platform REST microservices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Illinois&lt;/strong&gt; — Travel Midwest POST endpoints with bbox body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five states, five completely different APIs. Different authentication, different response formats, different field names for the same data. And that's just cameras — add events, road conditions, and message signs, and you're looking at 20+ integrations for one cross-state route.&lt;/p&gt;

&lt;p&gt;Now multiply that by the entire continent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Format Zoo
&lt;/h2&gt;

&lt;p&gt;Here's a sample of what "traffic event" looks like across different platforms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transnomis (GA, ID, WI, and 15 more states)&lt;/strong&gt; — flat JSON with &lt;code&gt;FlexString&lt;/code&gt; fields that can be either strings or numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Head"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Crash on I-85 NB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"EventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incidents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Major"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"33.7590"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-84.3880"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WZDx (24 US states)&lt;/strong&gt; — GeoJSON with deeply nested properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LineString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"core_details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"event_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"work-zone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"road_names"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"I-95"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"northbound"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lane closure for bridge repair"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ArcGIS FeatureServer (KY, OH, TX, and 15 more)&lt;/strong&gt; — Esri's paginated query format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"OBJECTID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"INCIDENTTYPE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"DESCRIPTION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multi-vehicle crash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"LATITUDE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.0406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"LONGITUDE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-84.5037&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-84.5037&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.0406&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exceededTransferLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wyoming&lt;/strong&gt; — Protobuf binary files, Base64-encoded and XOR'd with a static key. Seriously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CARS GraphQL (NE, CO)&lt;/strong&gt; — a GraphQL BFF layered over REST microservices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quebec&lt;/strong&gt; — WFS GeoJSON from one endpoint, ASP.NET Element.ashx markers from another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New England Compass (ME, NH, VT)&lt;/strong&gt; — C2C XML portal with ATMS-format feeds.&lt;/p&gt;

&lt;p&gt;That's 8+ distinct protocols. Same data, wildly different delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The solution is a plugin registry with a single function signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;FetchFunc&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt; &lt;span class="n"&gt;SourceResource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FetchResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;FetchResult&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Events&lt;/span&gt;        &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;TrafficEvent&lt;/span&gt;
    &lt;span class="n"&gt;Features&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Feature&lt;/span&gt;
    &lt;span class="n"&gt;ResponseBytes&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every adapter — whether it's parsing Transnomis JSON, decoding Wyoming protobuf, or querying ArcGIS pagination — implements this one interface. It takes a source resource config (URL, credentials, jurisdiction code) and returns normalized events and features.&lt;/p&gt;

&lt;p&gt;Registration happens at init time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ky_arcgis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchKYArcGISEvents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ky_arcgis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cameras"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchKYArcGISCameras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ky_arcgis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rest_areas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchKYArcGISRestAreas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ky_arcgis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ferries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchKYArcGISFerries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ky_arcgis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"signs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchKYArcGISSigns&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;523 &lt;code&gt;Register()&lt;/code&gt; calls across 76 adapter files. The scheduler doesn't know or care what format each source uses — it just calls &lt;code&gt;Fetch()&lt;/code&gt; and gets back normalized data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Normalized Model
&lt;/h2&gt;

&lt;p&gt;Everything converges into two tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;traffic_events&lt;/code&gt;&lt;/strong&gt; — time-bounded incidents with lifecycle tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TrafficEvent&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;                 &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Source&lt;/span&gt;             &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="c"&gt;// "on", "ga", "tx"&lt;/span&gt;
    &lt;span class="n"&gt;Jurisdiction&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="c"&gt;// "ON", "GA", "TX"&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;               &lt;span class="n"&gt;EventType&lt;/span&gt;   &lt;span class="c"&gt;// incident, construction, closure, weather&lt;/span&gt;
    &lt;span class="n"&gt;Severity&lt;/span&gt;           &lt;span class="n"&gt;Severity&lt;/span&gt;    &lt;span class="c"&gt;// minor, moderate, major, critical&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt;             &lt;span class="n"&gt;EventStatus&lt;/span&gt; &lt;span class="c"&gt;// active, archived&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;              &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;AffectedRoads&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;LanesAffected&lt;/span&gt;      &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Longitude&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;
    &lt;span class="n"&gt;StartTime&lt;/span&gt;          &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;EndTime&lt;/span&gt;            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;EstimatedEndTime&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;RoadClass&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="c"&gt;// interstate, us_highway, state_highway, local&lt;/span&gt;
    &lt;span class="n"&gt;Metadata&lt;/span&gt;           &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawMessage&lt;/span&gt; &lt;span class="c"&gt;// source-specific fields preserved&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;features&lt;/code&gt;&lt;/strong&gt; — generic table for everything else. Cameras, signs, weather stations, rest areas, bridge clearances, truck routes, EV charging — all use the same table with a &lt;code&gt;feature_type&lt;/code&gt; discriminator and type-specific fields in a JSONB &lt;code&gt;properties&lt;/code&gt; column. Adding a new data type requires zero schema migrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Lifecycle Tracking
&lt;/h3&gt;

&lt;p&gt;A traffic incident isn't a static record. It escalates (minor to major), lanes change, ETAs shift, and eventually it clears. Each source reports the current state differently — some send updates, some just stop including the event.&lt;/p&gt;

&lt;p&gt;The solution: diff the current state against the previous fetch. Track every change in an &lt;code&gt;event_history&lt;/code&gt; table — severity changes, description updates, lane changes, archival. This enables analytics like clearance time percentiles and corridor reliability scoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Coordinate Chaos
&lt;/h3&gt;

&lt;p&gt;Most sources send WGS84 (lat/lng). Some send Web Mercator (EPSG:3857). Some send state plane coordinates. Wyoming sends coordinates embedded in XOR'd protobuf. ArcGIS returns coordinates in its own spatial reference that may or may not be 4326.&lt;/p&gt;

&lt;p&gt;PostGIS handles the heavy lifting, but each adapter has to know what it's receiving and transform accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rate Limiting at Scale
&lt;/h3&gt;

&lt;p&gt;57 jurisdictions, each with multiple resource types (events, cameras, signs, weather), each polling every 1-5 minutes. That's hundreds of outbound requests per minute to external 511 systems.&lt;/p&gt;

&lt;p&gt;The scheduler uses per-server semaphores with configurable concurrency limits and request gaps. Circuit breakers back off on repeated failures. Adaptive backoff increases poll intervals when a source is slow or returning errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Schema-Free Feature Types
&lt;/h3&gt;

&lt;p&gt;When I started, I had separate tables for cameras, signs, rest areas. Every new data type meant a migration. The pivot to a generic &lt;code&gt;features&lt;/code&gt; table with JSONB properties was the best architectural decision in the project. Adding EV charging stations (100k+ from NREL) or bridge clearances (621k from FHWA) required zero schema changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;After all that normalization work, the API is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get active incidents in Ontario:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.road511.com/api/v1/events?jurisdiction=ON&amp;amp;type=incident&amp;amp;status=active"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your_key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"on_ev_12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"major"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multi-vehicle collision on Highway 401 WB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"affected_roads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Highway 401"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Westbound"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lanes_affected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2 of 4 lanes closed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43.6532&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-79.3832&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-29T08:15:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"estimated_end_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-29T12:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"has_more"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Get traffic cameras in Georgia as GeoJSON:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.road511.com/api/v1/features/geojson?type=cameras&amp;amp;jurisdiction=GA"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your_key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that response directly into Leaflet, Mapbox, or any GeoJSON-compatible tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query truck restrictions along a corridor:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.road511.com/api/v1/truck/corridor?from_lat=41.88&amp;amp;from_lng=-87.63&amp;amp;to_lat=40.71&amp;amp;to_lng=-74.01&amp;amp;buffer_km=5&amp;amp;height=4.2&amp;amp;weight=36"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your_key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns every bridge clearance, weight restriction, and truck route segment within 5km of the Chicago-to-NYC corridor that your truck can't clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Data
&lt;/h2&gt;

&lt;p&gt;This isn't just traffic events. The normalized dataset includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10,000+ live cameras&lt;/strong&gt; with direct image/stream URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic message signs&lt;/strong&gt; with current displayed text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;621,000 bridges&lt;/strong&gt; from the FHWA National Bridge Inventory (height, weight, condition)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;479,000 truck route segments&lt;/strong&gt; from the FHWA National Network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100,000+ EV charging stations&lt;/strong&gt; from NREL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather stations&lt;/strong&gt; with real-time RWIS readings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rest areas and truck parking&lt;/strong&gt; with amenity data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal weight restrictions&lt;/strong&gt; (spring load limits from multiple states)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Road conditions&lt;/strong&gt; (surface state, chain requirements)&lt;/li&gt;
&lt;li&gt;Work zones, ferries, weigh stations, service vehicles, and more&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; — chi router, pgx v5, slog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL + PostGIS&lt;/strong&gt; — spatial queries, GeoJSON generation, corridor intersection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — response caching, feature detail caching (nil-safe, optional)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vue 3 + Leaflet&lt;/strong&gt; — &lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;live traffic map&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The API is live with a free tier (no credit card):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — explore the data visually&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.road511.com" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — full endpoint reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com" rel="noopener noreferrer"&gt;Developer portal&lt;/a&gt; — sign up and get an API key&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Road511/road511-examples" rel="noopener noreferrer"&gt;GitHub examples&lt;/a&gt; — Python, JavaScript, Go, curl, Postman collection&lt;/li&gt;
&lt;li&gt;Also available on &lt;a href="https://rapidapi.com" rel="noopener noreferrer"&gt;RapidAPI&lt;/a&gt; if that's your preferred marketplace&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're building anything with traffic data — fleet routing, navigation, insurance risk, smart city dashboards — I'd love to hear what data you need. The hardest part isn't the code, it's knowing which 511 systems have which data in which format. After 57 jurisdictions, I've got a pretty good map.&lt;/p&gt;

</description>
      <category>go</category>
      <category>api</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
