package.format.GPX.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/format/GPX
*/
import Feature from '../Feature.js';
import LineString from '../geom/LineString.js';
import MultiLineString from '../geom/MultiLineString.js';
import Point from '../geom/Point.js';
import XMLFeature from './XMLFeature.js';
import {
OBJECT_PROPERTY_NODE_FACTORY,
XML_SCHEMA_INSTANCE_URI,
createElementNS,
isDocument,
makeArrayPusher,
makeArraySerializer,
makeChildAppender,
makeObjectPropertySetter,
makeSequence,
makeSimpleNodeFactory,
makeStructureNS,
parse,
parseNode,
pushParseAndPop,
pushSerializeAndPop,
} from '../xml.js';
import {get as getProjection} from '../proj.js';
import {
readDateTime,
readDecimal,
readPositiveInteger,
readString,
writeDateTimeTextNode,
writeDecimalTextNode,
writeNonNegativeIntegerTextNode,
writeStringTextNode,
} from './xsd.js';
import {transformGeometryWithOptions} from './Feature.js';
/**
* @const
* @type {Array}
*/
const NAMESPACE_URIS = [
null,
'http://www.topografix.com/GPX/1/0',
'http://www.topografix.com/GPX/1/1',
];
/**
* @const
* @type {string}
*/
const SCHEMA_LOCATION =
'http://www.topografix.com/GPX/1/1 ' +
'http://www.topografix.com/GPX/1/1/gpx.xsd';
/**
* @const
* @type {Object): (Feature|undefined)>}
*/
const FEATURE_READER = {
'rte': readRte,
'trk': readTrk,
'wpt': readWpt,
};
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const GPX_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'rte': makeArrayPusher(readRte),
'trk': makeArrayPusher(readTrk),
'wpt': makeArrayPusher(readWpt),
});
/**
* @typedef {Object} GPXLink
* @property {string} [text] text
* @property {string} [type] type
*/
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const LINK_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'text': makeObjectPropertySetter(readString, 'linkText'),
'type': makeObjectPropertySetter(readString, 'linkType'),
});
/**
* @typedef {Object} GPXAuthor
* @property {string} [name] name
* @property {string} [email] email
* @property {GPXLink} [link] link
*/
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const AUTHOR_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeObjectPropertySetter(readString),
'email': parseEmail,
'link': parseLink,
});
/**
* @typedef {Object} GPXMetadata
* @property {string} [name] name
* @property {string} [desc] desc
* @property {GPXAuthor} [author] author
* @property {GPXLink} [link] link
* @property {number} [time] time
* @property {string} [keywords] keywords
* @property {Array} [bounds] bounds
* @property {Object} [extensions] extensions
*
*/
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const METADATA_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeObjectPropertySetter(readString),
'desc': makeObjectPropertySetter(readString),
'author': makeObjectPropertySetter(readAuthor),
'copyright': makeObjectPropertySetter(readCopyright),
'link': parseLink,
'time': makeObjectPropertySetter(readDateTime),
'keywords': makeObjectPropertySetter(readString),
'bounds': parseBounds,
'extensions': parseExtensions,
});
/**
* @typedef {Object} GPXCopyright
* @property {string} [author] author
* @property {number} [year] year
* @property {string} [license] license
*/
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const COPYRIGHT_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'year': makeObjectPropertySetter(readPositiveInteger),
'license': makeObjectPropertySetter(readString),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const GPX_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'rte': makeChildAppender(writeRte),
'trk': makeChildAppender(writeTrk),
'wpt': makeChildAppender(writeWpt),
});
/**
* @typedef {Object} Options
* @property {function(Feature, Node):void} [readExtensions] Callback function
* to process `extensions` nodes. To prevent memory leaks, this callback function must
* not store any references to the node. Note that the `extensions`
* node is not allowed in GPX 1.0. Moreover, only `extensions`
* nodes from `wpt`, `rte` and `trk` can be processed, as those are
* directly mapped to a feature.
*/
/**
* @typedef {Object} LayoutOptions
* @property {boolean} [hasZ] HasZ.
* @property {boolean} [hasM] HasM.
*/
/**
* @classdesc
* Feature format for reading and writing data in the GPX format.
*
* Note that {@link module:ol/format/GPX~GPX#readFeature} only reads the first
* feature of the source.
*
* When reading, routes (``) are converted into LineString geometries, and
* tracks (``) into MultiLineString. Any properties on route and track
* waypoints are ignored.
*
* When writing, LineString geometries are output as routes (``), and
* MultiLineString as tracks (``).
*
* @api
*/
class GPX extends XMLFeature {
/**
* @param {Options} [options] Options.
*/
constructor(options) {
super();
options = options ? options : {};
/**
* @type {import("../proj/Projection.js").default}
*/
this.dataProjection = getProjection('EPSG:4326');
/**
* @type {function(Feature, Node): void|undefined}
* @private
*/
this.readExtensions_ = options.readExtensions;
}
/**
* @param {Array} features List of features.
* @private
*/
handleReadExtensions_(features) {
if (!features) {
features = [];
}
for (let i = 0, ii = features.length; i < ii; ++i) {
const feature = features[i];
if (this.readExtensions_) {
const extensionsNode = feature.get('extensionsNode_') || null;
this.readExtensions_(feature, extensionsNode);
}
feature.set('extensionsNode_', undefined);
}
}
/**
* Reads a GPX file's metadata tag, reading among other things:
* - the name and description of this GPX
* - its author
* - the copyright associated with this GPX file
*
* Will return null if no metadata tag is present (or no valid source is given).
*
* @param {Document|Element|Object|string} source Source.
* @return {GPXMetadata | null} Metadata
* @api
*/
readMetadata(source) {
if (!source) {
return null;
}
if (typeof source === 'string') {
return this.readMetadataFromDocument(parse(source));
}
if (isDocument(source)) {
return this.readMetadataFromDocument(/** @type {Document} */ (source));
}
return this.readMetadataFromNode(source);
}
/**
* @param {Document} doc Document.
* @return {GPXMetadata | null} Metadata
*/
readMetadataFromDocument(doc) {
for (let n = /** @type {Node} */ (doc.firstChild); n; n = n.nextSibling) {
if (n.nodeType === Node.ELEMENT_NODE) {
const metadata = this.readMetadataFromNode(/** @type {Element} */ (n));
if (metadata) {
return metadata;
}
}
}
return null;
}
/**
* @param {Element} node Node.
* @return {Object} Metadata
*/
readMetadataFromNode(node) {
if (!NAMESPACE_URIS.includes(node.namespaceURI)) {
return null;
}
for (let n = node.firstElementChild; n; n = n.nextElementSibling) {
if (
NAMESPACE_URIS.includes(n.namespaceURI) &&
n.localName === 'metadata'
) {
return pushParseAndPop({}, METADATA_PARSERS, n, []);
}
}
return null;
}
/**
* @param {Element} node Node.
* @param {import("./Feature.js").ReadOptions} [options] Options.
* @return {import("../Feature.js").default} Feature.
* @override
*/
readFeatureFromNode(node, options) {
if (!NAMESPACE_URIS.includes(node.namespaceURI)) {
return null;
}
const featureReader = FEATURE_READER[node.localName];
if (!featureReader) {
return null;
}
const feature = featureReader(node, [this.getReadOptions(node, options)]);
if (!feature) {
return null;
}
this.handleReadExtensions_([feature]);
return feature;
}
/**
* @param {Element} node Node.
* @param {import("./Feature.js").ReadOptions} [options] Options.
* @return {Array} Features.
* @override
*/
readFeaturesFromNode(node, options) {
if (!NAMESPACE_URIS.includes(node.namespaceURI)) {
return [];
}
if (node.localName == 'gpx') {
/** @type {Array} */
const features = pushParseAndPop([], GPX_PARSERS, node, [
this.getReadOptions(node, options),
]);
if (features) {
this.handleReadExtensions_(features);
return features;
}
return [];
}
return [];
}
/**
* Encode an array of features in the GPX format as an XML node.
* LineString geometries are output as routes (``), and MultiLineString
* as tracks (``).
*
* @param {Array} features Features.
* @param {import("./Feature.js").WriteOptions} [options] Options.
* @return {Node} Node.
* @api
* @override
*/
writeFeaturesNode(features, options) {
options = this.adaptOptions(options);
//FIXME Serialize metadata
const gpx = createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
const xmlnsUri = 'http://www.w3.org/2000/xmlns/';
gpx.setAttributeNS(xmlnsUri, 'xmlns:xsi', XML_SCHEMA_INSTANCE_URI);
gpx.setAttributeNS(
XML_SCHEMA_INSTANCE_URI,
'xsi:schemaLocation',
SCHEMA_LOCATION,
);
gpx.setAttribute('version', '1.1');
gpx.setAttribute('creator', 'OpenLayers');
pushSerializeAndPop(
/** @type {import("../xml.js").NodeStackItem} */
({node: gpx}),
GPX_SERIALIZERS,
GPX_NODE_FACTORY,
features,
[options],
);
return gpx;
}
}
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const RTE_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeObjectPropertySetter(readString),
'cmt': makeObjectPropertySetter(readString),
'desc': makeObjectPropertySetter(readString),
'src': makeObjectPropertySetter(readString),
'link': parseLink,
'number': makeObjectPropertySetter(readPositiveInteger),
'extensions': parseExtensions,
'type': makeObjectPropertySetter(readString),
'rtept': parseRtePt,
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const RTEPT_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'ele': makeObjectPropertySetter(readDecimal),
'time': makeObjectPropertySetter(readDateTime),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRK_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeObjectPropertySetter(readString),
'cmt': makeObjectPropertySetter(readString),
'desc': makeObjectPropertySetter(readString),
'src': makeObjectPropertySetter(readString),
'link': parseLink,
'number': makeObjectPropertySetter(readPositiveInteger),
'type': makeObjectPropertySetter(readString),
'extensions': parseExtensions,
'trkseg': parseTrkSeg,
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRKSEG_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'trkpt': parseTrkPt,
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRKPT_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'ele': makeObjectPropertySetter(readDecimal),
'time': makeObjectPropertySetter(readDateTime),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const WPT_PARSERS = makeStructureNS(NAMESPACE_URIS, {
'ele': makeObjectPropertySetter(readDecimal),
'time': makeObjectPropertySetter(readDateTime),
'magvar': makeObjectPropertySetter(readDecimal),
'geoidheight': makeObjectPropertySetter(readDecimal),
'name': makeObjectPropertySetter(readString),
'cmt': makeObjectPropertySetter(readString),
'desc': makeObjectPropertySetter(readString),
'src': makeObjectPropertySetter(readString),
'link': parseLink,
'sym': makeObjectPropertySetter(readString),
'type': makeObjectPropertySetter(readString),
'fix': makeObjectPropertySetter(readString),
'sat': makeObjectPropertySetter(readPositiveInteger),
'hdop': makeObjectPropertySetter(readDecimal),
'vdop': makeObjectPropertySetter(readDecimal),
'pdop': makeObjectPropertySetter(readDecimal),
'ageofdgpsdata': makeObjectPropertySetter(readDecimal),
'dgpsid': makeObjectPropertySetter(readPositiveInteger),
'extensions': parseExtensions,
});
/**
* @const
* @type {Array}
*/
const LINK_SEQUENCE = ['text', 'type'];
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const LINK_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'text': makeChildAppender(writeStringTextNode),
'type': makeChildAppender(writeStringTextNode),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const RTE_SEQUENCE = makeStructureNS(NAMESPACE_URIS, [
'name',
'cmt',
'desc',
'src',
'link',
'number',
'type',
'rtept',
]);
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const RTE_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeChildAppender(writeStringTextNode),
'cmt': makeChildAppender(writeStringTextNode),
'desc': makeChildAppender(writeStringTextNode),
'src': makeChildAppender(writeStringTextNode),
'link': makeChildAppender(writeLink),
'number': makeChildAppender(writeNonNegativeIntegerTextNode),
'type': makeChildAppender(writeStringTextNode),
'rtept': makeArraySerializer(makeChildAppender(writeWptType)),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const RTEPT_TYPE_SEQUENCE = makeStructureNS(NAMESPACE_URIS, ['ele', 'time']);
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRK_SEQUENCE = makeStructureNS(NAMESPACE_URIS, [
'name',
'cmt',
'desc',
'src',
'link',
'number',
'type',
'trkseg',
]);
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRK_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'name': makeChildAppender(writeStringTextNode),
'cmt': makeChildAppender(writeStringTextNode),
'desc': makeChildAppender(writeStringTextNode),
'src': makeChildAppender(writeStringTextNode),
'link': makeChildAppender(writeLink),
'number': makeChildAppender(writeNonNegativeIntegerTextNode),
'type': makeChildAppender(writeStringTextNode),
'trkseg': makeArraySerializer(makeChildAppender(writeTrkSeg)),
});
/**
* @const
* @type {function(*, Array<*>, string=): (Node|undefined)}
*/
const TRKSEG_NODE_FACTORY = makeSimpleNodeFactory('trkpt');
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const TRKSEG_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'trkpt': makeChildAppender(writeWptType),
});
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const WPT_TYPE_SEQUENCE = makeStructureNS(NAMESPACE_URIS, [
'ele',
'time',
'magvar',
'geoidheight',
'name',
'cmt',
'desc',
'src',
'link',
'sym',
'type',
'fix',
'sat',
'hdop',
'vdop',
'pdop',
'ageofdgpsdata',
'dgpsid',
]);
/**
* @const
* @type {Object>}
*/
// @ts-ignore
const WPT_TYPE_SERIALIZERS = makeStructureNS(NAMESPACE_URIS, {
'ele': makeChildAppender(writeDecimalTextNode),
'time': makeChildAppender(writeDateTimeTextNode),
'magvar': makeChildAppender(writeDecimalTextNode),
'geoidheight': makeChildAppender(writeDecimalTextNode),
'name': makeChildAppender(writeStringTextNode),
'cmt': makeChildAppender(writeStringTextNode),
'desc': makeChildAppender(writeStringTextNode),
'src': makeChildAppender(writeStringTextNode),
'link': makeChildAppender(writeLink),
'sym': makeChildAppender(writeStringTextNode),
'type': makeChildAppender(writeStringTextNode),
'fix': makeChildAppender(writeStringTextNode),
'sat': makeChildAppender(writeNonNegativeIntegerTextNode),
'hdop': makeChildAppender(writeDecimalTextNode),
'vdop': makeChildAppender(writeDecimalTextNode),
'pdop': makeChildAppender(writeDecimalTextNode),
'ageofdgpsdata': makeChildAppender(writeDecimalTextNode),
'dgpsid': makeChildAppender(writeNonNegativeIntegerTextNode),
});
/**
* @const
* @type {Object}
*/
const GEOMETRY_TYPE_TO_NODENAME = {
'Point': 'wpt',
'LineString': 'rte',
'MultiLineString': 'trk',
};
/**
* @param {*} value Value.
* @param {Array<*>} objectStack Object stack.
* @param {string} [nodeName] Node name.
* @return {Node|undefined} Node.
*/
function GPX_NODE_FACTORY(value, objectStack, nodeName) {
const geometry = /** @type {Feature} */ (value).getGeometry();
if (geometry) {
const nodeName = GEOMETRY_TYPE_TO_NODENAME[geometry.getType()];
if (nodeName) {
const parentNode = objectStack[objectStack.length - 1].node;
return createElementNS(parentNode.namespaceURI, nodeName);
}
}
}
/**
* @param {Array} flatCoordinates Flat coordinates.
* @param {LayoutOptions} layoutOptions Layout options.
* @param {Element} node Node.
* @param {!Object} values Values.
* @return {Array} Flat coordinates.
*/
function appendCoordinate(flatCoordinates, layoutOptions, node, values) {
flatCoordinates.push(
parseFloat(node.getAttribute('lon')),
parseFloat(node.getAttribute('lat')),
);
if ('ele' in values) {
flatCoordinates.push(/** @type {number} */ (values['ele']));
delete values['ele'];
layoutOptions.hasZ = true;
} else {
flatCoordinates.push(0);
}
if ('time' in values) {
flatCoordinates.push(/** @type {number} */ (values['time']));
delete values['time'];
layoutOptions.hasM = true;
} else {
flatCoordinates.push(0);
}
return flatCoordinates;
}
/**
* Choose GeometryLayout based on flags in layoutOptions and adjust flatCoordinates
* and ends arrays by shrinking them accordingly (removing unused zero entries).
*
* @param {LayoutOptions} layoutOptions Layout options.
* @param {Array} flatCoordinates Flat coordinates.
* @param {Array} [ends] Ends.
* @return {import("../geom/Geometry.js").GeometryLayout} Layout.
*/
function applyLayoutOptions(layoutOptions, flatCoordinates, ends) {
/** @type {import("../geom/Geometry.js").GeometryLayout} */
let layout = 'XY';
let stride = 2;
if (layoutOptions.hasZ && layoutOptions.hasM) {
layout = 'XYZM';
stride = 4;
} else if (layoutOptions.hasZ) {
layout = 'XYZ';
stride = 3;
} else if (layoutOptions.hasM) {
layout = 'XYM';
stride = 3;
}
if (stride !== 4) {
for (let i = 0, ii = flatCoordinates.length / 4; i < ii; i++) {
flatCoordinates[i * stride] = flatCoordinates[i * 4];
flatCoordinates[i * stride + 1] = flatCoordinates[i * 4 + 1];
if (layoutOptions.hasZ) {
flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 2];
}
if (layoutOptions.hasM) {
flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 3];
}
}
flatCoordinates.length = (flatCoordinates.length / 4) * stride;
if (ends) {
for (let i = 0, ii = ends.length; i < ii; i++) {
ends[i] = (ends[i] / 4) * stride;
}
}
}
return layout;
}
/**
* @param {Element} node Node.
* @param {Array} objectStack Object stack.
* @return {GPXAuthor | undefined} Person object.
*/
function readAuthor(node, objectStack) {
const values = pushParseAndPop({}, AUTHOR_PARSERS, node, objectStack);
if (values) {
return values;
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array} objectStack Object stack.
* @return {GPXCopyright | undefined} Person object.
*/
function readCopyright(node, objectStack) {
const values = pushParseAndPop({}, COPYRIGHT_PARSERS, node, objectStack);
if (values) {
const author = node.getAttribute('author');
if (author !== null) {
values['author'] = author;
}
return values;
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseBounds(node, objectStack) {
const values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
const minlat = node.getAttribute('minlat');
const minlon = node.getAttribute('minlon');
const maxlat = node.getAttribute('maxlat');
const maxlon = node.getAttribute('maxlon');
if (
minlon !== null &&
minlat !== null &&
maxlon !== null &&
maxlat !== null
) {
values['bounds'] = [
[parseFloat(minlon), parseFloat(minlat)],
[parseFloat(maxlon), parseFloat(maxlat)],
];
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseEmail(node, objectStack) {
const values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
const id = node.getAttribute('id');
const domain = node.getAttribute('domain');
if (id !== null && domain !== null) {
values['email'] = `${id}@${domain}`;
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseLink(node, objectStack) {
const values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
const href = node.getAttribute('href');
if (href !== null) {
values['link'] = href;
}
parseNode(LINK_PARSERS, node, objectStack);
}
/**
* @param {Node} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseExtensions(node, objectStack) {
const values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
values['extensionsNode_'] = node;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseRtePt(node, objectStack) {
const values = pushParseAndPop({}, RTEPT_PARSERS, node, objectStack);
if (values) {
const rteValues = /** @type {!Object} */ (
objectStack[objectStack.length - 1]
);
const flatCoordinates = /** @type {Array} */ (
rteValues['flatCoordinates']
);
const layoutOptions = /** @type {LayoutOptions} */ (
rteValues['layoutOptions']
);
appendCoordinate(flatCoordinates, layoutOptions, node, values);
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseTrkPt(node, objectStack) {
const values = pushParseAndPop({}, TRKPT_PARSERS, node, objectStack);
if (values) {
const trkValues = /** @type {!Object} */ (
objectStack[objectStack.length - 1]
);
const flatCoordinates = /** @type {Array} */ (
trkValues['flatCoordinates']
);
const layoutOptions = /** @type {LayoutOptions} */ (
trkValues['layoutOptions']
);
appendCoordinate(flatCoordinates, layoutOptions, node, values);
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function parseTrkSeg(node, objectStack) {
const values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
parseNode(TRKSEG_PARSERS, node, objectStack);
const flatCoordinates =
/** @type {Array} */
(values['flatCoordinates']);
const ends = /** @type {Array} */ (values['ends']);
ends.push(flatCoordinates.length);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Feature|undefined} Track.
*/
function readRte(node, objectStack) {
const options = /** @type {import("./Feature.js").ReadOptions} */ (
objectStack[0]
);
const values = pushParseAndPop(
{
'flatCoordinates': [],
'layoutOptions': {},
},
RTE_PARSERS,
node,
objectStack,
);
if (!values) {
return undefined;
}
const flatCoordinates =
/** @type {Array} */
(values['flatCoordinates']);
delete values['flatCoordinates'];
const layoutOptions = /** @type {LayoutOptions} */ (values['layoutOptions']);
delete values['layoutOptions'];
const layout = applyLayoutOptions(layoutOptions, flatCoordinates);
const geometry = new LineString(flatCoordinates, layout);
transformGeometryWithOptions(geometry, false, options);
const feature = new Feature(geometry);
feature.setProperties(values, true);
return feature;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Feature|undefined} Track.
*/
function readTrk(node, objectStack) {
const options = /** @type {import("./Feature.js").ReadOptions} */ (
objectStack[0]
);
const values = pushParseAndPop(
{
'flatCoordinates': [],
'ends': [],
'layoutOptions': {},
},
TRK_PARSERS,
node,
objectStack,
);
if (!values) {
return undefined;
}
const flatCoordinates =
/** @type {Array} */
(values['flatCoordinates']);
delete values['flatCoordinates'];
const ends = /** @type {Array} */ (values['ends']);
delete values['ends'];
const layoutOptions = /** @type {LayoutOptions} */ (values['layoutOptions']);
delete values['layoutOptions'];
const layout = applyLayoutOptions(layoutOptions, flatCoordinates, ends);
const geometry = new MultiLineString(flatCoordinates, layout, ends);
transformGeometryWithOptions(geometry, false, options);
const feature = new Feature(geometry);
feature.setProperties(values, true);
return feature;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Feature|undefined} Waypoint.
*/
function readWpt(node, objectStack) {
const options = /** @type {import("./Feature.js").ReadOptions} */ (
objectStack[0]
);
const values = pushParseAndPop({}, WPT_PARSERS, node, objectStack);
if (!values) {
return undefined;
}
const layoutOptions = /** @type {LayoutOptions} */ ({});
const coordinates = appendCoordinate([], layoutOptions, node, values);
const layout = applyLayoutOptions(layoutOptions, coordinates);
const geometry = new Point(coordinates, layout);
transformGeometryWithOptions(geometry, false, options);
const feature = new Feature(geometry);
feature.setProperties(values, true);
return feature;
}
/**
* @param {Element} node Node.
* @param {string} value Value for the link's `href` attribute.
* @param {Array<*>} objectStack Node stack.
*/
function writeLink(node, value, objectStack) {
node.setAttribute('href', value);
const context = objectStack[objectStack.length - 1];
const properties = context['properties'];
const link = [properties['linkText'], properties['linkType']];
pushSerializeAndPop(
/** @type {import("../xml.js").NodeStackItem} */ ({node: node}),
LINK_SERIALIZERS,
OBJECT_PROPERTY_NODE_FACTORY,
link,
objectStack,
LINK_SEQUENCE,
);
}
/**
* @param {Element} node Node.
* @param {import("../coordinate.js").Coordinate} coordinate Coordinate.
* @param {Array<*>} objectStack Object stack.
*/
function writeWptType(node, coordinate, objectStack) {
const context = objectStack[objectStack.length - 1];
const parentNode = context.node;
const namespaceURI = parentNode.namespaceURI;
const properties = context['properties'];
//FIXME Projection handling
node.setAttributeNS(null, 'lat', String(coordinate[1]));
node.setAttributeNS(null, 'lon', String(coordinate[0]));
const geometryLayout = context['geometryLayout'];
switch (geometryLayout) {
case 'XYZM':
if (coordinate[3] !== 0) {
properties['time'] = coordinate[3];
}
// fall through
case 'XYZ':
if (coordinate[2] !== 0) {
properties['ele'] = coordinate[2];
}
break;
case 'XYM':
if (coordinate[2] !== 0) {
properties['time'] = coordinate[2];
}
break;
default:
// pass
}
const orderedKeys =
node.nodeName == 'rtept'
? RTEPT_TYPE_SEQUENCE[namespaceURI]
: WPT_TYPE_SEQUENCE[namespaceURI];
const values = makeSequence(properties, orderedKeys);
pushSerializeAndPop(
/** @type {import("../xml.js").NodeStackItem} */
({node: node, 'properties': properties}),
WPT_TYPE_SERIALIZERS,
OBJECT_PROPERTY_NODE_FACTORY,
values,
objectStack,
orderedKeys,
);
}
/**
* @param {Node} node Node.
* @param {Feature} feature Feature.
* @param {Array<*>} objectStack Object stack.
*/
function writeRte(node, feature, objectStack) {
const options = /** @type {import("./Feature.js").WriteOptions} */ (
objectStack[0]
);
const properties = feature.getProperties();
const context = {node: node};
context['properties'] = properties;
const geometry = feature.getGeometry();
if (geometry.getType() == 'LineString') {
const lineString = /** @type {LineString} */ (
transformGeometryWithOptions(geometry, true, options)
);
context['geometryLayout'] = lineString.getLayout();
properties['rtept'] = lineString.getCoordinates();
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = RTE_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys);
pushSerializeAndPop(
context,
RTE_SERIALIZERS,
OBJECT_PROPERTY_NODE_FACTORY,
values,
objectStack,
orderedKeys,
);
}
/**
* @param {Element} node Node.
* @param {Feature} feature Feature.
* @param {Array<*>} objectStack Object stack.
*/
function writeTrk(node, feature, objectStack) {
const options = /** @type {import("./Feature.js").WriteOptions} */ (
objectStack[0]
);
const properties = feature.getProperties();
/** @type {import("../xml.js").NodeStackItem} */
const context = {node: node};
context['properties'] = properties;
const geometry = feature.getGeometry();
if (geometry.getType() == 'MultiLineString') {
const multiLineString = /** @type {MultiLineString} */ (
transformGeometryWithOptions(geometry, true, options)
);
properties['trkseg'] = multiLineString.getLineStrings();
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = TRK_SEQUENCE[parentNode.namespaceURI];
const values = makeSequence(properties, orderedKeys);
pushSerializeAndPop(
context,
TRK_SERIALIZERS,
OBJECT_PROPERTY_NODE_FACTORY,
values,
objectStack,
orderedKeys,
);
}
/**
* @param {Element} node Node.
* @param {LineString} lineString LineString.
* @param {Array<*>} objectStack Object stack.
*/
function writeTrkSeg(node, lineString, objectStack) {
/** @type {import("../xml.js").NodeStackItem} */
const context = {node: node};
context['geometryLayout'] = lineString.getLayout();
context['properties'] = {};
pushSerializeAndPop(
context,
TRKSEG_SERIALIZERS,
TRKSEG_NODE_FACTORY,
lineString.getCoordinates(),
objectStack,
);
}
/**
* @param {Element} node Node.
* @param {Feature} feature Feature.
* @param {Array<*>} objectStack Object stack.
*/
function writeWpt(node, feature, objectStack) {
const options = /** @type {import("./Feature.js").WriteOptions} */ (
objectStack[0]
);
const context = objectStack[objectStack.length - 1];
context['properties'] = feature.getProperties();
const geometry = feature.getGeometry();
if (geometry.getType() == 'Point') {
const point = /** @type {Point} */ (
transformGeometryWithOptions(geometry, true, options)
);
context['geometryLayout'] = point.getLayout();
writeWptType(node, point.getCoordinates(), objectStack);
}
}
export default GPX;