AI Assistance - QO-100

Simon Brown • August 26, 2026

QO-100 Footprints

In my map software I've wanted to correctly display a set of footprints for the QO-100 / Es' Hail 2 satellite but could not find the algorithm anywhere, especially one which corrects for atmospheric refraction which is the reason why conact can be made when the satellite is just below the horizon. Microsoft's CoPilot took ~30 seconds to show me the algorithm and then deliver a nice C++ class!


Below is the code - very readable and fully commented. The only change I made is to play 1440 points (360 * 4).


#pragma once

#include <cmath>

#include <vector>


//

// Lat/Lon pair (in radians)

//

struct LatLon {

  double lat;  // latitude (radians)

  double lon;  // longitude (radians)

};


//

// Compute satellite footprint polygon with atmospheric refraction correction.

//

// This uses the standard radio‑propagation "k‑factor" model:

//

//   R_eff = k * R_E

//

// where k ≈ 4/3 for normal atmospheric conditions.

//

// IMPORTANT:

// - R_eff is used ONLY inside the geometry to compute the central angle ψ.

// - The final footprint is still drawn on the REAL Earth (R_E).

//

class RefractedSatelliteFootprintPolygon

{

public:

  // Physical Earth radius (mean WGS‑84)

  static constexpr double EarthRadius = 6371008.8;


  //

  // Compute effective Earth radius for refraction.

  // k_factor = 1.0 → no refraction

  // k_factor = 4/3 → standard radio horizon model

  //

  static double EffectiveEarthRadius(double k_factor)

  {

    return k_factor * EarthRadius;

  }


  //

  // Compute central angle ψ (radians) from satellite to footprint boundary.

  //

  // This is the key geometry:

  //

  //   ψ = acos( (R_eff / (R_eff + h)) * cos(e) ) - e

  //

  // where:

  //   R_eff = effective Earth radius (refraction corrected)

  //   h   = satellite altitude above surface (meters)

  //   e   = minimum elevation angle (radians)

  //

  // ψ is the angular distance from the sub‑satellite point to the footprint edge.

  //

  static double CentralAngle(double altitude_m,

                double elevation_deg,

                double k_factor = 4.0 / 3.0)

  {

    // Convert elevation angle from degrees → radians

    const double e = elevation_deg * M_PI / 180.0;


    // Effective Earth radius (refraction corrected)

    const double Re = EffectiveEarthRadius(k_factor);


    // Satellite distance from Earth's center

    const double r = Re + altitude_m;


    // Compute geometric term inside acos()

    const double term = (Re / r) * std::cos(e);


    // Clamp to [-1, 1] to avoid numerical domain errors in acos()

    const double x = std::max(-1.0, std::min(1.0, term));


    // Full central angle formula

    return std::acos(x) - e;

  }


  //

  // Compute the full 360‑point footprint polygon.

  //

  // For each bearing θ = 0…359 degrees:

  //

  //   - Move ψ radians away from the sub‑satellite point

  //   - Along bearing θ

  //   - Using great‑circle forward‑geodesic equations

  //

  // Output lat/lon are in radians.

  //

  static std::vector<LatLon> ComputePolygon(double satLat_deg,

                       double satLon_deg,

                       double altitude_m,

                       double elevation_deg,

                       double k_factor = 4.0 / 3.0)

  {

    // Convert satellite sub‑point to radians

    const double lat0 = satLat_deg * M_PI / 180.0;

    const double lon0 = satLon_deg * M_PI / 180.0;


    // Compute refraction‑corrected central angle ψ

    const double psi = CentralAngle(altitude_m, elevation_deg, k_factor);


    // Precompute trig values for speed

    const double sin_lat0 = std::sin(lat0);

    const double cos_lat0 = std::cos(lat0);

    const double sin_psi = std::sin(psi);

    const double cos_psi = std::cos(psi);


    std::vector<LatLon> poly;

    poly.reserve(360);


    //

    // Loop over bearings 0° → 359°

    //

    for (int bearing_deg = 0; bearing_deg < 360; ++bearing_deg)

    {

      // Bearing θ in radians

      const double theta = bearing_deg * M_PI / 180.0;


      //

      // Great‑circle forward‑geodesic:

      //

      // lat = asin( sin(lat0)*cos(ψ) + cos(lat0)*sin(ψ)*cos(θ) )

      //

      const double sin_lat =

        sin_lat0 * cos_psi +

        cos_lat0 * sin_psi * std::cos(theta);


      const double lat = std::asin(sin_lat);


      //

      // lon = lon0 + atan2( sin(θ)*sin(ψ)*cos(lat0),

      //           cos(ψ) - sin(lat0)*sin(lat) )

      //

      const double y = std::sin(theta) * sin_psi * cos_lat0;

      const double x = cos_psi - sin_lat0 * sin_lat;


      const double lon = lon0 + std::atan2(y, x);


      poly.push_back({ lat, lon });

    }


    return poly;

  }

};



