npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apexcoords

v1.0.0

Published

Pure TypeScript implementation of ApexPy for converting geodetic coordinates to Quasi-Dipole and Modified Apex magnetic coordinates

Readme

apexcoords

Pure TypeScript implementation of ApexPy for converting geodetic coordinates to Quasi-Dipole (QD) and Modified Apex magnetic coordinates.

Features

  • Pure TypeScript – no WebAssembly, no native dependencies
  • Bidirectional conversion: geodetic ↔ QD and Modified Apex coordinates
  • Flexible epoch support: accepts decimal years, Date objects, or ISO 8601 strings
  • GeoJSON & TopoJSON: transform entire geometries between coordinate systems
  • D3 integration: helpers for graticules and circles
  • Magnetic Local Time: convert between magnetic longitude and MLT
  • Fast: ~80,000 conversions/second
  • Small: ~15 KB library + ~100 KB coefficient file (per 50 years of epochs)

Installation

npm install apexcoords

Coefficient File Setup

This package requires a binary coefficient file containing pre-computed spherical harmonic expansion coefficients. The coefficients encode the mapping between geodetic and magnetic coordinates for specific time periods (epochs).

Option 1: Download Pre-built Coefficients

Download the coefficient file for your desired epoch range from the releases page:

| File | Epochs | Size | IGRF Version | |------|--------|------|--------------| | apex_coeffs_1980_2030.bin | 1980-2030 | 101 KB | IGRF-14 | | apex_coeffs_1900_2030.bin | 1900-2030 | 254 KB | IGRF-14 |

Place the file in your project's public/ or static/ directory (depending on your framework).

Option 2: Generate Custom Coefficients

To create a coefficient file with a custom epoch range, or when new IGRF versions are released:

  1. Get the apexsh.dat file from the apexpy Python package:

    pip install apexpy
    python -c "import apexpy; print(apexpy.__file__.replace('__init__.py', 'apexsh.dat'))"

    Or download from apexpy on GitHub.

  2. Run the conversion script:

    # Show available epochs in the file
    python scripts/convert_apexsh.py /path/to/apexsh.dat --info
    
    # Convert specific epoch range
    python scripts/convert_apexsh.py /path/to/apexsh.dat \
        -o public/apex_coeffs.bin \
        --start 1980 --end 2030
    
    # Convert all available epochs
    python scripts/convert_apexsh.py /path/to/apexsh.dat -o public/apex_coeffs.bin

Framework-Specific Setup

SvelteKit:

static/
  apex_coeffs_1980_2030.bin

Vite / Vue / React:

public/
  apex_coeffs_1980_2030.bin

Next.js:

public/
  apex_coeffs_1980_2030.bin

Usage

Basic Example

import { createApex } from 'apexcoords';

// Initialize with coefficient file path
// epoch accepts a number, Date object, or ISO 8601 string
const apex = await createApex({
    epoch: '2025-06-15',
    coeffUrl: '/apex_coeffs_1980_2030.bin'
});

// Convert geodetic to Quasi-Dipole coordinates
const { qlat, qlon } = apex.geo2qd(60.0, 15.0, 300.0);
console.log(`QD: ${qlat.toFixed(4)}°, ${qlon.toFixed(4)}°`);
// QD: 56.8177°, 92.5790°

// Convert geodetic to Modified Apex coordinates
const { alat, alon } = apex.geo2apex(60.0, 15.0, 300.0);
console.log(`Apex: ${alat.toFixed(4)}°, ${alon.toFixed(4)}°`);
// Apex: 57.6658°, 92.5790°

Inverse Conversion

// Convert QD coordinates back to geodetic
const { glat, glon } = apex.qd2geo(56.82, 92.58, 300.0);
console.log(`Geodetic: ${glat.toFixed(4)}°, ${glon.toFixed(4)}°`);

// Convert Modified Apex coordinates back to geodetic
const geo = apex.apex2geo(57.67, 92.58, 300.0);
console.log(`Geodetic: ${geo.glat.toFixed(4)}°, ${geo.glon.toFixed(4)}°`);

Changing Epochs

// Check available epoch range
const [minEpoch, maxEpoch] = apex.getEpochRange();
console.log(`Available: ${minEpoch} - ${maxEpoch}`);

// Change to a different epoch (accepts number, Date, or ISO string)
apex.setEpoch(new Date('2020-03-15'));

// Get all available epochs
const epochs = apex.getAvailableEpochs();
console.log(epochs);  // [1980, 1985, 1990, ..., 2025, 2030]

Batch Conversion

// Convert multiple points at once (forward)
const { qlats, qlons } = apex.geo2qdBatch(
    [60, 65, 70],      // latitudes
    [15, 20, 25],      // longitudes
    300                 // altitude (single value applied to all)
);

