Skip to content

Conversation

@thkruz
Copy link
Owner

@thkruz thkruz commented Nov 16, 2025

No description provided.

thkruz and others added 3 commits November 16, 2025 14:34
Update Documentation and Implement User API V2
Add comprehensive eclipse and solar analysis capabilities for satellite operations:

Core Features:
- Real-time eclipse status monitoring (sunlit/penumbral/umbral)
- Eclipse entry/exit time predictions
- Solar beta angle calculations and trending
- Sun-target-satellite geometry analysis
- Eclipse duration statistics and analysis
- CSV export for mission planning

Implementation:
- eclipse-types.ts: Type definitions for eclipse events and solar data
- eclipse-calculations.ts: Core calculation engine for eclipse prediction and beta angles
- eclipse-solar-analysis.ts: Main plugin with UI and real-time updates
- Integrated with existing SatMath utilities and astronomy-engine library
- Follows established plugin architecture pattern

UI Components:
- Current eclipse status display with color coding
- Solar beta angle monitoring with rate calculation
- Eclipse prediction form with configurable parameters
- Event timeline table showing entry/exit times
- Statistics panel with eclipse duration and fraction
- Export functionality for data analysis

This plugin enables critical power/thermal analysis and observation planning
for satellite operations.
Copilot AI review requested due to automatic review settings November 16, 2025 23:24
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ thkruz
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new Eclipse/Solar Analysis plugin that provides comprehensive eclipse prediction and solar geometry analysis capabilities for satellites in KeepTrack.

Key Changes:

  • New plugin for analyzing satellite eclipse events, calculating solar beta angles, and predicting eclipse transitions
  • Integration with existing satellite tracking and time management systems
  • UI for displaying real-time eclipse status, predictions, and statistics with CSV export functionality

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/settings/default-plugins.ts Registers the new EclipseSolarAnalysis plugin with order 93
src/plugins/plugins.ts Imports and initializes the EclipseSolarAnalysis plugin
src/plugins/keeptrack-plugins-configuration.ts Adds TypeScript type definition for EclipseSolarAnalysis configuration
src/plugins/eclipse-solar-analysis/eclipse-types.ts Defines TypeScript interfaces and enums for eclipse events, periods, statistics, and configuration
src/plugins/eclipse-solar-analysis/eclipse-solar-analysis.ts Implements the main plugin UI, real-time status updates, event handling, and data export
src/plugins/eclipse-solar-analysis/eclipse-calculations.ts Provides core calculation methods for eclipse predictions, beta angles, and sun geometry
src/app/analysis/sat-math.ts Modifies eclipse status checking logic and reorganizes imports

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +194 to +204
private registerEventHandlers_(): void {
// Predict eclipses button
getEl('predict-eclipses-btn')?.addEventListener('click', () => {
this.predictEclipses_();
});

// Export data button
getEl('export-eclipse-data-btn')?.addEventListener('click', () => {
this.exportEclipseData_();
});
}
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Event listeners are registered in registerEventHandlers_() but are never removed. If the plugin is reinitialized or the DOM elements are recreated, this could lead to duplicate event listeners being attached.

Consider either:

  1. Removing event listeners when the plugin is destroyed/closed
  2. Using a flag to ensure handlers are only registered once
  3. Using event delegation on a parent element that persists

Copilot uses AI. Check for mistakes.
Comment on lines +515 to +517
setInterval(() => {
this.updateBetaAngle_();
}, 10000);
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A second interval is created for beta angle updates but is never stored or cleared. This causes a memory leak as the interval will continue running even after the menu is closed.

Consider storing this interval reference and clearing it in stopRealTimeUpdates_():

private betaAngleInterval_: NodeJS.Timeout | null = null;

// In startRealTimeUpdates_():
this.betaAngleInterval_ = setInterval(() => {
  this.updateBetaAngle_();
}, 10000);

// In stopRealTimeUpdates_():
if (this.betaAngleInterval_) {
  clearInterval(this.betaAngleInterval_);
  this.betaAngleInterval_ = null;
}

Copilot uses AI. Check for mistakes.


