<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: balyakin</title>
    <description>The latest articles on Forem by balyakin (@balyakin).</description>
    <link>https://forem.com/balyakin</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%2F3649636%2Fd506b383-315d-4e15-9c7f-1fb6ad54de80.jpeg</url>
      <title>Forem: balyakin</title>
      <link>https://forem.com/balyakin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/balyakin"/>
    <language>en</language>
    <item>
      <title>Building SkyMoment: A 4K Personalized Star Map Generator with Python, Skyfield and Astronomical Data</title>
      <dc:creator>balyakin</dc:creator>
      <pubDate>Sat, 06 Dec 2025 20:47:43 +0000</pubDate>
      <link>https://forem.com/balyakin/building-skymoment-a-4k-personalized-star-map-generator-with-python-skyfield-and-astronomical-data-11h9</link>
      <guid>https://forem.com/balyakin/building-skymoment-a-4k-personalized-star-map-generator-with-python-skyfield-and-astronomical-data-11h9</guid>
      <description>&lt;h1&gt;
  
  
  Building SkyMoment: A 4K Personalized Star Map Generator with Python, Skyfield and Real Astronomical Data
&lt;/h1&gt;

&lt;p&gt;I recently shipped a small side project called &lt;strong&gt;SkyMoment&lt;/strong&gt; — a generator that creates personalized 4K star map posters using &lt;strong&gt;real astronomical data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You enter a date, time, and location.&lt;br&gt;&lt;br&gt;
SkyMoment recreates the exact night sky for that moment and renders it as a minimalist, print-ready 4000×4000 poster.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://skymoment.art/" rel="noopener noreferrer"&gt;https://skymoment.art/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is a breakdown of how the system works — from astronomical calculations to rendering and backend automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌌 The idea
&lt;/h2&gt;

&lt;p&gt;Most “star map posters” online aren’t actually based on real sky data.&lt;br&gt;&lt;br&gt;
Many use approximations, fake constellations, or pre-rendered visuals.&lt;/p&gt;

&lt;p&gt;I wanted something more accurate and scientific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real positions of stars,
&lt;/li&gt;
&lt;li&gt;real coordinate transforms,
&lt;/li&gt;
&lt;li&gt;proper field of view,
&lt;/li&gt;
&lt;li&gt;natural-looking Milky Way,
&lt;/li&gt;
&lt;li&gt;clean and minimal layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I wanted to turn this into a small automated product:&lt;br&gt;&lt;br&gt;
enter a moment → get a PDF/PNG → done.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛰 Tech stack overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Python&lt;/strong&gt; — main rendering pipeline
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Skyfield&lt;/strong&gt; — planetary ephemeris &amp;amp; star calculations
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Hipparcos catalog&lt;/strong&gt; — star positions &amp;amp; magnitudes
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Astropy&lt;/strong&gt; — coordinate transforms
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Matplotlib&lt;/strong&gt; — final 4K rendering
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Django&lt;/strong&gt; — backend &amp;amp; API
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Lemon Squeezy&lt;/strong&gt; — payments + webhooks
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;SMTP&lt;/strong&gt; — email delivery of the final poster
&lt;/h3&gt;

&lt;p&gt;The frontend is intentionally minimal. The “magic” happens in the rendering pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔭 Astronomical calculations step-by-step
&lt;/h2&gt;

&lt;p&gt;When a user enters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;date
&lt;/li&gt;
&lt;li&gt;time
&lt;/li&gt;
&lt;li&gt;location (lat/lon)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the pipeline does:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Convert local time to UTC&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Skyfield requires accurate UTC timestamps.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Load planetary ephemeris&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using DE421 / DE422 depending on the build.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Load Hipparcos star catalog&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RA/Dec
&lt;/li&gt;
&lt;li&gt;magnitude
&lt;/li&gt;
&lt;li&gt;proper motion
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Transform everything into the user's sky coordinates&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;Astropy&lt;/code&gt; AltAz transforms based on the observer’s position.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Filter stars&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Only stars above a certain brightness threshold are rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Split bright/faint stars&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Bright stars use a stylized 8-point marker.&lt;br&gt;&lt;br&gt;
Faint stars use simple dots.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Render the Milky Way&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A custom multi-noise layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low-frequency shapes
&lt;/li&gt;
&lt;li&gt;medium cloud structure
&lt;/li&gt;
&lt;li&gt;smooth alpha falloff
&lt;/li&gt;
&lt;li&gt;suppression of “sand grain” artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This part took the longest to tune.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;8. Render at 4000×4000 resolution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Matplotlib actually does a great job when used carefully.&lt;/p&gt;

&lt;p&gt;Total render time: &lt;strong&gt;~35–40 seconds on server hardware&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🖥 Rendering performance challenges
&lt;/h2&gt;

&lt;p&gt;4K resolution + thousands of stars + Milky Way noise layers = heavy.&lt;/p&gt;

&lt;p&gt;Some optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preload catalogs once per worker
&lt;/li&gt;
&lt;li&gt;reuse figure objects
&lt;/li&gt;
&lt;li&gt;avoid expensive Python loops
&lt;/li&gt;
&lt;li&gt;limit use of transparency
&lt;/li&gt;
&lt;li&gt;skip stars below a certain magnitude
&lt;/li&gt;
&lt;li&gt;cache static layers when possible
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small changes can save 10–20% time.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Backend architecture (Django)
&lt;/h2&gt;

&lt;p&gt;SkyMoment uses a simple pipeline:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. User configures their moment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Chooses date/time/location and sees a preview.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. User is redirected to Lemon Squeezy checkout&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. After payment, LS sends a webhook to Django&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The webhook contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the order ID
&lt;/li&gt;
&lt;li&gt;the custom fields (title, location, datetime)
&lt;/li&gt;
&lt;li&gt;the product variant
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Django creates a generation job&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Status: &lt;code&gt;pending&lt;/code&gt; → &lt;code&gt;generating&lt;/code&gt; → &lt;code&gt;ready&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. A worker process runs the Python renderer&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. The final 4K poster is saved to disk&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Django sends an email with a download link&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Important detail:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;the webhook responds immediately&lt;/strong&gt; — rendering happens asynchronously to avoid LS timeouts.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Example of what the system produces
&lt;/h2&gt;

&lt;p&gt;Here is a reduced-size preview of a generated poster:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Insert image URL here if you want)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The 4K version includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;precise star positions
&lt;/li&gt;
&lt;li&gt;constellation lines
&lt;/li&gt;
&lt;li&gt;minimal typography
&lt;/li&gt;
&lt;li&gt;custom title &amp;amp; subtitle
&lt;/li&gt;
&lt;li&gt;a clean dark theme
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 What’s next
&lt;/h2&gt;

&lt;p&gt;On the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple poster templates
&lt;/li&gt;
&lt;li&gt;faster rendering (possibly caching or partial reuse)
&lt;/li&gt;
&lt;li&gt;more curated landing pages
&lt;/li&gt;
&lt;li&gt;detailed generational logs and analytics
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to try generating a star map for your own moment:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://skymoment.art/" rel="noopener noreferrer"&gt;https://skymoment.art/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer any technical questions about astronomical data, rendering, or backend architecture!&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>astronomy</category>
    </item>
  </channel>
</rss>
