Task 266: .GPX File Format

Task 266: .GPX File Format

1. List of All Properties Intrinsic to the .GPX File Format

The .GPX (GPS Exchange Format) is an XML-based file format defined by the GPX 1.1 schema. It uses the namespace http://www.topografix.com/GPX/1/1 and follows WGS84 datum for coordinates. The format is lightweight, extensible via <extensions>, and supports lax validation for non-GPX namespace elements. All coordinates are in decimal degrees. Below is a comprehensive hierarchical list of all intrinsic properties (elements, attributes, data types, cardinalities, and constraints) derived from the official GPX 1.1 schema.

Root Element: gpx

  • Attributes:
  • version: string (fixed value: "1.1", required)
  • creator: string (required, identifies the software/device that created the file)
  • Child Elements (sequence, in any order but typically metadata first):
  • metadata (0..1, optional): Contains file-level metadata.
  • name (0..1): string (file name or title)
  • desc (0..1): string (description)
  • author (0..1): person or organization.
  • name (0..1): string
  • email (0..1): emailType (format: id@domain)
  • link (0..1): link to author's site.
  • href: anyURI (required)
  • text (0..1): string
  • type (0..1): string (MIME type)
  • copyright (0..1): copyright info.
  • year (0..1): gYear (e.g., 2025)
  • author: string (required)
  • license (0..1): anyURI
  • link (0..1): Same structure as above (general file link)
  • time (0..1): dateTime (ISO 8601, e.g., 2025-09-25T12:00:00Z)
  • keywords (0..1): string (comma-separated)
  • bounds (0..1): bounding box.
  • minlat: decimal (-90.0 to 90.0, required)
  • minlon: decimal (-180.0 to 180.0, required)
  • maxlat: decimal (-90.0 to 90.0, required)
  • maxlon: decimal (-180.0 to 180.0, required)
  • extensions (0..1): extensionsType (any XML from other namespaces, lax validation)
  • wpt (0..unbounded): Waypoint (single GPS point).
  • Attributes:
  • lat: decimal (-90.0 to 90.0, required)
  • lon: decimal (-180.0 to 180.0, required)
  • Child Elements (all 0..1 unless noted):
  • ele: decimal (elevation in meters)
  • time: dateTime
  • magvar: degreesType (-180.0 to 180.0, magnetic variation)
  • geoidheight: decimal (height of geoid above WGS84 ellipsoid)
  • name: string
  • cmt: string (comment)
  • desc: string (description)
  • src: string (source of data)
  • link: linkType (as above)
  • sym: string (symbol name)
  • type: string (waypoint category)
  • fix: fixType (enum: none | 2d | 3d | dgps | pps)
  • sat: nonNegativeInteger (number of satellites)
  • hdop: decimal (horizontal dilution of precision)
  • vdop: decimal (vertical DOP)
  • pdop: decimal (position DOP)
  • ageofdgpsdata: decimal (seconds since last DGPS update)
  • dgpsid: integer (0 to 1023, DGPS station ID)
  • extensions (0..1)
  • rte (0..unbounded): Route (ordered list of waypoints).
  • Child Elements:
  • name (0..1): string
  • cmt (0..1): string
  • desc (0..1): string
  • src (0..1): string
  • link (0..1): linkType
  • number (0..1): nonNegativeInteger (route number)
  • type (0..1): string
  • extensions (0..1)
  • rtept (1..unbounded): wptType (same as wpt, lat/lon required)
  • trk (0..unbounded): Track (GPS track log, time-ordered).
  • Child Elements:
  • name (0..1): string
  • cmt (0..1): string
  • desc (0..1): string
  • src (0..1): string
  • link (0..1): linkType
  • number (0..1): nonNegativeInteger
  • type (0..1): string
  • extensions (0..1)
  • trkseg (0..unbounded): Track segment.
  • trkpt (0..unbounded): wptType (same as wpt)
  • extensions (0..1): File-level extensions (any XML, lax validation)

General Constraints

  • Data Types:
  • Coordinates (lat/lon): decimal, precise to 5+ decimal places.
  • Degrees (magvar): decimal, -180 to 180 or 0 to 360.
  • Time: UTC dateTime, optional fractional seconds.
  • Elevation/Heights: decimal meters.
  • Enumerations: fixType as listed; others are free-form strings unless specified.
  • File Structure: XML declaration optional; must be well-formed XML. No strict order within sequences except for logical grouping (e.g., metadata first).
  • Extensibility: <extensions> allows vendor-specific elements (e.g., Garmin extensions).
  • Validation: Conforms to GPX 1.1 XSD; coordinates relative to WGS84.

This list covers all defined properties; extensions may add more but are not intrinsic.

3. Ghost Blog Embedded HTML JavaScript

Embed this full HTML snippet in a Ghost blog post (via HTML card). It creates a drag-and-drop zone for .GPX files, parses the XML, and dumps all intrinsic properties (elements/attributes/values) to a <pre> block as structured JSON for readability.

Drag and drop a .GPX file here


4. Python Class

This class uses xml.etree.ElementTree (standard library) to load a .GPX file, recursively extract and print all properties to console (as indented key-value pairs), and write the unmodified content back to a new file (output.gpx).

import xml.etree.ElementTree as ET

class GPXParser:
    def __init__(self, file_path):
        self.tree = ET.parse(file_path)
        self.root = self.tree.getroot()

    def print_properties(self):
        self._print_recursive(self.root, '')

    def _print_recursive(self, node, indent):
        tag = node.tag.split('}')[-1]  # Strip namespace
        attrs = dict(node.attrib)
        text = node.text.strip() if node.text else None

        print(f"{indent}{tag}:")
        if attrs:
            print(f"{indent}  Attributes: {attrs}")
        if text:
            print(f"{indent}  Text: {text}")

        for child in node:
            self._print_recursive(child, indent + '  ')

    def write(self, output_path='output.gpx'):
        self.tree.write(output_path, encoding='utf-8', xml_declaration=True)

# Usage example:
# parser = GPXParser('sample.gpx')
# parser.print_properties()
# parser.write()

5. Java Class

This class uses javax.xml.parsers and org.w3c.dom (standard JDK) to load a .GPX file, recursively extract and print all properties to console (as indented structure), and write the unmodified content back to output.gpx.

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
import java.io.File;

public class GPXParser {
    private Document doc;

    public GPXParser(String filePath) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        this.doc = builder.parse(new File(filePath));
    }

    public void printProperties() {
        printRecursive(doc.getDocumentElement(), "");
    }

    private void printRecursive(Node node, String indent) {
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            String tagName = node.getNodeName().split(":")[1]; // Strip namespace if present
            NamedNodeMap attrs = node.getAttributes();
            System.out.println(indent + tagName + ":");
            if (attrs != null && attrs.getLength() > 0) {
                System.out.print(indent + "  Attributes: ");
                for (int i = 0; i < attrs.getLength(); i++) {
                    Node attr = attrs.item(i);
                    System.out.print(attr.getNodeName() + "=" + attr.getNodeValue() + " ");
                }
                System.out.println();
            }
            String text = node.getTextContent().trim();
            if (!text.isEmpty()) {
                System.out.println(indent + "  Text: " + text);
            }
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                printRecursive(children.item(i), indent + "  ");
            }
        }
    }

    public void write(String outputPath) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(new File(outputPath));
        transformer.transform(source, result);
    }

    // Usage: GPXParser parser = new GPXParser("sample.gpx"); parser.printProperties(); parser.write("output.gpx");
}

6. JavaScript Class

This class (for browser/Node.js with fs for Node) loads a .GPX file (via File or path), parses with DOMParser/XMLHttpRequest, recursively extracts and prints all properties to console (as indented object), and writes the unmodified content back to output.gpx (using Blob for browser or fs for Node).

class GPXParser {
  constructor(filePathOrFile) {
    this.doc = null;
    if (typeof filePathOrFile === 'string') {
      // Node.js: assume fs is available
      const fs = require('fs');
      const xmlString = fs.readFileSync(filePathOrFile, 'utf8');
      const parser = new DOMParser();
      this.doc = parser.parseFromString(xmlString, 'text/xml');
    } else {
      // Browser: assume File object
      const reader = new FileReader();
      reader.onload = (e) => {
        const parser = new DOMParser();
        this.doc = parser.parseFromString(e.target.result, 'text/xml');
        this.printProperties();
      };
      reader.readAsText(filePathOrFile);
      return; // Async, print called in onload
    }
  }

