Skip to content

GPX metadataType #111

@Raruto

Description

@Raruto

Hi Tom,

over years I come back to ask myself this question: Is it valid to have a properties element in an geoJSON featureCollection?

So first of all here's just a friendly reminder:

RFC 7946 - Extending GeoJSON

6.1. Foreign Members

Members not described in this specification ("foreign members") MAY be used in a GeoJSON document. Note that support for foreign members can vary across implementations, and no normative processing model for foreign members is defined. Accordingly, implementations that rely too heavily on the use of foreign members might experience reduced interoperability with other implementations.

For example, in the (abridged) Feature object shown below

{
  "type": "Feature",
  "id": "f1",
  "geometry": {...},
  "properties": {...},
  "title": "Example Feature"
}

the name/value pair of "title": "Example Feature" is a foreign member. When the value of a foreign member is an object, all the descendant members of that object are themselves foreign members.

GPX 1.1 Schema Documentation - metadataType

<xsd:complexType name="metadataType">
  <xsd:sequence>
    <-- elements must appear in this order -->
    <xsd:element name="name" type="[xsd](https://www.topografix.com/GPX/1/1/#ns_xsd):string" minOccurs="0"/>
    <xsd:element name="desc" type="[xsd](https://www.topografix.com/GPX/1/1/#ns_xsd):string" minOccurs="0"/>
    <xsd:element name="author" type="[personType](https://www.topografix.com/GPX/1/1/#type_personType)" minOccurs="0"/>
    <xsd:element name="copyright" type="[copyrightType](https://www.topografix.com/GPX/1/1/#type_copyrightType)" minOccurs="0"/>
    <xsd:element name="link" type="[linkType](https://www.topografix.com/GPX/1/1/#type_linkType)" minOccurs="0" maxOccurs="unbounded"/>
    <xsd:element name="time" type="[xsd](https://www.topografix.com/GPX/1/1/#ns_xsd):dateTime" minOccurs="0"/>
    <xsd:element name="keywords" type="[xsd](https://www.topografix.com/GPX/1/1/#ns_xsd):string" minOccurs="0"/>
   <xsd:element name="bounds" type="[boundsType](https://www.topografix.com/GPX/1/1/#type_boundsType)" minOccurs="0"/>
   <xsd:element name="extensions" type="[extensionsType](https://www.topografix.com/GPX/1/1/#type_extensionsType)" minOccurs="0"/>
  </xsd:sequence>
</xsd:complexType>

Motivation

Mainly, make it easier to access the root gpx file <name>.

Right now, others can achieve this more or less by doing like so:

// Ref: https://github.com/Raruto/leaflet-elevation/blob/e0c68cba9a71d140e4c5a4179c51ae34588b7327/src/control.js#L965-L973

let xml  = (new DOMParser()).parseFromString(data, "text/xml");
let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
let name = xml.getElementsByTagName('name');
if (xml.getElementsByTagName('parsererror').length) {
  throw 'Invalid XML';
}
if (!(type in toGeoJSON)) {
  type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
}
let geojson  = toGeoJSON[type](xml);
geojson.name = name.length > 0 ? (Array.from(name).find(tag => tag.parentElement.tagName == "trk") ?? name[0]).textContent : '';

Draft implementation

Essentially, augmenting the FeatureCollection interface by providing a new property: metadata

lib/gpx.ts#L181-L197

import { extractMetadata } from "./gpx/metadata";

...

export function gpx(node: Document): FeatureCollection {
  return {
    type: "FeatureCollection",
    features: Array.from(gpxGen(node)),
    metadata: extractMetadata(node),
  };
}

lib/gpx/metadata.ts

Almost the same as: lib/gpx/properties.ts#L1-L36

NB some functions parameters types invoked in here must be double-checked (ref: Element and Document interfaces)

import { $, getMulti, nodeVal } from "../shared";

export function extractMetadata(node: Document) {
  const properties = getMulti(node, [
    "name",
    "desc",
    // "author",
    // "copyright",
    "time",
    "keywords",
    // bounds
  ]);

  const extensions = Array.from(
    node.getElementsByTagNameNS(
      "http://www.garmin.com/xmlschemas/GpxExtensions/v3",
      "*"
    )
  );
  for (const child of extensions) {
    if (child.parentNode?.parentNode === node) {
      properties[child.tagName.replace(":", "_")] = nodeVal(child);
    }
  }

  const links = $(node, "link");
  if (links.length) {
    properties.links = links.map((link) =>
      Object.assign(
        { href: link.getAttribute("href") },
        getMulti(link, ["text", "type"])
      )
    );
  }

  return properties;
}

lib/index.d.ts

I'm not very knowledgeable about typescript, anyway I think it could result in something like this:

// Based on https://stackoverflow.com/questions/42262565/how-to-augment-typescript-interface-in-d-ts

import * as geojson from 'geojson';

declare module 'geojson' {
  namespace GeoJSON {
    export interface FeatureCollection<G extends geojson.Geometry | null = geojson.Geometry, P = geojson.GeoJsonProperties> extends geojson.GeoJsonObject {
      metadata?: {
        name?: string;
        desc?: string;
        author?: {
          name?: string;
          email?: string;
          link?: {
            href: string;
            text?: string;
            type?: string;
          };
        };
        copyright?: {
          author?: string;
          year?: string;
          license?: string;
        };
        time?: string;
        keywords?: string;
        bounds?: [number, number, number, number]
        extensions?: any;
      };
    }
  }
}

I'm really sorry, but at the moment I can't say how much additional re-work might be needed to integrate it in the generation of current dist/index.d.ts file.

Hoping that somehow these notes can be useful

👋 Raruto

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions