terriajs
Version:
Geospatial data visualization platform.
889 lines (754 loc) • 55.1 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Models/ImageryLayerCatalogItem.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: Models/ImageryLayerCatalogItem.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict';
/*global require*/
var ClockRange =require('terriajs-cesium/Source/Core/ClockRange');
var clone = require('terriajs-cesium/Source/Core/clone');
var DataSourceClock = require('terriajs-cesium/Source/DataSources/DataSourceClock');
var defaultValue = require('terriajs-cesium/Source/Core/defaultValue');
var defined = require('terriajs-cesium/Source/Core/defined');
var defineProperties = require('terriajs-cesium/Source/Core/defineProperties');
var DeveloperError = require('terriajs-cesium/Source/Core/DeveloperError');
var formatError = require('terriajs-cesium/Source/Core/formatError');
var freezeObject = require('terriajs-cesium/Source/Core/freezeObject');
var ImagerySplitDirection = require('terriajs-cesium/Source/Scene/ImagerySplitDirection');
var JulianDate = require('terriajs-cesium/Source/Core/JulianDate');
var knockout = require('terriajs-cesium/Source/ThirdParty/knockout');
var loadWithXhr = require('../Core/loadWithXhr');
var Rectangle = require('terriajs-cesium/Source/Core/Rectangle');
var TimeInterval = require('terriajs-cesium/Source/Core/TimeInterval');
var TimeIntervalCollection = require('terriajs-cesium/Source/Core/TimeIntervalCollection');
var when = require('terriajs-cesium/Source/ThirdParty/when');
var CatalogItem = require('./CatalogItem');
var CompositeCatalogItem = require('./CompositeCatalogItem');
var getUrlForImageryTile = require('../Map/getUrlForImageryTile');
var inherit = require('../Core/inherit');
var TerriaError = require('../Core/TerriaError');
var overrideProperty = require('../Core/overrideProperty');
var { setOpacity, fixNextLayerOrder } = require('./ImageryLayerPreloadHelpers');
var setClockCurrentTime = require('./setClockCurrentTime');
/**
* A {@link CatalogItem} that is added to the map as a rasterized imagery layer.
*
* @alias ImageryLayerCatalogItem
* @constructor
* @extends CatalogItem
* @abstract
*
* @param {Terria} terria The Terria instance.
*/
var ImageryLayerCatalogItem = function(terria) {
CatalogItem.call(this, terria);
this._imageryLayer = undefined;
this._clock = undefined;
this._currentIntervalIndex = -1;
this._nextIntervalIndex = undefined;
this._nextLayer = undefined;
/**
* Gets or sets the opacity (alpha) of the data item, where 0.0 is fully transparent and 1.0 is
* fully opaque. This property is observable.
* @type {Number}
* @default 0.6
*/
this.opacity = 0.6;
/**
* Gets or sets a value indicating whether a 404 response code when requesting a tile should be
* treated as an error. If false, 404s are assumed to just be missing tiles and need not be
* reported to the user.
* @type {Boolean}
* @default false
*/
this.treat404AsError = false;
/**
* Gets or sets a value indicating whether a 403 response code when requesting a tile should be
* treated as an error. If false, 403s are assumed to just be missing tiles and need not be
* reported to the user.
* @type {Boolean}
* @default false
*/
this.treat403AsError = true;
/**
* Gets or sets a value indicating whether non-specific (no HTTP status code) tile errors should be ignored. This is a
* last resort, for dealing with odd cases such as data sources that return non-images (eg XML) with a 200 status code.
* No error messages will be shown to the user.
* @type {Boolean}
* @default false
*/
this.ignoreUnknownTileErrors = false;
/**
* Gets or sets the {@link TimeIntervalCollection} defining the intervals of distinct imagery. If this catalog item
* is not time-dynamic, property is undefined. This property is observable.
* @type {ImageryLayerInterval[]}
* @default undefined
*/
this.intervals = undefined;
/**
* Keeps the layer on top of all other imagery layers. This property is observable.
* @type {Boolean}
* @default false
*/
this.keepOnTop = false;
/**
* Gets or sets a value indicating whether this dataset should be clipped to the {@link CatalogItem#rectangle}.
* If true, no part of the dataset will be displayed outside the rectangle. This property is true by default,
* leading to better performance and avoiding tile request errors that might occur when requesting tiles outside the
* server-specified rectangle. However, it may also cause features to be cut off in some cases, such as if a server
* reports an extent that does not take into account that the representation of features sometimes require a larger
* spatial extent than the features themselves. For example, if a point feature on the edge of the extent is drawn
* as a circle with a radius of 5 pixels, half of that circle will be cut off.
* @type {Boolean}
* @default false
*/
this.clipToRectangle = true;
/**
* Gets or sets a value indicating whether tiles of this catalog item are required to be loaded before terrain
* tiles to which they're attached can be rendered. This should usually be set to true for base layers and
* false for all others.
* @type {Boolean}
* @default false
*/
this.isRequiredForRendering = false;
/**
* Options for the value of the animation timeline at start. Valid options in config file are:
* initialTimeSource: "present" // closest to today's date
* initialTimeSource: "start" // start of time range of animation
* initialTimeSource: "end" // end of time range of animation
* initialTimeSource: An ISO8601 date e.g. "2015-08-08" // specified date or nearest if date is outside range
* @type {String}
*/
this.initialTimeSource = undefined;
/**
* Gets or sets which side of the splitter (if present) to display this imagery layer on. Defaults to both sides.
* This property is observable.
* @type {ImagerySplitDirection}
*/
this.splitDirection = ImagerySplitDirection.NONE; // NONE means show on both sides of the splitter, if there is one.
// Need to track initialTimeSource so we can set it in the specs after setting intervals, and then have the current time update (via the clock property).
knockout.track(this, ['_clock', 'opacity', 'treat404AsError', 'ignoreUnknownTileErrors', 'intervals', 'clipToRectangle', 'splitDirection', 'initialTimeSource']);
overrideProperty(this, 'clock', {
get : function() {
var clock = this._clock;
if (!clock && this.intervals && this.intervals.length > 0) {
var startTime = this.intervals.start;
var stopTime = this.intervals.stop;
// Average about 5 seconds per interval.
var totalDuration = JulianDate.secondsDifference(stopTime, startTime);
var numIntervals = this.intervals.length;
var averageDuration = totalDuration / numIntervals;
var timePerSecond = averageDuration / 5;
clock = new DataSourceClock();
clock.startTime = startTime;
clock.stopTime = stopTime;
clock.multiplier = timePerSecond;
setClockCurrentTime(clock, this.initialTimeSource);
}
return clock;
},
set : function(value) {
this._clock = value;
}
});
/**
* Gets javascript dates describing the discrete datetimes (or intervals) available for this item.
* By declaring this as a knockout defined property, it is cached.
* @member {Date[]} An array of discrete dates or intervals available for this item, or [] if none.
* @memberOf ImageryLayerCatalogItem.prototype
*/
knockout.defineProperty(this, 'availableDates', {
get: function() {
if (defined(this.intervals)) {
const datetimes = [];
// Only show the start of each interval. If only time instants were given, this is the instant.
for (let i = 0; i < this.intervals.length; i++) {
datetimes.push(JulianDate.toDate(this.intervals.get(i).start, 3));
}
return datetimes;
}
return [];
}
}, this);
knockout.getObservable(this, 'opacity').subscribe(function() {
updateOpacity(this);
}, this);
knockout.getObservable(this, 'splitDirection').subscribe(function() {
updateSplitDirection(this);
}, this);
};
inherit(CatalogItem, ImageryLayerCatalogItem);
defineProperties(ImageryLayerCatalogItem.prototype, {
/**
* Gets a value indicating whether this {@link ImageryLayerCatalogItem} supports the {@link ImageryLayerCatalogItem#intervals}
* property for configuring time-dynamic imagery.
* @type {Boolean}
*/
supportsIntervals : {
get : function() {
return false;
}
},
/**
* Gets the Cesium or Leaflet imagery layer object associated with this data source.
* This property is undefined if the data source is not enabled.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {Object}
*/
imageryLayer : {
get : function() {
return this._imageryLayer;
}
},
/**
* Gets a value indicating whether this data source, when enabled, can be reordered with respect to other data sources.
* Data sources that cannot be reordered are typically displayed above reorderable data sources.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {Boolean}
*/
supportsReordering : {
get : function() {
return !this.keepOnTop;
}
},
/**
* Gets a value indicating whether the opacity of this data source can be changed.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {Boolean}
*/
supportsOpacity : {
get : function() {
return true;
}
},
/**
* Gets a value indicating whether this layer can be split so that it is
* only shown on the left or right side of the screen.
* @memberOf ImageryLayerCatalogItem.prototype
*/
supportsSplitting : {
get : function() {
return true;
}
},
/**
* Gets the set of functions used to update individual properties in {@link CatalogMember#updateFromJson}.
* When a property name in the returned object literal matches the name of a property on this instance, the value
* will be called as a function and passed a reference to this instance, a reference to the source JSON object
* literal, and the name of the property.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {Object}
*/
updaters : {
get : function() {
return ImageryLayerCatalogItem.defaultUpdaters;
}
},
/**
* Gets the set of functions used to serialize individual properties in {@link CatalogMember#serializeToJson}.
* When a property name on the model matches the name of a property in the serializers object literal,
* the value will be called as a function and passed a reference to the model, a reference to the destination
* JSON object literal, and the name of the property.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {Object}
*/
serializers : {
get : function() {
return ImageryLayerCatalogItem.defaultSerializers;
}
},
/**
* Gets the set of names of the properties to be serialized for this object when {@link CatalogMember#serializeToJson} is called
* for a share link.
* @memberOf ImageryLayerCatalogItem.prototype
* @type {String[]}
*/
propertiesForSharing : {
get : function() {
return ImageryLayerCatalogItem.defaultPropertiesForSharing;
}
}
});
ImageryLayerCatalogItem.defaultUpdaters = clone(CatalogItem.defaultUpdaters);
ImageryLayerCatalogItem.defaultUpdaters.intervals = function(catalogItem, json, propertyName) {
if (!defined(json.intervals)) {
return;
}
if (!catalogItem.supportsIntervals) {
throw new TerriaError({
sender: catalogItem,
title: 'Intervals not supported',
message: 'Sorry, ' + catalogItem.typeName + ' (' + catalogItem.type + ') catalog items cannot currently be made time-varying by specifying the "intervals" property.'
});
}
var result = new TimeIntervalCollection();
for (var i = 0; i < json.intervals.length; ++i) {
var interval = json.intervals[i];
result.addInterval(TimeInterval.fromIso8601({
iso8601: interval.interval,
data: interval.data
}));
}
catalogItem.intervals = result;
};
ImageryLayerCatalogItem.defaultUpdaters.availableDates = function() {
// Do not update/serialize availableDates.
};
freezeObject(ImageryLayerCatalogItem.defaultUpdaters);
ImageryLayerCatalogItem.defaultSerializers = clone(CatalogItem.defaultSerializers);
ImageryLayerCatalogItem.defaultSerializers.intervals = function(catalogItem, json, propertyName) {
if (defined(catalogItem.intervals)) {
var result = [];
for (var i = 0; i < catalogItem.intervals.length; ++i) {
var interval = catalogItem.intervals.get(i);
result.push({
interval: TimeInterval.toIso8601(interval),
data: interval.data
});
}
json.intervals = result;
}
};
// Do not serialize the original intialTimeSource - serialize the current time.
// That way if the item is shared, the desired time is used.
ImageryLayerCatalogItem.defaultSerializers.initialTimeSource = function(catalogItem, json, propertyName) {
if (defined(catalogItem.clock)) {
json.initialTimeSource = JulianDate.toIso8601(catalogItem.clock.currentTime);
} else {
json.initialTimeSource = catalogItem.initialTimeSource;
}
};
ImageryLayerCatalogItem.defaultSerializers.clock = function() {
// Do not serialize the clock when duplicating the item.
// Since this is not shared, it is not serialized for sharing anyway.
};
ImageryLayerCatalogItem.defaultSerializers.availableDates = function() {
// Do not update/serialize availableDates.
};
freezeObject(ImageryLayerCatalogItem.defaultSerializers);
/**
* Gets or sets the default set of properties that are serialized when serializing a {@link CatalogItem}-derived object
* for a share link.
* @type {String[]}
*/
ImageryLayerCatalogItem.defaultPropertiesForSharing = clone(CatalogItem.defaultPropertiesForSharing);
ImageryLayerCatalogItem.defaultPropertiesForSharing.push('opacity');
ImageryLayerCatalogItem.defaultPropertiesForSharing.push('keepOnTop');
ImageryLayerCatalogItem.defaultPropertiesForSharing.push('initialTimeSource');
ImageryLayerCatalogItem.defaultPropertiesForSharing.push('splitDirection');
freezeObject(ImageryLayerCatalogItem.defaultPropertiesForSharing);
/**
* Creates the {@link ImageryProvider} for this catalog item.
* @param {ImageryLayerTime} [time] The time for which to create an imagery provider. In layers that are not time-dynamic,
* this parameter is ignored.
* @return {ImageryProvider} The created imagery provider.
*/
ImageryLayerCatalogItem.prototype.createImageryProvider = function(time) {
var result = this._createImageryProvider(time);
return result;
};
/**
* When implemented in a derived class, creates the {@link ImageryProvider} for this catalog item.
* @abstract
* @protected
* @param {ImageryLayerTime} [time] The time for which to create an imagery provider. In layers that are not time-dynamic,
* this parameter is ignored.
* @return {ImageryProvider} The created imagery provider.
*/
ImageryLayerCatalogItem.prototype._createImageryProvider = function(time) {
throw new DeveloperError('_createImageryProvider must be implemented in the derived class.');
};
ImageryLayerCatalogItem.prototype._enable = function(layerIndex) {
if (defined(this._imageryLayer)) {
return;
}
var isTimeDynamic = false;
var currentTimeIdentifier;
var nextTimeIdentifier;
if (defined(this.intervals) && (this.intervals.length > 0) && defined(this.clock)) {
isTimeDynamic = true;
var index = this.intervals.indexOf(this.clock.currentTime);
// Here we use the terria clock because we want to optomise for the case where the item is playing on the
// timeline (which is linked to the terria clock) and preload the layer at the next time that the timeslider
// will move to.
const multiplier = this.terria.clock.multiplier;
var nextIndex;
if (index < 0) {
this._currentIntervalIndex = -1;
currentTimeIdentifier = undefined;
nextIndex = ~index;
if (multiplier < 0.0) {
--nextIndex;
}
} else {
this._currentIntervalIndex = index;
currentTimeIdentifier = this.intervals.get(index).data;
if (multiplier < 0.0) {
nextIndex = index - 1;
} else {
nextIndex = index + 1;
}
}
// Ideally we should also check (this.terria.clock.clockRange === ClockRange.LOOP_STOP) here (to save preloading
// where it won't be used), but due to initaliseation order this.terria.clock.clockRange is not necessarily in
// the state that it will be when nextIndex is needed. So we make the assumption that this is the most likely
// case and optomise for this (since for the other cases UNBOUNDED / CLAMPED there is nothing to effectively preload).
if (nextIndex === this.intervals.length) {
nextIndex = 0;
}
if (nextIndex >= 0 && nextIndex < this.intervals.length) {
this._nextIntervalIndex = nextIndex;
nextTimeIdentifier = this.intervals.get(nextIndex).data;
} else {
this._nextIntervalIndex = -1;
}
}
if (!isTimeDynamic || defined(currentTimeIdentifier)) {
var currentImageryProvider = this.createImageryProvider(currentTimeIdentifier);
this._imageryLayer = ImageryLayerCatalogItem.enableLayer(this, currentImageryProvider, this.opacity, layerIndex);
}
if (isTimeDynamic) {
this._currentTimeSubscription = knockout.getObservable(this, 'currentTime').subscribe(function() {
onClockTick(this);
}, this);
}
if (defined(nextTimeIdentifier)) {
var nextImageryProvider = this.createImageryProvider(nextTimeIdentifier);
// Do not allow picking from the preloading layer.
nextImageryProvider.enablePickFeatures = false;
this._nextLayer = ImageryLayerCatalogItem.enableLayer(this, nextImageryProvider, 0.0, layerIndex + 1);
}
updateSplitDirection(this);
};
ImageryLayerCatalogItem.prototype._disable = function() {
if (defined(this._currentTimeSubscription)) {
this._currentTimeSubscription.dispose();
this._currentTimeSubscription = undefined;
}
ImageryLayerCatalogItem.disableLayer(this, this._imageryLayer);
this._imageryLayer = undefined;
ImageryLayerCatalogItem.disableLayer(this, this._nextLayer);
this._nextLayer = undefined;
};
ImageryLayerCatalogItem.prototype._show = function() {
// When the layer is not shown .showDataForTime() has no effect so if someone has updated the currentTime while the
// item was not shown update the layer now.
showDataForTime(this, this.currentTime);
ImageryLayerCatalogItem.showLayer(this, this._imageryLayer);
ImageryLayerCatalogItem.showLayer(this, this._nextLayer);
};
ImageryLayerCatalogItem.prototype._hide = function() {
ImageryLayerCatalogItem.hideLayer(this, this._imageryLayer);
ImageryLayerCatalogItem.hideLayer(this, this._nextLayer);
};
ImageryLayerCatalogItem.prototype.showOnSeparateMap = function(globeOrMap) {
var imageryProvider = this._createImageryProvider();
var layer = ImageryLayerCatalogItem.enableLayer(this, imageryProvider, this.opacity, undefined, globeOrMap);
globeOrMap.updateItemForSplitter(this); // equivalent to updateSplitDirection(catalogItem), but for any viewer (globeOrMap).
ImageryLayerCatalogItem.showLayer(this, layer, globeOrMap);
var that = this;
return function() {
ImageryLayerCatalogItem.hideLayer(that, layer, globeOrMap);
ImageryLayerCatalogItem.disableLayer(that, layer, globeOrMap);
};
};
/**
* Refreshes this layer on the map. This is useful when, for example, parameters that went into
* {@link ImageryLayerCatalogItem#_createImageryProvider} change.
*/
ImageryLayerCatalogItem.prototype.refresh = function() {
if (!defined(this._imageryLayer)) {
return;
}
var currentIndex;
if (defined(this.terria.cesium)) {
var imageryLayers = this.terria.cesium.scene.imageryLayers;
currentIndex = imageryLayers.indexOf(this._imageryLayer);
}
this._hide();
this._disable();
if (this.isEnabled) {
this._enable(currentIndex);
if (this.isShown) {
this._show();
}
}
this.terria.currentViewer.notifyRepaintRequired();
};
function updateOpacity(item) {
if (defined(item._imageryLayer) && item.isEnabled && item.isShown) {
if (defined(item._imageryLayer.alpha)) {
item._imageryLayer.alpha = item.opacity;
}
if (defined(item._imageryLayer.setOpacity)) {
item._imageryLayer.setOpacity(item.opacity);
}
item.terria.currentViewer.notifyRepaintRequired();
}
}
function updateSplitDirection(item) {
item.terria.currentViewer.updateItemForSplitter(item);
}
ImageryLayerCatalogItem.enableLayer = function(catalogItem, imageryProvider, opacity, layerIndex, globeOrMap) {
globeOrMap = defaultValue(globeOrMap, catalogItem.terria.currentViewer);
let tileFailures = 0;
const layer = globeOrMap.addImageryProvider({
imageryProvider: imageryProvider,
rectangle: catalogItem.rectangle,
clipToRectangle: catalogItem.clipToRectangle,
opacity: opacity,
layerIndex: layerIndex,
treat403AsError: catalogItem.treat403AsError,
treat404AsError: catalogItem.treat404AsError,
ignoreUnknownTileErrors: catalogItem.ignoreUnknownTileErrors,
isRequiredForRendering: catalogItem.isRequiredForRendering,
onLoadError: function(tileProviderError) {
if (!defined(layer) || !globeOrMap.isImageryLayerShown({layer})) {
// If the layer is no longer shown, ignore errors and don't retry.
return undefined;
}
if (tileProviderError.timesRetried === 0) {
tileFailures = 0;
}
tileProviderError.retry = undefined;
if (defined(tileProviderError.error) && defined(tileProviderError.error.statusCode)) {
tileProviderError.retry = when.reject(tileProviderError.error);
} else if (defined(tileProviderError.x) && defined(tileProviderError.y) && defined(tileProviderError.level)) {
// Something went wrong, but we don't really know what, probably because this is a failed image load.
// Browsers tell us almost nothing on a failed image load.
// So re-do the request with XMLHttpRequest so we can get more details of the failure. We need to
// hackily get the URL out in order to do this.
const tileUrl = getUrlForImageryTile(imageryProvider, tileProviderError.x, tileProviderError.y, tileProviderError.level);
if (tileUrl) {
tileProviderError.retry = loadWithXhr({
url: tileUrl
});
}
}
if (!tileProviderError.retry) {
// We couldn't get any details. Oh well, carry on.
tileProviderError.retry = when.reject(tileProviderError.error);
}
if (catalogItem.handleTileError) {
tileProviderError.retry = catalogItem.handleTileError(tileProviderError.retry, imageryProvider, tileProviderError.x, tileProviderError.y, tileProviderError.level);
}
tileProviderError.retry = tileProviderError.retry.then(function() {
// Hey wait, the request succeeded this time! Let's just try the image again, then.
// Just letting the retry promise resolve will trigger a retry of the image load.
});
tileProviderError.retry = tileProviderError.retry.otherwise(function(e) {
e = e || {};
// The details request failed, as expected, and now we should know why. We can trigger retry
// by resolving or give up by rejecting, depending on the detailed nature of the failure.
// We're only concerned about failures for tiles that actually overlap this item's extent.
if (defined(catalogItem.rectangle)) {
var tilingScheme = imageryProvider.tilingScheme;
var tileExtent = tilingScheme.tileXYToRectangle(tileProviderError.x, tileProviderError.y, tileProviderError.level);
var intersection = Rectangle.intersection(tileExtent, catalogItem.rectangle);
if (!defined(intersection)) {
return when.reject(e); // tile failed and that's ok
}
}
// Note that ignoreUnknownTileErrors is only for genuinely unknown (no status code) issues.
if (e.statusCode === 404) {
if(!catalogItem.treat404AsError) {
return when.reject(e); // tile failed and that's ok
}
} else if (e.statusCode === 403) {
if(!catalogItem.treat403AsError) {
return when.reject(e); // tile failed and that's ok
}
} else if (catalogItem.ignoreUnknownTileErrors && !defined(e.statusCode)) {
return when.reject(e); // tile failed and that's ok
}
// This failure is not ok. We allow a few "not ok" failures and then turn off the layer and
// display a message to the user.
// Retry 5 times. We can't count on the TileProviderError's accounting of the times retried because it will
// quickly spin up without taking into account our logic above that allows some types of failures.
++tileFailures;
if (tileFailures <= 5) {
// Let this tile fail silently, even though it probably indicates something wrong with the server.
return when.reject(e);
}
// Too many failures!
if (defined(layer) && globeOrMap.isImageryLayerShown({layer})) {
if (catalogItem === catalogItem.terria.baseMap ||
catalogItem.terria.baseMap instanceof CompositeCatalogItem && catalogItem.terria.baseMap.items.indexOf(catalogItem) >= 0) {
globeOrMap.terria.error.raiseEvent(new TerriaError({
sender: catalogItem,
title: 'Error accessing base map',
message:
'An error occurred while attempting to download tiles for base map ' + catalogItem.terria.baseMap.name + '. This may indicate that there is a ' +
'problem with your internet connection, that the base map is temporarily unavailable, or that the base map ' +
'is invalid. Please select a different base map using the Map button in the top-right corner. Further technical details may be found below.<br/><br/>' +
'<pre>' + formatError(e) + '</pre>'
}));
globeOrMap.hideImageryLayer({
layer: layer
});
catalogItem.terria.baseMap = undefined;
// Don't use this base map again on startup.
catalogItem.terria.setLocalProperty('basemap', undefined);
} else {
globeOrMap.terria.error.raiseEvent(new TerriaError({
sender: catalogItem,
title: 'Error accessing catalogue item',
message:
'An error occurred while attempting to download tiles for catalogue item ' + catalogItem.name + '. This may indicate that there is a ' +
'problem with your internet connection, that the catalogue item is temporarily unavailable, or that the catalogue item ' +
'is invalid. The catalogue item has been hidden from the map. You may re-show it in the Now Viewing panel to try again. Further technical details may be found below.<br/><br/>' +
'<pre>' + formatError(e) + '</pre>'
}));
if (globeOrMap === catalogItem.terria.currentViewer) {
catalogItem.isShown = false;
} else {
globeOrMap.hideImageryLayer({
layer: layer
});
}
}
}
// Don't retry again.
return when.reject(e);
});
},
onProjectionError: function() {
// If the TileLayer experiences an error, hide the catalog item and inform the user.
globeOrMap.terria.error.raiseEvent({
sender: catalogItem,
title: 'Unable to display dataset',
message:
catalogItem.name + '" cannot be shown in 2D because it does not support the standard Web Mercator (EPSG:3857) projection. ' +
'Please switch to 3D if it is supported on your system, update the dataset to support the projection, or use a different dataset.'
});
if (globeOrMap === catalogItem.terria.currentViewer) {
catalogItem.isShown = false;
} else {
globeOrMap.hideImageryLayer({
layer: layer
});
}
}
});
return layer;
};
ImageryLayerCatalogItem.disableLayer = function(catalogItem, layer, globeOrMap) {
if (!defined(layer)) {
return;
}
globeOrMap = defaultValue(globeOrMap, catalogItem.terria.currentViewer);
globeOrMap.removeImageryLayer({
layer: layer
});
};
function showDataForTime(catalogItem, currentTime, preloadNext) {
if (!defined(currentTime)) {
return;
}
var intervals = catalogItem.intervals;
if (!defined(intervals) || !catalogItem.isEnabled || !catalogItem.isShown) {
return;
}
const preload = defaultValue(preloadNext, true);
var index = catalogItem._currentIntervalIndex;
if (index < 0 || index >= intervals.length || !TimeInterval.contains(intervals.get(index), currentTime)) {
// Find the interval containing the current time.
index = intervals.indexOf(currentTime);
if (index < 0) {
// No interval contains this time, so do not show imagery at this time.
ImageryLayerCatalogItem.disableLayer(catalogItem, catalogItem._imageryLayer);
catalogItem._imageryLayer = undefined;
catalogItem._currentIntervalIndex = -1;
return;
}
// If the "next" layer is not applicable to this time, throw it away and create a new one.
if (index !== catalogItem._nextIntervalIndex) {
// Throw away the "next" layer, since it's not applicable.
ImageryLayerCatalogItem.disableLayer(catalogItem, catalogItem._nextLayer);
catalogItem._nextLayer = undefined;
catalogItem._nextIntervalIndex = -1;
// Create the new "next" layer
var imageryProvider = catalogItem.createImageryProvider(catalogItem.intervals.get(index).data);
imageryProvider.enablePickFeatures = false;
catalogItem._nextLayer = ImageryLayerCatalogItem.enableLayer(catalogItem, imageryProvider, 0.0);
updateSplitDirection(catalogItem);
ImageryLayerCatalogItem.showLayer(catalogItem, catalogItem._nextLayer);
}
// At this point we can assume that _nextLayer is applicable to this time.
// Make it visible
setOpacity(catalogItem, catalogItem._nextLayer, catalogItem.opacity);
fixNextLayerOrder(catalogItem, catalogItem._imageryLayer, catalogItem._nextLayer);
ImageryLayerCatalogItem.disableLayer(catalogItem, catalogItem._imageryLayer);
catalogItem._imageryLayer = catalogItem._nextLayer;
if (defined(catalogItem._nextLayer))
{
catalogItem._imageryLayer.imageryProvider.enablePickFeatures = true;
}
catalogItem._nextLayer = undefined;
catalogItem._nextIntervalIndex = -1;
catalogItem._currentIntervalIndex = index;
if (preload) {
// Prefetch the (predicted) next layer.
// Here we use the terria clock because we want to optomise for the case where the item is playing on the
// timeline (which is linked to the terria clock) and preload the layer at the next time that the timeslider
// will move to.
var nextIndex = catalogItem.terria.clock.multiplier >= 0.0 ? index + 1 : index - 1;
if ((nextIndex === intervals.length) && (catalogItem.terria.clock.clockRange === ClockRange.LOOP_STOP)) {
nextIndex = 0;
}
if (nextIndex >= 0 && nextIndex < intervals.length && nextIndex !== catalogItem._nextIntervalIndex) {
var nextImageryProvider = catalogItem.createImageryProvider(catalogItem.intervals.get(nextIndex).data);
nextImageryProvider.enablePickFeatures = false;
catalogItem._nextLayer = ImageryLayerCatalogItem.enableLayer(catalogItem, nextImageryProvider, 0.0);
updateSplitDirection(catalogItem);
ImageryLayerCatalogItem.showLayer(catalogItem, catalogItem._nextLayer);
catalogItem._nextIntervalIndex = nextIndex;
}
}
}
}
function onClockTick(catalogItem) {
var intervals = catalogItem.intervals;
if (!defined(intervals) || !catalogItem.isEnabled || !catalogItem.isShown || !defined(catalogItem.clock)) {
return;
}
showDataForTime(catalogItem, catalogItem.clock.currentTime);
}
ImageryLayerCatalogItem.showLayer = function(catalogItem, layer, globeOrMap) {
if (!defined(layer)) {
return;
}
globeOrMap = defaultValue(globeOrMap, catalogItem.terria.currentViewer);
globeOrMap.showImageryLayer({
layer: layer
});
};
ImageryLayerCatalogItem.hideLayer = function(catalogItem, layer, globeOrMap) {
if (!defined(layer)) {
return;
}
globeOrMap = defaultValue(globeOrMap, catalogItem.terria.currentViewer);
globeOrMap.hideImageryLayer({
layer: layer
});
};
module.exports = ImageryLayerCatalogItem;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="AbsCode.html">AbsCode</a></li><li><a href="AbsConcept.html">AbsConcept</a></li><li><a href="AbsDataset.html">AbsDataset</a></li><li><a href="AbsIttCatalogGroup.html">AbsIttCatalogGroup</a></li><li><a href="AbsIttCatalogItem.html">AbsIttCatalogItem</a></li><li><a href="AddressGeocoder.html">AddressGeocoder</a></li><li><a href="ArcGisCatalogGroup.html">ArcGisCatalogGroup</a></li><li><a href="ArcGisFeatureServerCatalogGroup.html">ArcGisFeatureServerCatalogGroup</a></li><li><a href="ArcGisFeatureServerCatalogItem.html">ArcGisFeatureServerCatalogItem</a></li><li><a href="ArcGisMapServerCatalogGroup.html">ArcGisMapServerCatalogGroup</a></li><li><a href="ArcGisMapServerCatalogItem.html">ArcGisMapServerCatalogItem</a></li><li><a href="AugmentedVirtuality.html">AugmentedVirtuality</a></li><li><a href="BingMapsCatalogItem.html">BingMapsCatalogItem</a></li><li><a href="BooleanParameter.html">BooleanParameter</a></li><li><a href="BulkAddressGeocoderResult.html">BulkAddressGeocoderResult</a></li><li><a href="CameraView.html">CameraView</a></li><li><a href="Catalog.html">Catalog</a></li><li><a href="CatalogFunction.html">CatalogFunction</a></li><li><a href="CatalogGroup.html">CatalogGroup</a></li><li><a href="CatalogItem.html">CatalogItem</a></li><li><a href="CatalogMember.html">CatalogMember</a></li><li><a href="Cesium.html">Cesium</a></li><li><a href="Cesium3DTilesCatalogItem.html">Cesium3DTilesCatalogItem</a></li><li><a href="CesiumDragPoints.html">CesiumDragPoints</a></li><li><a href="CesiumTerrainCatalogItem.html">CesiumTerrainCatalogItem</a></li><li><a href="CkanCatalogGroup.html">CkanCatalogGroup</a></li><li><a href="CkanCatalogItem.html">CkanCatalogItem</a></li><li><a href="Clock.html">Clock</a></li><li><a href="CompositeCatalogItem.html">CompositeCatalogItem</a></li><li><a href="Concept.html">Concept</a></li><li><a href="CorsProxy.html">CorsProxy</a></li><li><a href="CsvCatalogItem.html">CsvCatalogItem</a></li><li><a href="CswCatalogGroup.html">CswCatalogGroup</a></li><li><a href="CustomComponentType.html">CustomComponentType</a></li><li><a href="CzmlCatalogItem.html">CzmlCatalogItem</a></li><li><a href="DataSourceCatalogItem.html">DataSourceCatalogItem</a></li><li><a href="DateTimeParameter.html">DateTimeParameter</a></li><li><a href="DisplayVariablesConcept.html">DisplayVariablesConcept</a></li><li><a href="EnumerationParameter.html">EnumerationParameter</a></li><li><a href="Feature.html">Feature</a></li><li><a href="FunctionParameter.html">FunctionParameter</a></li><li><a href="GeoJsonCatalogItem.html">GeoJsonCatalogItem</a></li><li><a href="GlobeOrMap.html">GlobeOrMap</a></li><li><a href="GnafAddressGeocoder.html">GnafAddressGeocoder</a></li><li><a href="GnafApi.html">GnafApi</a></li><li><a href="GnafSearchProviderViewModel.html">GnafSearchProviderViewModel</a></li><li><a href="GpxCatalogItem.html">GpxCatalogItem</a></li><li><a href="HelpScreen.html">HelpScreen</a></li><li><a href="HelpSequence.html">HelpSequence</a></li><li><a href="HelpSequences.html">HelpSequences</a></li><li><a href="HelpViewState.html">HelpViewState</a></li><li><a href="ImageryLayerCatalogItem____.html">ImageryLayerCatalogItem</a></li><li><a href="IonImageryCatalogItem.html">IonImageryCatalogItem</a></li><li><a href="KmlCatalogItem.html">KmlCatalogItem</a></li><li><a href="Leaflet.html">Leaflet</a></li><li><a href="LeafletDataSourceDisplay.html">LeafletDataSourceDisplay</a></li><li><a href="LeafletDragPoints.html">LeafletDragPoints</a></li><li><a href="LeafletGeomVisualizer.html">LeafletGeomVisualizer</a></li><li><a href="LegendHelper.html">LegendHelper</a></li><li><a href="LegendUrl.html">LegendUrl</a></li><li><a href="LineParameter.html">LineParameter</a></li><li><a href="MagdaCatalogItem.html">MagdaCatalogItem</a></li><li><a href="MapboxMapCatalogItem.html">MapboxMapCatalogItem</a></li><li><a href="MapInteractionMode.html">MapInteractionMode</a></li><li><a href="Metadata.html">Metadata</a></li><li><a href="MetadataItem.html">MetadataItem</a></li><li><a href="module.html#.exports">exports</a></li><li><a href="OgrCatalogItem.html">OgrCatalogItem</a></li><li><a href="OpenStreetMapCatalogItem.html">OpenStreetMapCatalogItem</a></li><li><a href="PlacesLikeMeCatalogfunction.html">PlacesLikeMeCatalogfunction</a></li><li><a href="PointParameter.html">PointParameter</a></li><li><a href="Polling.html">Polling</a></li><li><a href="PolygonParameter.html">PolygonParameter</a></li><li><a href="RectangleParameter.html">RectangleParameter</a></li><li><a href="RegionDataParameter.html">RegionDataParameter</a></li><li><a href="RegionMapping.html">RegionMapping</a></li><li><a href="RegionParameter.html">RegionParameter</a></li><li><a href="RegionProvider.html">RegionProvider</a></li><li><a href="RegionProviderList.html">RegionProviderList</a></li><li><a href="RegionTypeParameter.html">RegionTypeParameter</a></li><li><a href="ResultPendingCatalogItem.html">ResultPendingCatalogItem</a></li><li><a href="SdmxJsonCatalogItem.html">SdmxJsonCatalogItem</a></li><li><a href="SensorObservationServiceCatalogItem.html">SensorObservationServiceCatalogItem</a></li><li><a href="SocrataCatalogGroup.html">SocrataCatalogGroup</a></li><li><a href="SpatialDetailingCatalogFunction.html">SpatialDetailingCatalogFunction</a></li><li><a href="StringParameter.html">StringParameter</a></li><li><a href="SummaryConcept.html">SummaryConcept</a></li><li><a href="TableCatalogItem.html">TableCatalogItem</a></li><li><a href="TableColumn.html">TableColumn</a></li><li><a href="TableColumnStyle.html">TableColumnStyle</a></li><li><a href="TableDataSource.html">TableDataSource</a></li><li><a href="TableStructure.html">TableStructure</a></li><li><a href="TableStyle.html">TableStyle</a></li><li><a href="TerrainCatalogItem.html">TerrainCatalogItem</a></li><li><a href="Terria.html">Terria</a></li><li><a href="TerriaError.html">TerriaError</a></li><li><a href="TerriaJsonCatalogFunction.html">TerriaJsonCatalogFunction</a></li><li><a href="TimeSeriesStack.html">TimeSeriesStack</a></li><li><a href="UrlTemplateCatalogItem.html">UrlTemplateCatalogItem</a></li><li><a href="UrthecastCatalogGroup.html">UrthecastCatalogGroup</a></li><li><a href="UrthecastServerCatalogItem.html">UrthecastServerCatalogItem</a></li><li><a href="UserDrawing.html">UserDrawing</a></li><li><a href="VariableConcept.html">VariableConcept</a></li><li><a href="ViewerModes..html">ViewerModes.</a></li><li><a href="WebFeatureServiceCatalogGroup.html">WebFeatureServiceCatalogGroup</a></li><li><a href="WebFeatureServiceCatalogItem.html">WebFeatureServiceCatalogItem</a></li><li><a href="WebMapServiceCatalogGroup.html">WebMapServiceCatalogGroup</a></li><li><a href="WebMapServiceCatalogItem.html">WebMapServiceCatalogItem</a></li><li><a href="WebMapTileServiceCatalogGroup.html">WebMapTileServiceCatalogGroup</a></li><li><a href="WebMapTileServiceCatalogItem.html">WebMapTileServiceCatalogItem</a></li><li><a href="WebProcessingServiceCatalogFunction.html">WebProcessingServiceCatalogFunction</a></li><li><a href="WebProcessingServiceCatalogGroup.html">WebProcessingServiceCatalogGroup</a></li><li><a href="WebProcessingServiceCatalogItem.html">WebProcessingServiceCatalogItem</a></li><li><a href="WfsFeaturesCatalogGroup.html">WfsFeaturesCatalogGroup</a></li><li><a href="WhyAmISpecialCatalogFunction.html">WhyAmISpecialCatalogFunction</a></li></ul><h3>Global</h3><ul><li><a href="global.html#_bumpyTerrainProvider">_bumpyTerrainProvider</a></li><li><a href="global.html#_terrain">_terrain</a></li><li><a href="global.html#activeTimeColumnNameIdOrIndex">activeTimeColumnNameIdOrIndex</a></li><li><a href="global.html#addBoundingBox">addBoundingBox</a></li><li><a href="global.html#addMarker">addMarker</a></li><li><a href="global.html#addUserCatalogMember">addUserCatalogMember</a></li><li><a href="global.html#allFeaturesAvailablePromise">allFeaturesAvailablePromise</a></li><li><a href="global.html#allShareKeys">allShareKeys</a></li><li><a href="global.html#arrayProduct">arrayProduct</a></li><li><a href="global.html#barHeightMax">barHeightMax</a></li><li><a href="global.html#barHeightMin">barHeightMin</a></li><li><a href="global.html#barLeft">barLeft</a></li><li><a href="global.html#barTop">barTop</a></li><li><a href="global.html#buildEmptyAccumulator">buildEmptyAccumulator</a></li><li><a href="global.html#buildRequestData">buildRequestData</a></li><li><a href="global.html#buildShareLink">buildShareLink</a></li><li><a href="global.html#buildShortShareLink">buildShortShareLink</a></li><li><a href="global.html#calculateFinishDatesFromStartDates">calculateFinishDatesFromStartDates</a></li><li><a href="global.html#canShorten">canShorten</a></li><li><a href="global.html#categoryName">categoryName</a></li><li><a href="global.html#ChartData">ChartData</a></li><li><a href="global.html#color">color</a></li><li><a href="global.html#ColorMap">ColorMap</a></li><li><a href="global.html#combineData">combineData</a></li><li><a href="global.html#combineFilters">combineFilters</a></li><li><a href="global.html#combineRepeated">combineRepeated</a></li><li><a href="global.html#combineValueArrays">combineValueArrays</a></li><li><a href="global.html#computeRingWindingOrder">computeRingWindingOrder</a></li><li><a href="global.html#computeScreenSpacePosition">computeScreenSpacePosition</a></li><li><a href="global.html#config">config</a></li><li><a href="global.html#containsAny">containsAny</a></li><li><a href="global.html#convertLuceneHit">convertLuceneHit</a></li><li><a href="global.html#convertToDates">convertToDates</a></li><li><a href="global.html#correctEntityHeight">correctEntityHeight</a></li><li><a href="global.html#createCatalogItemFromFileOrUrl">createCatalogItemFromFileOrUrl</a></li><li><a href="global.html#createCatalogItemFromUrl">createCatalogItemFromUrl</a></li><li><a href="global.html#createCatalogMemberFromType">createCatalogMemberFromType</a></li><li><a href="global.html#createLeafletCredit">createLeafletCredit</a></li><li><a href="global.html#createParameterFromType">createParameterFromType</a></li><li><a href="global.html#createRegexDeserializer">createRegexDeserializer</a></li><li><a href="global.html#createRegexSerializer">createRegexSerializer</a></li><li><a href="global.html#cssClass">cssClass</a></li><li><a href="global.html#CustomComponents">CustomComponents</a></li><li><a href="global.html#deIndexWithDescendants">deIndexWithDescendants</a></li><li><a href="global.html#Description">Description</a></li><li><a href="global.html#direction">direction</a></li><li><a href="global.html#disposeSubscription">disposeSubscription</a></li><li><a href="global.html#EarthGravityModel1996">EarthGravityModel1996</a></li><li><a href="global.html#error">error</a></li><li><a href="global.html#extendLoad">extendLoad</a></li><li><a href="global.html#extent">extent</a></li><li><a href="global.html#featureClicked">featureClicked</a></li><li><a href="global.html#featureDataToGeoJson">featureDataToGeoJson</a></li><li><a href="global.html#featureMousedown">featureMousedown</a></li><li><a href="global.html#features">features</a></li><li><a href="global.html#findKeyForGroupElement">findKeyForGroupElement</a></li><li><a href="global.html#flattenCatalog">flattenCatalog</a></li><li><a href="global.html#formatDate">formatDate</a></li><li><a href="global.html#formatDateTime">formatDateTime</a></li><li><a href="global.html#formatNumberForLocale">formatNumberForLocale</a></li><li><a href="global.html#formatPropertyValue">formatPropertyValue</a></li><li><a href="global.html#formatTime">formatTime</a></li><li><a href="global.html#getAncestors">getAncestors</a></li><li><a href="global.html#getColumnOptions">getColumnOptions</a></li><li><a href="global.html#getColumnWithNameIdOrIndex">getColumnWithNameIdOrIndex</a></li><li><a href="global.html#getDataUriFormat">getDataUriFormat</a></li><li><a href="global.html#getGroupChildren">getGroupChildren</a></li><li><a href="global.html#getShareData">getShareData</a></li><li><a href="global.html#getTemporalFiltersContext">getTemporalFiltersContext</a></li><li><a href="global.html#getUniqueValues">getUniqueValues</a></li><li><a href="global.html#gmlToGeoJson">gmlToGeoJson</a></li><li><a href="global.html#gradientColorMap">gradientColorMap</a></li><li><a href="