if (obj instanceof BaseObject) {
if (obj instanceof BaseObject || !(obj.position instanceof Boolean)) {
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition !(obj.position instanceof Boolean) is logically incorrect. The instanceof Boolean check will never be true for primitive boolean values. If obj.position is a boolean primitive (e.g., false), false instanceof Boolean returns false, so !(false instanceof Boolean) becomes true, which would incorrectly allow the code to proceed.

The intent appears to be checking if obj.position is not a boolean. The correct check should be:

if (obj instanceof BaseObject || typeof obj.position !== 'boolean') {
Suggested change
if (obj instanceof BaseObject || !(obj.position instanceof Boolean)) {
if (obj instanceof BaseObject || typeof obj.position !== 'boolean') {

Copilot uses AI. Check for mistakes.
Comment on lines +218 to +220
x = obj.position.x;
y = obj.position.y;
z = obj.position.z;
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertions have been removed (e.g., changing obj.position.x as Kilometers to just obj.position.x), but the variables x, y, and z are still declared with the type Kilometers earlier in the function. This could lead to type safety issues if the values don't match the expected type.

Consider either:

  1. Keeping the type assertions if the values should be Kilometers
  2. Changing the variable declarations to match the actual types

Without seeing the full function context, if these values should be Kilometers, the original code was more type-safe.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +462
(<HTMLInputElement>getEl('eclipse-stats-total')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-duration')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-fraction')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-avg')).value = '--';
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null reference errors when clearing statistics. If any of the elements don't exist, attempting to set the value property on null will throw a runtime error.

Consider adding null checks:

const totalEl = getEl('eclipse-stats-total');
const durationEl = getEl('eclipse-stats-duration');
const fractionEl = getEl('eclipse-stats-fraction');
const avgEl = getEl('eclipse-stats-avg');

if (totalEl) (<HTMLInputElement>totalEl).value = '--';
if (durationEl) (<HTMLInputElement>durationEl).value = '--';
if (fractionEl) (<HTMLInputElement>fractionEl).value = '--';
if (avgEl) (<HTMLInputElement>avgEl).value = '--';
Suggested change
(<HTMLInputElement>getEl('eclipse-stats-total')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-duration')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-fraction')).value = '--';
(<HTMLInputElement>getEl('eclipse-stats-avg')).value = '--';
const totalEl = getEl('eclipse-stats-total');
if (totalEl) (<HTMLInputElement>totalEl).value = '--';
const durationEl = getEl('eclipse-stats-duration');
if (durationEl) (<HTMLInputElement>durationEl).value = '--';
const fractionEl = getEl('eclipse-stats-fraction');
if (fractionEl) (<HTMLInputElement>fractionEl).value = '--';
const avgEl = getEl('eclipse-stats-avg');
if (avgEl) (<HTMLInputElement>avgEl).value = '--';

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +144
sideMenuElementHtml: string = html`
<div id="eclipse-solar-menu" class="side-menu-parent start-hidden text-select">
<div id="eclipse-solar-content" class="side-menu">
<div class="row">
<h5 class="center-align">Eclipse & Solar Analysis</h5>
</div>
<!-- Current Status Section -->
<div class="row">
<h6>Current Status</h6>
<div class="eclipse-status-container">
<div class="input-field col s12">
<label for="eclipse-status-current" class="active">Eclipse Status</label>
<input disabled value="Unknown" id="eclipse-status-current" type="text" />
</div>
<div class="input-field col s12">
<label for="eclipse-time-in-shadow" class="active">Time in Current State</label>
<input disabled value="--:--:--" id="eclipse-time-in-shadow" type="text" />
</div>
</div>
</div>
<!-- Solar Beta Angle Section -->
<div class="row">
<h6>Solar Beta Angle</h6>
<div class="input-field col s12">
<label for="beta-angle-current" class="active">Current Beta Angle</label>
<input disabled value="0.00°" id="beta-angle-current" type="text" />
</div>
<div class="input-field col s12">
<label for="beta-angle-rate" class="active">Beta Angle Rate</label>
<input disabled value="0.00 °/day" id="beta-angle-rate" type="text" />
</div>
</div>
<!-- Eclipse Prediction Section -->
<div class="row">
<h6>Eclipse Predictions</h6>
<form id="eclipse-prediction-form">
<div class="input-field col s12">
<input value="24" id="prediction-duration" type="number" min="1" max="168" />
<label for="prediction-duration" class="active">Prediction Duration (hours)</label>
</div>
<div class="input-field col s12">
<input value="60" id="time-step" type="number" min="10" max="300" />
<label for="time-step" class="active">Time Step (seconds)</label>
</div>
<div class="center-align">
<button id="predict-eclipses-btn" class="btn btn-ui waves-effect waves-light" type="button">
Predict Eclipses &#9658;
</button>
</div>
</form>
</div>
<!-- Eclipse Results Section -->
<div class="row">
<h6>Eclipse Events</h6>
<div id="eclipse-events-container">
<p class="center-align">Click "Predict Eclipses" to see upcoming events</p>
</div>
</div>
<!-- Statistics Section -->
<div class="row">
<h6>Eclipse Statistics</h6>
<div id="eclipse-statistics-container">
<div class="input-field col s12">
<label for="eclipse-stats-total" class="active">Total Eclipse Events</label>
<input disabled value="--" id="eclipse-stats-total" type="text" />
</div>
<div class="input-field col s12">
<label for="eclipse-stats-duration" class="active">Total Eclipse Time</label>
<input disabled value="--" id="eclipse-stats-duration" type="text" />
</div>
<div class="input-field col s12">
<label for="eclipse-stats-fraction" class="active">Eclipse Fraction</label>
<input disabled value="--" id="eclipse-stats-fraction" type="text" />
</div>
<div class="input-field col s12">
<label for="eclipse-stats-avg" class="active">Average Eclipse Duration</label>
<input disabled value="--" id="eclipse-stats-avg" type="text" />
</div>
</div>
</div>
<!-- Export Section -->
<div class="row">
<div class="center-align">
<button id="export-eclipse-data-btn" class="btn btn-ui waves-effect waves-light" type="button" disabled>
Export Data (CSV) &#9658;
</button>
</div>
</div>
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The HTML input elements use inconsistent ID naming conventions. Most use kebab-case (e.g., eclipse-status-current, beta-angle-current), but the pattern isn't entirely consistent. Consider using a consistent prefix like eclipse- or esa- (for Eclipse Solar Analysis) for all IDs in this plugin to avoid potential conflicts with other plugins.

Copilot uses AI. Check for mistakes.
Comment on lines +308 to +315
// Sun elevation angle (angle above satellite's horizon)
// For simplicity, we calculate it relative to the satellite's velocity vector as "forward"
const velocity = vec3.fromValues(posvel.velocity.x, posvel.velocity.y, posvel.velocity.z);

vec3.normalize(velocity, velocity);

const elevationDot = vec3.dot(satToSun, velocity);
const sunElevation = (Math.asin(Math.max(-1, Math.min(1, elevationDot))) * RAD2DEG) as Degrees;
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculateSunGeometry method's sun elevation calculation could be misleading. The comment states "angle above satellite's horizon" but the implementation uses the velocity vector as "forward", which doesn't represent the satellite's actual horizon. The horizon would typically be perpendicular to the position vector (nadir direction). This could confuse future maintainers.

Consider clarifying the documentation to match the implementation:

// Sun elevation angle relative to satellite's velocity vector

Or adjust the implementation to calculate true horizon-based elevation.

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +308
// Calculate beta angle rate (change over 1 day)
const nextDayTime = new Date(currentTime.getTime() + 24 * 3600 * 1000);
const nextDayBeta = EclipseCalculations.calculateSolarBetaAngle(this.currentSatellite_, nextDayTime);
const betaRate = nextDayBeta - betaAngle;

const rateEl = getEl('beta-angle-rate');

if (rateEl) {
(<HTMLInputElement>rateEl).value = `${betaRate.toFixed(2)} °/day`;
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The beta angle rate calculation in updateBetaAngle_() performs a full position propagation 24 hours into the future on every call. Since this is called every 10 seconds when the menu is open, this could be a performance concern for long simulation runs or when the propagation is computationally expensive.

Consider:

  1. Caching the beta angle rate and updating it less frequently (e.g., every minute)
  2. Using a numerical derivative with a shorter time step (e.g., 1 hour) for faster computation
  3. Only calculating the rate when specifically requested by the user
Suggested change
// Calculate beta angle rate (change over 1 day)
const nextDayTime = new Date(currentTime.getTime() + 24 * 3600 * 1000);
const nextDayBeta = EclipseCalculations.calculateSolarBetaAngle(this.currentSatellite_, nextDayTime);
const betaRate = nextDayBeta - betaAngle;
const rateEl = getEl('beta-angle-rate');
if (rateEl) {
(<HTMLInputElement>rateEl).value = `${betaRate.toFixed(2)} °/day`;
// Calculate beta angle rate (change over 1 hour)
const nextHourTime = new Date(currentTime.getTime() + 1 * 3600 * 1000);
const nextHourBeta = EclipseCalculations.calculateSolarBetaAngle(this.currentSatellite_, nextHourTime);
const betaRate = nextHourBeta - betaAngle;
const rateEl = getEl('beta-angle-rate');
if (rateEl) {
(<HTMLInputElement>rateEl).value = `${betaRate.toFixed(2)} °/hr`;

Copilot uses AI. Check for mistakes.
eclipseFraction: totalDuration > 0 ? totalEclipseTime / totalDuration : 0,
averageEclipseDuration: (totalEclipseTime / periods.length) as Milliseconds,
maxEclipseDuration: maxDuration as Milliseconds,
minEclipseDuration: minDuration as Milliseconds,
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no eclipse periods are found, minEclipseDuration is set to 0 as Milliseconds, but the variable minDuration is initialized to Infinity. This is inconsistent. If periods exist but all have very long durations, minDuration would remain as Infinity after the loop (though this scenario is unlikely).

Consider initializing differently:

let minDuration = periods.length > 0 ? periods[0].duration : 0;

Or ensure the return value is consistent:

minEclipseDuration: (minDuration === Infinity ? 0 : minDuration) as Milliseconds,
Suggested change
minEclipseDuration: minDuration as Milliseconds,
minEclipseDuration: (minDuration === Infinity ? 0 : minDuration) as Milliseconds,

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

@thkruz thkruz merged commit df67e13 into develop Nov 16, 2025
18 of 19 checks passed
@thkruz thkruz deleted the claude/eclipse-solar-analysis-plugin-01B6uavovuzvqP2p6bhD8ctv branch November 16, 2025 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants