@@ -17,10 +17,12 @@ import type {
1717
1818import { convertEclipticToEquatorial , convertEquatorialToHorizontal } from './coordinates'
1919
20+ import { getLunarEquatorialCoordinate } from './moon'
21+
2022import {
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+ /*****************************************************************************************************************/
0 commit comments