<h1 align="center">gpx-export</h1>

<p align="center">
  <a href="https://www.npmjs.com/package/gpx-export">
    <img src="https://img.shields.io/npm/v/gpx-export" alt="npm version" />
  </a>
  <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="dependencies" />
  <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="types" />
  <img src="https://github.com/cmyers/gpx-export/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
</p>

<h3 align="center">
  A lightweight, zero-dependency GPX 1.1 generator for Node.js, browsers, Capacitor, and other JavaScript runtimes.
</h3>

---

## **What gpx-export is for**

gpx-export converts GPS data into valid GPX 1.1 XML.

It supports:
- `generateGpx(track, metadata?)` for tracks-only input
- `generateGpx(document, metadata?)` for full document input
- waypoints, routes, tracks, and metadata
- Garmin TrackPointExtension v2 (`gpxtpx`) for speed, heart rate, and cadence

---

## **Why gpx-export**

Many apps collect location points but still need a clean, portable output format for exports, backups, and interoperability with mapping tools.

gpx-export focuses on that narrow problem:
- simple TypeScript-first API
- deterministic XML output order for identical inputs
- no external dependencies
- compatible across Node.js and browser-like runtimes

---

## **Features**

### **GPX 1.1 Output**
Generates valid GPX 1.1 XML with metadata, routes, tracks, and waypoints.

### **Garmin TrackPointExtension Support**
Supports `gpxtpx` values on track points (`trkpt`) for:
- speed
- heart rate
- cadence

### **Zero Dependencies**
No runtime packages required.

---

## **Installation**

```bash
npm install gpx-export
```

---

## **Quick Start**

```ts
import { generateGpx } from 'gpx-export';

const now = new Date();

const gpx = generateGpx(
  {
    name: 'Morning Ride',
    points: [
      {
        lat: 54.5741,
        lon: -1.318,
        time: now,
        elevation: 32.4,
        speed: 5.2,
      },
    ],
  },
  {
    time: now,
  },
);

console.log(gpx);
```

---

## **Full Document Example**

```ts
import { generateGpx } from 'gpx-export';

const now = new Date('2026-01-01T12:00:00.000Z');

const gpx = generateGpx({
  metadata: {
    name: 'My Export',
    desc: 'Generated by gpx-export',
    author: { name: 'Chris' },
    link: { href: 'https://github.com/cmyers/gpx-export' },
    time: now,
    keywords: 'gps,gpx,export',
  },
  waypoints: [
    {
      lat: 54.5,
      lon: -1.3,
      name: 'Start',
      time: now,
    },
  ],
  routes: [
    {
      name: 'Route 1',
      points: [
        { lat: 54.5, lon: -1.3, time: now },
        { lat: 54.6, lon: -1.2, time: now },
      ],
    },
  ],
  tracks: [
    {
      name: 'Track 1',
      segments: [
        {
          points: [
            {
              lat: 54.5,
              lon: -1.3,
              time: now,
              elevation: 20.12,
              extensions: {
                speed: 3.45,
                heartRate: 152,
                cadence: 86,
              },
            },
          ],
        },
      ],
    },
  ],
});
```

---

## **API**

### `generateGpx(track: GpxTrack, metadata?: GpxMetadata): string`
### `generateGpx(document: GpxDocument, metadata?: GpxMetadata): string`

Generates a GPX 1.1 document from either a `GpxTrack` or `GpxDocument`, with optional metadata that shallow-merges into `document.metadata`.

---

## **Supported Types (Summary)**

```ts
interface GpxPointExtensions {
  speed?: number; // gpxtpx:speed (m/s)
  heartRate?: number; // gpxtpx:hr (bpm)
  cadence?: number; // gpxtpx:cad (rpm)
  rawXml?: string; // trusted XML inside <extensions>
}

interface GpxPoint {
  lat: number;
  lon: number;
  time: Date;
  speed?: number; // legacy alias for extensions.speed
  elevation?: number; // <ele>
  extensions?: GpxPointExtensions;
}

interface GpxTrack {
  name: string;
  createdAt?: Date;
  points?: GpxPoint[];
  segments?: { points: GpxPoint[] }[];
  cmt?: string;
  desc?: string;
  extensions?: GpxPointExtensions;
}
```

Why speed appears in two places:
- `GpxPoint.speed` exists for backward compatibility with older callers.
- `GpxPoint.extensions.speed` is the canonical field for new code.
- When both are provided, `extensions.speed` wins and only one GPX speed tag is emitted.

All exported type definitions are available from the package root.

---

## **Notes**

- elevation is formatted to 2 decimal places
- speed is formatted to 4 decimal places
- the Garmin `gpxtpx` namespace is included when Garmin metrics are present on track points
- Garmin `gpxtpx` metric tags are emitted on track points (`trkpt`) only
- `rawXml` values are inserted as trusted XML and are not escaped
- element output order is deterministic for identical inputs

---

## **Testing**

Run tests:

```bash
npm test
```

---

Pull requests are welcome.

## **License**

MIT
