swisseph

Dart FFI bindings to the Swiss Ephemeris C library.

Compute planetary positions, house cusps, ayanamsa values, rise/set times, eclipses, crossings, heliacal events, and coordinate transforms — with no code generation, no Flutter dependency, and full isolate safety.

Features

  • ~88 methods covering the near-complete Swiss Ephemeris API
  • All 47 standard ayanamsas — Lahiri, Fagan-Bradley, Raman, Krishnamurti, and 43 more
  • 14 house systems — Placidus, Koch, Whole Sign, Campanus, Equal, Gauquelin sectors, and more
  • Solar and lunar eclipses — global and local search, attributes, geographic location of greatest eclipse
  • Planetary occultations — lunar occultation search and location
  • Sun/Moon/heliocentric crossings — find when bodies cross a longitude
  • Heliacal events — rising, setting, visibility limits
  • Coordinate transforms — horizon, ecliptic, equatorial, refraction
  • Nodes and apsides — mean, osculating, barycentric
  • Orbital elements — osculating elements, distance extremes
  • Phase and magnitude — elongation, phase angle, apparent magnitude
  • Three ephemeris engines — Moshier (no files needed), Swiss Ephemeris (.se1 files), JPL
  • Multiple coordinate systems — tropical, sidereal, heliocentric, barycentric, equatorial, topocentric
  • Isolate-safe — each instance wraps its own DynamicLibrary; copy the .so per isolate for independent C global state
  • Instance-based API — no singletons, no hidden state
  • Native asset build hookdart pub get compiles the vendored C source automatically via CBuilder

Platform support

Platform Status
Linux Supported (gcc/clang)
macOS Supported (clang)
Windows Supported (MSVC)
Android Supported (NDK)
Web Supported (WASM via Emscripten)

Native platforms require Dart SDK 3.11+ and a C compiler. Web requires the pre-built assets/swisseph.{js,wasm} served from your app.

Install

dart pub add swisseph

Or add it to your pubspec.yaml directly:

dependencies:
  swisseph: ^0.5.0

Alternatively, install from the Git repository:

dependencies:
  swisseph:
    git:
      url: https://gitlab.com/ninthhouse/swisseph.dart
      ref: master

Then run dart pub get, which triggers the native build hook to compile the C source.

Quick start

import 'package:swisseph/swisseph.dart';

void main() async {
  final swe = await SwissEph.load();

  final jd = swe.julday(2000, 1, 1, 12.0);
  final sun = swe.calcUt(jd, seSun, seFlgMosEph | seFlgSpeed);
  print('Sun: ${sun.longitude}°');

  swe.close();
}

SwissEph.load() works on both native and web. On native it finds the build hook output automatically; on web it loads the WASM module.

Native only

SwissEph.find() scans .dart_tool/ for the build-hook output, which is fine during local development from the package root. In production deployments — compiled binaries, apps launched from a different working directory, or non-default native-asset layouts — pass an explicit path to SwissEph.load(path) or the SwissEph(path) constructor. The Dart native-assets pipeline is the supported production loading path.

import 'package:swisseph/swisseph.dart';

void main() {
  final swe = SwissEph.find();

  // Julian Day for 2000-01-01 12:00 UT
  final jd = swe.julday(2000, 1, 1, 12.0);

  // Sun position (Moshier — no data files needed)
  final sun = swe.calcUt(jd, seSun, seFlgMosEph | seFlgSpeed);
  print('Sun: ${sun.longitude}°');

  // Sidereal position (Lahiri)
  swe.setSidMode(seSidmLahiri);
  final sidSun = swe.calcUt(
    jd, seSun, seFlgMosEph | seFlgSpeed | seFlgSidereal,
  );
  print('Sidereal Sun: ${sidSun.longitude}°');

  // House cusps (Campanus, Washington DC)
  final houses = swe.houses(jd, 38.8977, -77.0365, hsysCampanus);
  print('Ascendant: ${houses.ascendant}°');
  print('MC: ${houses.mc}°');

  // Sunrise
  final rise = swe.riseTrans(
    jd, seSun,
    rsmi: seCalcRise, geolon: -77.0365, geolat: 38.8977,
  );
  print('Sunrise JD: ${rise.transitTime}');

  // Next solar eclipse visible from Washington DC
  final eclipse = swe.solEclipseWhenLoc(
    jd, seFlgSwiEph,
    geolon: -77.0365, geolat: 38.8977,
  );
  print('Next solar eclipse: JD ${eclipse.maxEclipse}');

  // Next Sun crossing of 0° Aries
  final cross = swe.solCrossUt(0.0, jd, seFlgSwiEph);
  print('Next vernal equinox: JD $cross');

  swe.close();
}

