Skip to content

Commit 15bf528

Browse files
feat: add findConjunctions() utility to conjunction module in @observerly/astrometry
feat: add findConjunctions() utility to conjunction module in @observerly/astrometry
1 parent 04be643 commit 15bf528

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

src/conjunction.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717

1818
import { convertEclipticToEquatorial, convertEquatorialToHorizontal } from './coordinates'
1919

20+
import { getLunarEquatorialCoordinate } from './moon'
21+
2022
import {
2123
getPlanetaryGeocentricEclipticCoordinate,
2224
getPlanetaryPositions,
@@ -393,3 +395,138 @@ export const findPlanetaryConjunctions = (
393395
}
394396

395397
/*****************************************************************************************************************/
398+
399+
/**
400+
* findConjunctions
401+
*
402+
* Finds all conjunctions of the planets, Spica, Regulus and the Moon within a given time interval,
403+
* returning only those that are inconjunction with each other (as determined by the angular
404+
* separation threshold).
405+
*
406+
* @param interval - The interval to search for the initial conjunction.
407+
* @param observer - The geographic coordinate of the observer.
408+
* @param horizon - The minimum altitude of the planets above the horizon.
409+
* @param angularSeparationThreshold - The minimum angular separation for conjunction.
410+
* @param stepMinutes - The step size in minutes for checking conjunction.
411+
* @returns An array of conjunctions found.
412+
*
413+
*/
414+
export const findConjunctions = (
415+
interval: Interval,
416+
observer: GeographicCoordinate,
417+
params: {
418+
horizon?: number // six degrees above the horizon
419+
angularSeparationThreshold?: number // three degrees of separation
420+
stepMinutes?: number // check every 1/3 hour
421+
} = {
422+
horizon: 6,
423+
angularSeparationThreshold: ANGULAR_SEPARATION_THRESHOLD,
424+
stepMinutes: 20
425+
}
426+
): Map<string, Conjunction> => {
427+
// A conjunction is a close apparent approach of two celestial objects in the sky.
428+
const conjunctions = new Map<string, Conjunction>()
429+
430+
/*eslint prefer-const: ["error", {"destructuring": "all"}]*/
431+
let { from, to } = interval
432+
433+
const {
434+
horizon = 6,
435+
angularSeparationThreshold = ANGULAR_SEPARATION_THRESHOLD,
436+
stepMinutes = 20
437+
} = params
438+
439+
// Spica is close to the ecliptic, so we know that we may see it close to a planet:
440+
// We are using the J2000.0 coordinates for Spica, we therefore need to convert them to the current epoch:
441+
const spica = {
442+
ra: 201.298,
443+
dec: -11.1613
444+
}
445+
446+
// Regulus is close to the ecliptic, so we know that we may see it close to a planet:
447+
// We are using the J2000.0 coordinates for Regulus, we therefore need to convert them to the current epoch:
448+
const regulus = {
449+
ra: 152.093,
450+
dec: 11.9672
451+
}
452+
453+
while (from <= to) {
454+
const moon = getLunarEquatorialCoordinate(from)
455+
456+
// Collate the positions of all planets other than Earth and those below the horizon in the sky:
457+
// N.B. They may be in conjunction, but they won't be visible to our local observer if they are
458+
// below the horizon.
459+
const positions = [
460+
...getPlanetaryPositions(from, observer),
461+
{
462+
name: 'Moon',
463+
...moon,
464+
...convertEquatorialToHorizontal(from, observer, moon)
465+
},
466+
{
467+
name: 'Spica',
468+
...spica,
469+
...convertEquatorialToHorizontal(from, observer, spica)
470+
},
471+
{
472+
name: 'Regulus',
473+
...regulus,
474+
...convertEquatorialToHorizontal(from, observer, regulus)
475+
}
476+
]
477+
478+
// Loop over all pairs of planets and check for conjunctions:
479+
for (let i = 0; i < positions.length; i++) {
480+
for (let j = i + 1; j < positions.length; j++) {
481+
// If either of the planets is below the horizon, skip this pair:
482+
if (positions[i].alt < horizon || positions[j].alt < horizon) continue
483+
484+
// Get the positions of the two planets:
485+
const alterior = positions[i]
486+
const ulterior = positions[j]
487+
488+
// Create a unique key for the conjunction between the two planets, sorted by name:
489+
const key = [alterior.name, ulterior.name].sort().join('-')
490+
491+
// Check for a conjunction between the two planets by comparing their angular separation:
492+
const separation = getAngularSeparation(
493+
{
494+
θ: positions[i].alt,
495+
φ: positions[i].az
496+
},
497+
{
498+
θ: positions[j].alt,
499+
φ: positions[j].az
500+
}
501+
)
502+
503+
// Update the conjunction if the angular separation is less than the threshold,
504+
// and the conjunction is the closest one found so far:
505+
if (
506+
isConjunction(from, [alterior, ulterior], {
507+
horizon,
508+
angularSeparationThreshold
509+
}) &&
510+
(!conjunctions.has(key) || conjunctions.get(key)!.angularSeparation > separation)
511+
) {
512+
const conjunction: Conjunction = {
513+
datetime: from,
514+
targets: [alterior, ulterior],
515+
angularSeparation: separation,
516+
ra: (alterior.ra + ulterior.ra) / 2,
517+
dec: (alterior.dec + ulterior.dec) / 2
518+
}
519+
520+
conjunctions.set(key, conjunction)
521+
}
522+
}
523+
}
524+
525+
// Increment the from date by the step size:
526+
from = new Date(from.getTime() + stepMinutes * 60000)
527+
}
528+
529+
return conjunctions
530+
}
531+
532+
/*****************************************************************************************************************/

0 commit comments

Comments
 (0)