Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run lint
run: npm run lint

test:
name: Test
runs-on: ubuntu-latest
needs: lint

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

build:
name: Build
runs-on: ubuntu-latest
needs: test

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build
215 changes: 181 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,229 @@
# **gpx-export**
<h1 align="center">gpx-export</h1>

Zero‑dependency GPX 1.1 generator with optional Garmin TrackPointExtension v2 support.
Works in Node.js, browsers, Capacitor, and any JS runtime.
<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>

---

## Install
## **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

```sh
### **Zero Dependencies**
No runtime packages required.

---

## **Installation**

```bash
npm install gpx-export
```

---

## Usage
## **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({
name: 'Morning Ride',
points: [
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: [
{
lat: 54.5741,
lon: -1.3180,
time: new Date(),
elevation: 32.4,
speed: 5.2, // m/s
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,
},
},
],
},
],
},
],
});

console.log(gpx);
```

---

## API
## **API**

### `generateGpx(track: GpxTrack, options?: GpxOptions): string`
### `generateGpx(track: GpxTrack, metadata?: GpxMetadata): string`
### `generateGpx(document: GpxDocument, metadata?: GpxMetadata): string`

Returns a GPX 1.1 XML document as a string.
Generates a GPX 1.1 document from either a `GpxTrack` or `GpxDocument`, with optional metadata that shallow-merges into `document.metadata`.

---

## Types
## **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; // m/s — gpxtpx:speed
elevation?: number; // metres — <ele>
speed?: number; // legacy alias for extensions.speed
elevation?: number; // <ele>
extensions?: GpxPointExtensions;
}

interface GpxTrack {
name: string;
createdAt?: Date; // defaults to first point time
points: GpxPoint[];
}

interface GpxOptions {
creator?: string; // defaults to "gpx-export"
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

---

## Notes
## **Testing**

- Adds Garmin `gpxtpx` namespace only when speed values are present.
- If `createdAt` is omitted, metadata time uses the first point time; if there are no points, it falls back to the current time.
- Elevation is formatted to 2 decimal places; speed is formatted to 4 decimal places.
- Saving/downloading the GPX is left to the caller.
Run tests:

```bash
npm test
```

---

## License
Pull requests are welcome.

## **License**

MIT
MIT
Loading
Loading