<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>astronomy &#8211; interloper.</title>
	<atom:link href="https://interloper.ie/tag/astronomy/feed/" rel="self" type="application/rss+xml" />
	<link>https://interloper.ie</link>
	<description>Marcus Craig - Artist &#38; Technician</description>
	<lastBuildDate>Fri, 02 Jan 2026 16:54:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://interloper.ie/wp-content/uploads/2023/10/Untitled-2-150x150.png</url>
	<title>astronomy &#8211; interloper.</title>
	<link>https://interloper.ie</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ISS API</title>
		<link>https://interloper.ie/iss-api/</link>
		
		<dc:creator><![CDATA[root]]></dc:creator>
		<pubDate>Mon, 30 Jun 2025 06:00:35 +0000</pubDate>
				<category><![CDATA[astronomy]]></category>
		<guid isPermaLink="false">http://217.170.204.3/?page_id=2</guid>

					<description><![CDATA[Attaching to open-source publicly available APIs are always a good way to learn about how to add external dynamic parameters. One particular source I love to begin with is the the latitude and longitude of the International Space Station. This is a fairly immutable external source (until it is decommissioned in 2030🔥) so I often [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Attaching to open-source publicly available APIs are always a good way to learn about how to add external dynamic parameters. One particular source I love to begin with is the the latitude and longitude of the International Space Station. This is a fairly immutable external source (until it is decommissioned in 2030🔥) so I often use it as a basis for my work. Despite utlising JavaScript, these sketches are relatively lightweight. All examples both pull and parse JSON via a REST API &#8211; then draws to a &#8216;canvas&#8217;. With just 4kb being the largest file size so far!</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Latitude &amp; Longitude Terminal print (3kb)</summary>
<p>Below is an example where I just print the Lat and Long, in the aesthetics of a traditional CLI terminal. This is live data, provided as a string. In creative programming, these parameters can manipulated in the code using basic arithmetic to allow for subtle or exaggerated changes in parameters.</p>



<div class="wp-block-gutenbergp5-p5js gutenbergp5-align-center"><iframe srcdoc="
        <!DOCTYPE html&gt;
        <html&gt;
            <body style=&quot;padding: 0; margin: 0;&quot;&gt;</body&gt;
            <script src=&quot;https://www.interloper.ie/wp-content/plugins/easy-p5-js-block//assets/js/p5.min.js&quot;&gt;</script&gt;
            <script&gt;
                <!-- Include p5.js --&gt;
<script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js&quot;&gt;</script&gt;

<!-- Full-screen flex wrapper to center content --&gt;
<div id=&quot;iss-wrapper&quot; style=&quot;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
&quot;&gt;
  <!-- Container for the sketch --&gt;
  <div id=&quot;iss-terminal&quot; style=&quot;width:300px; height:300px;&quot;&gt;</div&gt;
</div&gt;

<script&gt;
(function() {
  const SIZE       = 300;  // canvas width &amp; height
  const MAX_LINES  = 16;   // number of lines to display
  const FIXED_LEAD = 18;   // pixel spacing between lines
  let lines = [];
  let cursorOn = true;
  let canvas;              // to hold our p5 canvas reference

  // Blink cursor every 500 ms
  setInterval(() =&gt; cursorOn = !cursorOn, 500);

  new p5(p =&gt; {
    p.setup = () =&gt; {
      // Create and parent the canvas
      canvas = p.createCanvas(SIZE, SIZE).parent('iss-terminal');

      // Text styling
      p.textFont('monospace');
      p.textSize(16);
      p.textAlign(p.LEFT, p.TOP);
      p.fill(0,255,0);
      p.textLeading(FIXED_LEAD);

      // Fetch data immediately and every second
      fetchISS();
      setInterval(fetchISS, 1000);

      // Toggle fullscreen on click
      canvas.mousePressed(() =&gt; {
        const fs = p.fullscreen();       // current fullscreen state
        p.fullscreen(!fs);               // toggle it
      });
    };

    function fetchISS() {
      p.loadJSON('https://api.wheretheiss.at/v1/satellites/25544',
        data =&gt; {
          const lat = Number(data.latitude).toFixed(2);
          const lon = Number(data.longitude).toFixed(2);
          lines.push(`Lat: ${lat}°, Lon: ${lon}°`);
          if (lines.length &gt; MAX_LINES) lines.shift();
        },
        () =&gt; {
          lines.push('Error fetching data');
          if (lines.length &gt; MAX_LINES) lines.shift();
        }
      );
    }

    p.draw = () =&gt; {
      p.background(0);

      // Phosphor glow
      const ctx = p.drawingContext;
      ctx.shadowBlur  = 10;
      ctx.shadowColor = '#0f0';

      // Draw each line
      for (let i = 0; i < lines.length; i++) {
        p.text(lines[i], 10, i * FIXED_LEAD);
      }

      // Draw blinking cursor
      if (cursorOn &amp;&amp; lines.length) {
        const last = lines[lines.length - 1];
        const x = p.textWidth(last) + 12;
        const y = (lines.length - 1) * FIXED_LEAD;
        p.text('_', x, y);
      }
    };
  });
})();
</script&gt;
            </script&gt;
        </html&gt;" sandbox="allow-scripts allow-same-origin" scrolling="no" style="overflow:hidden;" width="" height="" class="" title="p5.js canvas"></iframe></div>



<p></p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Gizmo &#8211; interpolated motion (2kb)</summary>
<p>Here the Gizmo lags behind a single fetch-cycle, this is because the ISS API only refreshes every second. I wanted a smooth transition for the 3d element and had to interpolate from the previous frame to the current one. I have also exaggerated its rotational axis by a factor of 50 so that movement is easier to interpret at a glance.</p>



<div class="wp-block-gutenbergp5-p5js gutenbergp5-align-center"><iframe srcdoc="
        <!DOCTYPE html&gt;
        <html&gt;
            <body style=&quot;padding: 0; margin: 0;&quot;&gt;</body&gt;
            <script src=&quot;https://www.interloper.ie/wp-content/plugins/easy-p5-js-block//assets/js/p5.min.js&quot;&gt;</script&gt;
            <script&gt;
                <!DOCTYPE html&gt;
<html&gt;
<head&gt;
  <meta charset=&quot;utf-8&quot;&gt;
  <title&gt;Interpolated-Motion ISS Gizmo</title&gt;
  <style&gt;
    body { margin: 0; overflow: hidden; background: #111; }
    canvas { display: block; }
  </style&gt;
</head&gt;
<body&gt;
  <script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js&quot;&gt;</script&gt;
  <script&gt;
    // Configuration
    const CANVAS_SIZE      = 400;   // Canvas width &amp; height
    const ROT_EXAGGERATION = 50;    // Amplification factor

    // State for interpolation
    let prevLat = 0, prevLon = 0;
    let targetLat = 0, targetLon = 0;
    let lastFetchTime = 0;          // ms timestamp of the last data fetch

    function setup() {
      createCanvas(CANVAS_SIZE, CANVAS_SIZE, WEBGL);
      frameRate(60);                 // ensure ~60 FPS
      // Initial fetch + repeat every second
      updateISS();
      setInterval(updateISS, 1000);
    }

    function updateISS() {
      loadJSON(
        'https://api.wheretheiss.at/v1/satellites/25544',
        data =&gt; {
          // Shift target → prev, then set new target
          prevLat = targetLat;
          prevLon = targetLon;
          targetLat = data.latitude;
          targetLon = data.longitude;
          lastFetchTime = millis();
        },
        err =&gt; console.error('Fetch error:', err)
      );
    }

    function draw() {
      background(17);

      // Compute interpolation factor t in [0,1]
      let t = (millis() - lastFetchTime) / 1000;
      t = constrain(t, 0, 1);

      // Interpolated lat/lon
      const interpLat = lerp(prevLat, targetLat, t);
      const interpLon = lerp(prevLon, targetLon, t);

      // Apply rotations based on interpolated position
      rotateX(radians(interpLat) * ROT_EXAGGERATION);
      rotateY(radians(interpLon) * ROT_EXAGGERATION);

      // Draw XYZ axes
      strokeWeight(4);
      // X axis (red)
      stroke(255, 50, 50);
      line(0, 0, 0, 100, 0, 0);
      // Y axis (green)
      stroke(50, 255, 50);
      line(0, 0, 0, 0, 100, 0);
      // Z axis (blue)
      stroke(50, 50, 255);
      line(0, 0, 0, 0, 0, 100);
    }
  </script&gt;
</body&gt;
</html&gt;
            </script&gt;
        </html&gt;" sandbox="allow-scripts allow-same-origin" scrolling="no" style="overflow:hidden;" width="" height="" class="" title="p5.js canvas"></iframe></div>



<p></p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Gizmo with CRT shader (3kb)</summary>
<p>I wanted the Gizmo to maintain the aesthetic of the CLI Terminal and so removed the interpolation and added a shader that introduces a CRT flicker, and retains the single-second update.</p>



<div class="wp-block-gutenbergp5-p5js gutenbergp5-align-center"><iframe srcdoc="
        <!DOCTYPE html&gt;
        <html&gt;
            <body style=&quot;padding: 0; margin: 0;&quot;&gt;</body&gt;
            <script src=&quot;https://www.interloper.ie/wp-content/plugins/easy-p5-js-block//assets/js/p5.min.js&quot;&gt;</script&gt;
            <script&gt;
                <!DOCTYPE html&gt;
<html lang=&quot;en&quot;&gt;
<head&gt;
  <meta charset=&quot;utf-8&quot;&gt;
  <title&gt;CRT Gizmo with Fixed Shader</title&gt;
  <style&gt;
    body { margin: 0; overflow: hidden; background: #000; }
    canvas { display: block; }
  </style&gt;
</head&gt;
<body&gt;
  <script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js&quot;&gt;</script&gt;
  <script&gt;
    // ——— INLINE SHADER SOURCE ———
    const vertSrc = `
      precision mediump float;
      attribute vec3 aPosition;
      attribute vec2 aTexCoord;
      varying vec2 vTexCoord;
      // p5.js provides these two uniforms for you:
      uniform mat4 uModelViewMatrix;
      uniform mat4 uProjectionMatrix;
      void main() {
        vTexCoord = aTexCoord;
        // Multiply position by model/view then projection
        gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
      }
    `;

    const fragSrc = `
      precision mediump float;
      varying vec2 vTexCoord;
      uniform sampler2D uTex;
      uniform float uTime;
      void main() {
        vec4 color = texture2D(uTex, vTexCoord);
        float lum = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
        vec3 green = vec3(0.0, lum, 0.0);
        green -= sin(vTexCoord.y * 800.0) * 0.04;   // scanlines
        green += 0.02 * sin(uTime * 60.0);         // flicker
        vec2 uv = vTexCoord - 0.5;
        green *= 1.0 - dot(uv, uv) * 0.5;          // vignette
        gl_FragColor = vec4(green, 1.0);
      }
    `;

    // ——— STATE &amp; CONFIG ———
    let pg, crtShader;
    let prevLat = 0, prevLon = 0;
    let accRotX = 0, accRotY = 0;
    const EXAG = 50;  // exaggeration factor

    function setup() {
      createCanvas(400, 400, WEBGL);
      noStroke();

      // Compile the shaders
      crtShader = createShader(vertSrc, fragSrc);

      // Offscreen buffer for gizmo
      pg = createGraphics(width, height, WEBGL);

      // Fetch ISS data once and then every second
      updateISS();
      setInterval(updateISS, 1000);
    }

    function updateISS() {
      loadJSON(
        'https://api.wheretheiss.at/v1/satellites/25544',
        data =&gt; {
          const dLat = data.latitude  - prevLat;
          const dLon = data.longitude - prevLon;
          prevLat = data.latitude;
          prevLon = data.longitude;
          accRotX += radians(dLat) * EXAG;
          accRotY += radians(dLon) * EXAG;
        },
        err =&gt; console.error('Fetch error:', err)
      );
    }

    function draw() {
      // 1) Draw gizmo into offscreen buffer
      pg.push();
        pg.clear();
        pg.background(0);
        pg.rotateX(accRotX);
        pg.rotateY(accRotY);
        pg.strokeWeight(4);
        pg.stroke(255,50,50); pg.line(0,0,0,100,0,0);   // X-axis
        pg.stroke(50,255,50); pg.line(0,0,0,0,100,0);   // Y-axis
        pg.stroke(50,50,255); pg.line(0,0,0,0,0,100);   // Z-axis
      pg.pop();

      // 2) Apply CRT shader to full canvas
      shader(crtShader);
      crtShader.setUniform('uTex', pg);
      crtShader.setUniform('uTime', millis() * 0.001);

      // Draw a quad over the viewport
      quad(
        -width/2, -height/2,
         width/2, -height/2,
         width/2,  height/2,
        -width/2,  height/2
      );
    }
  </script&gt;
</body&gt;
</html&gt;
            </script&gt;
        </html&gt;" sandbox="allow-scripts allow-same-origin" scrolling="no" style="overflow:hidden;" width="" height="" class="" title="p5.js canvas"></iframe></div>



<p></p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Combined</summary>
<div class="wp-block-gutenbergp5-p5js gutenbergp5-align-center"><iframe srcdoc="
        <!DOCTYPE html&gt;
        <html&gt;
            <body style=&quot;padding: 0; margin: 0;&quot;&gt;</body&gt;
            <script src=&quot;https://www.interloper.ie/wp-content/plugins/easy-p5-js-block//assets/js/p5.min.js&quot;&gt;</script&gt;
            <script&gt;
                <!DOCTYPE html&gt;
<html lang=&quot;en&quot;&gt;
<head&gt;
  <meta charset=&quot;utf-8&quot; /&gt;
  <title&gt;ISS Terminal + CRT Gizmo (Unified)</title&gt;
  <style&gt;
    body { margin:0; overflow:hidden; background:#000; }
    canvas { display:block; }
  </style&gt;
</head&gt;
<body&gt;
  <script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js&quot;&gt;</script&gt;
  <script&gt;
    // ——— CONFIGURATION ———
    const TERM_W      = 300,   // terminal width
          GIZMO_W     = 400,   // gizmo width
          CANVAS_H    = 300,   // canvas height
          CANVAS_W    = TERM_W + GIZMO_W, // total width
          MAX_LINES   = 16,    // how many lines to show
          LEAD        = 18,    // line spacing
          ROT_EXAG    = 50;    // gizmo rotation exaggeration

    // ——— STATE ———
    let lines = [],
        cursorOn = true,
        pgGizmo, shaderCRT,
        prevLat = 0, prevLon = 0,
        accX = 0, accY = 0,
        terminalFont;

    function preload() {
      // load a monospace font for WebGL text
      terminalFont = loadFont('https://cdn.jsdelivr.net/npm/source-code-pro@2.30.1/TTF/SourceCodePro-Regular.ttf');
    }

    function setup() {
      // Use WEBGL so shaders compile
      createCanvas(CANVAS_W, CANVAS_H, WEBGL);
      noStroke();

      // Terminal text style
      textFont(terminalFont);
      textSize(16);
      textLeading(LEAD);
      fill(0,255,0);

      // Blink cursor
      setInterval(() =&gt; cursorOn = !cursorOn, 500);

      // Offscreen buffer for gizmo
      pgGizmo = createGraphics(GIZMO_W, CANVAS_H, WEBGL);
      pgGizmo.noStroke();

      // Inline CRT shader
      const vert = `
        precision mediump float;
        attribute vec3 aPosition;
        attribute vec2 aTexCoord;
        varying vec2 vTexCoord;
        uniform mat4 uModelViewMatrix;
        uniform mat4 uProjectionMatrix;
        void main(){
          vTexCoord = aTexCoord;
          gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition,1.0);
        }`;
      const frag = `
        precision mediump float;
        varying vec2 vTexCoord;
        uniform sampler2D uTex;
        uniform float uTime;
        void main(){
          vec4 c = texture2D(uTex, vTexCoord);
          float lum = dot(c.rgb, vec3(0.2126,0.7152,0.0722));
          vec3 g = vec3(0.0, lum, 0.0);
          g -= sin(vTexCoord.y*800.0)*0.04;
          g += 0.02*sin(uTime*60.0);
          vec2 uv = vTexCoord - 0.5;
          g *= 1.0 - dot(uv,uv)*0.5;
          gl_FragColor = vec4(g,1.0);
        }`;
      shaderCRT = createShader(vert, frag);

      // Start fetching ISS data
      fetchISS();
      setInterval(fetchISS, 1000);
    }

    function fetchISS() {
      loadJSON('https://api.wheretheiss.at/v1/satellites/25544',
        data =&gt; {
          // Terminal lines
          const lat = Number(data.latitude).toFixed(2),
                lon = Number(data.longitude).toFixed(2);
          lines.push(`Lat: ${lat}°, Lon: ${lon}°`);
          if (lines.length &gt; MAX_LINES) lines.shift();

          // Delta-based gizmo rotation
          const dLat = data.latitude  - prevLat,
                dLon = data.longitude - prevLon;
          prevLat = data.latitude;
          prevLon = data.longitude;
          accX += radians(dLat) * ROT_EXAG;
          accY += radians(dLon) * ROT_EXAG;
        },
        () =&gt; {
          lines.push('Error fetching data');
          if (lines.length &gt; MAX_LINES) lines.shift();
        }
      );
    }

    function draw() {
      // Clear
      background(0);

      // ——— TERMINAL (LEFT) ———
      resetShader();            // back to regular pipeline
      textFont(terminalFont);   // ensure font
      for (let i = 0; i < lines.length; i++) {
        text(
          lines[i],
          -CANVAS_W/2 + 10,
          -CANVAS_H/2 + 10 + i * LEAD
        );
      }
      // Blinking cursor
      if (cursorOn &amp;&amp; lines.length) {
        const last = lines[lines.length - 1],
              x = textWidth(last) + (-CANVAS_W/2 + 12),
              y = -CANVAS_H/2 + 10 + (lines.length - 1) * LEAD;
        text('_', x, y);
      }

      // ——— GIZMO OFFSCREEN RENDER ———
      pgGizmo.push();
        pgGizmo.clear();
        pgGizmo.background(0);
        pgGizmo.rotateX(accX);
        pgGizmo.rotateY(accY);
        pgGizmo.strokeWeight(4);
        pgGizmo.stroke(255,50,50); pgGizmo.line(0,0,0,100,0,0);
        pgGizmo.stroke(50,255,50); pgGizmo.line(0,0,0,0,100,0);
        pgGizmo.stroke(50,50,255); pgGizmo.line(0,0,0,0,0,100);
      pgGizmo.pop();

      // ——— CRT SHADER &amp; BLIT GIZMO (RIGHT) ———
      push();
        // move gizmo 50px left and 20px up
        translate(-90, -20);
        shader(shaderCRT);
        shaderCRT.setUniform('uTex', pgGizmo);
        shaderCRT.setUniform('uTime', millis() * 0.001);
        // draw full quad for right half
        quad(
          0,             -CANVAS_H/2,
          GIZMO_W,       -CANVAS_H/2,
          GIZMO_W,        CANVAS_H/2,
          0,              CANVAS_H/2
        );
      pop();
    }
  </script&gt;
</body&gt;
</html&gt;
            </script&gt;
        </html&gt;" sandbox="allow-scripts allow-same-origin" scrolling="no" style="overflow:hidden;" width="" height="" class="" title="p5.js canvas"></iframe></div>



<p></p>
</details>



<details id="plot" class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>ISS Plot (4kb)</summary>
<p>All of my examples thus far have been predictable use cases for ISS data. One last one is below and utilises the current altitude of the ISS. It plots its course over a rough projection of the earth. These examples live as a pseudo-instrumentation for monitoring such a satellite. The stroke-weight is larger when at a higher altitude and smaller at a lower altitude.</p>



<div class="wp-block-gutenbergp5-p5js gutenbergp5-align-center"><iframe srcdoc="
        <!DOCTYPE html&gt;
        <html&gt;
            <body style=&quot;padding: 0; margin: 0;&quot;&gt;</body&gt;
            <script src=&quot;https://www.interloper.ie/wp-content/plugins/easy-p5-js-block//assets/js/p5.min.js&quot;&gt;</script&gt;
            <script&gt;
                <script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js&quot;&gt;</script&gt;
<div id=&quot;orbit-overlay&quot;&gt;</div&gt;
<script&gt;
new p5(p =&gt; {
  // ——— CONFIG ———
  const CANVAS_SIZE     = 480;
  const R               = CANVAS_SIZE/2 - 10;
  const HISTORY_SECONDS = 3600;    // fetch 1 hour on startup
  const FETCH_INTERVAL  = 1000;    // live updates every second
  const MAX_DRAW_POINTS = 600;     // cap segments drawn

  // ——— STATE ———
  let trail = [];
  let minAlt = Infinity, maxAlt = -Infinity;
  let isLoadingHistory = true;
  let showLoading = true;

  // ——— FETCH 1 HOUR HISTORY ———
  async function fetchHistory() {
    const now   = Math.floor(Date.now()/1000);
    const start = now - HISTORY_SECONDS + 1;
    const timestamps = [];
    for (let t = start; t <= now; t++) timestamps.push(t);
    for (let i = 0; i < timestamps.length; i += 10) {
      const chunk = timestamps.slice(i, i + 10).join(',');
      const url   = `https://api.wheretheiss.at/v1/satellites/25544/positions?timestamps=${chunk}`;
      try {
        let arr = await fetch(url).then(r =&gt; r.json());
        arr.sort((a,b) =&gt; a.timestamp - b.timestamp);
        arr.forEach(d =&gt; {
          trail.push({ lat:d.latitude, lon:d.longitude, alt:d.altitude });
          minAlt = p.min(minAlt, d.altitude);
          maxAlt = p.max(maxAlt, d.altitude);
        });
      } catch(e) {
        console.error('History fetch error', e);
      }
    }
  }

  // ——— LIVE UPDATE ———
  async function fetchISS() {
    try {
      const d = await fetch('https://api.wheretheiss.at/v1/satellites/25544')
                        .then(r =&gt; r.json());
      trail.push({ lat:d.latitude, lon:d.longitude, alt:d.altitude });
      if (trail.length &gt; HISTORY_SECONDS) trail.shift();
      minAlt = p.min(minAlt, d.altitude);
      maxAlt = p.max(maxAlt, d.altitude);
    } catch(e) {
      console.error('Live fetch error', e);
    }
  }

  // ——— PROJECTION ———
  function project(lat, lon) {
    const φ = p.radians(lat), θ = p.radians(lon);
    return p.createVector(
      R * p.cos(φ) * p.sin(θ),
     -R * p.sin(φ)
    );
  }

  p.setup = () =&gt; {
    p.createCanvas(CANVAS_SIZE, CANVAS_SIZE).parent('orbit-overlay');
    p.frameRate(60);

    // Blink the loading text
    setInterval(() =&gt; showLoading = !showLoading, 500);

    // Load history, then switch to live
    fetchHistory().then(() =&gt; {
      isLoadingHistory = false;
      fetchISS();
      setInterval(fetchISS, FETCH_INTERVAL);
    });
  };

  p.draw = () =&gt; {
    p.background(0);
    p.translate(CANVAS_SIZE/2, CANVAS_SIZE/2);

    // — Geometry lines — thin white
    p.stroke(255); p.strokeWeight(0.5);
    p.line(-R, 0, R, 0); // equator
    p.line(0, -R, 0, R); // prime meridian

    // — Earth outline — thin green
    p.noFill();
    p.stroke(0,255,0);
    p.strokeWeight(1);
    p.circle(0, 0, R*2);

    // — Full-hour trail (sampled) — violet
    const totalPts = trail.length;
    const skip     = Math.max(1, Math.floor(totalPts / MAX_DRAW_POINTS));
    for (let i = skip; i < totalPts; i += skip) {
      const a0 = trail[i - skip], a1 = trail[i];
      const v0 = project(a0.lat, a0.lon);
      const v1 = project(a1.lat, a1.lon);
      const sw = p.map(a1.alt, minAlt, maxAlt, 0.5, 4);
      p.stroke(148,0,211);
      p.strokeWeight(sw);
      p.line(v0.x, v0.y, v1.x, v1.y);
    }

    // — Current altitude label — bottom-left
    if (trail.length) {
      const curr = trail[trail.length - 1].alt;
      p.push();
        p.resetMatrix();
        p.fill(255);
        p.noStroke();
        p.textSize(14);
        p.textAlign(p.LEFT, p.BOTTOM);
        p.text(`Alt: ${curr.toFixed(1)} km`, 10, CANVAS_SIZE - 10);
      p.pop();
    }

    // — Loading overlay — centered, blinking, monospace
    if (isLoadingHistory &amp;&amp; showLoading) {
      p.push();
        p.resetMatrix();
        p.fill(255);
        p.textFont('monospace');
        p.textSize(20);
        p.textAlign(p.CENTER, p.CENTER);
        p.text(
          'Loading historical positioning..',
          CANVAS_SIZE/2, CANVAS_SIZE/2
        );
      p.pop();
    }
  };
});
</script&gt;
            </script&gt;
        </html&gt;" sandbox="allow-scripts allow-same-origin" scrolling="no" style="overflow:hidden;" width="" height="" class="" title="p5.js canvas"></iframe></div>



<p></p>
</details>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>It Comes in Waves</title>
		<link>https://interloper.ie/waves/</link>
		
		<dc:creator><![CDATA[root]]></dc:creator>
		<pubDate>Tue, 03 Oct 2023 17:38:54 +0000</pubDate>
				<category><![CDATA[Lens-based]]></category>
		<category><![CDATA[astronomy]]></category>
		<category><![CDATA[open-source]]></category>
		<category><![CDATA[photography]]></category>
		<category><![CDATA[satellite]]></category>
		<guid isPermaLink="false">https://www.interloper.ie/?p=116</guid>

					<description><![CDATA[It Comes in Waves NOAA weather satellite]]></description>
										<content:encoded><![CDATA[
<h2 data-wp-context---core-fit-text="core/fit-text::{&quot;fontSize&quot;:&quot;&quot;}" data-wp-init---core-fit-text="core/fit-text::callbacks.init" data-wp-interactive data-wp-style--font-size="core/fit-text::context.fontSize" class="wp-block-heading has-fit-text">It Comes in Waves</h2>



<figure data-wp-context="{&quot;imageId&quot;:&quot;69d8025eaa850&quot;}" data-wp-interactive="core/image" data-wp-key="69d8025eaa850" class="wp-block-image aligncenter size-large wp-lightbox-container"><img loading="lazy" decoding="async" width="882" height="1024" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://interloper.ie/wp-content/uploads/2023/09/sample-2-882x1024.png" alt="" class="wp-image-77" srcset="https://interloper.ie/wp-content/uploads/2023/09/sample-2-882x1024.png 882w, https://interloper.ie/wp-content/uploads/2023/09/sample-2-258x300.png 258w, https://interloper.ie/wp-content/uploads/2023/09/sample-2-768x891.png 768w, https://interloper.ie/wp-content/uploads/2023/09/sample-2.png 959w" sizes="auto, (max-width: 882px) 100vw, 882px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			aria-label="Enlarge"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.imageButtonRight"
			data-wp-style--top="state.imageButtonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p>NOAA weather satellite </p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
