<?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: Andrew Yong</title>
    <description>The latest articles on DEV Community by Andrew Yong (@andrewctf).</description>
    <link>https://dev.to/andrewctf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4198320%2Fa31f4751-7431-4276-92e0-93ed3f418e47.webp</url>
      <title>DEV Community: Andrew Yong</title>
      <link>https://dev.to/andrewctf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrewctf"/>
    <language>en</language>
    <item>
      <title>I drew 47 million aircraft positions from my home tracker</title>
      <dc:creator>Andrew Yong</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:37:52 +0000</pubDate>
      <link>https://dev.to/andrewctf/i-drew-47-million-aircraft-positions-from-my-home-tracker-1h8h</link>
      <guid>https://dev.to/andrewctf/i-drew-47-million-aircraft-positions-from-my-home-tracker-1h8h</guid>
      <description>&lt;p&gt;I run a flight and ship tracker at home that writes every position it sees to disk. After two months I had a 27 GB SQLite file and no idea what was in it, so I started drawing it. No basemap, no coastlines, just one pixel per position bucket on a log scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six hours of global air traffic
&lt;/h2&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%2Fz3fhrevmtl4xpu22skcg.jpg" 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%2Fz3fhrevmtl4xpu22skcg.jpg" alt="World map drawn only from aircraft positions" width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;47.6 million positions from 50,202 aircraft, 10 October 2026, 11:00 to 17:00 UTC. The data comes from public ADS-B aggregators (adsb.lol, OpenSky and a few others), which collect what volunteers' receivers pick up.&lt;/p&gt;

&lt;p&gt;I posted this on r/dataisbeautiful and the comments taught me more about my own data than I knew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;China and Russia look almost empty.&lt;/strong&gt; That's coverage, not traffic. Very few people there feed public aggregators, so the map mostly shows where receivers are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Melbourne to Sydney is one of the busiest routes in the world and barely shows up.&lt;/strong&gt; The window is 10pm to 4am there, and Sydney airport has a curfew from 11pm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six hours skews everything.&lt;/strong&gt; It's afternoon in Europe, morning in the Americas and night in East Asia. A full 24 hours is next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Europe, same six hours
&lt;/h2&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%2Fkq54aa0aihxs5803tnea.jpg" 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%2Fkq54aa0aihxs5803tnea.jpg" alt="Europe drawn only from aircraft tracks" width="800" height="826"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;14.6 million positions from 11,127 aircraft. With no coastlines it took one commenter a moment to find Europe at all. London is the brightest knot top left, and once you have it Paris, Frankfurt and Madrid fall into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ships along the Norwegian coast
&lt;/h2&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%2Fstgwrjxymad34f2036of.jpg" 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%2Fstgwrjxymad34f2036of.jpg" alt="Ship positions along Norway's coast" width="800" height="1004"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.4 million AIS positions over 12 days from Kystverket, the Norwegian Coastal Administration, which publishes a free live AIS feed. The bright dots out at sea are oil and gas fields. I looked up the ships parked at the brightest ones: drilling rigs like Deepsea Stavanger and Scarabeo 8, the Alvheim production ship, and the supply and standby boats that sit next to them for days. A ship that doesn't move piles up thousands of positions in one pixel.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the maps are made
&lt;/h2&gt;

&lt;p&gt;The tracker stores positions in a &lt;code&gt;positions&lt;/code&gt; table with &lt;code&gt;lon&lt;/code&gt;, &lt;code&gt;lat&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt; and &lt;code&gt;kind&lt;/code&gt;. The whole map is a 2D histogram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;  &lt;span class="c1"&gt;# 0.1 degree cells
&lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;select lon, lat from positions where t &amp;gt;= ? and t &amp;lt; ? and kind = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aircraft&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;hist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchmany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2_000_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;np.log1p&lt;/code&gt;, divide by the 99.7th percentile so a few airports don't blow out the scale, map through a colour ramp, and save with Pillow. Reading 47 million rows in 2-million-row chunks keeps memory flat. For the Norway map I scaled longitude by cos(63°) so the coast isn't stretched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tracker
&lt;/h2&gt;

&lt;p&gt;It's called &lt;a href="https://github.com/AndrewCTF/velocity" rel="noopener noreferrer"&gt;Velocity&lt;/a&gt; and it's open source (AGPL). I built it because free trackers keep shrinking how far back you can look: Flightradar24 gives 7 days of replay free, MarineTraffic 24 hours. Velocity pulls the same public feeds, needs no API keys, and keeps whatever you tell it to on your own disk, with a scrubber to rewind the map.&lt;/p&gt;

&lt;p&gt;If you want to make your own version of these maps, &lt;code&gt;docker compose up&lt;/code&gt; and leave it running for a day. Next up is the full 24-hour world map and a Nordic one with Danish waters filled in.&lt;/p&gt;

</description>
      <category>dataviz</category>
      <category>python</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a flight and ship tracker that keeps its own history</title>
      <dc:creator>Andrew Yong</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:17:04 +0000</pubDate>
      <link>https://dev.to/andrewctf/i-built-a-flight-and-ship-tracker-that-keeps-its-own-history-2ed5</link>
      <guid>https://dev.to/andrewctf/i-built-a-flight-and-ship-tracker-that-keeps-its-own-history-2ed5</guid>
      <description>&lt;p&gt;Free flight trackers keep shrinking their history. Flightradar24 now needs a subscription for anything older than 12 hours, MarineTraffic cut its free window to 24 hours, and ADS-B Exchange ended its free API.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/AndrewCTF/velocity" rel="noopener noreferrer"&gt;Velocity&lt;/a&gt;. It runs on your own machine (&lt;code&gt;docker compose up&lt;/code&gt;, no API keys) and records every aircraft and ship position it sees to your own disk. Mine has 132M positions since August in 27 GB of SQLite. Drag the scrubber and the map rewinds; for aircraft it can also pull public readsb archives back to 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I use it for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An alert when a military or emergency aircraft enters an area I care about, pushed to my phone through ntfy.&lt;/li&gt;
&lt;li&gt;Checking what was flying or sailing near something after it made the news.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things I cared about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every contact says where it came from.&lt;/strong&gt; Each aircraft and ship shows which feeds reported it and how old the fix is, so a stale dot doesn't pretend to be live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own receiver counts.&lt;/strong&gt; Point it at your tar1090 and it folds that in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The history is a byte budget you set,&lt;/strong&gt; not a paywall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It answers agents too.&lt;/strong&gt; It runs as an MCP server, so an AI agent can ask what is flying near a place right now and get sourced positions back.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's rough
&lt;/h2&gt;

&lt;p&gt;Coverage is only as good as the public feeds and thin over oceans. The 3D globe wants a decent GPU (there's a lighter 2D map). And I wrote it with a lot of help from Claude Code; I review every change and there are 3,159 backend tests.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/AndrewCTF/velocity" rel="noopener noreferrer"&gt;https://github.com/AndrewCTF/velocity&lt;/a&gt; (AGPL-3.0)&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>python</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