// Batch with varying altitudes
const { alats, alons } = apex.geo2apexBatch(
    [60, 65, 70],
    [15, 20, 25],
    [300, 400, 500]    // different altitude for each point
);

// Inverse batch conversions
const { glats, glons } = apex.qd2geoBatch(qlats, qlons, 300);

GeoJSON Transformation

Transform entire GeoJSON objects between geodetic and magnetic coordinate systems. Supports Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection, Feature, and FeatureCollection.

import { createApex } from 'apexcoords';

const apex = await createApex({ epoch: 2025.0, coeffUrl: '/apex_coeffs_1980_2030.bin' });

// Transform a FeatureCollection to Quasi-Dipole coordinates
const qdFeatures = apex.geoJsonConvertGeodeticToQuasiDipole(geojsonData);

// Or to Modified Apex coordinates
const apexFeatures = apex.geoJsonConvertGeodeticToApex(geojsonData);

// Convert back to geodetic
const geoFeatures = apex.geoJsonConvertQuasiDipoleToGeodetic(qdFeatures);

// Specify default altitude for 2D coordinates (default: 0 km)
const qdHigh = apex.geoJsonConvertGeodeticToQuasiDipole(geojsonData, { defaultAlt: 300 });

TopoJSON Transformation

Transform TopoJSON topologies. Handles both quantized (delta-encoded) and non-quantized formats. Returns dequantized absolute coordinates.

// Transform world topology to magnetic coordinates
const qdWorld = apex.topoJsonConvertGeodeticToQuasiDipole(worldTopology);
const apexWorld = apex.topoJsonConvertGeodeticToApex(worldTopology);

// Convert back
const geoWorld = apex.topoJsonConvertQuasiDipoleToGeodetic(qdWorld);

D3 Integration

Helpers for transforming D3 geo primitives:

import * as d3 from 'd3';
import * as topojson from 'topojson-client';

// Transform a D3 graticule to magnetic coordinates
const graticule = d3.geoGraticule()();
const qdGraticule = apex.graticuleConvertGeodeticToQuasiDipole(graticule);

// Transform a D3 circle (e.g., polar cap boundary)
const circle = d3.geoCircle().center([0, 90]).radius(30)();
const qdCircle = apex.circleConvertGeodeticToQuasiDipole(circle);

// Transform world coastlines (TopoJSON)
const world = await d3.json('countries-110m.json');
const qdWorld = apex.topoJsonConvertGeodeticToQuasiDipole(world);

// Render with standard D3 path generator
const projection = d3.geoEquirectangular();
const path = d3.geoPath().projection(projection);

svg.append('path').datum(qdGraticule).attr('d', path);
svg.append('path').datum(topojson.feature(qdWorld, qdWorld.objects.land)).attr('d', path);
svg.append('path').datum(qdCircle).attr('d', path);

For real-time transformations in D3's projection pipeline (e.g., animated epochs), you can use d3.geoTransform() as a stream transform:

// Stream transform: converts coordinates on-the-fly in the projection pipeline
const magneticTransform = d3.geoTransform({
    point(x, y) {
        const { qlat, qlon } = apex.geo2qd(y, x, 0);  // x=lon, y=lat
        this.stream.point(qlon, qlat);
    }
});

// Compose with a base projection
const composed = { stream: (s) => magneticTransform.stream(projection.stream(s)) };
const path = d3.geoPath().projection(composed);

// Now geodetic GeoJSON renders in magnetic coordinates automatically
svg.selectAll('path').data(features).attr('d', path);

Magnetic Local Time

// Convert magnetic longitude to Magnetic Local Time
const mlt = apex.mlonToMLT(0, new Date('2025-06-21T12:00:00Z'));
console.log(`MLT: ${mlt.toFixed(1)} hours`);

// Convert MLT back to magnetic longitude
const mlon = apex.mltToMlon(12, '2025-06-21T12:00:00Z');

Apex Height Utilities

// Calculate apex height for a given magnetic latitude
const apexHeight = apex.getApexHeight(60);  // ~19113 km

// Inverse: find height along field line at a given latitude
const height = apex.getHeightFromApex(60, 19113);  // ~0 km

Svelte Example

<script lang="ts">
  import { onMount } from 'svelte';
  import { createApex, type ApexConverter } from 'apexcoords';

  let apex: ApexConverter | null = null;
  let qlat = 0, qlon = 0;
  let loading = true;
  let error = '';

  onMount(async () => {
    try {
      apex = await createApex({
        epoch: 2025.0,
        coeffUrl: '/apex_coeffs_1980_2030.bin'
      });
      loading = false;
    } catch (e) {
      error = e instanceof Error ? e.message : 'Failed to load';
      loading = false;
    }
  });

  function convert(glat: number, glon: number, alt: number) {
    if (apex) {
      ({ qlat, qlon } = apex.geo2qd(glat, glon, alt));
    }
  }
