/*
* Copyright 2015-2017 WorldWind Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports WmtsLayer
*/
define([
'../util/AbsentResourceList',
'../error/ArgumentError',
'../util/Logger',
'../geom/Sector',
'../layer/Layer',
'../cache/MemoryCache',
'../render/Texture',
'../util/WmsUrlBuilder',
'../layer/WmtsLayerTile',
'../util/WWMath',
'../util/WWUtil'
],
function (AbsentResourceList,
ArgumentError,
Logger,
Sector,
Layer,
MemoryCache,
Texture,
WmsUrlBuilder,
WmtsLayerTile,
WWMath,
WWUtil) {
"use strict";
// TODO: Test Mercator layers.
// TODO: Support tile matrix limits.
// TODO: Extensibility for other projections.
// TODO: Finish parsing capabilities document (ServiceIdentification and ServiceProvider).
// TODO: Time dimensions.
/**
* Constructs a WMTS image layer.
* @alias WmtsLayer
* @constructor
* @augments Layer
* @classdesc Displays a WMTS image layer.
* @param {{}} config Specifies configuration information for the layer. Must contain the following
* properties:
* <ul>
* <li>identifier: {String} The layer name.</li>
* <li>service: {String} The URL of the WMTS server</li>
* <li>format: {String} The mime type of the image format to request, e.g., image/png.</li>
* <li>tileMatrixSet: {{}} The tile matrix set to use for this layer.</li>
* <li>style: {String} The style to use for this layer.</li>
* <li>title: {String} The display name for this layer.</li>
* </ul>
* @param {String} timeString The time parameter passed to the WMTS server when imagery is requested. May be
* null, in which case no time parameter is passed to the server.
* @throws {ArgumentError} If the specified layer capabilities reference is null or undefined.
*/
var WmtsLayer = function (config, timeString) {
if (!config) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
"No layer configuration specified."));
}
Layer.call(this, "WMTS Layer");
/**
* The WMTS layer identifier of this layer.
* @type {String}
* @readonly
*/
this.layerIdentifier = config.identifier;
/**
* The style identifier specified to this layer's constructor.
* @type {String}
* @readonly
*/
this.styleIdentifier = config.style;
/**
* The time string passed to this layer's constructor.
* @type {String}
* @readonly
*/
this.timeString = timeString;
/**
* The image format specified to this layer's constructor.
* @type {String}
* @readonly
*/
this.imageFormat = config.format;
/**
* The url specified to this layer's constructor.
* @type {String}
* @readonly
*/
this.resourceUrl = config.resourceUrl;
this.serviceUrl = config.service;
/**
* The tileMatrixSet specified to this layer's constructor.
* @type {String}
* @readonly
*/
this.tileMatrixSet = config.tileMatrixSet;
// Determine the layer's sector if possible. Mandatory for EPSG:4326 tile matrix sets. (Others compute
// it from tile Matrix Set metadata.)
// Sometimes BBOX defined in Matrix and not in Layer
if (!config.wgs84BoundingBox && !config.boundingBox) {
if (this.tileMatrixSet.boundingBox) {
this.sector = new Sector(
config.tileMatrixSet.boundingBox.lowerCorner[1],
config.tileMatrixSet.boundingBox.upperCorner[1],
config.tileMatrixSet.boundingBox.lowerCorner[0],
config.tileMatrixSet.boundingBox.upperCorner[0]);
} else {
// Throw an exception if there is no bounding box.
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
"No bounding box was specified in the layer or tile matrix set capabilities."));
}
} else if (config.wgs84BoundingBox) {
this.sector = config.wgs84BoundingBox.getSector();
} else if (this.tileMatrixSet.boundingBox &&
WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.boundingBox.crs)) {
this.sector = new Sector(
this.tileMatrixSet.boundingBox.lowerCorner[1],
this.tileMatrixSet.boundingBox.upperCorner[1],
this.tileMatrixSet.boundingBox.lowerCorner[0],
this.tileMatrixSet.boundingBox.upperCorner[0]);
} else if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
// Throw an exception if there is no 4326 bounding box.
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
"No EPSG:4326 bounding box was specified in the layer or tile matrix set capabilities."));
}
// Check if tile subdivision is valid
var tileMatrix = config.tileMatrixSet.tileMatrix,
widthArray = [],
heightArray = [],
invalidLevel;
tileMatrix.forEach(function (matrix) {
widthArray.push(matrix.matrixWidth);
heightArray.push(matrix.matrixHeight);
});
if (WmtsLayer.checkTileSubdivision(widthArray) !== 0) {
invalidLevel = WmtsLayer.checkTileSubdivision(widthArray);
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
"Tile subdivision not supported for layer : " + config.identifier + ". Display until level " + (invalidLevel - 1));
tileMatrix.splice(invalidLevel);
} else if (WmtsLayer.checkTileSubdivision(heightArray) !== 0) {
invalidLevel = WmtsLayer.checkTileSubdivision(heightArray);
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
"Tile subdivision not supported for layer : " + config.identifier + ". Display until level " + (invalidLevel - 1));
tileMatrix.splice(invalidLevel);
}
// Form a unique string to identify cache entries.
this.cachePath = (this.resourceUrl || this.serviceUrl) +
this.layerIdentifier + this.styleIdentifier + this.tileMatrixSet.identifier;
if (timeString) {
this.cachePath = this.cachePath + timeString;
}
/**
* The displayName specified to this layer's constructor.
* @type {String}
* @readonly
*/
this.displayName = config.title;
this.currentTiles = [];
this.currentTilesInvalid = true;
this.tileCache = new MemoryCache(500, 400);
this.currentRetrievals = [];
this.absentResourceList = new AbsentResourceList(3, 50e3);
this.pickEnabled = false;
/**
* Controls the level of detail switching for this layer. The next highest resolution level is
* used when an image's texel size is greater than this number of pixels, up to the maximum resolution
* of this layer.
* @type {Number}
* @default 1.75
*/
this.detailControl = 1.75;
};
WmtsLayer.checkTileSubdivision = function (dimensionArray) {
if (dimensionArray.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "checkTileSubdivision",
"Empty dimension array"));
}
var ratio,
invalidLevel = 0,
i = 0;
while (++i < dimensionArray.length && invalidLevel == 0) {
var newRatio = dimensionArray[i] / dimensionArray[i - 1];
// If the ratio is not an integer, the level is invalid
if ((dimensionArray[i] % dimensionArray[i - 1]) !== 0) {
invalidLevel = i;
} else if (ratio && (ratio !== newRatio)) {
// If ratios are different, the level is invalid
invalidLevel = i;
}
ratio = newRatio;
}
// Tile subdivision is valid when invalidLevel == 0
return invalidLevel;
};
/**
* Constructs a tile matrix set object.
* @param {{}} params Specifies parameters for the tile matrix set. Must contain the following
* properties:
* <ul>
* <li>matrixSet: {String} The matrix name.</li>
* <li>prefix: {Boolean} It represents if the identifier of the matrix must be prefixed by the matrix name.</li>
* <li>projection: {String} The projection of the tiles.</li>
* <li>topLeftCorner: {Array} The coordinates of the top left corner.</li>
* <li>extent: {Array} The boundinx box for this matrix.</li>
* <li>resolutions: {Array} The resolutions array.</li>
* <li>matrixSet: {Number} The tile size.</li>
* </ul>
* @throws {ArgumentError} If the specified params.matrixSet is null or undefined. The name of the matrix to
* use for this layer.
* @throws {ArgumentError} If the specified params.prefix is null or undefined. It represents if the
* identifier of the matrix must be prefixed by the matrix name
* @throws {ArgumentError} If the specified params.projection is null or undefined.
* @throws {ArgumentError} If the specified params.extent is null or undefined.
* @throws {ArgumentError} If the specified params.resolutions is null or undefined.
* @throws {ArgumentError} If the specified params.tileSize is null or undefined.
* @throws {ArgumentError} If the specified params.topLeftCorner is null or undefined.
*/
WmtsLayer.createTileMatrixSet = function (params) {
if (!params.matrixSet) { // matrixSet
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No matrixSet provided."));
}
if (!params.projection) { // projection
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No projection provided."));
}
if (!params.extent || params.extent.length != 4) { // extent
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No extent provided."));
}
// Define the boundingBox
var boundingBox = {
lowerCorner: [params.extent[0], params.extent[1]],
upperCorner: [params.extent[2], params.extent[3]]
};
// Resolutions
if (!params.resolutions) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No resolutions provided."));
}
// Tile size
if (!params.tileSize) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No tile size provided."));
}
// Top left corner
if (!params.topLeftCorner || params.topLeftCorner.length != 2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"No extent provided."));
}
// Prefix
if (params.prefix === undefined) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"Prefix not provided."));
}
// Check if the projection is supported
if (!(WmtsLayer.isEpsg4326Crs(params.projection) || WmtsLayer.isOGCCrs84(params.projection) || WmtsLayer.isEpsg3857Crs(params.projection))) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
"Projection provided not supported."));
}
var tileMatrixSet = [],
scale;
// Construct the tileMatrixSet
for (var i = 0; i < params.resolutions.length; i++) {
// Compute the scaleDenominator
if (WmtsLayer.isEpsg4326Crs(params.projection) || WmtsLayer.isOGCCrs84(params.projection)) {
scale = params.resolutions[i] * 6378137.0 * 2.0 * Math.PI / 360 / 0.00028;
} else if (WmtsLayer.isEpsg3857Crs(params.projection)) {
scale = params.resolutions[i] / 0.00028;
}
// Compute the matrix width / height
var unitWidth = params.tileSize * params.resolutions[i];
var unitHeight = params.tileSize * params.resolutions[i];
var matrixWidth = Math.ceil((params.extent[2] - params.extent[0] - 0.01 * unitWidth) / unitWidth);
var matrixHeight = Math.ceil((params.extent[3] - params.extent[1] - 0.01 * unitHeight) / unitHeight);
// Define the tile matrix
var tileMatrix = {
identifier: params.prefix ? params.matrixSet + ":" + i : i,
levelNumber: i,
matrixHeight: matrixHeight,
matrixWidth: matrixWidth,
tileHeight: params.tileSize,
tileWidth: params.tileSize,
topLeftCorner: params.topLeftCorner,
scaleDenominator: scale
};
tileMatrixSet.push(tileMatrix);
}
return {
identifier: params.matrixSet,
supportedCRS: params.projection,
boundingBox: boundingBox,
tileMatrix: tileMatrixSet
};
};
/**
* Forms a configuration object for a specified {@link WmtsLayerCapabilities} layer description. The
* configuration object created and returned is suitable for passing to the WmtsLayer constructor.
* <p>
* This method also parses any time dimensions associated with the layer and returns them in the
* configuration object's "timeSequences" property. This property is a mixed array of Date objects
* and {@link PeriodicTimeSequence} objects describing the dimensions found.
* @param wmtsLayerCapabilities {WmtsLayerCapabilities} The WMTS layer capabilities to create a configuration for.
* @param style {string} The style to apply for this layer. May be null, in which case the first style recognized is used.
* @param matrixSet {string} The matrix to use for this layer. May be null, in which case the first tileMatrixSet recognized is used.
* @param imageFormat {string} The image format to use with this layer. May be null, in which case the first image format recognized is used.
* @returns {{}} A configuration object.
* @throws {ArgumentError} If the specified WMTS layer capabilities is null or undefined.
*/
WmtsLayer.formLayerConfiguration = function (wmtsLayerCapabilities, style, matrixSet, imageFormat) {
var config = {};
/**
* The WMTS layer identifier of this layer.
* @type {String}
* @readonly
*/
config.identifier = wmtsLayerCapabilities.identifier;
// Validate that the specified image format exists, or determine one if not specified.
if (imageFormat) {
var formatIdentifierFound = false;
for (var i = 0; i < wmtsLayerCapabilities.format.length; i++) {
if (wmtsLayerCapabilities.format[i] === imageFormat) {
formatIdentifierFound = true;
config.format = wmtsLayerCapabilities.format[i];
break;
}
}
if (!formatIdentifierFound) {
Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
"The specified image format is not available. Another one will be used.");
config.format = null;
}
}
if (!config.format) {
if (wmtsLayerCapabilities.format.indexOf("image/png") >= 0) {
config.format = "image/png";
} else if (wmtsLayerCapabilities.format.indexOf("image/jpeg") >= 0) {
config.format = "image/jpeg";
} else if (wmtsLayerCapabilities.format.indexOf("image/tiff") >= 0) {
config.format = "image/tiff";
} else if (wmtsLayerCapabilities.format.indexOf("image/gif") >= 0) {
config.format = "image/gif";
} else {
config.format = wmtsLayerCapabilities.format[0];
}
}
if (!config.format) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
"Layer does not provide a supported image format."));
}
// Configure URL
if (wmtsLayerCapabilities.resourceUrl && (wmtsLayerCapabilities.resourceUrl.length >= 1)) {
for (var i = 0; i < wmtsLayerCapabilities.resourceUrl.length; i++) {
if (config.format === wmtsLayerCapabilities.resourceUrl[i].format) {
config.resourceUrl = wmtsLayerCapabilities.resourceUrl[i].template;
break;
}
}
} else { // resource-oriented interface not supported, so use KVP interface
config.service = wmtsLayerCapabilities.capabilities.getGetTileKvpAddress();
if (config.service) {
config.service = WmsUrlBuilder.fixGetMapString(config.service);
}
}
if (!config.resourceUrl && !config.service) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
"No resource URL or KVP GetTile service URL specified in WMTS capabilities."));
}
// Validate that the specified style identifier exists, or determine one if not specified.
if (style) {
var styleIdentifierFound = false;
for (var i = 0; i < wmtsLayerCapabilities.style.length; i++) {
if (wmtsLayerCapabilities.style[i].identifier === style) {
styleIdentifierFound = true;
config.style = wmtsLayerCapabilities.style[i].identifier;
break;
}
}
if (!styleIdentifierFound) {
Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
"The specified style identifier is not available. The server's default style will be used.");
config.style = null;
}
}
if (!config.style) {
for (i = 0; i < wmtsLayerCapabilities.style.length; i++) {
if (wmtsLayerCapabilities.style[i].isDefault) {
config.style = wmtsLayerCapabilities.style[i].identifier;
break;
}
}
}
if (!config.style) {
Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
"No default style available. A style will not be specified in tile requests.");
}
// Retrieve the supported tile matrix sets for testing against provided tile matrix set or for tile matrix
// set negotiation.
var supportedTileMatrixSets = wmtsLayerCapabilities.getLayerSupportedTileMatrixSets();
// Validate that the specified style identifier exists, or determine one if not specified.
if (matrixSet) {
var tileMatrixSetFound = false;
for (var i = 0, len = supportedTileMatrixSets.length; i < len; i++) {
if (supportedTileMatrixSets[i].identifier === matrixSet) {
tileMatrixSetFound = true;
config.tileMatrixSet = supportedTileMatrixSets[i];
break;
}
}
if (!tileMatrixSetFound) {
Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
"The specified tileMatrixSet is not available. Another one will be used.");
config.tileMatrixSet = null;
}
}
if (!config.tileMatrixSet) {
// Find the tile matrix set we want to use. Prefer EPSG:4326, then EPSG:3857.
var tms, tms4326 = null, tms3857 = null, tmsCRS84 = null;
for (var i = 0, len = supportedTileMatrixSets.length; i < len; i++) {
tms = supportedTileMatrixSets[i];
if (WmtsLayer.isEpsg4326Crs(tms.supportedCRS)) {
tms4326 = tms4326 || tms;
} else if (WmtsLayer.isEpsg3857Crs(tms.supportedCRS)) {
tms3857 = tms3857 || tms;
} else if (WmtsLayer.isOGCCrs84(tms.supportedCRS)) {
tmsCRS84 = tmsCRS84 || tms;
}
}
config.tileMatrixSet = tms4326 || tms3857 || tmsCRS84;
}
if (!config.tileMatrixSet) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
"No supported Tile Matrix Set could be found."));
}
// Configure boundingBox
config.boundingBox = wmtsLayerCapabilities.boundingBox;
config.wgs84BoundingBox = wmtsLayerCapabilities.wgs84BoundingBox;
// Determine a default display name.
if (wmtsLayerCapabilities.titles.length > 0) {
config.title = wmtsLayerCapabilities.titles[0].value;
} else {
config.title = wmtsLayerCapabilities.identifier;
}
return config;
};
WmtsLayer.prototype = Object.create(Layer.prototype);
WmtsLayer.prototype.doRender = function (dc) {
if (!dc.terrain)
return;
if (this.currentTilesInvalid
|| !this.lasTtMVP || !dc.navigatorState.modelviewProjection.equals(this.lasTtMVP)
|| dc.globeStateKey != this.lastGlobeStateKey) {
this.currentTilesInvalid = false;
this.assembleTiles(dc);
}
this.lasTtMVP = dc.navigatorState.modelviewProjection;
this.lastGlobeStateKey = dc.globeStateKey;
if (this.currentTiles.length > 0) {
dc.surfaceTileRenderer.renderTiles(dc, this.currentTiles, this.opacity);
dc.frameStatistics.incrementImageTileCount(this.currentTiles.length);
this.inCurrentFrame = true;
}
};
WmtsLayer.prototype.isLayerInView = function (dc) {
return dc.terrain && dc.terrain.sector && dc.terrain.sector.intersects(this.sector);
};
WmtsLayer.prototype.isTileVisible = function (dc, tile) {
if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
return false;
}
return tile.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
};
WmtsLayer.prototype.assembleTiles = function (dc) {
this.currentTiles = [];
if (!this.topLevelTiles || (this.topLevelTiles.length === 0)) {
this.createTopLevelTiles(dc);
}
for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
var tile = this.topLevelTiles[i];
tile.update(dc);
this.currentAncestorTile = null;
if (this.isTileVisible(dc, tile)) {
this.addTileOrDescendants(dc, tile);
}
}
};
WmtsLayer.prototype.addTileOrDescendants = function (dc, tile) {
// Check if the new sub-tile fits in TileMatrix ranges
if (tile.column >= tile.tileMatrix.matrixWidth) {
tile.column = tile.column - tile.tileMatrix.matrixWidth;
}
if (tile.column < 0) {
tile.column = tile.column + tile.tileMatrix.matrixWidth;
}
if (this.tileMeetsRenderingCriteria(dc, tile)) {
this.addTile(dc, tile);
return;
}
var ancestorTile = null;
try {
if (this.isTileTextureInMemory(dc, tile) || tile.tileMatrix.levelNumber === 0) {
ancestorTile = this.currentAncestorTile;
this.currentAncestorTile = tile;
}
var nextLevel = this.tileMatrixSet.tileMatrix[tile.tileMatrix.levelNumber + 1],
subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);
for (var i = 0, len = subTiles.length; i < len; i++) {
var child = subTiles[i];
child.update(dc);
if (this.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
this.addTileOrDescendants(dc, child);
}
}
} finally {
if (ancestorTile) {
this.currentAncestorTile = ancestorTile;
}
}
};
WmtsLayer.prototype.addTile = function (dc, tile) {
tile.fallbackTile = null;
var texture = dc.gpuResourceCache.resourceForKey(tile.imagePath);
if (texture) {
this.currentTiles.push(tile);
// If the tile's texture has expired, cause it to be re-retrieved. Note that the current,
// expired texture is still used until the updated one arrives.
if (this.expiration && this.isTextureExpired(texture)) {
this.retrieveTileImage(dc, tile);
}
return;
}
this.retrieveTileImage(dc, tile);
if (this.currentAncestorTile) {
if (this.isTileTextureInMemory(dc, this.currentAncestorTile)) {
this.currentTiles.push(this.currentAncestorTile);
}
}
};
WmtsLayer.prototype.isTextureExpired = function (texture) {
return this.expiration && (texture.creationTime.getTime() <= this.expiration.getTime());
};
WmtsLayer.prototype.isTileTextureInMemory = function (dc, tile) {
return dc.gpuResourceCache.containsResource(tile.imagePath);
};
WmtsLayer.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
var s = this.detailControl;
if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
s *= 1.2;
}
return tile.tileMatrix.levelNumber === (this.tileMatrixSet.tileMatrix.length - 1) || !tile.mustSubdivide(dc, s);
};
WmtsLayer.prototype.retrieveTileImage = function (dc, tile) {
if (this.currentRetrievals.indexOf(tile.imagePath) < 0) {
if (this.absentResourceList.isResourceAbsent(tile.imagePath)) {
return;
}
var url = this.resourceUrlForTile(tile, this.imageFormat),
image = new Image(),
imagePath = tile.imagePath,
cache = dc.gpuResourceCache,
canvas = dc.currentGlContext.canvas,
layer = this;
if (!url) {
this.currentTilesInvalid = true;
return;
}
image.onload = function () {
Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + url);
var texture = layer.createTexture(dc, tile, image);
layer.removeFromCurrentRetrievals(imagePath);
if (texture) {
cache.putResource(imagePath, texture, texture.size);
layer.currentTilesInvalid = true;
layer.absentResourceList.unmarkResourceAbsent(imagePath);
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
canvas.dispatchEvent(e);
}
};
image.onerror = function () {
layer.removeFromCurrentRetrievals(imagePath);
layer.absentResourceList.markResourceAbsent(imagePath);
Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + url);
};
this.currentRetrievals.push(imagePath);
image.crossOrigin = 'anonymous';
image.src = url;
}
};
WmtsLayer.prototype.resourceUrlForTile = function (tile, imageFormat) {
var url;
if (this.resourceUrl) {
url = this.resourceUrl.replace("{Style}", this.styleIdentifier).replace("{TileMatrixSet}", this.tileMatrixSet.identifier).replace("{TileMatrix}", tile.tileMatrix.identifier).replace("{TileCol}", tile.column).replace("{TileRow}", tile.row);
if (this.timeString) {
url = url.replace("{Time}", this.timeString);
}
} else {
url = this.serviceUrl + "service=WMTS&request=GetTile&version=1.0.0";
url += "&Layer=" + this.layerIdentifier;
if (this.styleIdentifier) {
url += "&Style=" + this.styleIdentifier;
}
url += "&Format=" + imageFormat;
if (this.timeString) {
url += "&Time=" + this.timeString;
}
url += "&TileMatrixSet=" + this.tileMatrixSet.identifier;
url += "&TileMatrix=" + tile.tileMatrix.identifier;
url += "&TileRow=" + tile.row;
url += "&TileCol=" + tile.column;
}
return url;
};
WmtsLayer.prototype.removeFromCurrentRetrievals = function (imagePath) {
var index = this.currentRetrievals.indexOf(imagePath);
if (index > -1) {
this.currentRetrievals.splice(index, 1);
}
};
WmtsLayer.prototype.createTopLevelTiles = function (dc) {
var tileMatrix = this.tileMatrixSet.tileMatrix[0];
this.topLevelTiles = [];
for (var j = 0; j < tileMatrix.matrixHeight; j++) {
for (var i = 0; i < tileMatrix.matrixWidth; i++) {
this.topLevelTiles.push(this.createTile(tileMatrix, j, i));
}
}
};
WmtsLayer.prototype.createTile = function (tileMatrix, row, column) {
if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
return this.createTile4326(tileMatrix, row, column);
} else if (WmtsLayer.isEpsg3857Crs(this.tileMatrixSet.supportedCRS)) {
return this.createTile3857(tileMatrix, row, column);
} else if (WmtsLayer.isOGCCrs84(this.tileMatrixSet.supportedCRS)) {
return this.createTileCrs84(tileMatrix, row, column);
}
};
WmtsLayer.prototype.createTileCrs84 = function (tileMatrix, row, column) {
var tileDeltaLat = this.sector.deltaLatitude() / tileMatrix.matrixHeight,
tileDeltaLon = this.sector.deltaLongitude() / tileMatrix.matrixWidth,
maxLat = tileMatrix.topLeftCorner[1] - row * tileDeltaLat,
minLat = maxLat - tileDeltaLat,
minLon = tileMatrix.topLeftCorner[0] + tileDeltaLon * column,
maxLon = minLon + tileDeltaLon;
var sector = new Sector(minLat, maxLat, minLon, maxLon);
return this.makeTile(sector, tileMatrix, row, column);
};
WmtsLayer.prototype.createTile4326 = function (tileMatrix, row, column) {
var tileDeltaLat = this.sector.deltaLatitude() / tileMatrix.matrixHeight,
tileDeltaLon = this.sector.deltaLongitude() / tileMatrix.matrixWidth,
maxLat = tileMatrix.topLeftCorner[0] - row * tileDeltaLat,
minLat = maxLat - tileDeltaLat,
minLon = tileMatrix.topLeftCorner[1] + tileDeltaLon * column,
maxLon = minLon + tileDeltaLon;
var sector = new Sector(minLat, maxLat, minLon, maxLon);
return this.makeTile(sector, tileMatrix, row, column);
};
WmtsLayer.prototype.createTile3857 = function (tileMatrix, row, column) {
if (!tileMatrix.mapWidth) {
this.computeTileMatrixValues3857(tileMatrix);
}
var swX = WWMath.clamp(column * tileMatrix.tileWidth - 0.5, 0, tileMatrix.mapWidth),
neY = WWMath.clamp(row * tileMatrix.tileHeight - 0.5, 0, tileMatrix.mapHeight),
neX = WWMath.clamp(swX + (tileMatrix.tileWidth) + 0.5, 0, tileMatrix.mapWidth),
swY = WWMath.clamp(neY + (tileMatrix.tileHeight) + 0.5, 0, tileMatrix.mapHeight),
x, y, swLat, swLon, neLat, neLon;
x = swX / tileMatrix.mapWidth;
y = swY / tileMatrix.mapHeight;
swLon = tileMatrix.topLeftCorner[0] + x * tileMatrix.tileMatrixDeltaX;
swLat = tileMatrix.topLeftCorner[1] - y * tileMatrix.tileMatrixDeltaY;
var swDegrees = WWMath.epsg3857ToEpsg4326(swLon, swLat);
x = neX / tileMatrix.mapWidth;
y = neY / tileMatrix.mapHeight;
neLon = tileMatrix.topLeftCorner[0] + x * tileMatrix.tileMatrixDeltaX;
neLat = tileMatrix.topLeftCorner[1] - y * tileMatrix.tileMatrixDeltaY;
var neDegrees = WWMath.epsg3857ToEpsg4326(neLon, neLat);
var sector = new Sector(swDegrees[0], neDegrees[0], swDegrees[1], neDegrees[1]);
return this.makeTile(sector, tileMatrix, row, column);
};
WmtsLayer.prototype.computeTileMatrixValues3857 = function (tileMatrix) {
var pixelSpan = tileMatrix.scaleDenominator * 0.28e-3,
tileSpanX = tileMatrix.tileWidth * pixelSpan,
tileSpanY = tileMatrix.tileHeight * pixelSpan,
tileMatrixMaxX = tileMatrix.topLeftCorner[0] + tileSpanX * tileMatrix.matrixWidth,
tileMatrixMinY = tileMatrix.topLeftCorner[1] - tileSpanY * tileMatrix.matrixHeight,
bottomRightCorner = [tileMatrixMaxX, tileMatrixMinY],
topLeftCorner = tileMatrix.topLeftCorner;
tileMatrix.tileMatrixDeltaX = bottomRightCorner[0] - topLeftCorner[0];
tileMatrix.tileMatrixDeltaY = topLeftCorner[1] - bottomRightCorner[1];
tileMatrix.mapWidth = tileMatrix.tileWidth * tileMatrix.matrixWidth;
tileMatrix.mapHeight = tileMatrix.tileHeight * tileMatrix.matrixHeight;
};
WmtsLayer.prototype.makeTile = function (sector, tileMatrix, row, column) {
var path = this.cachePath + "-layer/" + tileMatrix.identifier + "/" + row + "/" + column + "."
+ WWUtil.suffixForMimeType(this.imageFormat);
return new WmtsLayerTile(sector, tileMatrix, row, column, path);
};
WmtsLayer.prototype.createTexture = function (dc, tile, image) {
if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
return new Texture(dc.currentGlContext, image);
} else if (WmtsLayer.isEpsg3857Crs(this.tileMatrixSet.supportedCRS)) {
return this.createTexture3857(dc, tile, image);
} else if (WmtsLayer.isOGCCrs84(this.tileMatrixSet.supportedCRS)) {
return new Texture(dc.currentGlContext, image);
}
};
WmtsLayer.prototype.createTexture3857 = function (dc, tile, image) {
if (!this.destCanvas) {
// Create a canvas we can use when unprojecting retrieved images.
this.destCanvas = document.createElement("canvas");
this.destContext = this.destCanvas.getContext("2d");
}
var srcCanvas = dc.canvas2D,
srcContext = dc.ctx2D,
srcImageData,
destCanvas = this.destCanvas,
destContext = this.destContext,
destImageData = destContext.createImageData(image.width, image.height),
sector = tile.sector,
tMin = WWMath.gudermannianInverse(sector.minLatitude),
tMax = WWMath.gudermannianInverse(sector.maxLatitude),
lat, g, srcRow, kSrc, kDest, sy, dy;
srcCanvas.width = image.width;
srcCanvas.height = image.height;
destCanvas.width = image.width;
destCanvas.height = image.height;
// Draw the original image to a canvas so image data can be had for it.
srcContext.drawImage(image, 0, 0, image.width, image.height);
srcImageData = srcContext.getImageData(0, 0, image.width, image.height);
// Unproject the retrieved image.
for (var n = 0; n < 1; n++) {
for (var y = 0; y < image.height; y++) {
sy = 1 - y / (image.height - 1);
lat = sy * sector.deltaLatitude() + sector.minLatitude;
g = WWMath.gudermannianInverse(lat);
dy = 1 - (g - tMin) / (tMax - tMin);
dy = WWMath.clamp(dy, 0, 1);
srcRow = Math.floor(dy * (image.height - 1));
for (var x = 0; x < image.width; x++) {
kSrc = 4 * (x + srcRow * image.width);
kDest = 4 * (x + y * image.width);
destImageData.data[kDest] = srcImageData.data[kSrc];
destImageData.data[kDest + 1] = srcImageData.data[kSrc + 1];
destImageData.data[kDest + 2] = srcImageData.data[kSrc + 2];
destImageData.data[kDest + 3] = srcImageData.data[kSrc + 3];
}
}
}
destContext.putImageData(destImageData, 0, 0);
return new Texture(dc.currentGlContext, destCanvas);
};
WmtsLayer.isEpsg4326Crs = function (crs) {
return ((crs.indexOf("EPSG") >= 0) && (crs.indexOf("4326") >= 0));
};
WmtsLayer.isEpsg3857Crs = function (crs) {
return (crs.indexOf("EPSG") >= 0)
&& ((crs.indexOf("3857") >= 0) || (crs.indexOf("900913") >= 0)); // 900913 is google's 3857 alias
};
WmtsLayer.isOGCCrs84 = function (crs) {
return (crs.indexOf("OGC") >= 0) && (crs.indexOf("CRS84") >= 0);
};
return WmtsLayer;
});