@typescriptify/sweph
v1.0.4
Published
Swiss Ephemeris for TypeScript — high-precision astronomical calculations
Maintainers
Readme
@typescriptify/sweph
A pure TypeScript translation of the Swiss Ephemeris — the gold standard library for astronomical and astrological calculations.
This library lets you calculate the positions of the Sun, Moon, planets, and stars with high precision. You can compute house cusps, eclipses, rise/set times, and much more — all from TypeScript or JavaScript, with no native dependencies.
What can it do?
- Planet positions — get the longitude, latitude, and distance of any planet at any date
- House systems — calculate house cusps for 24+ house systems (Placidus, Koch, Whole Sign, etc.)
- Fixed stars — positions and magnitudes of key stars (Spica, Regulus, Aldebaran, etc.)
- Eclipses — find solar/lunar eclipses, occultations, compute eclipse paths
- Rise/set times — sunrise, sunset, moonrise, planet transits
- Ayanamsa — sidereal zodiac support with 40+ ayanamsa modes (Lahiri, True Citra, etc.)
- Heliacal events — heliacal rising/setting of planets and stars
- Orbital elements — Keplerian elements, nodes, apsides
- Date conversions — Julian day, UTC, Delta T, sidereal time
Supported Environments
This is a pure TypeScript library with zero native dependencies — no WASM, no C bindings, no filesystem access. It runs anywhere JavaScript runs:
| Environment | Status | Notes |
|---|---|---|
| Node.js | Fully supported | Load .se1 files via fs.readFileSync() |
| Browser | Fully supported | Load .se1 files via fetch() or bundle as assets |
| React Native (Expo) | Fully supported | Load .se1 files via expo-asset + expo-file-system, or fetch() from a remote URL |
| React Native (Bare) | Fully supported | Load .se1 files via react-native-fs with manual platform asset placement, or fetch() |
| Cloudflare Workers / Edge | Fully supported | Load .se1 files via fetch() or bind as R2/KV assets |
| Deno / Bun | Fully supported | Load .se1 files via their respective file APIs |
The library uses only standard JavaScript APIs (ArrayBuffer, DataView, Float64Array, TextDecoder, Math) — no platform-specific code. Ephemeris data files are loaded as ArrayBuffers, so you can source them however your environment allows.
Moshier mode (the default) requires no data files at all — it uses built-in analytical models and works out of the box in every environment.
Installation
Node.js
npm install @typescriptify/swephBrowser (Vite, Webpack, etc.)
npm install @typescriptify/swephThen import as usual — your bundler will handle the rest. If you need .se1 files, serve them as static assets and load with fetch().
React Native (Expo)
npx expo install @typescriptify/swephExpo's asset bundler handles file placement for both Android and iOS. See Loading .se1 files with Expo below.
React Native (Bare / without Expo)
npm install @typescriptify/swephYou'll need react-native-fs to read bundled .se1 files, and you must place them manually in each platform's asset directory. See Loading .se1 files with bare React Native below.
React Native — engine compatibility
The library works with both the Hermes and JSC engines. TextDecoder (used internally for reading star names from binary files) is available in Hermes 0.70+ and JSC. If you target an older engine, add a polyfill like text-encoding.
Deno
import { SwissEph } from '@typescriptify/sweph/SwissEph';Bun
bun add @typescriptify/swephTwo API Styles
This library provides two ways to use it:
| | C-style API | Modern TypeScript API |
|---|---|---|
| Style | Direct translation of the C functions | Class-based wrapper with named return types |
| State | Thread swed: SweData manually | Managed inside SwissEph instance |
| Returns | Float64Array, positional indexing (xx[0]) | Named objects ({ longitude, latitude, ... }) |
| Errors | Check return codes (ERR = -1) | Throws SwissEphError |
| Time mode | Separate *Ut / *Et functions | Automatic routing via timeMode option |
| Docs | This README (below) | src/SwissEph/ — see README and UseCases |
Modern API — if you want a clean, idiomatic TypeScript experience:
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN, SE_MARS } from '@typescriptify/sweph/constants';
const swe = new SwissEph();
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(4)}°, speed: ${sun.longitudeSpeed.toFixed(4)}°/day`);
swe.close();See src/SwissEph/README.md for full documentation and src/SwissEph/UseCases/ for 27 detailed use case guides covering everything from planet positions to heliacal events.
C-style API — if you're familiar with the original Swiss Ephemeris C interface, the rest of this README is for you.
Quick Start
Every function takes a swed state object as its first argument. Create one at startup and reuse it:
import { createDefaultSweData } from '@typescriptify/sweph/types';
import { sweCalc } from '@typescriptify/sweph/sweph';
import { julDay } from '@typescriptify/sweph/swedate';
import {
SE_SUN, SE_MOON, SE_MARS,
SEFLG_MOSEPH, SEFLG_SPEED,
SE_GREG_CAL,
} from '@typescriptify/sweph/constants';
// 1. Create the state object (do this once)
const swed = createDefaultSweData();
// 2. Convert a calendar date to a Julian day number
// (January 1, 2025 at noon UT)
const jd = julDay(2025, 1, 1, 12.0, SE_GREG_CAL);
// 3. Calculate the Sun's position
const sun = sweCalc(swed, jd, SE_SUN, SEFLG_MOSEPH | SEFLG_SPEED);
console.log(`Sun longitude: ${sun.xx[0].toFixed(4)}°`); // ecliptic longitude
console.log(`Sun latitude: ${sun.xx[1].toFixed(4)}°`); // ecliptic latitude
console.log(`Sun distance: ${sun.xx[2].toFixed(6)} AU`); // distance in AU
console.log(`Sun speed: ${sun.xx[3].toFixed(4)}°/day`); // daily motionThe result xx is a Float64Array with 6 elements: [longitude, latitude, distance, lonSpeed, latSpeed, distSpeed].
Calculating the Moon
const moon = sweCalc(swed, jd, SE_MOON, SEFLG_MOSEPH | SEFLG_SPEED);
console.log(`Moon: ${moon.xx[0].toFixed(4)}° at ${moon.xx[3].toFixed(2)}°/day`);Calculating houses
import { sweHouses } from '@typescriptify/sweph/swehouse';
const cusps = new Array(37).fill(0); // cusp[1]..cusp[12]
const ascmc = new Array(10).fill(0); // ascmc[0]=AC, ascmc[1]=MC, ascmc[2]=ARMC, etc.
// Placidus houses for London at the given time
sweHouses(swed, jd, 51.5074, -0.1276, 'P', cusps, ascmc);
console.log(`Ascendant: ${ascmc[0].toFixed(2)}°`);
console.log(`Midheaven: ${ascmc[1].toFixed(2)}°`);
for (let i = 1; i <= 12; i++) {
console.log(` House ${i}: ${cusps[i].toFixed(2)}°`);
}House system codes: 'P' = Placidus, 'K' = Koch, 'E' = Equal, 'W' = Whole Sign, 'C' = Campanus, 'R' = Regiomontanus, 'B' = Alcabitius, and many more.
Ephemeris Modes
This library supports three levels of precision:
| Mode | Flag | Precision | Data files needed? |
|---|---|---|---|
| Moshier | SEFLG_MOSEPH | ~1 arcsecond | No (built-in) |
| Swiss Ephemeris | SEFLG_SWIEPH | ~0.001 arcsecond | Yes (.se1 files) |
| JPL | SEFLG_JPLEPH | ~0.001 arcsecond | Yes (JPL DE file) |
Moshier mode works out of the box with no data files. It uses built-in analytical models and is accurate enough for most purposes.
Swiss Ephemeris mode reads pre-computed binary data files (.se1) for higher precision. See Loading Ephemeris Data Files below.
JPL mode reads NASA JPL Development Ephemeris binary files (e.g., de441.eph). See Loading JPL Files below.
Loading Ephemeris Data Files
Where to download
Download .se1 files from the official Swiss Ephemeris site:
- https://www.astro.com/ftp/swisseph/ephe/
The most common files you'll need:
sepl_18.se1— planet data (1800 AD - 2400 AD)semo_18.se1— Moon data (1800 AD - 2400 AD)
For other date ranges, download the corresponding files (e.g., sepl_06.se1 for 600 AD - 1200 AD).
How to load them
Since this is a pure TypeScript library (no filesystem access), you load files as ArrayBuffers. How you get the ArrayBuffer depends on your environment.
All examples below use the Modern API (SwissEph class). For the C-style API equivalent, use sweSetEphemerisFile(filename, buffer, swed) instead.
Node.js
import { readFileSync } from 'fs';
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN } from '@typescriptify/sweph/constants';
const swe = new SwissEph({ ephemeris: 'swisseph' });
// Load .se1 files from disk
const seplBuf = readFileSync('./ephe/sepl_18.se1');
const semoBuf = readFileSync('./ephe/semo_18.se1');
swe.loadEphemerisFile(seplBuf.buffer.slice(seplBuf.byteOffset, seplBuf.byteOffset + seplBuf.byteLength), 'sepl_18.se1');
swe.loadEphemerisFile(semoBuf.buffer.slice(semoBuf.byteOffset, semoBuf.byteOffset + semoBuf.byteLength), 'semo_18.se1');
// Calculate with Swiss Ephemeris precision
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(6)}°`);
swe.close();Browser
Serve the .se1 files as static assets (e.g., in your public/ folder) and fetch them:
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN } from '@typescriptify/sweph/constants';
const swe = new SwissEph({ ephemeris: 'swisseph' });
// Fetch .se1 files from your static assets
const [seplBuf, semoBuf] = await Promise.all([
fetch('/ephe/sepl_18.se1').then(r => r.arrayBuffer()),
fetch('/ephe/semo_18.se1').then(r => r.arrayBuffer()),
]);
swe.loadEphemerisFile(seplBuf, 'sepl_18.se1');
swe.loadEphemerisFile(semoBuf, 'semo_18.se1');
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(6)}°`);
swe.close();React Native (Expo)
Expo handles asset bundling for both Android and iOS automatically. Place your .se1 files anywhere in your project (e.g., ./assets/ephe/), then use expo-asset and expo-file-system to load them.
1. Configure Metro to recognize .se1 files
Update your metro.config.js:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('se1');
module.exports = config;2. Load and use the ephemeris files
import { Asset } from 'expo-asset';
import * as FileSystem from 'expo-file-system';
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN } from '@typescriptify/sweph/constants';
async function loadEpheFile(asset: Asset): Promise<ArrayBuffer> {
await asset.downloadAsync();
const base64 = await FileSystem.readAsStringAsync(asset.localUri!, {
encoding: FileSystem.EncodingType.Base64,
});
// Decode base64 to ArrayBuffer
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
return bytes.buffer;
}
const swe = new SwissEph({ ephemeris: 'swisseph' });
const seplAsset = Asset.fromModule(require('./assets/ephe/sepl_18.se1'));
const semoAsset = Asset.fromModule(require('./assets/ephe/semo_18.se1'));
swe.loadEphemerisFile(await loadEpheFile(seplAsset), 'sepl_18.se1');
swe.loadEphemerisFile(await loadEpheFile(semoAsset), 'semo_18.se1');
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(6)}°`);
swe.close();React Native (Bare / without Expo)
Without Expo's asset bundler, you need to place files manually in each platform's asset location and use react-native-fs to read them.
1. Place .se1 files in the platform-specific asset directories
- Android: Copy
.se1files intoandroid/app/src/main/assets/ephe/ - iOS: Add
.se1files to your Xcode project and include them in the "Copy Bundle Resources" build phase
2. Load and use the ephemeris files
import RNFS from 'react-native-fs';
import { Platform } from 'react-native';
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN } from '@typescriptify/sweph/constants';
async function loadEpheFile(filename: string): Promise<ArrayBuffer> {
let base64: string;
if (Platform.OS === 'android') {
// Android reads from android/app/src/main/assets/
base64 = await RNFS.readFileAssets(`ephe/${filename}`, 'base64');
} else {
// iOS reads from the app bundle
const path = `${RNFS.MainBundlePath}/ephe/${filename}`;
base64 = await RNFS.readFile(path, 'base64');
}
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
return bytes.buffer;
}
const swe = new SwissEph({ ephemeris: 'swisseph' });
swe.loadEphemerisFile(await loadEpheFile('sepl_18.se1'), 'sepl_18.se1');
swe.loadEphemerisFile(await loadEpheFile('semo_18.se1'), 'semo_18.se1');
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(6)}°`);
swe.close();React Native — fetch from a remote URL
This works with both Expo and bare React Native. It's the simplest approach if you don't want to bundle the files with your app:
import { SwissEph } from '@typescriptify/sweph/SwissEph';
import { SE_SUN } from '@typescriptify/sweph/constants';
const swe = new SwissEph({ ephemeris: 'swisseph' });
const [seplBuf, semoBuf] = await Promise.all([
fetch('https://your-server.com/ephe/sepl_18.se1').then(r => r.arrayBuffer()),
fetch('https://your-server.com/ephe/semo_18.se1').then(r => r.arrayBuffer()),
]);
swe.loadEphemerisFile(seplBuf, 'sepl_18.se1');
swe.loadEphemerisFile(semoBuf, 'semo_18.se1');
const jd = SwissEph.julianDay(2025, 1, 1, 12);
const sun = swe.calc(jd, SE_SUN);
console.log(`Sun: ${sun.longitude.toFixed(6)}°`);
swe.close();Tip: The
.se1files are ~1.5 MB each. Consider caching them locally after the first download usingAsyncStorageor the device filesystem to avoid re-downloading on every app launch.
Loading JPL files
For the highest precision using NASA JPL ephemerides:
import { sweLoadJplFile, sweCalc } from '@typescriptify/sweph/sweph';
import { SEFLG_JPLEPH, SEFLG_SPEED, SE_MARS } from '@typescriptify/sweph/constants';
// Download a JPL DE file (e.g., de441.eph) from:
// https://ssd.jpl.nasa.gov/ftp/eph/planets/Linux/
const jplBuffer = readFileSync('./ephe/de441.eph');
const result = sweLoadJplFile(swed, jplBuffer.buffer, 'de441.eph');
if (result.retc < 0) {
console.error('Failed to load JPL file:', result.serr);
}
// Now use SEFLG_JPLEPH
const mars = sweCalc(swed, 2451545.0, SE_MARS, SEFLG_JPLEPH | SEFLG_SPEED);More Examples
Sidereal zodiac (ayanamsa)
import { sweSetSidMode, sweGetAyanamsa } from '@typescriptify/sweph/sweph';
import { SE_SIDM_LAHIRI } from '@typescriptify/sweph/constants';
sweSetSidMode(swed, SE_SIDM_LAHIRI, 0, 0);
const ayanamsa = sweGetAyanamsa(swed, jd);
console.log(`Lahiri ayanamsa: ${ayanamsa.toFixed(4)}°`);
// To get sidereal positions, add SEFLG_SIDEREAL to your flags:
import { SEFLG_SIDEREAL } from '@typescriptify/sweph/constants';
const sunSid = sweCalc(swed, jd, SE_SUN, SEFLG_MOSEPH | SEFLG_SIDEREAL);
console.log(`Sun (sidereal): ${sunSid.xx[0].toFixed(4)}°`);Fixed stars
import { sweFixstar } from '@typescriptify/sweph/sweph';
const spica = sweFixstar(swed, 'Spica', jd, SEFLG_MOSEPH);
console.log(`Spica: ${spica.xx[0].toFixed(4)}°`);Sunrise and sunset
import { sweRiseTrans } from '@typescriptify/sweph/swecl';
import { SE_CALC_RISE, SE_CALC_SET } from '@typescriptify/sweph/constants';
import { revJul } from '@typescriptify/sweph/swedate';
const geopos = [-0.1276, 51.5074, 0]; // [longitude, latitude, altitude_meters]
const rise = sweRiseTrans(
swed, jd, SE_SUN, null, SEFLG_MOSEPH,
SE_CALC_RISE, geopos, 1013.25, 10, null
);
if (rise.retval >= 0) {
const d = revJul(rise.tret, SE_GREG_CAL);
console.log(`Sunrise: ${d.year}-${d.month}-${d.day} ${d.hour.toFixed(2)} UT`);
}Solar eclipse search
import { sweSolEclipseWhenGlob, sweSolEclipseWhere } from '@typescriptify/sweph/swecl';
// Find the next solar eclipse after January 1, 2025
const startJd = julDay(2025, 1, 1, 0, SE_GREG_CAL);
const ecl = sweSolEclipseWhenGlob(swed, startJd, SEFLG_MOSEPH, 0, 0);
if (ecl.retval > 0) {
const d = revJul(ecl.tret[0], SE_GREG_CAL);
console.log(`Eclipse maximum: ${d.year}-${d.month}-${d.day} ${d.hour.toFixed(2)} UT`);
const where = sweSolEclipseWhere(swed, ecl.tret[0], SEFLG_MOSEPH);
console.log(`Central path: lon=${where.geopos[0].toFixed(1)}° lat=${where.geopos[1].toFixed(1)}°`);
}Azimuth and altitude
import { sweAzalt } from '@typescriptify/sweph/swecl';
import { SE_ECL2HOR } from '@typescriptify/sweph/constants';
const xaz = [0, 0, 0]; // [azimuth, trueAltitude, apparentAltitude]
sweAzalt(
swed, jd, SE_ECL2HOR,
[-0.1276, 51.5074, 0], // geopos
1013.25, 10, // pressure (mbar), temperature (C)
[sun.xx[0], sun.xx[1], sun.xx[2]], // ecliptic lon, lat, distance
xaz
);
console.log(`Sun azimuth: ${xaz[0].toFixed(2)}°, altitude: ${xaz[2].toFixed(2)}°`);Orbital elements
import { sweGetOrbitalElements } from '@typescriptify/sweph/swecl';
const orb = sweGetOrbitalElements(swed, jd, SE_MARS, SEFLG_MOSEPH);
console.log(`Mars semi-major axis: ${orb.dret[0].toFixed(4)} AU`);
console.log(`Mars eccentricity: ${orb.dret[1].toFixed(4)}`);
console.log(`Mars inclination: ${orb.dret[2].toFixed(2)}°`);Planetary nodes and apsides
import { sweNodAps } from '@typescriptify/sweph/swecl';
const nod = sweNodAps(swed, jd, SE_MARS, SEFLG_MOSEPH, 0);
console.log(`Mars ascending node: ${nod.xnasc[0].toFixed(4)}°`);
console.log(`Mars descending node: ${nod.xndsc[0].toFixed(4)}°`);
console.log(`Mars perihelion: ${nod.xperi[0].toFixed(4)}°`);
console.log(`Mars aphelion: ${nod.xaphe[0].toFixed(4)}°`);Heliacal rising
import { sweHeliacalUt } from '@typescriptify/sweph/swehel';
const hel = sweHeliacalUt(
swed, jd,
[-0.1276, 51.5074, 0], // geopos
[1013.25, 10, 50, 0.25, 0, 0], // atmospheric conditions
[0, 0, 0, 0, 0, 0], // observer conditions
'Venus', 1, SEFLG_MOSEPH
);
if (hel.retval >= 0) {
const d = revJul(hel.dret[0], SE_GREG_CAL);
console.log(`Venus heliacal rising: ${d.year}-${d.month}-${d.day}`);
}Topocentric positions
import { sweSetTopo } from '@typescriptify/sweph/sweph';
import { SEFLG_TOPOCTR } from '@typescriptify/sweph/constants';
// Set observer location: London
sweSetTopo(swed, -0.1276, 51.5074, 0);
const moonTopo = sweCalc(swed, jd, SE_MOON, SEFLG_MOSEPH | SEFLG_TOPOCTR | SEFLG_SPEED);
console.log(`Moon (topocentric): ${moonTopo.xx[0].toFixed(4)}°`);Cleanup
When you're done, free resources:
import { sweClose } from '@typescriptify/sweph/sweph';
sweClose(swed);House Systems
| Code | Name |
|---|---|
| P | Placidus |
| K | Koch |
| O | Porphyrius |
| R | Regiomontanus |
| C | Campanus |
| E | Equal (from Ascendant) |
| W | Whole Sign |
| B | Alcabitius |
| M | Morinus |
| X | Axial Rotation (Meridian) |
| H | Azimuthal (Horizontal) |
| T | Polich/Page (Topocentric) |
| G | Gauquelin sectors (36 cusps) |
| A | Equal (from Aries 0) |
| D | Equal (MC) |
| F | Carter (Poli-Equatorial) |
| I | Sunshine / Makransky |
| J | Sunshine / Treindl |
| L | Pullen SD (sinusoidal delta) |
| N | Pullen SR (sinusoidal ratio) |
| Q | Pullen cumulative |
| S | Sripati |
| U | Krusinski-Pisa-Goelzer |
| V | Vehlow Equal |
| Y | APC houses |
Planet Constants
| Constant | Planet |
|---|---|
| SE_SUN | Sun |
| SE_MOON | Moon |
| SE_MERCURY | Mercury |
| SE_VENUS | Venus |
| SE_MARS | Mars |
| SE_JUPITER | Jupiter |
| SE_SATURN | Saturn |
| SE_URANUS | Uranus |
| SE_NEPTUNE | Neptune |
| SE_PLUTO | Pluto |
| SE_MEAN_NODE | Mean Lunar Node |
| SE_TRUE_NODE | True Lunar Node |
| SE_MEAN_APOG | Mean Lunar Apogee (Black Moon Lilith) |
| SE_OSCU_APOG | Osculating Lunar Apogee |
| SE_CHIRON | Chiron |
| SE_EARTH | Earth |
About This Project
This is a line-by-line TypeScript translation of the Swiss Ephemeris C library (~30,000 lines of C translated to ~15 TypeScript source files). All 9 C source files have been translated, covering all 106 public API functions.
Key design decisions:
- No global state — all functions take an explicit
swed: SweDataparameter - No filesystem access — ephemeris files are loaded as
ArrayBuffers, making the library work in any JavaScript environment (Node.js, browsers, React Native, etc.) - No native dependencies — pure TypeScript, no WASM, no C bindings
This is a TypeScript translation based on the Node.js wrapper sweph by Timotej Valentin Rojko. The underlying Swiss Ephemeris is by Astrodienst AG.
License
This project is a TypeScript translation and is bound by the license of the original Swiss Ephemeris:
- AGPL-3.0 for open-source use
- LGPL-3.0 for holders of a professional Swiss Ephemeris license from Astrodienst AG
See the original repository for full license details: https://github.com/timotejroiko/sweph
The Swiss Ephemeris itself is Copyright (C) 1997-2021 Astrodienst AG, Switzerland.
