@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,019 lines (930 loc) • 31.1 kB
JavaScript
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import ClockRange from "../Core/ClockRange.js";
import ClockStep from "../Core/ClockStep.js";
import Color from "../Core/Color.js";
import createGuid from "../Core/createGuid.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Event from "../Core/Event.js";
import Iso8601 from "../Core/Iso8601.js";
import JulianDate from "../Core/JulianDate.js";
import NearFarScalar from "../Core/NearFarScalar.js";
import PinBuilder from "../Core/PinBuilder.js";
import Resource from "../Core/Resource.js";
import RuntimeError from "../Core/RuntimeError.js";
import TimeInterval from "../Core/TimeInterval.js";
import TimeIntervalCollection from "../Core/TimeIntervalCollection.js";
import HeightReference from "../Scene/HeightReference.js";
import HorizontalOrigin from "../Scene/HorizontalOrigin.js";
import LabelStyle from "../Scene/LabelStyle.js";
import VerticalOrigin from "../Scene/VerticalOrigin.js";
import Autolinker from "autolinker";
import BillboardGraphics from "./BillboardGraphics.js";
import ConstantProperty from "./ConstantProperty.js";
import DataSource from "./DataSource.js";
import DataSourceClock from "./DataSourceClock.js";
import EntityCluster from "./EntityCluster.js";
import EntityCollection from "./EntityCollection.js";
import LabelGraphics from "./LabelGraphics.js";
import PolylineGraphics from "./PolylineGraphics.js";
import PolylineOutlineMaterialProperty from "./PolylineOutlineMaterialProperty.js";
import SampledPositionProperty from "./SampledPositionProperty.js";
let parser;
if (typeof DOMParser !== "undefined") {
parser = new DOMParser();
}
const autolinker = new Autolinker({
stripPrefix: false,
email: false,
replaceFn: function (linker, match) {
//Prevent matching of non-explicit urls.
//i.e. foo.id won't match but http://foo.id will
return match.urlMatchType === "scheme" || match.urlMatchType === "www";
},
});
const BILLBOARD_SIZE = 32;
const BILLBOARD_NEAR_DISTANCE = 2414016;
const BILLBOARD_NEAR_RATIO = 1.0;
const BILLBOARD_FAR_DISTANCE = 1.6093e7;
const BILLBOARD_FAR_RATIO = 0.1;
const gpxNamespaces = [null, undefined, "http://www.topografix.com/GPX/1/1"];
const namespaces = {
gpx: gpxNamespaces,
};
function readBlobAsText(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", function () {
resolve(reader.result);
});
reader.addEventListener("error", function () {
reject(reader.error);
});
reader.readAsText(blob);
});
}
function getOrCreateEntity(node, entityCollection) {
let id = queryStringAttribute(node, "id");
id = defined(id) ? id : createGuid();
const entity = entityCollection.getOrCreateEntity(id);
return entity;
}
function readCoordinateFromNode(node) {
const longitude = queryNumericAttribute(node, "lon");
const latitude = queryNumericAttribute(node, "lat");
const elevation = queryNumericValue(node, "ele", namespaces.gpx);
return Cartesian3.fromDegrees(longitude, latitude, elevation);
}
function queryNumericAttribute(node, attributeName) {
if (!defined(node)) {
return undefined;
}
const value = node.getAttribute(attributeName);
if (value !== null) {
const result = parseFloat(value);
return !isNaN(result) ? result : undefined;
}
return undefined;
}
function queryStringAttribute(node, attributeName) {
if (!defined(node)) {
return undefined;
}
const value = node.getAttribute(attributeName);
return value !== null ? value : undefined;
}
function queryFirstNode(node, tagName, namespace) {
if (!defined(node)) {
return undefined;
}
const childNodes = node.childNodes;
const length = childNodes.length;
for (let q = 0; q < length; q++) {
const child = childNodes[q];
if (
child.localName === tagName &&
namespace.indexOf(child.namespaceURI) !== -1
) {
return child;
}
}
return undefined;
}
function queryNodes(node, tagName, namespace) {
if (!defined(node)) {
return undefined;
}
const result = [];
const childNodes = node.getElementsByTagName(tagName);
const length = childNodes.length;
for (let q = 0; q < length; q++) {
const child = childNodes[q];
if (
child.localName === tagName &&
namespace.indexOf(child.namespaceURI) !== -1
) {
result.push(child);
}
}
return result;
}
function queryNumericValue(node, tagName, namespace) {
const resultNode = queryFirstNode(node, tagName, namespace);
if (defined(resultNode)) {
const result = parseFloat(resultNode.textContent);
return !isNaN(result) ? result : undefined;
}
return undefined;
}
function queryStringValue(node, tagName, namespace) {
const result = queryFirstNode(node, tagName, namespace);
if (defined(result)) {
return result.textContent.trim();
}
return undefined;
}
function createDefaultBillboard(image) {
const billboard = new BillboardGraphics();
billboard.width = BILLBOARD_SIZE;
billboard.height = BILLBOARD_SIZE;
billboard.scaleByDistance = new NearFarScalar(
BILLBOARD_NEAR_DISTANCE,
BILLBOARD_NEAR_RATIO,
BILLBOARD_FAR_DISTANCE,
BILLBOARD_FAR_RATIO,
);
billboard.pixelOffsetScaleByDistance = new NearFarScalar(
BILLBOARD_NEAR_DISTANCE,
BILLBOARD_NEAR_RATIO,
BILLBOARD_FAR_DISTANCE,
BILLBOARD_FAR_RATIO,
);
billboard.verticalOrigin = new ConstantProperty(VerticalOrigin.BOTTOM);
billboard.image = image;
return billboard;
}
function createDefaultLabel() {
const label = new LabelGraphics();
label.translucencyByDistance = new NearFarScalar(3000000, 1.0, 5000000, 0.0);
label.pixelOffset = new Cartesian2(17, 0);
label.horizontalOrigin = HorizontalOrigin.LEFT;
label.font = "16px sans-serif";
label.style = LabelStyle.FILL_AND_OUTLINE;
return label;
}
function createDefaultPolyline(color) {
const polyline = new PolylineGraphics();
polyline.width = 4;
polyline.material = new PolylineOutlineMaterialProperty();
polyline.material.color = defined(color) ? color : Color.RED;
polyline.material.outlineWidth = 2;
polyline.material.outlineColor = Color.BLACK;
return polyline;
}
// This is a list of the Optional Description Information:
// <cmt> GPS comment of the waypoint
// <desc> Descriptive description of the waypoint
// <src> Source of the waypoint data
// <type> Type (category) of waypoint
const descriptiveInfoTypes = {
time: {
text: "Time",
tag: "time",
},
comment: {
text: "Comment",
tag: "cmt",
},
description: {
text: "Description",
tag: "desc",
},
source: {
text: "Source",
tag: "src",
},
number: {
text: "GPS track/route number",
tag: "number",
},
type: {
text: "Type",
tag: "type",
},
};
let scratchDiv;
if (typeof document !== "undefined") {
scratchDiv = document.createElement("div");
}
function processDescription(node, entity) {
let i;
let text = "";
const infoTypeNames = Object.keys(descriptiveInfoTypes);
const length = infoTypeNames.length;
for (i = 0; i < length; i++) {
const infoTypeName = infoTypeNames[i];
const infoType = descriptiveInfoTypes[infoTypeName];
infoType.value = queryStringValue(node, infoType.tag, namespaces.gpx) ?? "";
if (defined(infoType.value) && infoType.value !== "") {
text = `${text}<p>${infoType.text}: ${infoType.value}</p>`;
}
}
if (!defined(text) || text === "") {
// No description
return;
}
// Turns non-explicit links into clickable links.
text = autolinker.link(text);
// Use a temporary div to manipulate the links
// so that they open in a new window.
scratchDiv.innerHTML = text;
const links = scratchDiv.querySelectorAll("a");
for (i = 0; i < links.length; i++) {
links[i].setAttribute("target", "_blank");
}
const background = Color.WHITE;
const foreground = Color.BLACK;
let tmp = '<div class="cesium-infoBox-description-lighter" style="';
tmp += "overflow:auto;";
tmp += "word-wrap:break-word;";
tmp += `background-color:${background.toCssColorString()};`;
tmp += `color:${foreground.toCssColorString()};`;
tmp += '">';
tmp += `${scratchDiv.innerHTML}</div>`;
scratchDiv.innerHTML = "";
// return the final HTML as the description.
return tmp;
}
function processWpt(dataSource, geometryNode, entityCollection, options) {
const position = readCoordinateFromNode(geometryNode);
const entity = getOrCreateEntity(geometryNode, entityCollection);
entity.position = position;
// Get billboard image
const image = defined(options.waypointImage)
? options.waypointImage
: dataSource._pinBuilder.fromMakiIconId(
"marker",
Color.RED,
BILLBOARD_SIZE,
);
entity.billboard = createDefaultBillboard(image);
const name = queryStringValue(geometryNode, "name", namespaces.gpx);
entity.name = name;
entity.label = createDefaultLabel();
entity.label.text = name;
entity.description = processDescription(geometryNode, entity);
if (options.clampToGround) {
entity.billboard.heightReference = HeightReference.CLAMP_TO_GROUND;
entity.label.heightReference = HeightReference.CLAMP_TO_GROUND;
}
}
// rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination
function processRte(dataSource, geometryNode, entityCollection, options) {
const entity = getOrCreateEntity(geometryNode, entityCollection);
entity.description = processDescription(geometryNode, entity);
// a list of waypoint
const routePoints = queryNodes(geometryNode, "rtept", namespaces.gpx);
const coordinateTuples = new Array(routePoints.length);
for (let i = 0; i < routePoints.length; i++) {
processWpt(dataSource, routePoints[i], entityCollection, options);
coordinateTuples[i] = readCoordinateFromNode(routePoints[i]);
}
entity.polyline = createDefaultPolyline(options.routeColor);
if (options.clampToGround) {
entity.polyline.clampToGround = true;
}
entity.polyline.positions = coordinateTuples;
}
// trk represents a track - an ordered list of points describing a path.
function processTrk(dataSource, geometryNode, entityCollection, options) {
const entity = getOrCreateEntity(geometryNode, entityCollection);
entity.description = processDescription(geometryNode, entity);
const trackSegs = queryNodes(geometryNode, "trkseg", namespaces.gpx);
let positions = [];
let times = [];
let trackSegInfo;
let isTimeDynamic = true;
const property = new SampledPositionProperty();
for (let i = 0; i < trackSegs.length; i++) {
trackSegInfo = processTrkSeg(trackSegs[i]);
positions = positions.concat(trackSegInfo.positions);
if (trackSegInfo.times.length > 0) {
times = times.concat(trackSegInfo.times);
property.addSamples(times, positions);
// if one track segment is non dynamic the whole track must also be
isTimeDynamic = isTimeDynamic && true;
} else {
isTimeDynamic = false;
}
}
if (isTimeDynamic) {
// Assign billboard image
const image = defined(options.waypointImage)
? options.waypointImage
: dataSource._pinBuilder.fromMakiIconId(
"marker",
Color.RED,
BILLBOARD_SIZE,
);
entity.billboard = createDefaultBillboard(image);
entity.position = property;
if (options.clampToGround) {
entity.billboard.heightReference = HeightReference.CLAMP_TO_GROUND;
}
entity.availability = new TimeIntervalCollection();
entity.availability.addInterval(
new TimeInterval({
start: times[0],
stop: times[times.length - 1],
}),
);
}
entity.polyline = createDefaultPolyline(options.trackColor);
entity.polyline.positions = positions;
if (options.clampToGround) {
entity.polyline.clampToGround = true;
}
}
function processTrkSeg(node) {
const result = {
positions: [],
times: [],
};
const trackPoints = queryNodes(node, "trkpt", namespaces.gpx);
let time;
for (let i = 0; i < trackPoints.length; i++) {
const position = readCoordinateFromNode(trackPoints[i]);
result.positions.push(position);
time = queryStringValue(trackPoints[i], "time", namespaces.gpx);
if (defined(time)) {
result.times.push(JulianDate.fromIso8601(time));
}
}
return result;
}
// Processes a metadataType node and returns a metadata object
// {@link http://www.topografix.com/gpx/1/1/#type_metadataType|GPX Schema}
function processMetadata(node) {
const metadataNode = queryFirstNode(node, "metadata", namespaces.gpx);
if (defined(metadataNode)) {
const metadata = {
name: queryStringValue(metadataNode, "name", namespaces.gpx),
desc: queryStringValue(metadataNode, "desc", namespaces.gpx),
author: getPerson(metadataNode),
copyright: getCopyright(metadataNode),
link: getLink(metadataNode),
time: queryStringValue(metadataNode, "time", namespaces.gpx),
keywords: queryStringValue(metadataNode, "keywords", namespaces.gpx),
bounds: getBounds(metadataNode),
};
if (
defined(metadata.name) ||
defined(metadata.desc) ||
defined(metadata.author) ||
defined(metadata.copyright) ||
defined(metadata.link) ||
defined(metadata.time) ||
defined(metadata.keywords) ||
defined(metadata.bounds)
) {
return metadata;
}
}
return undefined;
}
// Receives a XML node and returns a personType object, refer to
// {@link http://www.topografix.com/gpx/1/1/#type_personType|GPX Schema}
function getPerson(node) {
const personNode = queryFirstNode(node, "author", namespaces.gpx);
if (defined(personNode)) {
const person = {
name: queryStringValue(personNode, "name", namespaces.gpx),
email: getEmail(personNode),
link: getLink(personNode),
};
if (defined(person.name) || defined(person.email) || defined(person.link)) {
return person;
}
}
return undefined;
}
// Receives a XML node and returns an email address (from emailType), refer to
// {@link http://www.topografix.com/gpx/1/1/#type_emailType|GPX Schema}
function getEmail(node) {
const emailNode = queryFirstNode(node, "email", namespaces.gpx);
if (defined(emailNode)) {
const id = queryStringValue(emailNode, "id", namespaces.gpx);
const domain = queryStringValue(emailNode, "domain", namespaces.gpx);
return `${id}@${domain}`;
}
return undefined;
}
// Receives a XML node and returns a linkType object, refer to
// {@link http://www.topografix.com/gpx/1/1/#type_linkType|GPX Schema}
function getLink(node) {
const linkNode = queryFirstNode(node, "link", namespaces.gpx);
if (defined(linkNode)) {
const link = {
href: queryStringAttribute(linkNode, "href"),
text: queryStringValue(linkNode, "text", namespaces.gpx),
mimeType: queryStringValue(linkNode, "type", namespaces.gpx),
};
if (defined(link.href) || defined(link.text) || defined(link.mimeType)) {
return link;
}
}
return undefined;
}
// Receives a XML node and returns a copyrightType object, refer to
// {@link http://www.topografix.com/gpx/1/1/#type_copyrightType|GPX Schema}
function getCopyright(node) {
const copyrightNode = queryFirstNode(node, "copyright", namespaces.gpx);
if (defined(copyrightNode)) {
const copyright = {
author: queryStringAttribute(copyrightNode, "author"),
year: queryStringValue(copyrightNode, "year", namespaces.gpx),
license: queryStringValue(copyrightNode, "license", namespaces.gpx),
};
if (
defined(copyright.author) ||
defined(copyright.year) ||
defined(copyright.license)
) {
return copyright;
}
}
return undefined;
}
// Receives a XML node and returns a boundsType object, refer to
// {@link http://www.topografix.com/gpx/1/1/#type_boundsType|GPX Schema}
function getBounds(node) {
const boundsNode = queryFirstNode(node, "bounds", namespaces.gpx);
if (defined(boundsNode)) {
const bounds = {
minLat: queryNumericValue(boundsNode, "minlat", namespaces.gpx),
maxLat: queryNumericValue(boundsNode, "maxlat", namespaces.gpx),
minLon: queryNumericValue(boundsNode, "minlon", namespaces.gpx),
maxLon: queryNumericValue(boundsNode, "maxlon", namespaces.gpx),
};
if (
defined(bounds.minLat) ||
defined(bounds.maxLat) ||
defined(bounds.minLon) ||
defined(bounds.maxLon)
) {
return bounds;
}
}
return undefined;
}
const complexTypes = {
wpt: processWpt,
rte: processRte,
trk: processTrk,
};
function processGpx(dataSource, node, entityCollection, options) {
const complexTypeNames = Object.keys(complexTypes);
const complexTypeNamesLength = complexTypeNames.length;
for (let i = 0; i < complexTypeNamesLength; i++) {
const typeName = complexTypeNames[i];
const processComplexTypeNode = complexTypes[typeName];
const childNodes = node.childNodes;
const length = childNodes.length;
for (let q = 0; q < length; q++) {
const child = childNodes[q];
if (
child.localName === typeName &&
namespaces.gpx.indexOf(child.namespaceURI) !== -1
) {
processComplexTypeNode(dataSource, child, entityCollection, options);
}
}
}
}
function loadGpx(dataSource, gpx, options) {
const entityCollection = dataSource._entityCollection;
entityCollection.removeAll();
const element = gpx.documentElement;
const version = queryStringAttribute(element, "version");
const creator = queryStringAttribute(element, "creator");
let name;
const metadata = processMetadata(element);
if (defined(metadata)) {
name = metadata.name;
}
if (element.localName === "gpx") {
processGpx(dataSource, element, entityCollection, options);
} else {
console.log(`GPX - Unsupported node: ${element.localName}`);
}
let clock;
const availability = entityCollection.computeAvailability();
let start = availability.start;
let stop = availability.stop;
const isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
const isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
if (!isMinStart || !isMaxStop) {
let date;
// If start is min time just start at midnight this morning, local time
if (isMinStart) {
date = new Date();
date.setHours(0, 0, 0, 0);
start = JulianDate.fromDate(date);
}
// If stop is max value just stop at midnight tonight, local time
if (isMaxStop) {
date = new Date();
date.setHours(24, 0, 0, 0);
stop = JulianDate.fromDate(date);
}
clock = new DataSourceClock();
clock.startTime = start;
clock.stopTime = stop;
clock.currentTime = JulianDate.clone(start);
clock.clockRange = ClockRange.LOOP_STOP;
clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
clock.multiplier = Math.round(
Math.min(
Math.max(JulianDate.secondsDifference(stop, start) / 60, 1),
3.15569e7,
),
);
}
let changed = false;
if (dataSource._name !== name) {
dataSource._name = name;
changed = true;
}
if (dataSource._creator !== creator) {
dataSource._creator = creator;
changed = true;
}
if (metadataChanged(dataSource._metadata, metadata)) {
dataSource._metadata = metadata;
changed = true;
}
if (dataSource._version !== version) {
dataSource._version = version;
changed = true;
}
if (clock !== dataSource._clock) {
changed = true;
dataSource._clock = clock;
}
if (changed) {
dataSource._changed.raiseEvent(dataSource);
}
DataSource.setLoading(dataSource, false);
return dataSource;
}
function metadataChanged(old, current) {
if (!defined(old) && !defined(current)) {
return false;
} else if (defined(old) && defined(current)) {
if (
old.name !== current.name ||
old.dec !== current.desc ||
old.src !== current.src ||
old.author !== current.author ||
old.copyright !== current.copyright ||
old.link !== current.link ||
old.time !== current.time ||
old.bounds !== current.bounds
) {
return true;
}
return false;
}
return true;
}
function load(dataSource, entityCollection, data, options) {
options = options ?? Frozen.EMPTY_OBJECT;
let promise = data;
if (typeof data === "string" || data instanceof Resource) {
data = Resource.createIfNeeded(data);
promise = data.fetchBlob();
// Add resource credits to our list of credits to display
const resourceCredits = dataSource._resourceCredits;
const credits = data.credits;
if (defined(credits)) {
const length = credits.length;
for (let i = 0; i < length; i++) {
resourceCredits.push(credits[i]);
}
}
}
return Promise.resolve(promise)
.then(function (dataToLoad) {
if (dataToLoad instanceof Blob) {
return readBlobAsText(dataToLoad).then(function (text) {
// There's no official way to validate if a parse was successful.
// The following check detects the error on various browsers.
// IE raises an exception
let gpx;
let error;
try {
gpx = parser.parseFromString(text, "application/xml");
} catch (e) {
error = e.toString();
}
// The parse succeeds on Chrome and Firefox, but the error
// handling is different in each.
if (
defined(error) ||
gpx.body ||
gpx.documentElement.tagName === "parsererror"
) {
// Firefox has error information as the firstChild nodeValue.
let msg = defined(error)
? error
: gpx.documentElement.firstChild.nodeValue;
// Chrome has it in the body text.
if (!msg) {
msg = gpx.body.innerText;
}
// Return the error
throw new RuntimeError(msg);
}
return loadGpx(dataSource, gpx, options);
});
}
return loadGpx(dataSource, dataToLoad, options);
})
.catch(function (error) {
dataSource._error.raiseEvent(dataSource, error);
console.log(error);
return Promise.reject(error);
});
}
/**
* A {@link DataSource} which processes the GPS Exchange Format (GPX).
*
* @alias GpxDataSource
* @constructor
*
* @see {@link http://www.topografix.com/gpx.asp|Topografix GPX Standard}
* @see {@link http://www.topografix.com/gpx/1/1/|Topografix GPX Documentation}
*
* @demo {@link http://sandcastle.cesium.com/index.html?src=GPX.html}
*
* @example
* const viewer = new Cesium.Viewer('cesiumContainer');
* viewer.dataSources.add(Cesium.GpxDataSource.load('../../SampleData/track.gpx'));
*/
function GpxDataSource() {
this._changed = new Event();
this._error = new Event();
this._loading = new Event();
this._clock = undefined;
this._entityCollection = new EntityCollection(this);
this._entityCluster = new EntityCluster();
this._name = undefined;
this._version = undefined;
this._creator = undefined;
this._metadata = undefined;
this._isLoading = false;
this._pinBuilder = new PinBuilder();
}
/**
* Creates a Promise to a new instance loaded with the provided GPX data.
*
* @param {string|Document|Blob} data A url, parsed GPX document, or Blob containing binary GPX data.
* @param {object} [options] An object with the following properties:
* @param {boolean} [options.clampToGround] True if the symbols should be rendered at the same height as the terrain
* @param {string} [options.waypointImage] Image to use for waypoint billboards.
* @param {string} [options.trackImage] Image to use for track billboards.
* @param {string} [options.trackColor] Color to use for track lines.
* @param {string} [options.routeColor] Color to use for route lines.
* @returns {Promise<GpxDataSource>} A promise that will resolve to a new GpxDataSource instance once the gpx is loaded.
*/
GpxDataSource.load = function (data, options) {
return new GpxDataSource().load(data, options);
};
Object.defineProperties(GpxDataSource.prototype, {
/**
* Gets a human-readable name for this instance.
* This will be automatically be set to the GPX document name on load.
* @memberof GpxDataSource.prototype
* @type {string}
*/
name: {
get: function () {
return this._name;
},
},
/**
* Gets the version of the GPX Schema in use.
* @memberof GpxDataSource.prototype
* @type {string}
*/
version: {
get: function () {
return this._version;
},
},
/**
* Gets the creator of the GPX document.
* @memberof GpxDataSource.prototype
* @type {string}
*/
creator: {
get: function () {
return this._creator;
},
},
/**
* Gets an object containing metadata about the GPX file.
* @memberof GpxDataSource.prototype
* @type {object}
*/
metadata: {
get: function () {
return this._metadata;
},
},
/**
* Gets the clock settings defined by the loaded GPX. This represents the total
* availability interval for all time-dynamic data. If the GPX does not contain
* time-dynamic data, this value is undefined.
* @memberof GpxDataSource.prototype
* @type {DataSourceClock}
*/
clock: {
get: function () {
return this._clock;
},
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof GpxDataSource.prototype
* @type {EntityCollection}
*/
entities: {
get: function () {
return this._entityCollection;
},
},
/**
* Gets a value indicating if the data source is currently loading data.
* @memberof GpxDataSource.prototype
* @type {boolean}
*/
isLoading: {
get: function () {
return this._isLoading;
},
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof GpxDataSource.prototype
* @type {Event}
*/
changedEvent: {
get: function () {
return this._changed;
},
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof GpxDataSource.prototype
* @type {Event}
*/
errorEvent: {
get: function () {
return this._error;
},
},
/**
* Gets an event that will be raised when the data source either starts or stops loading.
* @memberof GpxDataSource.prototype
* @type {Event}
*/
loadingEvent: {
get: function () {
return this._loading;
},
},
/**
* Gets whether or not this data source should be displayed.
* @memberof GpxDataSource.prototype
* @type {boolean}
*/
show: {
get: function () {
return this._entityCollection.show;
},
set: function (value) {
this._entityCollection.show = value;
},
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof GpxDataSource.prototype
* @type {EntityCluster}
*/
clustering: {
get: function () {
return this._entityCluster;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value)) {
throw new DeveloperError("value must be defined.");
}
//>>includeEnd('debug');
this._entityCluster = value;
},
},
});
/**
* Updates the data source to the provided time. This function is optional and
* is not required to be implemented. It is provided for data sources which
* retrieve data based on the current animation time or scene state.
* If implemented, update will be called by {@link DataSourceDisplay} once a frame.
*
* @param {JulianDate} time The simulation time.
* @returns {boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
*/
GpxDataSource.prototype.update = function (time) {
return true;
};
/**
* Asynchronously loads the provided GPX data, replacing any existing data.
*
* @param {string|Document|Blob} data A url, parsed GPX document, or Blob containing binary GPX data or a parsed GPX document.
* @param {object} [options] An object with the following properties:
* @param {boolean} [options.clampToGround] True if the symbols should be rendered at the same height as the terrain
* @param {string} [options.waypointImage] Image to use for waypoint billboards.
* @param {string} [options.trackImage] Image to use for track billboards.
* @param {string} [options.trackColor] Color to use for track lines.
* @param {string} [options.routeColor] Color to use for route lines.
* @returns {Promise<GpxDataSource>} A promise that will resolve to this instances once the GPX is loaded.
*/
GpxDataSource.prototype.load = function (data, options) {
if (!defined(data)) {
throw new DeveloperError("data is required.");
}
options = options ?? Frozen.EMPTY_OBJECT;
DataSource.setLoading(this, true);
const oldName = this._name;
const that = this;
return load(this, this._entityCollection, data, options)
.then(function () {
let clock;
const availability = that._entityCollection.computeAvailability();
let start = availability.start;
let stop = availability.stop;
const isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
const isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
if (!isMinStart || !isMaxStop) {
let date;
// If start is min time just start at midnight this morning, local time
if (isMinStart) {
date = new Date();
date.setHours(0, 0, 0, 0);
start = JulianDate.fromDate(date);
}
// If stop is max value just stop at midnight tonight, local time
if (isMaxStop) {
date = new Date();
date.setHours(24, 0, 0, 0);
stop = JulianDate.fromDate(date);
}
clock = new DataSourceClock();
clock.startTime = start;
clock.stopTime = stop;
clock.currentTime = JulianDate.clone(start);
clock.clockRange = ClockRange.LOOP_STOP;
clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
clock.multiplier = Math.round(
Math.min(
Math.max(JulianDate.secondsDifference(stop, start) / 60, 1),
3.15569e7,
),
);
}
let changed = false;
if (clock !== that._clock) {
that._clock = clock;
changed = true;
}
if (oldName !== that._name) {
changed = true;
}
if (changed) {
that._changed.raiseEvent(that);
}
DataSource.setLoading(that, false);
return that;
})
.catch(function (error) {
DataSource.setLoading(that, false);
that._error.raiseEvent(that, error);
console.log(error);
return Promise.reject(error);
});
};
export default GpxDataSource;