</script>

{#if loading}
  <p>Loading coefficients...</p>
{:else if error}
  <p>Error: {error}</p>
{:else}
  <button on:click={() => convert(60, 15, 300)}>Convert</button>
  <p>QD: {qlat.toFixed(4)}°, {qlon.toFixed(4)}°</p>
{/if}

API Reference

createApex(options)

Create an ApexConverter instance.

Options: | Option | Type | Default | Description | |--------|------|---------|-------------| | epoch | number \| Date \| string | 2025.0 | Epoch as decimal year, Date, or ISO 8601 string | | refh | number | 0 | Reference height for Modified Apex coordinates (km) | | coeffUrl | string | './apex_coeffs_1980_2030.bin' | URL/path to coefficient file |

Returns: Promise<ApexConverter>

Throws: Error if coefficient file cannot be loaded or is invalid.

dateToDecimalYear(input)

Convert a Date, ISO string, or number to a decimal year. Numbers pass through unchanged.

import { dateToDecimalYear } from 'apexcoords';
dateToDecimalYear(new Date('2025-07-01'));  // ~2025.496
dateToDecimalYear('2025-01-01');            // 2025.0
dateToDecimalYear(2025.5);                  // 2025.5

subsolarPoint(datetime)

Calculate the geodetic coordinates where the Sun is directly overhead.

import { subsolarPoint } from 'apexcoords';
const { lat, lon } = subsolarPoint(new Date('2025-06-21T12:00:00Z'));
// lat ≈ 23.4° (summer solstice), lon depends on UTC time

ApexConverter Methods

Core Conversions

geo2qd(glat, glon, alt)

Convert geodetic to Quasi-Dipole coordinates.

| Parameter | Type | Description | |-----------|------|-------------| | glat | number | Geodetic latitude (-90 to 90°) | | glon | number | Geodetic longitude (°) | | alt | number | Altitude above WGS84 ellipsoid (km) |

Returns: { qlat: number, qlon: number }

geo2apex(glat, glon, alt)

Convert geodetic to Modified Apex coordinates. Uses the reference height set via createApex({ refh }) or setRefHeight().

| Parameter | Type | Description | |-----------|------|-------------| | glat | number | Geodetic latitude (-90 to 90°) | | glon | number | Geodetic longitude (°) | | alt | number | Altitude above WGS84 ellipsoid (km) |

Returns: { qlat: number, qlon: number, alat: number, alon: number }

qd2geo(qlat, qlon, alt, precision?)

Convert Quasi-Dipole to geodetic coordinates using iterative refinement.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | qlat | number | | QD latitude (°) | | qlon | number | | QD longitude (°) | | alt | number | | Altitude (km) | | precision | number | 1e-10 | Target precision in degrees. Negative = low-precision (no iteration). |

Returns: { glat: number, glon: number, error: number }

apex2geo(alat, alon, alt, precision?)

Convert Modified Apex to geodetic coordinates.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | alat | number | | Apex latitude (°) | | alon | number | | Apex longitude (°) | | alt | number | | Altitude (km) | | precision | number | 1e-10 | Target precision in degrees |

Returns: { glat: number, glon: number, error: number }

apex2qd(alat, alon, alt)

Convert Modified Apex to Quasi-Dipole coordinates.

Returns: { qlat: number, qlon: number }

Batch Conversions

geo2qdBatch(glats, glons, alts)

Returns: { qlats: Float64Array, qlons: Float64Array }

geo2apexBatch(glats, glons, alts)

Returns: { qlats: Float64Array, qlons: Float64Array, alats: Float64Array, alons: Float64Array }

qd2geoBatch(qlats, qlons, alts, precision?)

Returns: { glats: Float64Array, glons: Float64Array, errors: Float64Array }

apex2geoBatch(alats, alons, alts, precision?)

Returns: { glats: Float64Array, glons: Float64Array, errors: Float64Array }

All batch methods accept alts as a single number (applied to all points) or number[].

Epoch & Reference Height

| Method | Description | |--------|-------------| | setEpoch(epoch) | Change epoch. Accepts number \| Date \| string. | | getEpoch() | Get current epoch as decimal year. | | getEpochRange() | Get [minEpoch, maxEpoch] from loaded coefficients. | | getAvailableEpochs() | Get all epoch values as number[]. | | setRefHeight(refh) | Set reference height for apex coordinates (km). | | getRefHeight() | Get current reference height (km). |

GeoJSON Transformations

All GeoJSON methods accept any GeoJSON type (Geometry, Feature, FeatureCollection) and return the same type. Optional options.defaultAlt sets altitude for 2D coordinates (default: 0 km).

| Method | Description | |--------|-------------| | geoJsonConvertGeodeticToQuasiDipole(geojson, options?) | Geodetic → QD | | geoJsonConvertGeodeticToApex(geojson, options?) | Geodetic → Apex | | geoJsonConvertQuasiDipoleToGeodetic(geojson, options?) | QD → Geodetic | | geoJsonConvertApexToGeodetic(geojson, options?) | Apex → Geodetic |

TopoJSON Transformations

All TopoJSON methods accept a Topology object and return a new Topology with transformed arcs. Quantized topologies are automatically dequantized.

| Method | Description | |--------|-------------| | topoJsonConvertGeodeticToQuasiDipole(topology, options?) | Geodetic → QD | | topoJsonConvertGeodeticToApex(topology, options?) | Geodetic → Apex | | topoJsonConvertQuasiDipoleToGeodetic(topology, options?) | QD → Geodetic | | topoJsonConvertApexToGeodetic(topology, options?) | Apex → Geodetic |

D3 Helpers

| Method | Input | Description | |--------|-------|-------------| | graticuleConvertGeodeticToQuasiDipole(graticule, options?) | MultiLineString | Geodetic graticule → QD | | graticuleConvertGeodeticToApex(graticule, options?) | MultiLineString | Geodetic graticule → Apex | | graticuleConvertQuasiDipoleToGeodetic(graticule, options?) | MultiLineString | QD graticule → Geodetic | | graticuleConvertApexToGeodetic(graticule, options?) | MultiLineString | Apex graticule → Geodetic | | circleConvertGeodeticToQuasiDipole(circle, options?) | Polygon | Geodetic circle → QD | | circleConvertGeodeticToApex(circle, options?) | Polygon | Geodetic circle → Apex |

Utility Functions

getApexHeight(lat, height?)

Calculate the apex height (maximum altitude of the field line at the magnetic equator) for a given magnetic latitude.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | lat | number | | Apex/QD latitude (°) | | height | number | reference height | Height (km) |

Returns: Apex height in km.

getHeightFromApex(lat, apexHeight)

Inverse of getApexHeight: find the altitude along a field line at a given latitude.

Returns: Height in km.

mlonToMLT(mlon, datetime, ssheight?)

Convert magnetic longitude to Magnetic Local Time.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | mlon | number | | Magnetic longitude (°) | | datetime | Date \| string | | UTC date/time | | ssheight | number | 318550 | Subsolar point mapping altitude (km, ~50 R_E) |

Returns: MLT in hours [0, 24).

mltToMlon(mlt, datetime, ssheight?)

Convert Magnetic Local Time to magnetic longitude.

Returns: Magnetic longitude in degrees [0, 360).

Coordinate Systems

Quasi-Dipole (QD) Coordinates

QD coordinates approximate the actual geomagnetic field using spherical harmonic expansion. They are useful for organizing ionospheric and thermospheric data because they align with the magnetic field geometry.

Modified Apex Coordinates

Modified Apex coordinates extend QD coordinates by projecting along magnetic field lines to a reference height. This is useful for mapping phenomena that occur along field lines, such as field-aligned currents.

Updating Coefficients for New IGRF Versions

When a new IGRF version is released (typically every 5 years):

  1. Wait for the apexpy maintainers to release an updated apexsh.dat file
  2. Install the updated apexpy: pip install --upgrade apexpy
  3. Run the conversion script to create a new coefficient file
  4. Replace your existing coefficient file

The apexsh.dat file contains pre-computed spherical harmonic expansion coefficients that encode the geodetic-to-magnetic coordinate mapping. These coefficients are derived from the IGRF model through a complex field-line tracing process performed by the apexpy maintainers.

References

  1. Richmond, A. D. (1995), Ionospheric Electrodynamics Using Magnetic Apex Coordinates, J. Geomag. Geoelectr., 47, 191-212.

  2. Emmert, J. T., A. D. Richmond, and D. P. Drob (2010), A computationally compact representation of Magnetic-Apex and Quasi-Dipole coordinates with smooth base vectors, J. Geophys. Res., 115, A08322, doi:10.1029/2010JA015326.

  3. Original Python implementation: aburrell/apexpy

  4. IGRF: IAGA Division V-MOD

License

MIT License

Acknowledgments

  • John Emmert, Art Richmond, and D. P. Drob – Original Fortran code and algorithm
  • Angeline Burrell, Christer van der Meeren – Python apexpy implementation