UNPKG

swisseph-wasm

Version:

High-precision Swiss Ephemeris WebAssembly library for astronomical calculations in JavaScript

889 lines (684 loc) 20.1 kB
# SwissEph WebAssembly Library Documentation A JavaScript wrapper for the Swiss Ephemeris WebAssembly module, providing high-precision astronomical calculations for astrology, astronomy, and related applications. ## Table of Contents - [Installation](#installation) - [Quick Start](#quick-start) - [API Reference](#api-reference) - [Examples](#examples) - [Constants](#constants) - [Advanced Usage](#advanced-usage) - [Testing](#testing) - [Browser Support](#browser-support) ## Installation ### Option 1: Direct Download Download the library files and include them in your project: ``` src/swisseph.js # Main library file wasm/swisseph.js # WebAssembly module wasm/swisseph.wasm # WebAssembly binary ``` ### Option 2: ES Module Import ```javascript import SwissEph from './src/swisseph.js'; ``` ### Option 3: HTML Script Tag ```html <script type="module"> import SwissEph from './src/swisseph.js'; // Your code here </script> ``` ## Quick Start ### Basic Setup ```javascript import SwissEph from './src/swisseph.js'; // Create instance const swe = new SwissEph(); // Initialize the WebAssembly module await swe.initSwissEph(); // Calculate Julian Day const jd = swe.julday(2023, 6, 15, 12.0); // June 15, 2023, 12:00 UTC // Calculate Sun position const sunPosition = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH); console.log(`Sun longitude: ${sunPosition[0]}°`); // Clean up when done swe.close(); ``` ### Birth Chart Example ```javascript async function calculateBirthChart(year, month, day, hour, minute, timezone) { const swe = new SwissEph(); await swe.initSwissEph(); // Convert local time to UTC const utcHour = hour + minute / 60 - timezone; const jd = swe.julday(year, month, day, utcHour); // Define planets to calculate const planets = [ { id: swe.SE_SUN, name: 'Sun' }, { id: swe.SE_MOON, name: 'Moon' }, { id: swe.SE_MERCURY, name: 'Mercury' }, { id: swe.SE_VENUS, name: 'Venus' }, { id: swe.SE_MARS, name: 'Mars' }, { id: swe.SE_JUPITER, name: 'Jupiter' }, { id: swe.SE_SATURN, name: 'Saturn' }, { id: swe.SE_URANUS, name: 'Uranus' }, { id: swe.SE_NEPTUNE, name: 'Neptune' }, { id: swe.SE_PLUTO, name: 'Pluto' } ]; const chart = {}; for (const planet of planets) { const result = swe.calc_ut(jd, planet.id, swe.SEFLG_SWIEPH); chart[planet.name] = { longitude: result[0], latitude: result[1], distance: result[2], speed: result[3] }; } swe.close(); return chart; } // Usage const chart = await calculateBirthChart(1990, 5, 15, 14, 30, -5); // May 15, 1990, 2:30 PM, UTC-5 console.log(chart); ``` ## API Reference ### Core Methods #### `initSwissEph()` Initializes the WebAssembly module. Must be called before using any other methods. ```javascript await swe.initSwissEph(); ``` #### `julday(year, month, day, hour)` Calculates Julian Day Number for a given date and time. ```javascript const jd = swe.julday(2023, 6, 15, 12.5); // June 15, 2023, 12:30 UTC ``` **Parameters:** - `year` (number): Year (e.g., 2023) - `month` (number): Month (1-12) - `day` (number): Day (1-31) - `hour` (number): Hour in decimal format (0-24) **Returns:** Julian Day Number (number) #### `calc_ut(jd, planet, flags)` Calculates planetary positions for Universal Time. ```javascript const result = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH); ``` **Parameters:** - `jd` (number): Julian Day Number - `planet` (number): Planet constant (e.g., `swe.SE_SUN`) - `flags` (number): Calculation flags (e.g., `swe.SEFLG_SWIEPH`) **Returns:** Float64Array with [longitude, latitude, distance, speed] #### `calc(jd, planet, flags)` Calculates planetary positions (alternative method with more detailed output). ```javascript const result = swe.calc(jd, swe.SE_MOON, swe.SEFLG_SWIEPH); ``` **Returns:** Object with detailed position data: ```javascript { longitude: number, latitude: number, distance: number, longitudeSpeed: number, latitudeSpeed: number, distanceSpeed: number } ``` ### Time Functions #### `deltat(jd)` Calculates Delta T (difference between Terrestrial Time and Universal Time). ```javascript const deltaT = swe.deltat(jd); ``` #### `sidtime(jd)` Calculates sidereal time for a given Julian Day. ```javascript const siderealTime = swe.sidtime(jd); ``` #### `utc_to_jd(year, month, day, hour, minute, second, gregflag)` Converts UTC time to Julian Day. ```javascript const result = swe.utc_to_jd(2023, 6, 15, 12, 30, 0, swe.SE_GREG_CAL); // Returns: { julianDayET: number, julianDayUT: number } ``` ### Date Conversion Functions #### `revjul(jd, gregflag)` Converts Julian Day back to calendar date. ```javascript const date = swe.revjul(jd, swe.SE_GREG_CAL); // Returns: { year: number, month: number, day: number, hour: number } ``` #### `date_conversion(year, month, day, hour, gregflag)` Converts calendar date to Julian Day. ```javascript const jd = swe.date_conversion(2023, 6, 15, 12.5, swe.SE_GREG_CAL); ``` ### Utility Functions #### `degnorm(degrees)` Normalizes degrees to 0-360 range. ```javascript const normalized = swe.degnorm(370); // Returns 10 ``` #### `split_deg(degrees, roundflag)` Splits decimal degrees into degrees, minutes, seconds. ```javascript const split = swe.split_deg(123.456789, swe.SE_SPLIT_DEG_ROUND_SEC); // Returns: { degree: 123, min: 27, second: 24, fraction: 0, sign: 4 } ``` #### `day_of_week(jd)` Returns day of week for a Julian Day (0=Monday, 6=Sunday). ```javascript const dayOfWeek = swe.day_of_week(jd); ``` ### Sidereal Functions #### `set_sid_mode(sidmode, t0, ayan_t0)` Sets sidereal calculation mode. ```javascript swe.set_sid_mode(swe.SE_SIDM_LAHIRI, 0, 0); ``` #### `get_ayanamsa(jd)` Gets ayanamsa value for sidereal calculations. ```javascript const ayanamsa = swe.get_ayanamsa(jd); ``` ### Information Functions #### `version()` Returns Swiss Ephemeris version string. ```javascript const version = swe.version(); ``` #### `get_planet_name(planet)` Returns planet name for a planet constant. ```javascript const name = swe.get_planet_name(swe.SE_SUN); // Returns "Sun" ``` ### Cleanup #### `close()` Closes the Swiss Ephemeris and frees memory. Call when done. ```javascript swe.close(); ``` ## Examples ### Example 1: Current Planetary Positions ```javascript async function getCurrentPlanetaryPositions() { const swe = new SwissEph(); await swe.initSwissEph(); const now = new Date(); const jd = swe.julday( now.getUTCFullYear(), now.getUTCMonth() + 1, now.getUTCDate(), now.getUTCHours() + now.getUTCMinutes() / 60 ); const planets = [ swe.SE_SUN, swe.SE_MOON, swe.SE_MERCURY, swe.SE_VENUS, swe.SE_MARS, swe.SE_JUPITER, swe.SE_SATURN ]; const positions = {}; for (const planet of planets) { const result = swe.calc_ut(jd, planet, swe.SEFLG_SWIEPH); const name = swe.get_planet_name(planet); positions[name] = result[0]; // longitude in degrees } swe.close(); return positions; } ``` ### Example 2: Sidereal vs Tropical Positions ```javascript async function compareSiderealTropical(year, month, day, hour) { const swe = new SwissEph(); await swe.initSwissEph(); const jd = swe.julday(year, month, day, hour); // Set Lahiri ayanamsa for sidereal calculations swe.set_sid_mode(swe.SE_SIDM_LAHIRI, 0, 0); // Tropical position const tropical = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH); // Sidereal position const sidereal = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH | swe.SEFLG_SIDEREAL); // Get ayanamsa value const ayanamsa = swe.get_ayanamsa(jd); swe.close(); return { tropical: tropical[0], sidereal: sidereal[0], ayanamsa: ayanamsa, difference: tropical[0] - sidereal[0] }; } ``` ### Example 3: Time Zone Conversion ```javascript async function calculateWithTimeZone(year, month, day, hour, minute, second, timezone) { const swe = new SwissEph(); await swe.initSwissEph(); // Convert to UTC using built-in function const utcResult = swe.utc_to_jd(year, month, day, hour, minute, second, swe.SE_GREG_CAL); // Adjust for timezone const localJD = utcResult.julianDayUT - timezone / 24; // Calculate planetary positions const sunPos = swe.calc_ut(localJD, swe.SE_SUN, swe.SEFLG_SWIEPH); swe.close(); return { julianDay: localJD, sunLongitude: sunPos[0], deltaT: swe.deltat(localJD) }; } ``` ### Example 4: Moon Phases ```javascript async function getMoonPhase(year, month, day) { const swe = new SwissEph(); await swe.initSwissEph(); const jd = swe.julday(year, month, day, 12.0); // Get Sun and Moon positions const sunPos = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH); const moonPos = swe.calc_ut(jd, swe.SE_MOON, swe.SEFLG_SWIEPH); // Calculate phase angle let phaseAngle = moonPos[0] - sunPos[0]; if (phaseAngle < 0) phaseAngle += 360; // Determine phase let phase; if (phaseAngle < 45 || phaseAngle > 315) phase = "New Moon"; else if (phaseAngle < 135) phase = "Waxing"; else if (phaseAngle < 225) phase = "Full Moon"; else phase = "Waning"; swe.close(); return { phaseAngle: phaseAngle, phase: phase, sunLongitude: sunPos[0], moonLongitude: moonPos[0] }; } ``` ## Constants ### Planet Constants ```javascript // Major planets swe.SE_SUN = 0; swe.SE_MOON = 1; swe.SE_MERCURY = 2; swe.SE_VENUS = 3; swe.SE_MARS = 4; swe.SE_JUPITER = 5; swe.SE_SATURN = 6; swe.SE_URANUS = 7; swe.SE_NEPTUNE = 8; swe.SE_PLUTO = 9; swe.SE_EARTH = 14; // Lunar nodes and apogee swe.SE_MEAN_NODE = 10; swe.SE_TRUE_NODE = 11; swe.SE_MEAN_APOG = 12; swe.SE_OSCU_APOG = 13; // Major asteroids swe.SE_CHIRON = 15; swe.SE_PHOLUS = 16; swe.SE_CERES = 17; swe.SE_PALLAS = 18; swe.SE_JUNO = 19; swe.SE_VESTA = 20; // Uranian planets swe.SE_CUPIDO = 40; swe.SE_HADES = 41; swe.SE_ZEUS = 42; swe.SE_KRONOS = 43; swe.SE_APOLLON = 44; swe.SE_ADMETOS = 45; swe.SE_VULKANUS = 46; swe.SE_POSEIDON = 47; ``` ### Calculation Flags ```javascript // Ephemeris types swe.SEFLG_JPLEPH = 1; // JPL ephemeris swe.SEFLG_SWIEPH = 2; // Swiss ephemeris (default) swe.SEFLG_MOSEPH = 4; // Moshier ephemeris // Coordinate systems swe.SEFLG_HELCTR = 8; // Heliocentric swe.SEFLG_BARYCTR = 16384; // Barycentric swe.SEFLG_TOPOCTR = 32768; // Topocentric swe.SEFLG_EQUATORIAL = 2048; // Equatorial coordinates swe.SEFLG_XYZ = 4096; // Cartesian coordinates swe.SEFLG_RADIANS = 8192; // Radians instead of degrees // Special flags swe.SEFLG_SPEED = 256; // Calculate speed swe.SEFLG_TRUEPOS = 16; // True positions (no light-time correction) swe.SEFLG_J2000 = 32; // J2000 coordinates swe.SEFLG_NONUT = 64; // No nutation swe.SEFLG_NOGDEFL = 512; // No gravitational deflection swe.SEFLG_NOABERR = 1024; // No aberration swe.SEFLG_SIDEREAL = 65536; // Sidereal positions // Composite flags swe.SEFLG_ASTROMETRIC = 1536; // No aberration + no gravitational deflection ``` ### Sidereal Modes ```javascript swe.SE_SIDM_FAGAN_BRADLEY = 0; swe.SE_SIDM_LAHIRI = 1; swe.SE_SIDM_DELUCE = 2; swe.SE_SIDM_RAMAN = 3; swe.SE_SIDM_USHASHASHI = 4; swe.SE_SIDM_KRISHNAMURTI = 5; swe.SE_SIDM_DJWHAL_KHUL = 6; swe.SE_SIDM_YUKTESHWAR = 7; swe.SE_SIDM_JN_BHASIN = 8; swe.SE_SIDM_BABYL_KUGLER1 = 9; swe.SE_SIDM_BABYL_KUGLER2 = 10; swe.SE_SIDM_BABYL_KUGLER3 = 11; swe.SE_SIDM_BABYL_HUBER = 12; swe.SE_SIDM_BABYL_ETPSC = 13; swe.SE_SIDM_ALDEBARAN_15TAU = 14; swe.SE_SIDM_HIPPARCHOS = 15; swe.SE_SIDM_SASSANIAN = 16; swe.SE_SIDM_GALCENT_0SAG = 17; swe.SE_SIDM_J2000 = 18; swe.SE_SIDM_J1900 = 19; swe.SE_SIDM_B1950 = 20; swe.SE_SIDM_USER = 255; ``` ### Calendar Types ```javascript swe.SE_JUL_CAL = 0; // Julian calendar swe.SE_GREG_CAL = 1; // Gregorian calendar ``` ### Degree Splitting Flags ```javascript swe.SE_SPLIT_DEG_ROUND_SEC = 1; swe.SE_SPLIT_DEG_ROUND_MIN = 2; swe.SE_SPLIT_DEG_ROUND_DEG = 4; swe.SE_SPLIT_DEG_ZODIACAL = 8; swe.SE_SPLIT_DEG_KEEP_SIGN = 16; swe.SE_SPLIT_DEG_KEEP_DEG = 32; ``` ## Advanced Usage ### Working with Fixed Stars ```javascript async function getFixedStarPosition(starName, year, month, day) { const swe = new SwissEph(); await swe.initSwissEph(); const jd = swe.julday(year, month, day, 0); // Calculate fixed star position const starPos = swe.fixstar(starName, jd, swe.SEFLG_SWIEPH); if (starPos) { const result = { name: starName, longitude: starPos[0], latitude: starPos[1], distance: starPos[2], magnitude: swe.fixstar_mag(starName) }; swe.close(); return result; } swe.close(); return null; } // Usage const sirius = await getFixedStarPosition("Sirius", 2023, 6, 15); ``` ### House Calculations ```javascript async function calculateHouses(year, month, day, hour, latitude, longitude, houseSystem = 'P') { const swe = new SwissEph(); await swe.initSwissEph(); const jd = swe.julday(year, month, day, hour); // Calculate houses const houses = swe.houses(jd, latitude, longitude, houseSystem); // Calculate house positions for planets const planets = [swe.SE_SUN, swe.SE_MOON, swe.SE_MERCURY, swe.SE_VENUS, swe.SE_MARS]; const planetHouses = {}; for (const planet of planets) { const planetPos = swe.calc_ut(jd, planet, swe.SEFLG_SWIEPH); const housePos = swe.house_pos( swe.sidtime(jd) * 15, // ARMC latitude, 23.44, // obliquity houseSystem, planetPos[0], planetPos[1] ); planetHouses[swe.get_planet_name(planet)] = { longitude: planetPos[0], house: Math.floor(housePos) }; } swe.close(); return { houses: houses, planetHouses: planetHouses }; } ``` ### Eclipse Calculations ```javascript async function findNextSolarEclipse(startYear, startMonth, startDay) { const swe = new SwissEph(); await swe.initSwissEph(); const startJD = swe.julday(startYear, startMonth, startDay, 12); // Find next solar eclipse const eclipse = swe.sol_eclipse_when_glob( startJD, swe.SEFLG_SWIEPH, swe.SE_ECL_TOTAL | swe.SE_ECL_ANNULAR | swe.SE_ECL_PARTIAL, 0 // forward search ); if (eclipse) { const eclipseDate = swe.revjul(eclipse[1], swe.SE_GREG_CAL); swe.close(); return { julianDay: eclipse[1], date: eclipseDate, type: eclipse[0] & swe.SE_ECL_TOTAL ? 'Total' : eclipse[0] & swe.SE_ECL_ANNULAR ? 'Annular' : 'Partial', magnitude: eclipse[4] }; } swe.close(); return null; } ``` ### Topocentric Calculations ```javascript async function calculateTopocentric(year, month, day, hour, latitude, longitude, altitude) { const swe = new SwissEph(); await swe.initSwissEph(); // Set topocentric location swe.set_topo(longitude, latitude, altitude); const jd = swe.julday(year, month, day, hour); // Calculate topocentric positions const planets = [swe.SE_SUN, swe.SE_MOON, swe.SE_VENUS, swe.SE_MARS]; const positions = {}; for (const planet of planets) { // Geocentric position const geocentric = swe.calc_ut(jd, planet, swe.SEFLG_SWIEPH); // Topocentric position const topocentric = swe.calc_ut(jd, planet, swe.SEFLG_SWIEPH | swe.SEFLG_TOPOCTR); positions[swe.get_planet_name(planet)] = { geocentric: geocentric[0], topocentric: topocentric[0], difference: geocentric[0] - topocentric[0] }; } swe.close(); return positions; } ``` ## Error Handling ### Best Practices ```javascript async function safeCalculation(year, month, day, hour) { let swe = null; try { swe = new SwissEph(); await swe.initSwissEph(); // Validate input if (year < -5000 || year > 5000) { throw new Error('Year out of valid range (-5000 to 5000)'); } if (month < 1 || month > 12) { throw new Error('Month must be between 1 and 12'); } if (day < 1 || day > 31) { throw new Error('Day must be between 1 and 31'); } if (hour < 0 || hour >= 24) { throw new Error('Hour must be between 0 and 23.999'); } const jd = swe.julday(year, month, day, hour); // Check for valid Julian Day if (isNaN(jd) || jd === swe.TJD_INVALID) { throw new Error('Invalid Julian Day calculated'); } const result = swe.calc_ut(jd, swe.SE_SUN, swe.SEFLG_SWIEPH); if (!result || result.length < 4) { throw new Error('Failed to calculate planetary position'); } return { success: true, julianDay: jd, longitude: result[0], latitude: result[1], distance: result[2], speed: result[3] }; } catch (error) { return { success: false, error: error.message }; } finally { // Always clean up if (swe) { swe.close(); } } } ``` ## Performance Tips ### 1. Reuse SwissEph Instance ```javascript class AstrologyCalculator { constructor() { this.swe = null; this.initialized = false; } async init() { if (!this.initialized) { this.swe = new SwissEph(); await this.swe.initSwissEph(); this.initialized = true; } } async calculateChart(year, month, day, hour) { await this.init(); const jd = this.swe.julday(year, month, day, hour); const planets = [this.swe.SE_SUN, this.swe.SE_MOON, this.swe.SE_MERCURY]; const chart = {}; for (const planet of planets) { const result = this.swe.calc_ut(jd, planet, this.swe.SEFLG_SWIEPH); chart[this.swe.get_planet_name(planet)] = result[0]; } return chart; } destroy() { if (this.swe) { this.swe.close(); this.swe = null; this.initialized = false; } } } // Usage const calculator = new AstrologyCalculator(); const chart1 = await calculator.calculateChart(1990, 5, 15, 14.5); const chart2 = await calculator.calculateChart(1985, 12, 25, 8.0); calculator.destroy(); // Clean up when done ``` ### 2. Batch Calculations ```javascript async function batchCalculatePlanets(jd, planetList, flags) { const swe = new SwissEph(); await swe.initSwissEph(); const results = {}; // Calculate all planets in one session for (const planet of planetList) { const result = swe.calc_ut(jd, planet, flags); results[swe.get_planet_name(planet)] = result; } swe.close(); return results; } ``` ## Browser Support ### Modern Browsers - Chrome 61+ - Firefox 60+ - Safari 11+ - Edge 16+ ### Requirements - WebAssembly support - ES6 modules support - Async/await support ### Polyfills For older browsers, you may need: - WebAssembly polyfill - ES6 module loader - Promise polyfill ## Testing The library includes comprehensive tests. To run them: ```bash npm install npm test ``` ### Test Coverage - 106 tests covering all major functionality - 86.1% statement coverage - Mock WebAssembly module for isolated testing - Integration tests for real-world scenarios ## Troubleshooting ### Common Issues 1. **WebAssembly not loading** ```javascript // Ensure proper path to WASM files // Check browser console for loading errors ``` 2. **Invalid Julian Day** ```javascript // Validate date inputs before calculation if (isNaN(jd) || jd === swe.TJD_INVALID) { throw new Error('Invalid date'); } ``` 3. **Memory leaks** ```javascript // Always call close() when done try { // calculations } finally { swe.close(); } ``` 4. **Incorrect time zones** ```javascript // Convert to UTC before calculation const utcHour = localHour - timezoneOffset; ``` ## License This library is a wrapper around the Swiss Ephemeris, which is licensed under the GNU General Public License (GPL) for non-commercial use. For commercial use, a license from Astrodienst is required. ## Support For issues and questions: - Check the test files for usage examples - Review the Swiss Ephemeris documentation - File issues on the project repository ## Version History - v0.0.1: Initial release with core functionality - Comprehensive test suite - Full WebAssembly integration - Modern ES6+ JavaScript API ```