apexcoords
v1.0.0
Published
Pure TypeScript implementation of ApexPy for converting geodetic coordinates to Quasi-Dipole and Modified Apex magnetic coordinates
Maintainers
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,
Dateobjects, 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 apexcoordsCoefficient 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:
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.
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.binVite / Vue / React:
public/
apex_coeffs_1980_2030.binNext.js:
public/
apex_coeffs_1980_2030.binUsage
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 kmSvelte 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.5subsolarPoint(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 timeApexConverter 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):
- Wait for the apexpy maintainers to release an updated
apexsh.datfile - Install the updated apexpy:
pip install --upgrade apexpy - Run the conversion script to create a new coefficient file
- 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
Richmond, A. D. (1995), Ionospheric Electrodynamics Using Magnetic Apex Coordinates, J. Geomag. Geoelectr., 47, 191-212.
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.
Original Python implementation: aburrell/apexpy
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
