Skip to content

Commit 2fac1fe

Browse files
feat: add findConjunctions() utility to conjunction module in @observerly/astrometry
feat: add findConjunctions() utility to conjunction module in @observerly/astrometry
1 parent fcb67ea commit 2fac1fe

2 files changed

Lines changed: 149 additions & 2 deletions

File tree

src/conjunction.ts

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import type {
1717

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

20+
import { getLunarEquatorialCoordinate } from './moon'
21+
2022
import {
23+
type Planet,
2124
getPlanetaryGeocentricEclipticCoordinate,
22-
getPlanetaryPositions,
23-
type Planet
25+
getPlanetaryPositions
2426
} from './planets'
2527

2628
/*****************************************************************************************************************/
@@ -394,3 +396,139 @@ export const findPlanetaryConjunctions = (
394396
}
395397

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

tests/conjunction.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
convertEclipticToEquatorial,
1616
convertEquatorialToHorizontal,
1717
findConjunction,
18+
findConjunctions,
1819
findPlanetaryConjunction,
1920
findPlanetaryConjunctions,
2021
getLunarEquatorialCoordinate,
@@ -337,3 +338,11 @@ describe('findPlanetaryConjunctions()', () => {
337338
})
338339

339340
/*****************************************************************************************************************/
341+
342+
describe('findConjunctions', () => {
343+
it('should be defined', () => {
344+
expect(findConjunctions).toBeDefined()
345+
})
346+
})
347+
348+
/*****************************************************************************************************************/

0 commit comments

Comments
 (0)