  printProperties() {
    if (!this.doc) return;
    this._printRecursive(this.doc.documentElement, '');
  }

  _printRecursive(node, indent) {
    if (node.nodeType === Node.ELEMENT_NODE) {
      const tagName = node.tagName.split(':').pop(); // Strip namespace
      console.log(`${indent}${tagName}:`);
      const attrs = {};
      for (let attr of node.attributes) {
        attrs[attr.name] = attr.value;
      }
      if (Object.keys(attrs).length > 0) {
        console.log(`${indent}  Attributes:`, attrs);
      }
      const text = node.textContent.trim();
      if (text) {
        console.log(`${indent}  Text: ${text}`);
      }
      for (let child of node.children) {
        this._printRecursive(child, indent + '  ');
      }
    }
  }

  write(outputPath = 'output.gpx') {
    if (!this.doc) return;
    const serializer = new XMLSerializer();
    const xmlString = '<?xml version="1.0" encoding="UTF-8"?>' + serializer.serializeToString(this.doc);
    // Browser: download
    const blob = new Blob([xmlString], { type: 'application/gpx+xml' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputPath;
    a.click();
    // Node.js: fs.writeFileSync(outputPath, xmlString);
  }
}

// Usage (browser): new GPXParser(fileInput.files[0]); // prints in onload
// Usage (Node): new GPXParser('sample.gpx'); parser.write();

7. C "Class" (Struct with Functions)

C lacks classes, so this uses a struct with functions (assuming libxml2 is linked: gcc -o gpx_parser gpx_parser.c -lxml2). It loads a .GPX file, recursively prints all properties to stdout (indented), and writes unmodified content to output.gpx.

#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

typedef struct {
    xmlDocPtr doc;
} GPXParser;

void gpx_parser_init(GPXParser *parser, const char *file_path) {
    parser->doc = xmlReadFile(file_path, NULL, 0);
    if (parser->doc == NULL) {
        fprintf(stderr, "Error parsing file\n");
    }
}

void print_properties(GPXParser *parser) {
    if (!parser->doc) return;
    xmlNodePtr root = xmlDocGetRootElement(parser->doc);
    print_recursive(root, 0);
}

void print_recursive(xmlNodePtr node, int indent) {
    if (node == NULL || node->type != XML_ELEMENT_NODE) return;

    // Print indent
    for (int i = 0; i < indent; i++) printf("  ");
    printf("%s:\n", node->name);

    // Attributes
    xmlAttrPtr attr = node->properties;
    if (attr != NULL) {
        for (int i = 0; i < indent + 1; i++) printf("  ");
        printf("Attributes: ");
        while (attr != NULL) {
            printf("%s=%s ", attr->name, xmlNodeListGetString(parser->doc, attr->children, 1));
            attr = attr->next;
        }
        printf("\n");
    }

    // Text
    xmlChar *text = xmlNodeListGetString(parser->doc, node->children, 1);
    if (text && xmlStrlen(text) > 0) {
        for (int i = 0; i < indent + 1; i++) printf("  ");
        printf("Text: %s\n", text);
        xmlFree(text);
    }

    // Children
    for (xmlNodePtr child = node->children; child; child = child->next) {
        if (child->type == XML_ELEMENT_NODE) {
            print_recursive(child, indent + 1);
        }
    }
}

void gpx_parser_write(GPXParser *parser, const char *output_path) {
    if (!parser->doc) return;
    xmlSaveFormatFileEnc(output_path, parser->doc, "UTF-8", 1);
}

void gpx_parser_free(GPXParser *parser) {
    xmlFreeDoc(parser->doc);
    xmlCleanupParser();
}

// Usage:
// GPXParser parser = {0};
// gpx_parser_init(&parser, "sample.gpx");
// print_properties(&parser);
// gpx_parser_write(&parser, "output.gpx");
// gpx_parser_free(&parser);