See example/example.dart for a fuller example with formatted output.

Web setup

Serve the pre-built WASM assets from your web app:

  1. Copy assets/swisseph.js and assets/swisseph.wasm to your web app's asset directory
  2. Load the swisseph.js script in your HTML
  3. Use SwissEph.load() in your Dart code, or SwissEph.load('assets/swisseph') if you serve the module under a non-default URL (the path is passed through to wasm_ffi, which auto-resolves the .js/.wasm extension).

The WASM module exports all 94 Swiss Ephemeris functions (~539 KB wasm + ~81 KB JS glue). By default, web uses Moshier ephemeris (no files needed). For Swiss Ephemeris precision, load .se1 files into the in-memory virtual filesystem:

final swe = await SwissEph.load();

// Fetch .se1 files (your app's fetch logic — HTTP, Flutter asset bundle, etc.)
final planetBytes = await fetchFile('sepl_18.se1');
final moonBytes = await fetchFile('semo_18.se1');

// Push into MEMFS
swe.loadEpheFile('sepl_18.se1', planetBytes);
swe.loadEpheFile('semo_18.se1', moonBytes);
swe.setEphePath('/ephe');

// Now calcUt with seFlgSwiEph uses Swiss Ephemeris precision
final sun = swe.calcUt(jd, seSun, seFlgSwiEph | seFlgSpeed);

Only load the files you need — current-era planet + moon files are ~5 MB uncompressed (~3 MB gzipped).

API reference

Category Methods
Date/time julday, revjul, utcToJd, jdToUtc, jdetToUtc, utcTimeZone, dayOfWeek, deltat, deltatEx, timeEqu, sidTime, sidTime0, lmtToLat, latToLmt
Config setEphePath, setSidMode, setTopo, setJplFile, setInterpolateNut, setLapseRate, setDeltaTUserdef, setTidAcc, getTidAcc, getLibraryPath, getCurrentFileData, loadEpheFile, close, version
Positions calcUt, calc
Houses houses, housesEx, housesEx2, housesArmc, housesArmcEx2, housePos, gauquelinSector
Ayanamsa getAyanamsaUt, getAyanamsa, getAyanamsaExUt, getAyanamsaEx, getAyanamsaName
Fixed stars fixstar2Ut, fixstar2, fixstar2Mag
Eclipses solEclipseWhenLoc, solEclipseWhenGlob, solEclipseHow, solEclipseWhere, lunEclipseWhen, lunEclipseWhenLoc, lunEclipseHow, lunOccultWhenLoc, lunOccultWhenGlob, lunOccultWhere
Crossings solCrossUt, solCross, moonCrossUt, moonCross, moonCrossNodeUt, moonCrossNode, helioCrossUt, helioCross
Rise/set riseTrans, riseTransTrueHor
Nodes nodApsUt, nodAps
Orbits getOrbitalElements, orbitMaxMinTrueDistance
Phenomena phenoUt, pheno
Heliacal heliacalUt, heliacalPhenoUt, visLimitMag
Coordinates azAlt, azAltRev, cotrans, refrac, refracExtended
Names getPlanetName, houseName
Utilities degnorm, radNorm, degMidp, radMidp, difDegn, difDeg2n, splitDeg

All native memory uses Arena-scoped using() allocation. Errors throw SweException.

Ephemeris modes

Mode Flag Files needed Accuracy
Moshier seFlgMosEph None ~1 arcsecond
Swiss Ephemeris seFlgSwiEph .se1 data files Sub-arcsecond
JPL seFlgJplEph JPL ephemeris files Highest

Moshier works out of the box with no data files but is limited to ~1 arcsecond accuracy.

Bundled ephemeris files

This package includes Swiss Ephemeris data files in ephe/ for sub-arcsecond precision with no extra downloads:

Files Contents Coverage
sepl_*.se1 / seplm*.se1 Main planets (Sun–Pluto, lunar nodes) 5400 BC – 5400 AD
semo_*.se1 / semom*.se1 Moon (high precision) 5400 BC – 5400 AD
seas_*.se1 / seasm*.se1 Main asteroids (Ceres, Pallas, Juno, Vesta, Chiron, Pholus) 5400 BC – 5400 AD
sefstars.txt ~2,900 named fixed stars (expanded SIMBAD catalog) Current epoch
ast0/se00010s.se1 Asteroid 10 (Hygiea) Short range

