@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
361 lines (342 loc) • 10.1 kB
JavaScript
import defined from "../Core/defined.js";
import RuntimeError from "../Core/RuntimeError.js";
/**
* This class implements an I3S Field which is custom data attached
* to nodes
* @alias I3SField
* @internalConstructor
* @privateParam {I3SNode} parent The parent of that geometry
* @privateParam {object} storageInfo The structure containing the storage info of the field
*/
function I3SField(parent, storageInfo) {
this._storageInfo = storageInfo;
this._parent = parent;
this._dataProvider = parent._dataProvider;
this._loadPromise = undefined;
const uri = `attributes/${storageInfo.key}/0`;
if (defined(this._parent._nodeIndex)) {
this._resource = this._parent._layer.resource.getDerivedResource({
url: `nodes/${this._parent._data.mesh.attribute.resource}/${uri}`,
});
} else {
this._resource = this._parent.resource.getDerivedResource({ url: uri });
}
}
Object.defineProperties(I3SField.prototype, {
/**
* Gets the resource for the fields
* @memberof I3SField.prototype
* @type {Resource}
* @readonly
*/
resource: {
get: function () {
return this._resource;
},
},
/**
* Gets the header for this field.
* @memberof I3SField.prototype
* @type {object}
* @readonly
*/
header: {
get: function () {
return this._header;
},
},
/**
* Gets the values for this field.
* @memberof I3SField.prototype
* @type {object}
* @readonly
*/
values: {
get: function () {
if (defined(this._values)) {
// attribute data can be stored either as values or as object identifiers
if (defined(this._values.attributeValues)) {
return this._values.attributeValues;
}
if (defined(this._values.objectIds)) {
return this._values.objectIds;
}
}
return [];
},
},
/**
* Gets the name for the field.
* @memberof I3SField.prototype
* @type {string}
* @readonly
*/
name: {
get: function () {
return this._storageInfo.name;
},
},
});
function getNumericTypeSize(type) {
if (type === "UInt8" || type === "Int8") {
return 1;
} else if (type === "UInt16" || type === "Int16") {
return 2;
} else if (
type === "UInt32" ||
type === "Int32" ||
type === "Oid32" ||
type === "Float32"
) {
return 4;
} else if (type === "UInt64" || type === "Int64" || type === "Float64") {
return 8;
}
// Not a numeric type
return 0;
}
function getValueTypeSize(type) {
if (type === "String") {
return 1;
}
return getNumericTypeSize(type);
}
async function load(field) {
const data = await field._dataProvider._loadBinary(field._resource);
const dataView = new DataView(data);
field._data = data;
field._validateHeader(dataView);
const headerSize = field._parseHeader(dataView);
const offset = field._getBodyOffset(headerSize);
field._validateBody(dataView, offset);
field._parseBody(dataView, offset);
}
/**
* Loads the content.
* @returns {Promise<void>} A promise that is resolved when the field data is loaded
*/
I3SField.prototype.load = function () {
if (defined(this._loadPromise)) {
return this._loadPromise;
}
this._loadPromise = load(this).catch(function (error) {
console.error(error);
});
return this._loadPromise;
};
/**
* @private
*/
I3SField.prototype._parseValue = function (dataView, type, offset) {
let value;
if (type === "UInt8") {
value = dataView.getUint8(offset);
offset += 1;
} else if (type === "Int8") {
value = dataView.getInt8(offset);
offset += 1;
} else if (type === "UInt16") {
value = dataView.getUint16(offset, true);
offset += 2;
} else if (type === "Int16") {
value = dataView.getInt16(offset, true);
offset += 2;
} else if (type === "UInt32") {
value = dataView.getUint32(offset, true);
offset += 4;
} else if (type === "Oid32") {
value = dataView.getUint32(offset, true);
offset += 4;
} else if (type === "Int32") {
value = dataView.getInt32(offset, true);
offset += 4;
} else if (type === "UInt64") {
const left = dataView.getUint32(offset, true);
const right = dataView.getUint32(offset + 4, true);
value = left + Math.pow(2, 32) * right;
offset += 8;
} else if (type === "Int64") {
const left = dataView.getUint32(offset, true);
const right = dataView.getUint32(offset + 4, true);
if (right < Math.pow(2, 31)) {
// Positive number
value = left + Math.pow(2, 32) * right;
} else {
// Negative
value = left + Math.pow(2, 32) * (right - Math.pow(2, 32));
}
offset += 8;
} else if (type === "Float32") {
value = dataView.getFloat32(offset, true);
offset += 4;
} else if (type === "Float64") {
value = dataView.getFloat64(offset, true);
offset += 8;
} else if (type === "String") {
value = String.fromCharCode(dataView.getUint8(offset));
offset += 1;
}
return {
value: value,
offset: offset,
};
};
/**
* @private
*/
I3SField.prototype._parseHeader = function (dataView) {
let offset = 0;
this._header = {};
for (
let itemIndex = 0;
itemIndex < this._storageInfo.header.length;
itemIndex++
) {
const item = this._storageInfo.header[itemIndex];
const parsedValue = this._parseValue(dataView, item.valueType, offset);
this._header[item.property] = parsedValue.value;
offset = parsedValue.offset;
}
return offset;
};
/**
* @private
*/
I3SField.prototype._parseBody = function (dataView, offset) {
this._values = {};
for (
let itemIndex = 0;
itemIndex < this._storageInfo.ordering.length;
itemIndex++
) {
const orderingValue = this._storageInfo.ordering[itemIndex];
// all strings in the ordering array correspond to the property name, except ObjectIds
const item = orderingValue === "ObjectIds" ? "objectIds" : orderingValue;
const desc = this._storageInfo[item];
if (defined(desc)) {
this._values[item] = [];
for (let index = 0; index < this._header.count; ++index) {
if (desc.valueType !== "String") {
const parsedValue = this._parseValue(
dataView,
desc.valueType,
offset,
);
this._values[item].push(parsedValue.value);
offset = parsedValue.offset;
} else {
const stringLen = this._values.attributeByteCounts[index];
let stringContent = "";
for (let cIndex = 0; cIndex < stringLen; ++cIndex) {
const curParsedValue = this._parseValue(
dataView,
desc.valueType,
offset,
);
if (curParsedValue.value.charCodeAt(0) !== 0) {
stringContent += curParsedValue.value;
}
offset = curParsedValue.offset;
}
// We skip the last character of the string since it's a null terminator
this._values[item].push(stringContent);
}
}
}
}
};
/**
* @private
*/
I3SField.prototype._getBodyOffset = function (headerSize) {
let valueSize = 0;
if (defined(this._storageInfo.attributeValues)) {
valueSize = getNumericTypeSize(this._storageInfo.attributeValues.valueType);
} else if (defined(this._storageInfo.objectIds)) {
valueSize = getNumericTypeSize(this._storageInfo.objectIds.valueType);
}
if (valueSize > 0) {
// Values will be padded to align the addresses with the data size
return Math.ceil(headerSize / valueSize) * valueSize;
}
return headerSize;
};
/**
* @private
*/
I3SField.prototype._validateHeader = function (dataView) {
let headerSize = 0;
for (
let itemIndex = 0;
itemIndex < this._storageInfo.header.length;
itemIndex++
) {
const item = this._storageInfo.header[itemIndex];
headerSize += getValueTypeSize(item.valueType);
}
if (dataView.byteLength < headerSize) {
throw new RuntimeError(
`Invalid attribute buffer size (field: ${this.name}, header: ${headerSize}, actual: ${dataView.byteLength})`,
);
}
};
/**
* @private
*/
I3SField.prototype._validateBody = function (dataView, offset) {
if (!defined(this._header.count)) {
throw new RuntimeError(
`Invalid attribute buffer (field: ${this.name}, count is missing)`,
);
}
let attributeByteCountsOffset;
for (
let itemIndex = 0;
itemIndex < this._storageInfo.ordering.length &&
offset < dataView.byteLength;
itemIndex++
) {
const orderingValue = this._storageInfo.ordering[itemIndex];
// all strings in the ordering array correspond to the property name, except ObjectIds
const item = orderingValue === "ObjectIds" ? "objectIds" : orderingValue;
const desc = this._storageInfo[item];
if (defined(desc)) {
if (desc.valueType !== "String") {
if (item === "attributeByteCounts") {
attributeByteCountsOffset = offset;
}
const valueSize = getNumericTypeSize(desc.valueType);
offset += valueSize * this._header.count;
} else {
if (!defined(attributeByteCountsOffset)) {
throw new RuntimeError(
`Invalid attribute buffer (field: ${this.name}, attributeByteCounts is missing)`,
);
}
for (
let index = 0;
index < this._header.count && offset < dataView.byteLength;
++index
) {
const parsedValue = this._parseValue(
dataView,
this._storageInfo.attributeByteCounts.valueType,
attributeByteCountsOffset,
);
offset += parsedValue.value;
attributeByteCountsOffset = parsedValue.offset;
}
}
} else {
throw new RuntimeError(
`Invalid attribute buffer (field: ${this.name}, ${item} is missing)`,
);
}
}
if (dataView.byteLength < offset) {
throw new RuntimeError(
`Invalid attribute buffer size (field: ${this.name}, expected: ${offset}, actual: ${dataView.byteLength})`,
);
}
};
export default I3SField;