/**
* Astronomical calculations using SunCalc for determining solar/civil night status and moon phase
* Based on file midpoint time and cluster location coordinates
*/
import * as SunCalc from 'suncalc';
export interface AstronomicalData {
maybeSolarNight: boolean;
maybeCivilNight: boolean;
moonPhase: number; // 0-1 range, where 0.5 = full moon
}
export interface ClusterLocation {
latitude: number;
longitude: number;
}
/**
* Calculate astronomical data for a file based on its midpoint time
* @param timestampUTC - File start time in UTC ISO string format
* @param durationSeconds - File duration in seconds
* @param location - Cluster location coordinates
* @returns Astronomical data including solar/civil night status and moon phase
*/
export function calculateAstronomicalData(
timestampUTC: string,
durationSeconds: number,
location: ClusterLocation
): AstronomicalData {
// Parse the start time
const startTime = new Date(timestampUTC);
// Calculate midpoint time by adding half the duration in milliseconds
const midpointTime = new Date(startTime.getTime() + (durationSeconds * 1000 / 2));
// Get solar times for the midpoint
const times = SunCalc.getTimes(midpointTime, location.latitude, location.longitude);
// Get moon illumination data
const moonIllumination = SunCalc.getMoonIllumination(midpointTime);
// Determine if recording might be during solar night (between sunset and sunrise)
const maybeSolarNight = midpointTime < times.sunrise || midpointTime > times.sunset;
// Determine if recording might be during civil night (between dusk and dawn)
const maybeCivilNight = midpointTime < times.dawn || midpointTime > times.dusk;
return {
maybeSolarNight,
maybeCivilNight,
moonPhase: moonIllumination.phase
};
}
/**
* Batch calculate astronomical data for multiple files
* Optimized for when all files are from the same location
* @param files - Array of file data with timestamp and duration
* @param location - Cluster location coordinates (same for all files)
* @returns Array of astronomical data corresponding to input files
*/
export function batchCalculateAstronomicalData(
files: Array<{ timestampUTC: string; durationSeconds: number }>,
location: ClusterLocation
): AstronomicalData[] {
return files.map(file =>
calculateAstronomicalData(file.timestampUTC, file.durationSeconds, location)
);
}
/**
* Format moon phase as a percentage for easier understanding
* @param phase - Moon phase value from SunCalc (0-1)
* @returns Formatted string describing moon phase
*/
export function formatMoonPhase(phase: number): string {
const percentage = Math.round(phase * 100);
if (phase < 0.01) return `New Moon (${percentage}%)`;
if (phase < 0.24) return `Waxing Crescent (${percentage}%)`;
if (phase < 0.26) return `First Quarter (${percentage}%)`;
if (phase < 0.49) return `Waxing Gibbous (${percentage}%)`;
if (phase < 0.51) return `Full Moon (${percentage}%)`;
if (phase < 0.74) return `Waning Gibbous (${percentage}%)`;
if (phase < 0.76) return `Last Quarter (${percentage}%)`;
if (phase < 0.99) return `Waning Crescent (${percentage}%)`;
return `New Moon (${percentage}%)`;
}