Each .se1 file covers a 600-year range. The _00 through _48 files cover 0 AD – 5400 AD; the m06 through m54 files cover 5400 BC – 0 AD. Together they span roughly 10,800 years — more than enough for any practical astrological or historical use.

To use the bundled files, point setEphePath to the ephe/ directory inside the installed package. Use Isolate.resolvePackageUri to find it at runtime:

import 'dart:isolate';

final pkgUri = Uri.parse('package:swisseph/swisseph.dart');
final resolved = await Isolate.resolvePackageUri(pkgUri);
final ephePath = resolved!.resolve('../ephe').toFilePath();

swe.setEphePath(ephePath);
final sun = swe.calcUt(jd, seSun, seFlgSwiEph | seFlgSpeed);

This resolves to the ephe/ directory in the pub cache (e.g. ~/.pub-cache/hosted/pub.dev/swisseph-0.2.0/ephe/) regardless of where your project lives.

What's not included

  • JPL ephemeris files — for highest-precision research use
  • Additional numbered asteroids — only Hygiea (10) is bundled; thousands more are available separately
  • Dates beyond ~5400 BC / 5400 AD — the library falls back to Moshier for dates outside the file range

The full set of ephemeris files (including additional asteroids and extended date ranges) is available from astro.com/ftp/swisseph/ephe. Download any additional files you need into the same ephe/ directory.

Constants

Integer constants, not enums. Defined in lib/src/constants.dart.

Prefix Purpose Example
se Body IDs seSun, seMoon
seFlg Calculation flags seFlgSpeed
hsys House systems hsysPlacidus
seSidm Ayanamsa modes seSidmLahiri
seCalc Rise/set flags seCalcRise
seEcl Eclipse types seEclTotal
seNodBit Node/apsides flags seNodBitMean
seBit Rise/transit flags seBitDiscCenter
seHelFlag Heliacal flags seHelFlagLongSearch
seSidBit Sidereal mode bits seSidBitEclT0

Isolate safety

dlopen deduplicates shared libraries by device+inode. Two isolates loading the same .so path share C global state — sidereal mode, ephemeris path, and topocentric coordinates all leak between them.

The fix: copy the .so to a unique temp path per isolate. Each copy gets its own C globals.

// Copy the .so for this isolate
final tmpDir = Directory.systemTemp.createTempSync('swe_');
final libPath = '${tmpDir.path}/libswisseph_$id.so';
File(sharedLibPath).copySync(libPath);

final swe = SwissEph(libPath);

See test/isolate_test.dart for the full pattern. The stress tests run up to 4.11 billion calculations across 100 isolates covering all 88 public methods to prove this holds at scale.

Limitations

  • No Flutter plugin — this is a pure Dart package using native asset build hooks. Works with Dart CLI and can be used from Flutter, but is not a Flutter plugin.
  • Web: no JPL ephemeris — JPL data files are not supported on web. Swiss Ephemeris .se1 files can be loaded via loadEpheFile(); Moshier works with no files.
  • Limited asteroid coverage — main asteroids (Ceres, Pallas, Juno, Vesta, Chiron, Pholus, Hygiea) are bundled. Thousands more numbered asteroids are available from astro.com but are untested with this package.
  • C global state — the underlying C library uses global state. See Isolate safety above.

Tests

dart test
  • 26 unit tests across date, calc, houses, ayanamsa, and isolate safety
  • 545-value cross-validation suite against pyswisseph covering 13 bodies, 14 ayanamsas, 11 house systems, 7 locations, 7 dates
  • Stress test 0.1: 3.06 billion calculations across 100 isolates (calcUt + houses, Moshier + Swiss Ephemeris, all 47 ayanamsas, heliocentric, barycentric, equatorial). See test/stress-test.md.
  • Stress test 0.2: 4.11 billion calculations across 100 isolates covering all 88 public methods — positions, houses, ayanamsas, date/time, fixed stars, eclipses, crossings, rise/set, heliacal events, coordinates, nodes, orbital elements, and Gauquelin sectors. Tests two ephemeris engines, 52 ayanamsa modes, 11 house systems, 8 locations (including polar), and 5,000 years of dates. Verifies isolate isolation, API coverage, and expected error paths. See test/stress-test-0.2/stress-test-0.2.md.

License

This Dart package is licensed under AGPL-3.0-or-later.

The Swiss Ephemeris C library (vendored in csrc/) is copyright Astrodienst AG and dual-licensed under AGPL-3.0 and a commercial license. See astro.com/swisseph for commercial licensing details.

Issues and contributions

File issues and merge requests on GitLab.

Libraries

swisseph
Isolate-safe Dart FFI bindings to the Swiss Ephemeris C library.