Work in progress, buyer beware.
I'm an advocate for open data and being able to exchange map data between platforms. GPX/KML/GeoJSON are meant to be platform-agnostic interchange formats (or at least that's how I understand them). Cairn is my attempt to make that promise feel real for backcountry mapping: move between OnX and CalTopo while taking all the map customization with you (icons, colors, notes, and organization), not just raw shapes.
This tool started as an experiment and it surfaced a number of challenges. I'm not an expert β if my assumptions are wrong, I want to find out and correct them. The goal is a faithful migration, not "a file that happens to import."
In theory, these formats should make it easy to move between map platforms. In practice, platforms tend to:
- support only a subset of each format
- add non-standard fields or extensions
- rewrite data during import/export (sometimes subtly)
I built Cairn to make migration between systems easier without losing the customization that makes a map valuable (names, notes, colors, icons, and organizational intent) β not just the raw shapes. I have only developed this for OnX Backcountry and CalTopo but there are other platforms out there.
Heard you were heading up my way, here is a GPX file with some choice spots! cool-spots.gpx
That GPX file contains details of an area and lots of information, hiking and backpacking routes, great rock climbing, a cool tower and fishing spots. There are important waypoints that indicate hazards, water sources and approaches. When they constructed this dataset they took the time to assign colors, icons and other metadata beyond the lines, dots and polygons to help you and others make the most of this map.
Or maybe this
Sure it will work but it lost a lot of value while passing through the pipes and there is some garbage thrown in as a nice cherry on top. The data isn't lost, it just didn't make it from the GPX file into the mapping software. That is where Cairn comes in, it takes as much of that data as possible and drops it into your map.
Be sure to swing by and checkout the awesome cool Import track markup after visiting the Very cool tower π€!
# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and install
git clone https://github.com/moltude/cairn.git
cd cairn
uv sync # Or if that fails then 'uv pip install -e .'
# cairn is available
cairn --help
# To run directly
uv run cairncairn migrate onx ~/Downloads/my-caltopo-map.jsonCairn will guide you through:
- Selecting which file to convert
- Previewing waypoints with their icons/colors
- Editing any waypoints before export
- Handling unmapped icons
Output goes to onx_ready/ folder (GPX for waypoints/tracks, KML for polygons).
If you want to get a feel for the workflow before running a large map, use the included small CalTopo-style GeoJSON demo:
# Preview what will happen (no files written)
cairn convert --dry-run demo/demo_small_caltopo.json
# Export OnX-ready files (writes to ./onx_ready_demo/)
cairn migrate onx demo/demo_small_caltopo.json -o ./onx_ready_demoThis repo includes VHS tape scripts that record terminal runs as GIFs.
# Small demo walkthrough (dry run + export)
vhs demo/demo_small_storyboard.tape
# Focused regression test walkthrough
vhs demo/test_run_storyboard.tapecairn migrate caltopo ~/Downloads/my-onx-exports/Cairn will:
- Find your GPX and KML files
- Merge them (preferring polygons)
- Deduplicate shapes automatically
- Generate CalTopo-ready GeoJSON
Output goes to caltopo_ready/ folder.
The real value of Cairn is migrating the colors, icons, names and descriptions from one place to another. Cairn solves this challenge by allowing users to preview and edit all of that data ahead of time.
OnX supports discovery by searching across everything or within a specific content type. However, the only way to filter is by Color and Icon for waypoints.
Color is a key filtering property in OnX's "My Content" feature. When importing waypoints from CalTopo, having colors correctly mapped allows you to:
- Filter large sets of imported waypoints by color and icon
- Quickly find waypoints by combining text + filtering
- Maintain an organizational structure
OnX only allows specific colors and icon terms to be used.
See the tables below for the allowed OnX colors. If the data you want to import provides color information, Cairn will convert it to the closest OnX color. If no color is provided then OnX will use the default blue.
For icons and symbols, OnX accepts a set of ~40 icons but CalTopo exports a much larger set. Even when the icons are visually identical the text labels used may not match and the icon doesn't transfer. When the icon does not match in OnX the default will be used.
Cairn maintains a default mapping of common CalTopo --> OnX icons, and it will warn you when it sees an icon it can't map.
Example warning output:
β οΈ Found 3 unmapped CalTopo symbol(s):
Symbol Count Example Waypoint
climbing-2 3 Main Wall - Lost horse canyon
circle-p 1 Parking- Main Wall and Starlight Lounge
climbing-1 1 Pullout boulders
π‘ Add these to your config (default: cairn_config.yaml) to map them to OnX icons
Run 'cairn config export' to create a template
Run 'cairn config show' to see valid OnX icons already used in your mappingsTo permanently map climbing-1 to the OnX climber icon, add this to your cairn_config.yaml:
symbol_mappings:
climbing-1: ClimbingCairn uses cairn_config.yaml to store custom icon mappings and preferences.
symbol_mappings:
# CalTopo symbol β OnX icon name
climbing-1: Climbing
climbing-2: Climbing
campsite-1: Campground
circle-p: Parking
# Add more mappings as you encounter unmapped symbols# See what's in your current config (shows active mappings)
cairn config show
# Generate a template config file in your current directory
cairn config export
# Validate your config file
cairn config validate cairn_config.yamlThere are 10(ish) official OnX colors. Waypoints support 10 colors and Tracks/Lines support 11, all of the previous 10 plus Fuchsia.
If any of my assumptions are wrong, I want to know β the goal is a faithful migration.
-
OnX export variance: similar "linework" can export as
<trk>vs<rte>. Areas/polygons often only appear as polygons in KML. -
CalTopo's exported "GeoJSON" is CalTopo-flavored: it may include extra properties and 4D coordinate arrays like
[lon, lat, ele, time]. I treat this as normal normalization, not automatically a bug. -
Standards aren't fully standard in practice: GPX/KML/GeoJSON are interchange formats, but platform behavior still matters more than file validity.
-
Ordering is not reliable after import: even if I carefully write GPX/KML in a particular order, OnX may re-order items in folders after import and there isn't a stable user-visible "sort by name" / "sort by import order" workflow that guarantees the same outcome every time.
-
Waypoints and tracks use the same base colors, but tracks have one extra: OnX waypoints support 10 colors, while tracks/lines support 11 colors. The first 10 colors are identical between waypoints and tracks. Tracks have one additional color (Fuchsia) that waypoints don't support.
-
Feature with geometry: null CalTopo exports GeoJSON that matches their internal model by using Features with
geometry: nullto represent folder organization (with other features linked via folderId), and by sometimes emitting 4-value coordinate arrays like [lon, lat, 0, 0] even though RFC 7946 positions are intended to be 2D/3D. Cairn handles these CalTopo-specific patterns by parsing them into a normalized internal document model (folders + waypoints + tracks/shapes) and then exporting standards-based GPX/KML for OnX import, avoiding those GeoJSON interoperability pitfalls.
During this experiment I found cases where OnX exports include many distinct objects (different IDs) with identical names and identical geometry. CalTopo will happily import them all, which can look like "duplicates everywhere".
By default, Cairn produces a "most usable" CalTopo file by:
- preferring polygons (from KML) over track/route representations (from GPX) when they refer to the same OnX object
- deduplicating shapes using a fuzzy geometry match (rotation/direction tolerant, coordinate rounding tolerant)
Nothing is deleted permanently: every dropped duplicate is preserved in the secondary GeoJSON.
uv run --with pytest pytest -qIf you want to watch a full CalTopo β OnX migration run (including intentional bad inputs to exercise error handling, bulk edits, and re-editing a folder) without interacting, run the included replay script:
./scripts/run_chaos_demo.shThis runs cairn migrate onx against demo/bitterroots/ and writes outputs to demo/bitterroots/onx_ready_chaos_watch/ by default.
MIT License - see LICENSE