Rally Route Builder · Format version 1 · Namespace https://gpx.pub/ns/1

The GPX with every version (master GPX)

Rally Route Builder can export a route as one GPX file that carries every version of it. Garmin, Wahoo, Komoot, Ride with GPS and any other app read it as an ordinary route. Route Builder, Rally and other club apps can find the rest inside it, and offer riders the same choices the route's author made.

This page is for anyone writing software that reads these files.

What's in the file

The file is a valid GPX 1.1 document. It validates against the GPX 1.1 schema, because GPX allows other namespaces inside <extensions>, and readers skip what they don't know.

The namespace is https://gpx.pub/ns/1, and that address shows this page. Files written before October 10, 2026 use https://rosser-routes.web.app/ns/gpx/1; readers should accept both. Rally Route Builder writes the prefix rb, but readers should look up the prefix the file declares for that namespace and use it, since another tool may rename it.

<gpx version="1.1" creator="Rally Route Builder" xmlns="http://www.topografix.com/GPX/1/1"
     xmlns:rb="https://gpx.pub/ns/1">
  <metadata>…</metadata>
  <wpt …/>
  <trk><name>Sat Café A</name><trkseg>…</trkseg></trk>
  <extensions>
    <rb:route version="1" id="k3J8…" name="Sat Café">
      <rb:choices>
        <rb:option id="o-loop" name="Café loop" kind="loop"/>
        <rb:laps id="laps" name="Climb" min="1" max="5" default="3"/>
      </rb:choices>
      <rb:version letter="A" name="Sat Café A" label="Main" options="" laps="laps:3"
                  reversed="false" distance="42118" ascent="612" descent="609" loop="true">
        <rb:line>1…</rb:line>
        <rb:mark index="0" name="Start" kind="start"/>
        <rb:segment name="Cypress climb" start="812" end="1290"/>
      </rb:version>
      <rb:version letter="B" …>…</rb:version>
      <rb:plan format="routebuilder" version="1">{…}</rb:plan>
    </rb:route>
  </extensions>
</gpx>

rb:route

AttributeMeaning
versionThe format version, 1. A reader should refuse versions it doesn't know.
idThe route's id in Route Builder.
nameThe route's name.

rb:choices

These are the choices a rider can make:

An app can show these as toggles and steppers, as Route Builder's Export panel does.

rb:version

There is one rb:version for the main route (letter A) and one for each saved version (B, C, …), in that order. Saved versions that have problems are left out, such as one that bails out on a lap the route no longer reaches.

AttributeMeaning
letterA for the main route, then the saved versions' letters.
nameThe short device name, at most 15 characters, such as Sat Café B.
labelWhat the version is, such as Main or Café loop · Climb ×4.
optionsThe option ids turned on, separated by spaces.
lapsEach lap loop's count, as id:count pairs separated by spaces. Every lap loop is listed.
reversedtrue when the version is ridden backwards.
distance, ascent, descentMetres, rounded.
looptrue when the version finishes within 50 m of its start.

Inside each rb:version:

rb:plan

The whole route as a Route Builder file: the same JSON as a .routebuilder file (src/model/routefile.ts), written as XML text. It has every junction, section, option, lap loop and saved version. Route Builder opens it as the full route when the GPX is imported. An app can compile any other combination from it with compile in src/model/compile.ts, which needs no network.

The packed line

Lines are packed the way the cloud library stores them (encodeLine and decodeLine in src/model/codec.ts):

  1. The first character is the format, 1.
  2. Then, for each point, three numbers follow: the change in latitude × 10⁶, the change in longitude × 10⁶, and the elevation field. Each change is from the previous point, and the first point's change is from zero.
  3. The elevation field is twice the change in elevation in decimetres since the last known elevation. The value 1 means the point has no elevation.
  4. Each number is written as in Google's encoded polyline format: zig-zag to unsigned (n < 0 ? -2n − 1 : 2n), then 5-bit groups from the lowest, each with 32 added if more follow, plus 63, as a character.

That is about 6–8 bytes a point, against about 60 for a <trkpt>.

A decoder, in TypeScript:

export function decodeLine(s: string): [lon: number, lat: number, ele: number][] {
  if (!s) return [];
  if (s[0] !== '1') throw new Error(`Unknown line format ${s[0]}`);
  let at = 1, lat = 0, lon = 0, ele = 0;
  const next = () => {
    let n = 0, mult = 1, c: number;
    do {
      c = s.charCodeAt(at++) - 63;
      n += (c % 32) * mult;
      mult *= 32;
    } while (c >= 32);
    return n % 2 ? -(n + 1) / 2 : n / 2;
  };
  const out: [number, number, number][] = [];
  while (at < s.length) {
    lat += next();
    lon += next();
    const e = next();
    let value = NaN;
    if (e !== 1 && e % 2 === 0) {
      ele += e / 2;
      value = ele / 10;
    }
    out.push([lon / 1e6, lat / 1e6, value]);
  }
  return out;
}

Reading one

readMasterGpx(text) in src/model/master.ts returns the plan, the choices and the decoded versions, or null for any other GPX. It works on the text alone, with no XML parser, so it runs anywhere the model does: in Cloud Functions, React Native and JavaScriptCore. isMasterGpx(text) is a quick check.

To offer the route to riders:

  1. Show the versions by letter and label, with distance and ascent.
  2. Use the chosen version's points, waypoints and segments as they are, or trim a loop's end before sending it to a device.
  3. For a combination nobody saved, build a selection from rb:choices and compile it from the plan.

When another app changes the track

Other apps that edit a GPX and save it again usually drop extensions they don't know, so the file becomes an ordinary GPX. A few keep them. Then the track and the plan disagree, and the track is what the person meant. readMasterGpx sets trackEdited when the track strays more than 15 m from the main version, or its length differs by more than 80 m or 1%. Route Builder then imports the file as an ordinary GPX. Other readers should do the same.

For Rally

The Rally integration (integrations/rally/) doesn't read these files yet. When it does, a club route made from a master GPX can keep the plan, list the versions in the activity screen, and send the version a rider picks to their device. That work belongs in the Rally patch; the Rally repo isn't edited from here.