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
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# GPX Route Mapper
# GPX Route Editor

A 100% client-side tool to **draw ferry / sailing routes on OpenStreetMap and export them as GPX** — a [Ferry La](https://www.ferry.hk) × [Stealth Co.](https://stealth-company.co) project for hand-plotting the sea routes that automated routing can't get right.
A 100% client-side editor for **drawing, importing, reshaping, inspecting and exporting GPX tracks on OpenStreetMap**. Adjust route geometry and supported point metadata in the browser without uploading the GPX file. Framing an imported track still fetches OpenFreeMap tiles for that geographic area, so the tile service can infer the route’s approximate region.

Two things set it apart from a plain map drawer:
It is a [Ferry La](https://www.ferry.hk) × [Stealth Co.](https://stealth-company.co) open-source project, but it is not limited to ferry routes. Use it for walking, cycling, driving, sailing or any custom track that you want to shape manually. Optional OpenStreetMap ferry data remains available as one reference source.

- **An "Artistic" basemap** — the same OSM data rendered in ferry.hk's monochrome green + fine-line house style, toggleable against the standard map.
- **A live OSM ferry-route overlay, with bilingual (中文 · English) names** — the real published sailing lines drawn underneath your cursor, so you can *trace* them instead of guessing.
[Open the GPX Route Editor](https://stealth-engine.github.io/gpx-route-editor/)

No API keys, no backend, no build step — the whole app is one `index.html`, deliberately simpler than the big route-planning tools. Open the file and go.
No API keys, no backend and no build step are required. The whole app is one `index.html`, deliberately simpler than a full route-planning service: you control the track geometry and the editor does not invent turn-by-turn directions.

## Features

- **OpenStreetMap basemap** via [MapLibre GL JS](https://maplibre.org/) + [OpenFreeMap](https://openfreemap.org/) vector tiles — no key required.
- **Artistic (ferry.hk) basemap** — monochrome deep-green land/water with hairline roads and coastline, generated as a custom MapLibre style over the same vector data.
- **OSM ferry routes overlay** — real `route=ferry` lines drawn as a cyan dashed reference, **with bilingual labels** (`name:zh-Hant · name:en`, falling back gracefully). Works on *both* basemaps. Toggle on/off.
- **Standard and Artistic basemaps** — switch between the normal map and Ferry La's monochrome deep-green MapLibre style over the same OpenStreetMap vector data.
- **Optional OSM ferry references** — load or overlay real `route=ferry` lines, with bilingual labels where available, when a sailing line is useful to the track you are editing.
- **Edit a route** — click the map to append points, use the `+` handles to insert points between existing ones, and select a point to reveal contextual **Edit** and **Delete** actions directly on the map. Drag the selected point to move it. Undo with **⌘/Ctrl+Z** and redo with **⌘/Ctrl+Y** or **⌘/Ctrl+Shift+Z**.
- **Route shaping** — keep the original line, apply Catmull-Rom smoothing, or snap control points onto nearby OSM roads, paths, tracks, and ferry sailings. Snap follows mapped geometry across vector-tile boundaries; ferry matching also bridges small fragment gaps and can change lines at visible sailing-route intersections.
- **Place search** — Google-style start + optional destination boxes ([Nominatim](https://nominatim.org/) geocoding) that drop start/end markers and frame the view.
Expand All @@ -35,7 +34,8 @@ python3 -m http.server 8000
```

- Or open `index.html` directly in a browser.
- Or enable **GitHub Pages** (Settings → Pages → deploy from `main`) to host it at a public URL.
- Or use the hosted editor at **https://stealth-engine.github.io/gpx-route-editor/**.
- To host your own fork, enable **GitHub Pages** (Settings → Pages → deploy from `main`).

## How to use the editor

Expand Down Expand Up @@ -113,8 +113,8 @@ Use **Map** to collapse the control sheet and **Controls** to reopen it. When a

## Notes

- Ferry **labels appear only where OSM has a name** for the route. Major services (Star Ferry, Sun Ferry to the outlying islands) are named; some minor kaito services aren't and show only the dashed line.
- Everything runs in the browser — the only network calls are to the public OSM tile/geocoding services above.
- The ferry source and overlay are optional map references, not a restriction on the kinds of GPX tracks you can edit. Ferry labels appear only where OpenStreetMap provides a route name.
- Everything runs in the browser — the GPX file and exact track points are never uploaded. Network calls go only to the public OSM tile and geocoding services above; fitting the map to an imported or drawn route requests tiles for the visible viewport, which can reveal the geographic area being edited.

## License

Expand Down
22 changes: 11 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@
<meta name="description" content="Draw, import, edit, smooth and export GPX tracks on OpenStreetMap. Preserve timestamps, elevation and sensor metadata—all locally in your browser.">
<meta name="author" content="Ferry La">
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1">
<link rel="canonical" href="https://stealth-engine.github.io/gpx-route-mapper/">
<link rel="canonical" href="https://stealth-engine.github.io/gpx-route-editor/">

<meta property="og:type" content="website">
<meta property="og:locale" content="en_HK">
<meta property="og:site_name" content="Ferry La">
<meta property="og:title" content="GPX Route Editor — Draw, Edit &amp; Export Routes">
<meta property="og:description" content="Draw, import and edit GPX tracks without losing timestamps, elevation or sensor metadata. Everything stays in your browser.">
<meta property="og:url" content="https://stealth-engine.github.io/gpx-route-mapper/">
<meta property="og:image" content="https://stealth-engine.github.io/gpx-route-mapper/og-image.png">
<meta property="og:image:secure_url" content="https://stealth-engine.github.io/gpx-route-mapper/og-image.png">
<meta property="og:url" content="https://stealth-engine.github.io/gpx-route-editor/">
<meta property="og:image" content="https://stealth-engine.github.io/gpx-route-editor/og-image.png">
<meta property="og:image:secure_url" content="https://stealth-engine.github.io/gpx-route-editor/og-image.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="GPX Route Editor with an editable track and ferry route across a stylised Hong Kong harbour map">
<meta property="og:image:alt" content="GPX Route Editor showing an editable track with point and metadata controls on a stylised map">

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="GPX Route Editor — Draw, Edit &amp; Export Routes">
<meta name="twitter:description" content="Edit GPX tracks while preserving timestamps, elevation and sensor metadata—all locally in your browser.">
<meta name="twitter:image" content="https://stealth-engine.github.io/gpx-route-mapper/og-image.png">
<meta name="twitter:image:alt" content="GPX Route Editor with an editable track and ferry route across a stylised Hong Kong harbour map">
<meta name="twitter:image" content="https://stealth-engine.github.io/gpx-route-editor/og-image.png">
<meta name="twitter:image:alt" content="GPX Route Editor showing an editable track with point and metadata controls on a stylised map">

<meta name="theme-color" content="#2C5748">
<meta name="color-scheme" content="light">
Expand All @@ -45,8 +45,8 @@
"@type": "WebApplication",
"name": "GPX Route Editor",
"alternateName": "GPX Route Editor by Ferry La",
"url": "https://stealth-engine.github.io/gpx-route-mapper/",
"image": "https://stealth-engine.github.io/gpx-route-mapper/og-image.png",
"url": "https://stealth-engine.github.io/gpx-route-editor/",
"image": "https://stealth-engine.github.io/gpx-route-editor/og-image.png",
"description": "Draw, import, edit, smooth and export GPX tracks while preserving timestamps, elevation and sensor metadata locally in your browser.",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Any",
Expand Down Expand Up @@ -315,7 +315,7 @@
<div class="brand">
<div class="logo"><i data-lucide="route"></i></div>
<div><h1>GPX Route Editor</h1><div class="kicker"><a href="https://ferry.hk" target="_blank" rel="noopener">FERRY LA</a> · <a href="https://stealth-company.co" target="_blank" rel="noopener">STEALTH CO.</a></div></div>
<a class="gh" href="https://github.com/stealth-engine/gpx-route-mapper" target="_blank" rel="noopener" title="View source on GitHub · MIT licensed" aria-label="View source on GitHub">
<a class="gh" href="https://github.com/stealth-engine/gpx-route-editor" target="_blank" rel="noopener" title="View source on GitHub · MIT licensed" aria-label="View source on GitHub">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.11.79-.25.79-.56v-2.23c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.69-1.29-1.69-1.05-.72.08-.71.08-.71 1.17.08 1.78 1.2 1.78 1.2 1.04 1.78 2.72 1.27 3.38.97.1-.75.41-1.27.74-1.56-2.57-.29-5.27-1.29-5.27-5.69 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.47.11-3.05 0 0 .97-.31 3.16 1.18a10.9 10.9 0 0 1 5.76 0c2.19-1.49 3.16-1.18 3.16-1.18.63 1.58.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.42-2.71 5.4-5.28 5.69.42.36.78 1.06.78 2.14v3.18c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .7Z"/></svg>
</a>
<button id="mobile-panel-toggle" class="mobile-panel-toggle" type="button" aria-expanded="true" aria-controls="panel-body">Map</button>
Expand Down Expand Up @@ -395,7 +395,7 @@
<div class="statcell"><div class="sl">Distance</div><div class="sv"><span id="st-len">0.00</span> <small>km</small></div></div>
<div class="statcell"><div class="sl">Track points</div><div class="sv" id="st-pts">0</div></div>
</div>
<a class="help-link" href="https://github.com/stealth-engine/gpx-route-mapper#how-to-use-the-editor" target="_blank" rel="noopener"><i data-lucide="circle-help"></i> Help &amp; instructions</a>
<a class="help-link" href="https://github.com/stealth-engine/gpx-route-editor#how-to-use-the-editor" target="_blank" rel="noopener"><i data-lucide="circle-help"></i> Help &amp; instructions</a>
<button id="export" class="primary wide" style="margin-top:12px"><i data-lucide="download"></i> Export GPX</button>
<p class="footnote">Processed locally in your browser</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion robots.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://stealth-engine.github.io/gpx-route-mapper/sitemap.xml
Sitemap: https://stealth-engine.github.io/gpx-route-editor/sitemap.xml
2 changes: 1 addition & 1 deletion sitemap.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://stealth-engine.github.io/gpx-route-mapper/</loc>
<loc>https://stealth-engine.github.io/gpx-route-editor/</loc>
<lastmod>2026-07-13</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
Expand Down
6 changes: 3 additions & 3 deletions tests/route-editor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -649,19 +649,19 @@ test('Page, panel, and README use the Ferry La and Stealth Co. branding', () =>
assert.doesNotMatch(html, /<div class="logo"><i data-lucide="anchor"><\/i><\/div>/);
assert.match(html, /<a href="https:\/\/ferry\.hk" target="_blank" rel="noopener">FERRY LA<\/a> · <a href="https:\/\/stealth-company\.co" target="_blank" rel="noopener">STEALTH CO\.<\/a>/);
assert.match(readme, /\[Ferry La\]\(https:\/\/www\.ferry\.hk\) × \[Stealth Co\.\]\(https:\/\/stealth-company\.co\)/);
assert.match(html, /<a class="gh" href="https:\/\/github\.com\/stealth-engine\/gpx-route-mapper"[^>]+title="View source on GitHub · MIT licensed" aria-label="View source on GitHub"[^>]*>\s*<svg/);
assert.match(html, /<a class="gh" href="https:\/\/github\.com\/stealth-engine\/gpx-route-editor"[^>]+title="View source on GitHub · MIT licensed" aria-label="View source on GitHub"[^>]*>\s*<svg/);
assert.doesNotMatch(html, /<a class="gh"[^>]*>[\s\S]*?data-lucide="github"/);
});

test('The app links to a task-based usage guide in the README', () => {
assert.match(html, /<a class="help-link" href="https:\/\/github\.com\/stealth-engine\/gpx-route-mapper#how-to-use-the-editor"[^>]*>[\s\S]*?Help &amp; instructions<\/a>/);
assert.match(html, /<a class="help-link" href="https:\/\/github\.com\/stealth-engine\/gpx-route-editor#how-to-use-the-editor"[^>]*>[\s\S]*?Help &amp; instructions<\/a>/);
assert.match(readme, /## How to use the editor/);
assert.match(readme, /### 1\. Choose a route source[\s\S]*?### 3\. Draw or adjust the route[\s\S]*?### 6\. Export the route/);
assert.match(readme, /### Mobile controls[\s\S]*?Edit[\s\S]*?Delete/);
});

test('Page publishes canonical, social-preview, and structured SEO metadata', () => {
const canonicalUrl = 'https://stealth-engine.github.io/gpx-route-mapper/';
const canonicalUrl = 'https://stealth-engine.github.io/gpx-route-editor/';
const imageUrl = `${canonicalUrl}og-image.png`;
assert.ok(html.includes(`<link rel="canonical" href="${canonicalUrl}">`));
assert.ok(html.includes(`<meta property="og:url" content="${canonicalUrl}">`));
Expand Down