By Simon Brown August 26, 2026
SDR Television v1.1.4 User Interface Rearranged ribbon bar, added Forward Error Correction (FEC) to the Home panel. Receiver Currently trying to improve LDPC decoding for low SNR. Part of this work involves reducing CPU load in the LDPC decoder. Planning to add advanced decoding in v1.1.5. Changed the list of FEC / LDPC algorithms, removing min-sum and min-sum correction. Optimised offset min-sum algorithm to reduce CPU load. Optimised Bose-Chaudhuri-Hocquenghem (BCH) processing. Added sum-product. Corrected LLR soft scaling for QPSK. Changed default RRC filter length to 12 symbols, previous value of 30 was too long. Updated Feed-Forward AGC with a design from CoPilot. Optimised Gardner TED RRC filter length, now 25 taps. Increased the receive RRC filter rolloff factor by 25% to capture more energy with weak signals. Reset LDPC after 1 second of inactivity. FEC - LDPC There are now two LDPC algorithms, Offset min-sum, and Sum-product. Sum-product is more sensitive than offset min-sum but currently doesn't work at all for FEC rates lower than 1/2 (1/4, 1/3 and 2/5). When sum-product is selected and the rate is lower than 1/2 then offset min-sum is used. The default damping settings for sum-product are shown below.  The aim is to work on sum-product with the help of ChatGPT in 2027 to get the maximum possible sensitivity. Downloads are at the bottom of this page.
By Simon Brown July 27, 2026
July 26th, 2026 Reuter RSR200B Added initial support. Fobos Finalised support for Fobos SDR, only the Agile firmware is supported. Some early Fobos SDR units had HF2 and HF1 swapped on the case printing. FFT When using NVIDIA CUDA for the FFT the returned buffers now use memory from the heap rather than pinned memory (*). Reduced the CPU load in the FFT Helper DLL. The data from the FFT (CUDA, IPP, OpenCL) is processed to match the display DIPs. When running Broadcast-FM with x4 resolution there were 550 FFT bins per display pixel (DIP). These 550 bins were averaged which takes quite some processing, especially when running a matrix display with many receivers enabled. I now down-sample the bins to a maximum of (about) 32 which doesn't affect the display or any DSP but does reduce the CPU. Frequency Database Loading of the frequency database into memory-mapped backing now much faster. Pluto TX Doppler support for Pluto / LibreSDR. Have tested this by adding debug info, seems OK 🙂 . RDS Added variable font size in the RDS editor window. Forced uppercase of PS text in the RDS Editor removed. Change to RDS Logfile updates while the editor is open & visible. Updates are now allowed, previously not so. Recordings Fixed font size error in the playback, navigation window (was incorrect logic). When starting the video recorder a sanity check makes sure the selected folder exists and is accessible. Satellite Satellite definition file format has changed, no need to update your settings but support for the new OMM (.xml) format is included. I'm hosting satellite data on my (new) Akamai server, these is currently a cache of the Celestrak data. I'll add a web page soon which explains what's been happening. Transmit Audio mute is now a per-profile setting. Other Fixed an error opening the matrix display while the SDR was already started. Crash when selecting "Configure" from the Select Radio window fixed. Does not create an error when the graphics driver restarts.
By Simon Brown July 10, 2026
SDR Television v1.1.3 User Interface Fixed erroneous cursor flicker in Settings help text and Receive, Transmit panes. Fixed error in the spectrum squelch logic. Minor fix/improvement in QPSK LUT lookup for low values. Receiver Use highest possible quality decimation for symbol rates of 333ksps and lower. Added optional Doppler correction for LEO/MEO satellites such as MARMOTsat (July 2026). This will most probably be tweaked when the DVB-S2 transmissions started. Downloads are at the bottom of this page.
By Simon Brown June 12, 2026
SDR Television v1.1.2 June 12th, 2026: Add option to disable audio, thus making more bits available for video, this is for use in DATV contests. Note: 66 ksps sample rate is still experimental, will be improved. Downloads are at the bottom of this page. 
By Simon Brown June 9, 2026
SDR Television v1.1.1 June 9th, 2026: Fixes a fatal bug in the Settings, Camera page. Downloads are at the bottom of this page.
By Simon Brown May 23, 2026
Version 1.6.2 This release fixes two bugs in the new DX Spot feature. The spot age logic incorrectly affected spot display. When the map was zoomed, station markers off the map would be displayed at the top left (0, 0).  Downloads are on the World Map page . Version 1.6.1 This release fixes two fatal bugs in the new DX Spot feature. When the graphics engine is restarted, resources are correctly released. Fixed a resource leak when processing the MoseMove logic. Downloads are on the World Map page . Version 1.6 This release adds the display of DX Spots. Display spots are submitted to PSK Reporter which in turn are relayed by a MQTT Broker running on a high performance Akamia node. Spot Format Each spot consists of these fields: Sequence Frequency Band Mode Signal level Time stamp Sending station call, square, country Receiving station call, square, country Note: The country is the ADIF country. Spots are displayed as they arrive, currently no on-demand database. Bandwidth To reduce bandwidth from the broker, filtering is required for 80m up to and including 15m. Filtering requires at least one field below to be set in a definition: Receiver callsign, square or country. Sender callsign, square or country. Without filtering the bandwith from the broker could be excessive. For LF and VHF+ no filtering is required due to the lower number of spots sent on these bands. In a later version the bandwidth restriction may be reduced.
By Simon Brown May 21, 2026
Version 1.6.1 This release fixes two fatal bugs in the new DX Spot feature. When the graphics engine is restarted, resources are correctly released. Fixed a resource leak when processing the MoseMove logic.  Downloads are on the World Map page . Version 1.6 This release adds the display of DX Spots. Display spots are submitted to PSK Reporter which in turn are relayed by a MQTT Broker running on a high performance Akamia node. Spot Format Each spot consists of these fields: Sequence Frequency Band Mode Signal level Time stamp Sending station call, square, country Receiving station call, square, country Note: The country is the ADIF country. Spots are displayed as they arrive, currently no on-demand database. Bandwidth To reduce bandwidth from the broker, filtering is required for 80m up to and including 15m. Filtering requires at least one field below to be set in a definition: Receiver callsign, square or country. Sender callsign, square or country. Without filtering the bandwith from the broker could be excessive. For LF and VHF+ no filtering is required due to the lower number of spots sent on these bands. In a later version the bandwidth restriction may be reduced.
By Simon Brown May 20, 2026
Version 1.6 This release adds the display of DX Spots. Display spots are submitted to PSK Reporter which in turn are relayed by a MQTT Broker running on a high performance Akamia node. Spot Format Each spot consists of these fields: Sequence Frequency Band Mode Signal level Time stamp Sending station call, square, country Receiving station call, square, country Note: The country is the ADIF country. Spots are displayed as they arrive, currently no on-demand database. Bandwidth To reduce bandwidth from the broker, filtering is required for 80m up to and including 15m. Filtering requires at least one field below to be set in a definition: Receiver callsign, square or country. Sender callsign, square or country. Without filtering the bandwith from the broker could be excessive. For LF and VHF+ no filtering is required due to the lower number of spots sent on these bands. In a later version the bandwidth restriction may be reduced.
By Simon Brown May 19, 2026
SDR Television v1.1 May 18th, 2026: This is the official v1.1 release , code exactly the same as 1.0.16 which has survived testing for a few weeks. Lots of improvements since the last official kit. As with any software project, there's always room for improvements and new features, but for now here's a stable solution which works well with the QO-100 satellite. Many thanks to Sigi and the DATV test team. Downloads are at the bottom of this page.