cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,062 lines (943 loc) • 113 kB
JavaScript
/* This file is automatically rebuilt by the Cesium build process. */
define(['./when-e6e3e713', './Check-1df6b9a0', './Math-c5f6c994', './Cartesian2-1d7364fa', './Transforms-943e8463', './RuntimeError-717c34db', './WebGLConstants-7f7d68ac', './ComponentDatatype-2b8834a4', './AttributeCompression-d68d64ef', './IntersectionTests-c05f88ce', './Plane-2e419ea5', './WebMercatorProjection-2eb538cc', './createTaskProcessorWorker', './EllipsoidTangentPlane-c3f1b2da', './OrientedBoundingBox-02d47ca6', './TerrainEncoding-e37552cb'], function (when, Check, _Math, Cartesian2, Transforms, RuntimeError, WebGLConstants, ComponentDatatype, AttributeCompression, IntersectionTests, Plane, WebMercatorProjection, createTaskProcessorWorker, EllipsoidTangentPlane, OrientedBoundingBox, TerrainEncoding) { 'use strict';
/**
* The encoding that is used for a heightmap
*
* @exports HeightmapEncoding
*/
var HeightmapEncoding = {
/**
* No encoding
*
* @type {Number}
* @constant
*/
NONE: 0,
/**
* LERC encoding
*
* @type {Number}
* @constant
*
* @see {@link https://github.com/Esri/lerc|The LERC specification}
*/
LERC: 1
};
var HeightmapEncoding$1 = Object.freeze(HeightmapEncoding);
/**
* Contains functions to create a mesh from a heightmap image.
*
* @exports HeightmapTessellator
*
* @private
*/
var HeightmapTessellator = {};
/**
* The default structure of a heightmap, as given to {@link HeightmapTessellator.computeVertices}.
*
* @constant
*/
HeightmapTessellator.DEFAULT_STRUCTURE = Object.freeze({
heightScale : 1.0,
heightOffset : 0.0,
elementsPerHeight : 1,
stride : 1,
elementMultiplier : 256.0,
isBigEndian : false
});
var cartesian3Scratch = new Cartesian2.Cartesian3();
var matrix4Scratch = new Transforms.Matrix4();
var minimumScratch = new Cartesian2.Cartesian3();
var maximumScratch = new Cartesian2.Cartesian3();
/**
* Fills an array of vertices from a heightmap image.
*
* @param {Object} options Object with the following properties:
* @param {TypedArray} options.heightmap The heightmap to tessellate.
* @param {Number} options.width The width of the heightmap, in height samples.
* @param {Number} options.height The height of the heightmap, in height samples.
* @param {Number} options.skirtHeight The height of skirts to drape at the edges of the heightmap.
* @param {Rectangle} options.nativeRectangle A rectangle in the native coordinates of the heightmap's projection. For
* a heightmap with a geographic projection, this is degrees. For the web mercator
* projection, this is meters.
* @param {Number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
* @param {Rectangle} [options.rectangle] The rectangle covered by the heightmap, in geodetic coordinates with north, south, east and
* west properties in radians. Either rectangle or nativeRectangle must be provided. If both
* are provided, they're assumed to be consistent.
* @param {Boolean} [options.isGeographic=true] True if the heightmap uses a {@link GeographicProjection}, or false if it uses
* a {@link WebMercatorProjection}.
* @param {Cartesian3} [options.relativeToCenter=Cartesian3.ZERO] The positions will be computed as <code>Cartesian3.subtract(worldPosition, relativeToCenter)</code>.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to which the heightmap applies.
* @param {Object} [options.structure] An object describing the structure of the height data.
* @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
* the height above the heightOffset, in meters. The heightOffset is added to the resulting
* height after multiplying by the scale.
* @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
* height in meters. The offset is added after the height sample is multiplied by the
* heightScale.
* @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
* sample. This is usually 1, indicating that each element is a separate height sample. If
* it is greater than 1, that number of elements together form the height sample, which is
* computed according to the structure.elementMultiplier and structure.isBigEndian properties.
* @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
* one height to the first element of the next height.
* @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
* stride property is greater than 1. For example, if the stride is 4 and the strideMultiplier
* is 256, the height is computed as follows:
* `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
* This is assuming that the isBigEndian property is false. If it is true, the order of the
* elements is reversed.
* @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is
* not specified, no minimum value is enforced.
* @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
* than 65535. If this parameter is not specified, no maximum value is enforced.
* @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
* stride property is greater than 1. If this property is false, the first element is the
* low-order element. If it is true, the first element is the high-order element.
*
* @example
* var width = 5;
* var height = 5;
* var statistics = Cesium.HeightmapTessellator.computeVertices({
* heightmap : [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
* width : width,
* height : height,
* skirtHeight : 0.0,
* nativeRectangle : {
* west : 10.0,
* east : 20.0,
* south : 30.0,
* north : 40.0
* }
* });
*
* var encoding = statistics.encoding;
* var position = encoding.decodePosition(statistics.vertices, index * encoding.getStride());
*/
HeightmapTessellator.computeVertices = function(options) {
//>>includeStart('debug', pragmas.debug);
if (!when.defined(options) || !when.defined(options.heightmap)) {
throw new Check.DeveloperError('options.heightmap is required.');
}
if (!when.defined(options.width) || !when.defined(options.height)) {
throw new Check.DeveloperError('options.width and options.height are required.');
}
if (!when.defined(options.nativeRectangle)) {
throw new Check.DeveloperError('options.nativeRectangle is required.');
}
if (!when.defined(options.skirtHeight)) {
throw new Check.DeveloperError('options.skirtHeight is required.');
}
//>>includeEnd('debug');
// This function tends to be a performance hotspot for terrain rendering,
// so it employs a lot of inlining and unrolling as an optimization.
// In particular, the functionality of Ellipsoid.cartographicToCartesian
// is inlined.
var cos = Math.cos;
var sin = Math.sin;
var sqrt = Math.sqrt;
var atan = Math.atan;
var exp = Math.exp;
var piOverTwo = _Math.CesiumMath.PI_OVER_TWO;
var toRadians = _Math.CesiumMath.toRadians;
var heightmap = options.heightmap;
var width = options.width;
var height = options.height;
var skirtHeight = options.skirtHeight;
var isGeographic = when.defaultValue(options.isGeographic, true);
var ellipsoid = when.defaultValue(options.ellipsoid, Cartesian2.Ellipsoid.WGS84);
var oneOverGlobeSemimajorAxis = 1.0 / ellipsoid.maximumRadius;
var nativeRectangle = options.nativeRectangle;
var geographicWest;
var geographicSouth;
var geographicEast;
var geographicNorth;
var rectangle = options.rectangle;
if (!when.defined(rectangle)) {
if (isGeographic) {
geographicWest = toRadians(nativeRectangle.west);
geographicSouth = toRadians(nativeRectangle.south);
geographicEast = toRadians(nativeRectangle.east);
geographicNorth = toRadians(nativeRectangle.north);
} else {
geographicWest = nativeRectangle.west * oneOverGlobeSemimajorAxis;
geographicSouth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.south * oneOverGlobeSemimajorAxis)));
geographicEast = nativeRectangle.east * oneOverGlobeSemimajorAxis;
geographicNorth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.north * oneOverGlobeSemimajorAxis)));
}
} else {
geographicWest = rectangle.west;
geographicSouth = rectangle.south;
geographicEast = rectangle.east;
geographicNorth = rectangle.north;
}
var relativeToCenter = options.relativeToCenter;
var hasRelativeToCenter = when.defined(relativeToCenter);
relativeToCenter = hasRelativeToCenter ? relativeToCenter : Cartesian2.Cartesian3.ZERO;
var exaggeration = when.defaultValue(options.exaggeration, 1.0);
var includeWebMercatorT = when.defaultValue(options.includeWebMercatorT, false);
var structure = when.defaultValue(options.structure, HeightmapTessellator.DEFAULT_STRUCTURE);
var heightScale = when.defaultValue(structure.heightScale, HeightmapTessellator.DEFAULT_STRUCTURE.heightScale);
var heightOffset = when.defaultValue(structure.heightOffset, HeightmapTessellator.DEFAULT_STRUCTURE.heightOffset);
var elementsPerHeight = when.defaultValue(structure.elementsPerHeight, HeightmapTessellator.DEFAULT_STRUCTURE.elementsPerHeight);
var stride = when.defaultValue(structure.stride, HeightmapTessellator.DEFAULT_STRUCTURE.stride);
var elementMultiplier = when.defaultValue(structure.elementMultiplier, HeightmapTessellator.DEFAULT_STRUCTURE.elementMultiplier);
var isBigEndian = when.defaultValue(structure.isBigEndian, HeightmapTessellator.DEFAULT_STRUCTURE.isBigEndian);
var rectangleWidth = Cartesian2.Rectangle.computeWidth(nativeRectangle);
var rectangleHeight = Cartesian2.Rectangle.computeHeight(nativeRectangle);
var granularityX = rectangleWidth / (width - 1);
var granularityY = rectangleHeight / (height - 1);
if (!isGeographic) {
rectangleWidth *= oneOverGlobeSemimajorAxis;
rectangleHeight *= oneOverGlobeSemimajorAxis;
}
var radiiSquared = ellipsoid.radiiSquared;
var radiiSquaredX = radiiSquared.x;
var radiiSquaredY = radiiSquared.y;
var radiiSquaredZ = radiiSquared.z;
var minimumHeight = 65536.0;
var maximumHeight = -65536.0;
var fromENU = Transforms.Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid);
var toENU = Transforms.Matrix4.inverseTransformation(fromENU, matrix4Scratch);
var southMercatorY;
var oneOverMercatorHeight;
if (includeWebMercatorT) {
southMercatorY = WebMercatorProjection.WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth);
oneOverMercatorHeight = 1.0 / (WebMercatorProjection.WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY);
}
var minimum = minimumScratch;
minimum.x = Number.POSITIVE_INFINITY;
minimum.y = Number.POSITIVE_INFINITY;
minimum.z = Number.POSITIVE_INFINITY;
var maximum = maximumScratch;
maximum.x = Number.NEGATIVE_INFINITY;
maximum.y = Number.NEGATIVE_INFINITY;
maximum.z = Number.NEGATIVE_INFINITY;
var hMin = Number.POSITIVE_INFINITY;
var gridVertexCount = width * height;
var edgeVertexCount = skirtHeight > 0.0 ? (width * 2 + height * 2) : 0;
var vertexCount = gridVertexCount + edgeVertexCount;
var positions = new Array(vertexCount);
var heights = new Array(vertexCount);
var uvs = new Array(vertexCount);
var webMercatorTs = includeWebMercatorT ? new Array(vertexCount) : [];
var startRow = 0;
var endRow = height;
var startCol = 0;
var endCol = width;
if (skirtHeight > 0.0) {
--startRow;
++endRow;
--startCol;
++endCol;
}
var skirtOffsetPercentage = 0.00001;
for (var rowIndex = startRow; rowIndex < endRow; ++rowIndex) {
var row = rowIndex;
if (row < 0) {
row = 0;
}
if (row >= height) {
row = height - 1;
}
var latitude = nativeRectangle.north - granularityY * row;
if (!isGeographic) {
latitude = piOverTwo - (2.0 * atan(exp(-latitude * oneOverGlobeSemimajorAxis)));
} else {
latitude = toRadians(latitude);
}
var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth);
v = _Math.CesiumMath.clamp(v, 0.0, 1.0);
var isNorthEdge = rowIndex === startRow;
var isSouthEdge = rowIndex === endRow - 1;
if (skirtHeight > 0.0) {
if (isNorthEdge) {
latitude += skirtOffsetPercentage * rectangleHeight;
} else if (isSouthEdge) {
latitude -= skirtOffsetPercentage * rectangleHeight;
}
}
var cosLatitude = cos(latitude);
var nZ = sin(latitude);
var kZ = radiiSquaredZ * nZ;
var webMercatorT;
if (includeWebMercatorT) {
webMercatorT = (WebMercatorProjection.WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
}
for (var colIndex = startCol; colIndex < endCol; ++colIndex) {
var col = colIndex;
if (col < 0) {
col = 0;
}
if (col >= width) {
col = width - 1;
}
var terrainOffset = row * (width * stride) + col * stride;
var heightSample;
if (elementsPerHeight === 1) {
heightSample = heightmap[terrainOffset];
} else {
heightSample = 0;
var elementOffset;
if (isBigEndian) {
for (elementOffset = 0; elementOffset < elementsPerHeight; ++elementOffset) {
heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
}
} else {
for (elementOffset = elementsPerHeight - 1; elementOffset >= 0; --elementOffset) {
heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
}
}
}
heightSample = (heightSample * heightScale + heightOffset) * exaggeration;
maximumHeight = Math.max(maximumHeight, heightSample);
minimumHeight = Math.min(minimumHeight, heightSample);
var longitude = nativeRectangle.west + granularityX * col;
if (!isGeographic) {
longitude = longitude * oneOverGlobeSemimajorAxis;
} else {
longitude = toRadians(longitude);
}
var u = (longitude - geographicWest) / (geographicEast - geographicWest);
u = _Math.CesiumMath.clamp(u, 0.0, 1.0);
var index = row * width + col;
if (skirtHeight > 0.0) {
var isWestEdge = colIndex === startCol;
var isEastEdge = colIndex === endCol - 1;
var isEdge = isNorthEdge || isSouthEdge || isWestEdge || isEastEdge;
var isCorner = (isNorthEdge || isSouthEdge) && (isWestEdge || isEastEdge);
if (isCorner) {
// Don't generate skirts on the corners.
continue;
} else if (isEdge) {
heightSample -= skirtHeight;
if (isWestEdge) {
// The outer loop iterates north to south but the indices are ordered south to north, hence the index flip below
index = gridVertexCount + (height - row - 1);
longitude -= skirtOffsetPercentage * rectangleWidth;
} else if (isSouthEdge) {
// Add after west indices. South indices are ordered east to west.
index = gridVertexCount + height + (width - col - 1);
} else if (isEastEdge) {
// Add after west and south indices. East indices are ordered north to south. The index is flipped like above.
index = gridVertexCount + height + width + row;
longitude += skirtOffsetPercentage * rectangleWidth;
} else if (isNorthEdge) {
// Add after west, south, and east indices. North indices are ordered west to east.
index = gridVertexCount + height + width + height + col;
}
}
}
var nX = cosLatitude * cos(longitude);
var nY = cosLatitude * sin(longitude);
var kX = radiiSquaredX * nX;
var kY = radiiSquaredY * nY;
var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ));
var oneOverGamma = 1.0 / gamma;
var rSurfaceX = kX * oneOverGamma;
var rSurfaceY = kY * oneOverGamma;
var rSurfaceZ = kZ * oneOverGamma;
var position = new Cartesian2.Cartesian3();
position.x = rSurfaceX + nX * heightSample;
position.y = rSurfaceY + nY * heightSample;
position.z = rSurfaceZ + nZ * heightSample;
positions[index] = position;
heights[index] = heightSample;
uvs[index] = new Cartesian2.Cartesian2(u, v);
if (includeWebMercatorT) {
webMercatorTs[index] = webMercatorT;
}
Transforms.Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch);
Cartesian2.Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum);
Cartesian2.Cartesian3.maximumByComponent(cartesian3Scratch, maximum, maximum);
hMin = Math.min(hMin, heightSample);
}
}
var boundingSphere3D = Transforms.BoundingSphere.fromPoints(positions);
var orientedBoundingBox;
if (when.defined(rectangle)) {
orientedBoundingBox = OrientedBoundingBox.OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid);
}
var occludeePointInScaledSpace;
if (hasRelativeToCenter) {
var occluder = new TerrainEncoding.EllipsoidalOccluder(ellipsoid);
occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(relativeToCenter, positions, minimumHeight);
}
var aaBox = new EllipsoidTangentPlane.AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
var encoding = new TerrainEncoding.TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false, includeWebMercatorT);
var vertices = new Float32Array(vertexCount * encoding.getStride());
var bufferIndex = 0;
for (var j = 0; j < vertexCount; ++j) {
bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]);
}
return {
vertices : vertices,
maximumHeight : maximumHeight,
minimumHeight : minimumHeight,
encoding : encoding,
boundingSphere3D : boundingSphere3D,
orientedBoundingBox : orientedBoundingBox,
occludeePointInScaledSpace : occludeePointInScaledSpace
};
};
/* jshint forin: false, bitwise: false */
/*
Copyright 2015-2018 Esri
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.
A copy of the license and additional notices are located with the
source distribution at:
http://github.com/Esri/lerc/
Contributors: Johannes Schmid, (LERC v1)
Chayanika Khatua, (LERC v1)
Wenxue Ju (LERC v1, v2.x)
*/
/* Copyright 2015-2018 Esri. 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 @preserve */
var tmp = {};
/**
* a module for decoding LERC blobs
* @module Lerc
*/
(function() {
//the original LercDecode for Version 1
var LercDecode = (function() {
// WARNING: This decoder version can only read old version 1 Lerc blobs. Use with caution.
// Note: currently, this module only has an implementation for decoding LERC data, not encoding. The name of
// the class was chosen to be future proof.
var CntZImage = {};
CntZImage.defaultNoDataValue = -3.4027999387901484e+38; // smallest Float32 value
/**
* Decode a LERC byte stream and return an object containing the pixel data and some required and optional
* information about it, such as the image's width and height.
*
* @param {ArrayBuffer} input The LERC input byte stream
* @param {object} [options] Decoding options, containing any of the following properties:
* @config {number} [inputOffset = 0]
* Skip the first inputOffset bytes of the input byte stream. A valid LERC file is expected at that position.
* @config {Uint8Array} [encodedMask = null]
* If specified, the decoder will not read mask information from the input and use the specified encoded
* mask data instead. Mask header/data must not be present in the LERC byte stream in this case.
* @config {number} [noDataValue = LercCode.defaultNoDataValue]
* Pixel value to use for masked pixels.
* @config {ArrayBufferView|Array} [pixelType = Float32Array]
* The desired type of the pixelData array in the return value. Note that it is the caller's responsibility to
* provide an appropriate noDataValue if the default pixelType is overridden.
* @config {boolean} [returnMask = false]
* If true, the return value will contain a maskData property of type Uint8Array which has one element per
* pixel, the value of which is 1 or 0 depending on whether that pixel's data is present or masked. If the
* input LERC data does not contain a mask, maskData will not be returned.
* @config {boolean} [returnEncodedMask = false]
* If true, the return value will contain a encodedMaskData property, which can be passed into encode() as
* encodedMask.
* @config {boolean} [returnFileInfo = false]
* If true, the return value will have a fileInfo property that contains metadata obtained from the
* LERC headers and the decoding process.
* @config {boolean} [computeUsedBitDepths = false]
* If true, the fileInfo property in the return value will contain the set of all block bit depths
* encountered during decoding. Will only have an effect if returnFileInfo option is true.
* @returns {{width, height, pixelData, minValue, maxValue, noDataValue, maskData, encodedMaskData, fileInfo}}
*/
CntZImage.decode = function(input, options) {
options = options || {};
var skipMask = options.encodedMaskData || (options.encodedMaskData === null);
var parsedData = parse(input, options.inputOffset || 0, skipMask);
var noDataValue = (options.noDataValue !== null) ? options.noDataValue : CntZImage.defaultNoDataValue;
var uncompressedData = uncompressPixelValues(parsedData, options.pixelType || Float32Array,
options.encodedMaskData, noDataValue, options.returnMask);
var result = {
width: parsedData.width,
height: parsedData.height,
pixelData: uncompressedData.resultPixels,
minValue: uncompressedData.minValue,
maxValue: parsedData.pixels.maxValue,
noDataValue: noDataValue
};
if (uncompressedData.resultMask) {
result.maskData = uncompressedData.resultMask;
}
if (options.returnEncodedMask && parsedData.mask) {
result.encodedMaskData = parsedData.mask.bitset ? parsedData.mask.bitset : null;
}
if (options.returnFileInfo) {
result.fileInfo = formatFileInfo(parsedData);
if (options.computeUsedBitDepths) {
result.fileInfo.bitDepths = computeUsedBitDepths(parsedData);
}
}
return result;
};
var uncompressPixelValues = function(data, TypedArrayClass, maskBitset, noDataValue, storeDecodedMask) {
var blockIdx = 0;
var numX = data.pixels.numBlocksX;
var numY = data.pixels.numBlocksY;
var blockWidth = Math.floor(data.width / numX);
var blockHeight = Math.floor(data.height / numY);
var scale = 2 * data.maxZError;
var minValue = Number.MAX_VALUE, currentValue;
maskBitset = maskBitset || ((data.mask) ? data.mask.bitset : null);
var resultPixels, resultMask;
resultPixels = new TypedArrayClass(data.width * data.height);
if (storeDecodedMask && maskBitset) {
resultMask = new Uint8Array(data.width * data.height);
}
var blockDataBuffer = new Float32Array(blockWidth * blockHeight);
var xx, yy;
for (var y = 0; y <= numY; y++) {
var thisBlockHeight = (y !== numY) ? blockHeight : (data.height % numY);
if (thisBlockHeight === 0) {
continue;
}
for (var x = 0; x <= numX; x++) {
var thisBlockWidth = (x !== numX) ? blockWidth : (data.width % numX);
if (thisBlockWidth === 0) {
continue;
}
var outPtr = y * data.width * blockHeight + x * blockWidth;
var outStride = data.width - thisBlockWidth;
var block = data.pixels.blocks[blockIdx];
var blockData, blockPtr, constValue;
if (block.encoding < 2) {
// block is either uncompressed or bit-stuffed (encodings 0 and 1)
if (block.encoding === 0) {
// block is uncompressed
blockData = block.rawData;
} else {
// block is bit-stuffed
unstuff(block.stuffedData, block.bitsPerPixel, block.numValidPixels, block.offset, scale, blockDataBuffer, data.pixels.maxValue);
blockData = blockDataBuffer;
}
blockPtr = 0;
}
else if (block.encoding === 2) {
// block is all 0
constValue = 0;
}
else {
// block has constant value (encoding === 3)
constValue = block.offset;
}
var maskByte;
if (maskBitset) {
for (yy = 0; yy < thisBlockHeight; yy++) {
if (outPtr & 7) {
//
maskByte = maskBitset[outPtr >> 3];
maskByte <<= outPtr & 7;
}
for (xx = 0; xx < thisBlockWidth; xx++) {
if (!(outPtr & 7)) {
// read next byte from mask
maskByte = maskBitset[outPtr >> 3];
}
if (maskByte & 128) {
// pixel data present
if (resultMask) {
resultMask[outPtr] = 1;
}
currentValue = (block.encoding < 2) ? blockData[blockPtr++] : constValue;
minValue = minValue > currentValue ? currentValue : minValue;
resultPixels[outPtr++] = currentValue;
} else {
// pixel data not present
if (resultMask) {
resultMask[outPtr] = 0;
}
resultPixels[outPtr++] = noDataValue;
}
maskByte <<= 1;
}
outPtr += outStride;
}
} else {
// mask not present, simply copy block over
if (block.encoding < 2) {
// duplicating this code block for performance reasons
// blockData case:
for (yy = 0; yy < thisBlockHeight; yy++) {
for (xx = 0; xx < thisBlockWidth; xx++) {
currentValue = blockData[blockPtr++];
minValue = minValue > currentValue ? currentValue : minValue;
resultPixels[outPtr++] = currentValue;
}
outPtr += outStride;
}
}
else {
// constValue case:
minValue = minValue > constValue ? constValue : minValue;
for (yy = 0; yy < thisBlockHeight; yy++) {
for (xx = 0; xx < thisBlockWidth; xx++) {
resultPixels[outPtr++] = constValue;
}
outPtr += outStride;
}
}
}
if ((block.encoding === 1) && (blockPtr !== block.numValidPixels)) {
throw "Block and Mask do not match";
}
blockIdx++;
}
}
return {
resultPixels: resultPixels,
resultMask: resultMask,
minValue: minValue
};
};
var formatFileInfo = function(data) {
return {
"fileIdentifierString": data.fileIdentifierString,
"fileVersion": data.fileVersion,
"imageType": data.imageType,
"height": data.height,
"width": data.width,
"maxZError": data.maxZError,
"eofOffset": data.eofOffset,
"mask": data.mask ? {
"numBlocksX": data.mask.numBlocksX,
"numBlocksY": data.mask.numBlocksY,
"numBytes": data.mask.numBytes,
"maxValue": data.mask.maxValue
} : null,
"pixels": {
"numBlocksX": data.pixels.numBlocksX,
"numBlocksY": data.pixels.numBlocksY,
"numBytes": data.pixels.numBytes,
"maxValue": data.pixels.maxValue,
"noDataValue": data.noDataValue
}
};
};
var computeUsedBitDepths = function(data) {
var numBlocks = data.pixels.numBlocksX * data.pixels.numBlocksY;
var bitDepths = {};
for (var i = 0; i < numBlocks; i++) {
var block = data.pixels.blocks[i];
if (block.encoding === 0) {
bitDepths.float32 = true;
} else if (block.encoding === 1) {
bitDepths[block.bitsPerPixel] = true;
} else {
bitDepths[0] = true;
}
}
return Object.keys(bitDepths);
};
var parse = function(input, fp, skipMask) {
var data = {};
// File header
var fileIdView = new Uint8Array(input, fp, 10);
data.fileIdentifierString = String.fromCharCode.apply(null, fileIdView);
if (data.fileIdentifierString.trim() !== "CntZImage") {
throw "Unexpected file identifier string: " + data.fileIdentifierString;
}
fp += 10;
var view = new DataView(input, fp, 24);
data.fileVersion = view.getInt32(0, true);
data.imageType = view.getInt32(4, true);
data.height = view.getUint32(8, true);
data.width = view.getUint32(12, true);
data.maxZError = view.getFloat64(16, true);
fp += 24;
// Mask Header
if (!skipMask) {
view = new DataView(input, fp, 16);
data.mask = {};
data.mask.numBlocksY = view.getUint32(0, true);
data.mask.numBlocksX = view.getUint32(4, true);
data.mask.numBytes = view.getUint32(8, true);
data.mask.maxValue = view.getFloat32(12, true);
fp += 16;
// Mask Data
if (data.mask.numBytes > 0) {
var bitset = new Uint8Array(Math.ceil(data.width * data.height / 8));
view = new DataView(input, fp, data.mask.numBytes);
var cnt = view.getInt16(0, true);
var ip = 2, op = 0;
do {
if (cnt > 0) {
while (cnt--) { bitset[op++] = view.getUint8(ip++); }
} else {
var val = view.getUint8(ip++);
cnt = -cnt;
while (cnt--) { bitset[op++] = val; }
}
cnt = view.getInt16(ip, true);
ip += 2;
} while (ip < data.mask.numBytes);
if ((cnt !== -32768) || (op < bitset.length)) {
throw "Unexpected end of mask RLE encoding";
}
data.mask.bitset = bitset;
fp += data.mask.numBytes;
}
else if ((data.mask.numBytes | data.mask.numBlocksY | data.mask.maxValue) === 0) { // Special case, all nodata
data.mask.bitset = new Uint8Array(Math.ceil(data.width * data.height / 8));
}
}
// Pixel Header
view = new DataView(input, fp, 16);
data.pixels = {};
data.pixels.numBlocksY = view.getUint32(0, true);
data.pixels.numBlocksX = view.getUint32(4, true);
data.pixels.numBytes = view.getUint32(8, true);
data.pixels.maxValue = view.getFloat32(12, true);
fp += 16;
var numBlocksX = data.pixels.numBlocksX;
var numBlocksY = data.pixels.numBlocksY;
// the number of blocks specified in the header does not take into account the blocks at the end of
// each row/column with a special width/height that make the image complete in case the width is not
// evenly divisible by the number of blocks.
var actualNumBlocksX = numBlocksX + ((data.width % numBlocksX) > 0 ? 1 : 0);
var actualNumBlocksY = numBlocksY + ((data.height % numBlocksY) > 0 ? 1 : 0);
data.pixels.blocks = new Array(actualNumBlocksX * actualNumBlocksY);
var blockI = 0;
for (var blockY = 0; blockY < actualNumBlocksY; blockY++) {
for (var blockX = 0; blockX < actualNumBlocksX; blockX++) {
// Block
var size = 0;
var bytesLeft = input.byteLength - fp;
view = new DataView(input, fp, Math.min(10, bytesLeft));
var block = {};
data.pixels.blocks[blockI++] = block;
var headerByte = view.getUint8(0); size++;
block.encoding = headerByte & 63;
if (block.encoding > 3) {
throw "Invalid block encoding (" + block.encoding + ")";
}
if (block.encoding === 2) {
fp++;
continue;
}
if ((headerByte !== 0) && (headerByte !== 2)) {
headerByte >>= 6;
block.offsetType = headerByte;
if (headerByte === 2) {
block.offset = view.getInt8(1); size++;
} else if (headerByte === 1) {
block.offset = view.getInt16(1, true); size += 2;
} else if (headerByte === 0) {
block.offset = view.getFloat32(1, true); size += 4;
} else {
throw "Invalid block offset type";
}
if (block.encoding === 1) {
headerByte = view.getUint8(size); size++;
block.bitsPerPixel = headerByte & 63;
headerByte >>= 6;
block.numValidPixelsType = headerByte;
if (headerByte === 2) {
block.numValidPixels = view.getUint8(size); size++;
} else if (headerByte === 1) {
block.numValidPixels = view.getUint16(size, true); size += 2;
} else if (headerByte === 0) {
block.numValidPixels = view.getUint32(size, true); size += 4;
} else {
throw "Invalid valid pixel count type";
}
}
}
fp += size;
if (block.encoding === 3) {
continue;
}
var arrayBuf, store8;
if (block.encoding === 0) {
var numPixels = (data.pixels.numBytes - 1) / 4;
if (numPixels !== Math.floor(numPixels)) {
throw "uncompressed block has invalid length";
}
arrayBuf = new ArrayBuffer(numPixels * 4);
store8 = new Uint8Array(arrayBuf);
store8.set(new Uint8Array(input, fp, numPixels * 4));
var rawData = new Float32Array(arrayBuf);
block.rawData = rawData;
fp += numPixels * 4;
} else if (block.encoding === 1) {
var dataBytes = Math.ceil(block.numValidPixels * block.bitsPerPixel / 8);
var dataWords = Math.ceil(dataBytes / 4);
arrayBuf = new ArrayBuffer(dataWords * 4);
store8 = new Uint8Array(arrayBuf);
store8.set(new Uint8Array(input, fp, dataBytes));
block.stuffedData = new Uint32Array(arrayBuf);
fp += dataBytes;
}
}
}
data.eofOffset = fp;
return data;
};
var unstuff = function(src, bitsPerPixel, numPixels, offset, scale, dest, maxValue) {
var bitMask = (1 << bitsPerPixel) - 1;
var i = 0, o;
var bitsLeft = 0;
var n, buffer;
var nmax = Math.ceil((maxValue - offset) / scale);
// get rid of trailing bytes that are already part of next block
var numInvalidTailBytes = src.length * 4 - Math.ceil(bitsPerPixel * numPixels / 8);
src[src.length - 1] <<= 8 * numInvalidTailBytes;
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
}
if (bitsLeft >= bitsPerPixel) {
n = (buffer >>> (bitsLeft - bitsPerPixel)) & bitMask;
bitsLeft -= bitsPerPixel;
} else {
var missingBits = (bitsPerPixel - bitsLeft);
n = ((buffer & bitMask) << missingBits) & bitMask;
buffer = src[i++];
bitsLeft = 32 - missingBits;
n += (buffer >>> bitsLeft);
}
//pixel values may exceed max due to quantization
dest[o] = n < nmax ? offset + n * scale : maxValue;
}
return dest;
};
return CntZImage;
})();
//version 2. Supports 2.1, 2.2, 2.3
var Lerc2Decode = (function() {
// Note: currently, this module only has an implementation for decoding LERC data, not encoding. The name of
// the class was chosen to be future proof, following LercDecode.
/*****************************************
* private static class bitsutffer used by Lerc2Decode
*******************************************/
var BitStuffer = {
//methods ending with 2 are for the new byte order used by Lerc2.3 and above.
//originalUnstuff is used to unpack Huffman code table. code is duplicated to unstuffx for performance reasons.
unstuff: function(src, dest, bitsPerPixel, numPixels, lutArr, offset, scale, maxValue) {
var bitMask = (1 << bitsPerPixel) - 1;
var i = 0, o;
var bitsLeft = 0;
var n, buffer, missingBits, nmax;
// get rid of trailing bytes that are already part of next block
var numInvalidTailBytes = src.length * 4 - Math.ceil(bitsPerPixel * numPixels / 8);
src[src.length - 1] <<= 8 * numInvalidTailBytes;
if (lutArr) {
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
}
if (bitsLeft >= bitsPerPixel) {
n = (buffer >>> (bitsLeft - bitsPerPixel)) & bitMask;
bitsLeft -= bitsPerPixel;
}
else {
missingBits = (bitsPerPixel - bitsLeft);
n = ((buffer & bitMask) << missingBits) & bitMask;
buffer = src[i++];
bitsLeft = 32 - missingBits;
n += (buffer >>> bitsLeft);
}
dest[o] = lutArr[n];//offset + lutArr[n] * scale;
}
}
else {
nmax = Math.ceil((maxValue - offset) / scale);
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
}
if (bitsLeft >= bitsPerPixel) {
n = (buffer >>> (bitsLeft - bitsPerPixel)) & bitMask;
bitsLeft -= bitsPerPixel;
}
else {
missingBits = (bitsPerPixel - bitsLeft);
n = ((buffer & bitMask) << missingBits) & bitMask;
buffer = src[i++];
bitsLeft = 32 - missingBits;
n += (buffer >>> bitsLeft);
}
//pixel values may exceed max due to quantization
dest[o] = n < nmax ? offset + n * scale : maxValue;
}
}
},
unstuffLUT: function(src, bitsPerPixel, numPixels, offset, scale, maxValue) {
var bitMask = (1 << bitsPerPixel) - 1;
var i = 0, o = 0, missingBits = 0, bitsLeft = 0, n = 0;
var buffer;
var dest = [];
// get rid of trailing bytes that are already part of next block
var numInvalidTailBytes = src.length * 4 - Math.ceil(bitsPerPixel * numPixels / 8);
src[src.length - 1] <<= 8 * numInvalidTailBytes;
var nmax = Math.ceil((maxValue - offset) / scale);
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
}
if (bitsLeft >= bitsPerPixel) {
n = (buffer >>> (bitsLeft - bitsPerPixel)) & bitMask;
bitsLeft -= bitsPerPixel;
} else {
missingBits = (bitsPerPixel - bitsLeft);
n = ((buffer & bitMask) << missingBits) & bitMask;
buffer = src[i++];
bitsLeft = 32 - missingBits;
n += (buffer >>> bitsLeft);
}
//dest.push(n);
dest[o] = n < nmax ? offset + n * scale : maxValue;
}
dest.unshift(offset);//1st one
return dest;
},
unstuff2: function(src, dest, bitsPerPixel, numPixels, lutArr, offset, scale, maxValue) {
var bitMask = (1 << bitsPerPixel) - 1;
var i = 0, o;
var bitsLeft = 0, bitPos = 0;
var n, buffer, missingBits;
if (lutArr) {
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
bitPos = 0;
}
if (bitsLeft >= bitsPerPixel) {
n = ((buffer >>> bitPos) & bitMask);
bitsLeft -= bitsPerPixel;
bitPos += bitsPerPixel;
} else {
missingBits = (bitsPerPixel - bitsLeft);
n = (buffer >>> bitPos) & bitMask;
buffer = src[i++];
bitsLeft = 32 - missingBits;
n |= (buffer & ((1 << missingBits) - 1)) << (bitsPerPixel - missingBits);
bitPos = missingBits;
}
dest[o] = lutArr[n];
}
}
else {
var nmax = Math.ceil((maxValue - offset) / scale);
for (o = 0; o < numPixels; o++) {
if (bitsLeft === 0) {
buffer = src[i++];
bitsLeft = 32;
bitPos = 0;
}
if (bitsLeft >= bitsPerPixel) {
//no unsigned left shift
n = ((buffer >>> bitPos) & bitMask);
bitsLeft -= bitsPerPixel;
bitPos += bitsPerPixel;