@loaders.gl/i3s
Version:
i3s .
4 lines • 109 kB
Source Map (JSON)
{
"version": 3,
"sources": ["index.js", "lib/parsers/constants.js", "i3s-loader.js", "lib/parsers/parse-i3s-tile-content.js", "types.js", "lib/utils/url-utils.js", "i3s-content-loader.js", "lib/parsers/parse-i3s.js", "lib/helpers/i3s-nodepages-tiles.js", "i3s-node-page-loader.js", "i3s-slpk-loader.js", "lib/parsers/parse-slpk/parse-slpk.js", "lib/parsers/parse-slpk/slpk-archieve.js", "i3s-attribute-loader.js", "lib/parsers/parse-i3s-attribute.js", "lib/parsers/parse-i3s-building-scene-layer.js", "i3s-building-scene-layer-loader.js", "lib/parsers/parse-arcgis-webscene.js", "arcgis-webscene-loader.js", "lib/utils/customize-colors.js"],
"sourcesContent": ["// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nexport { COORDINATE_SYSTEM } from \"./lib/parsers/constants.js\";\nexport { I3SLoader } from \"./i3s-loader.js\";\nexport { SLPKLoader } from \"./i3s-slpk-loader.js\";\nexport { I3SContentLoader } from \"./i3s-content-loader.js\";\nexport { I3SAttributeLoader, loadFeatureAttributes } from \"./i3s-attribute-loader.js\";\nexport { I3SBuildingSceneLayerLoader } from \"./i3s-building-scene-layer-loader.js\";\nexport { I3SNodePageLoader } from \"./i3s-node-page-loader.js\";\nexport { ArcGISWebSceneLoader } from \"./arcgis-webscene-loader.js\";\nexport { SLPKArchive } from \"./lib/parsers/parse-slpk/slpk-archieve.js\";\nexport { parseSLPKArchive } from \"./lib/parsers/parse-slpk/parse-slpk.js\";\nexport { LayerError } from \"./lib/parsers/parse-arcgis-webscene.js\";\nexport { customizeColors } from \"./lib/utils/customize-colors.js\";\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport { GL } from '@loaders.gl/math';\nexport function getConstructorForDataFormat(dataType) {\n switch (dataType) {\n case 'UInt8':\n return Uint8Array;\n case 'UInt16':\n return Uint16Array;\n case 'UInt32':\n return Uint32Array;\n case 'Float32':\n return Float32Array;\n case 'UInt64':\n return Float64Array;\n default:\n throw new Error(`parse i3s tile content: unknown type of data: ${dataType}`);\n }\n}\nexport const GL_TYPE_MAP = {\n UInt8: GL.UNSIGNED_BYTE,\n UInt16: GL.UNSIGNED_SHORT,\n Float32: GL.FLOAT,\n UInt32: GL.UNSIGNED_INT,\n UInt64: GL.DOUBLE\n};\n/**\n * Returns how many bytes a type occupies\n * @param dataType\n * @returns\n */\nexport function sizeOf(dataType) {\n switch (dataType) {\n case 'UInt8':\n return 1;\n case 'UInt16':\n case 'Int16':\n return 2;\n case 'UInt32':\n case 'Int32':\n case 'Float32':\n return 4;\n case 'UInt64':\n case 'Int64':\n case 'Float64':\n return 8;\n default:\n throw new Error(`parse i3s tile content: unknown size of data: ${dataType}`);\n }\n}\nexport const STRING_ATTRIBUTE_TYPE = 'String';\nexport const OBJECT_ID_ATTRIBUTE_TYPE = 'Oid32';\nexport const FLOAT_64_TYPE = 'Float64';\nexport const INT_16_ATTRIBUTE_TYPE = 'Int16';\n// https://github.com/visgl/deck.gl/blob/9548f43cba2234a1f4877b6b17f6c88eb35b2e08/modules/core/src/lib/constants.js#L27\n// Describes the format of positions\nexport var COORDINATE_SYSTEM;\n(function (COORDINATE_SYSTEM) {\n /**\n * `LNGLAT` if rendering into a geospatial viewport, `CARTESIAN` otherwise\n */\n COORDINATE_SYSTEM[COORDINATE_SYSTEM[\"DEFAULT\"] = -1] = \"DEFAULT\";\n /**\n * Positions are interpreted as [lng, lat, elevation]\n * lng lat are degrees, elevation is meters. distances as meters.\n */\n COORDINATE_SYSTEM[COORDINATE_SYSTEM[\"LNGLAT\"] = 1] = \"LNGLAT\";\n /**\n * Positions are interpreted as meter offsets, distances as meters\n */\n COORDINATE_SYSTEM[COORDINATE_SYSTEM[\"METER_OFFSETS\"] = 2] = \"METER_OFFSETS\";\n /**\n * Positions are interpreted as lng lat offsets: [deltaLng, deltaLat, elevation]\n * deltaLng, deltaLat are delta degrees, elevation is meters.\n * distances as meters.\n */\n COORDINATE_SYSTEM[COORDINATE_SYSTEM[\"LNGLAT_OFFSETS\"] = 3] = \"LNGLAT_OFFSETS\";\n /**\n * Non-geospatial\n */\n COORDINATE_SYSTEM[COORDINATE_SYSTEM[\"CARTESIAN\"] = 0] = \"CARTESIAN\";\n})(COORDINATE_SYSTEM || (COORDINATE_SYSTEM = {}));\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright vis.gl contributors\nimport { parse } from '@loaders.gl/core';\nimport { I3SContentLoader } from \"./i3s-content-loader.js\";\nimport { normalizeTileData, normalizeTilesetData } from \"./lib/parsers/parse-i3s.js\";\nimport { COORDINATE_SYSTEM } from \"./lib/parsers/constants.js\";\nimport { getUrlWithoutParams } from \"./lib/utils/url-utils.js\";\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof \"4.3.2\" !== 'undefined' ? \"4.3.2\" : 'latest';\nconst TILESET_REGEX = /layers\\/[0-9]+$/;\nconst LOCAL_SLPK_REGEX = /\\.slpk$/;\nconst TILE_HEADER_REGEX = /nodes\\/([0-9-]+|root)$/;\nconst SLPK_HEX = '504b0304';\nconst POINT_CLOUD = 'PointCloud';\n/**\n * Loader for I3S - Indexed 3D Scene Layer\n */\nexport const I3SLoader = {\n dataType: null,\n batchType: null,\n name: 'I3S (Indexed Scene Layers)',\n id: 'i3s',\n module: 'i3s',\n version: VERSION,\n mimeTypes: ['application/octet-stream'],\n parse: parseI3S,\n extensions: ['bin'],\n options: {\n i3s: {\n token: null,\n isTileset: 'auto',\n isTileHeader: 'auto',\n tile: null,\n tileset: null,\n _tileOptions: null,\n _tilesetOptions: null,\n useDracoGeometry: true,\n useCompressedTextures: true,\n decodeTextures: true,\n coordinateSystem: COORDINATE_SYSTEM.METER_OFFSETS\n }\n }\n};\nasync function parseI3S(data, options = {}, context) {\n const url = context.url;\n options.i3s = options.i3s || {};\n const magicNumber = getMagicNumber(data);\n // check if file is slpk\n if (magicNumber === SLPK_HEX) {\n throw new Error('Files with .slpk extention currently are not supported by I3SLoader');\n }\n const urlWithoutParams = getUrlWithoutParams(url);\n // auto detect file type based on url\n let isTileset;\n if (options.i3s.isTileset === 'auto') {\n isTileset = TILESET_REGEX.test(urlWithoutParams) || LOCAL_SLPK_REGEX.test(urlWithoutParams);\n }\n else {\n isTileset = options.i3s.isTileset;\n }\n let isTileHeader;\n if (options.isTileHeader === 'auto') {\n isTileHeader = TILE_HEADER_REGEX.test(urlWithoutParams);\n }\n else {\n isTileHeader = options.i3s.isTileHeader;\n }\n if (isTileset) {\n data = await parseTileset(data, options, context);\n }\n else if (isTileHeader) {\n data = await parseTile(data, context);\n }\n else {\n data = await parseTileContent(data, options);\n }\n return data;\n}\nasync function parseTileContent(arrayBuffer, options) {\n return await parse(arrayBuffer, I3SContentLoader, options);\n}\nasync function parseTileset(data, options, context) {\n const tilesetJson = JSON.parse(new TextDecoder().decode(data));\n if (tilesetJson?.layerType === POINT_CLOUD) {\n throw new Error('Point Cloud layers currently are not supported by I3SLoader');\n }\n const tilesetPostprocessed = await normalizeTilesetData(tilesetJson, options, context);\n return tilesetPostprocessed;\n}\nasync function parseTile(data, context) {\n data = JSON.parse(new TextDecoder().decode(data));\n return normalizeTileData(data, context);\n}\nfunction getMagicNumber(data) {\n if (data instanceof ArrayBuffer) {\n // slice binary data (4 bytes from the beginning) and transform it to hexadecimal numeral system\n return [...new Uint8Array(data, 0, 4)]\n .map((value) => value.toString(16).padStart(2, '0'))\n .join('');\n }\n return null;\n}\n", "import { load, parse } from '@loaders.gl/core';\nimport { Vector3, Matrix4 } from '@math.gl/core';\nimport { Ellipsoid } from '@math.gl/geospatial';\nimport { parseFromContext } from '@loaders.gl/loader-utils';\nimport { ImageLoader } from '@loaders.gl/images';\nimport { DracoLoader } from '@loaders.gl/draco';\nimport { BasisLoader, CompressedTextureLoader } from '@loaders.gl/textures';\nimport { HeaderAttributeProperty } from \"../../types.js\";\nimport { getUrlWithToken } from \"../utils/url-utils.js\";\nimport { GL_TYPE_MAP, getConstructorForDataFormat, sizeOf, COORDINATE_SYSTEM } from \"./constants.js\";\nconst scratchVector = new Vector3([0, 0, 0]);\nfunction getLoaderForTextureFormat(textureFormat) {\n switch (textureFormat) {\n case 'ktx-etc2':\n case 'dds':\n return CompressedTextureLoader;\n case 'ktx2':\n return BasisLoader;\n case 'jpg':\n case 'png':\n default:\n return ImageLoader;\n }\n}\nconst I3S_ATTRIBUTE_TYPE = 'i3s-attribute-type';\nexport async function parseI3STileContent(arrayBuffer, tileOptions, tilesetOptions, options, context) {\n const content = {\n attributes: {},\n indices: null,\n featureIds: [],\n vertexCount: 0,\n modelMatrix: new Matrix4(),\n coordinateSystem: 0,\n byteLength: 0,\n texture: null\n };\n if (tileOptions.textureUrl) {\n // @ts-expect-error options is not properly typed\n const url = getUrlWithToken(tileOptions.textureUrl, options?.i3s?.token);\n const loader = getLoaderForTextureFormat(tileOptions.textureFormat);\n const fetchFunc = context?.fetch || fetch;\n const response = await fetchFunc(url); // options?.fetch\n const arrayBuffer = await response.arrayBuffer();\n // @ts-expect-error options is not properly typed\n if (options?.i3s.decodeTextures) {\n // TODO - replace with switch\n if (loader === ImageLoader) {\n const options = { ...tileOptions.textureLoaderOptions, image: { type: 'data' } };\n try {\n // Image constructor is not supported in worker thread.\n // Do parsing image data on the main thread by using context to avoid worker issues.\n const texture = await parseFromContext(arrayBuffer, [], options, context);\n content.texture = texture;\n }\n catch (e) {\n // context object is different between worker and node.js conversion script.\n // To prevent error we parse data in ordinary way if it is not parsed by using context.\n const texture = await parse(arrayBuffer, loader, options, context);\n content.texture = texture;\n }\n }\n else if (loader === CompressedTextureLoader || loader === BasisLoader) {\n let texture = await load(arrayBuffer, loader, tileOptions.textureLoaderOptions);\n if (loader === BasisLoader) {\n texture = texture[0];\n }\n content.texture = {\n compressed: true,\n mipmaps: false,\n width: texture[0].width,\n height: texture[0].height,\n data: texture\n };\n }\n }\n else {\n content.texture = arrayBuffer;\n }\n }\n content.material = makePbrMaterial(tileOptions.materialDefinition, content.texture);\n if (content.material) {\n content.texture = null;\n }\n return await parseI3SNodeGeometry(arrayBuffer, content, tileOptions, tilesetOptions, options);\n}\n/* eslint-disable max-statements */\nasync function parseI3SNodeGeometry(arrayBuffer, content, tileOptions, tilesetOptions, options) {\n const contentByteLength = arrayBuffer.byteLength;\n let attributes;\n let vertexCount;\n let byteOffset = 0;\n let featureCount = 0;\n let indices;\n if (tileOptions.isDracoGeometry) {\n const decompressedGeometry = await parse(arrayBuffer, DracoLoader, {\n draco: {\n attributeNameEntry: I3S_ATTRIBUTE_TYPE\n }\n });\n // @ts-expect-error\n vertexCount = decompressedGeometry.header.vertexCount;\n indices = decompressedGeometry.indices?.value;\n const { POSITION, NORMAL, COLOR_0, TEXCOORD_0, ['feature-index']: featureIndex, ['uv-region']: uvRegion } = decompressedGeometry.attributes;\n attributes = {\n position: POSITION,\n normal: NORMAL,\n color: COLOR_0,\n uv0: TEXCOORD_0,\n uvRegion,\n id: featureIndex\n };\n updateAttributesMetadata(attributes, decompressedGeometry);\n const featureIds = getFeatureIdsFromFeatureIndexMetadata(featureIndex);\n if (featureIds) {\n flattenFeatureIdsByFeatureIndices(attributes, featureIds);\n }\n }\n else {\n const { vertexAttributes, ordering: attributesOrder, featureAttributes, featureAttributeOrder } = tilesetOptions.store.defaultGeometrySchema;\n // First 8 bytes reserved for header (vertexCount and featureCount)\n const headers = parseHeaders(arrayBuffer, tilesetOptions);\n byteOffset = headers.byteOffset;\n vertexCount = headers.vertexCount;\n featureCount = headers.featureCount;\n // Getting vertex attributes such as positions, normals, colors, etc...\n const { attributes: normalizedVertexAttributes, byteOffset: offset } = normalizeAttributes(arrayBuffer, byteOffset, vertexAttributes, vertexCount, attributesOrder);\n // Getting feature attributes such as featureIds and faceRange\n const { attributes: normalizedFeatureAttributes } = normalizeAttributes(arrayBuffer, offset, featureAttributes, featureCount, featureAttributeOrder);\n flattenFeatureIdsByFaceRanges(normalizedFeatureAttributes);\n attributes = concatAttributes(normalizedVertexAttributes, normalizedFeatureAttributes);\n }\n if (!options?.i3s?.coordinateSystem ||\n // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n options.i3s.coordinateSystem === COORDINATE_SYSTEM.METER_OFFSETS) {\n const enuMatrix = parsePositions(attributes.position, tileOptions);\n content.modelMatrix = enuMatrix.invert();\n content.coordinateSystem = COORDINATE_SYSTEM.METER_OFFSETS;\n }\n else {\n content.modelMatrix = getModelMatrix(attributes.position);\n content.coordinateSystem = COORDINATE_SYSTEM.LNGLAT_OFFSETS;\n }\n content.attributes = {\n positions: attributes.position,\n normals: attributes.normal,\n colors: normalizeAttribute(attributes.color), // Normalize from UInt8\n texCoords: attributes.uv0,\n uvRegions: normalizeAttribute(attributes.uvRegion || attributes.region) // Normalize from UInt16\n };\n content.indices = indices || null;\n if (attributes.id && attributes.id.value) {\n content.featureIds = attributes.id.value;\n }\n // Remove undefined attributes\n for (const attributeIndex in content.attributes) {\n if (!content.attributes[attributeIndex]) {\n delete content.attributes[attributeIndex];\n }\n }\n content.vertexCount = vertexCount;\n content.byteLength = contentByteLength;\n return content;\n}\n/**\n * Update attributes with metadata from decompressed geometry.\n * @param decompressedGeometry\n * @param attributes\n */\nfunction updateAttributesMetadata(attributes, decompressedGeometry) {\n for (const key in decompressedGeometry.loaderData.attributes) {\n const dracoAttribute = decompressedGeometry.loaderData.attributes[key];\n switch (dracoAttribute.name) {\n case 'POSITION':\n attributes.position.metadata = dracoAttribute.metadata;\n break;\n case 'feature-index':\n attributes.id.metadata = dracoAttribute.metadata;\n break;\n default:\n break;\n }\n }\n}\n/**\n * Do concatenation of attribute objects.\n * Done as separate fucntion to avoid ts errors.\n * @param normalizedVertexAttributes\n * @param normalizedFeatureAttributes\n * @returns - result of attributes concatenation.\n */\nfunction concatAttributes(normalizedVertexAttributes, normalizedFeatureAttributes) {\n return { ...normalizedVertexAttributes, ...normalizedFeatureAttributes };\n}\n/**\n * Normalize attribute to range [0..1] . Eg. convert colors buffer from [255,255,255,255] to [1,1,1,1]\n * @param attribute - geometry attribute\n * @returns - geometry attribute in right format\n */\nfunction normalizeAttribute(attribute) {\n if (!attribute) {\n return attribute;\n }\n attribute.normalized = true;\n return attribute;\n}\nfunction parseHeaders(arrayBuffer, options) {\n let byteOffset = 0;\n // First 8 bytes reserved for header (vertexCount and featurecount)\n let vertexCount = 0;\n let featureCount = 0;\n for (const { property, type } of options.store.defaultGeometrySchema.header) {\n const TypedArrayTypeHeader = getConstructorForDataFormat(type);\n switch (property) {\n case HeaderAttributeProperty.vertexCount.toString():\n vertexCount = new TypedArrayTypeHeader(arrayBuffer, 0, 4)[0];\n byteOffset += sizeOf(type);\n break;\n case HeaderAttributeProperty.featureCount.toString():\n featureCount = new TypedArrayTypeHeader(arrayBuffer, 4, 4)[0];\n byteOffset += sizeOf(type);\n break;\n default:\n break;\n }\n }\n return {\n vertexCount,\n featureCount,\n byteOffset\n };\n}\n/* eslint-enable max-statements */\nfunction normalizeAttributes(arrayBuffer, byteOffset, vertexAttributes, attributeCount, attributesOrder) {\n const attributes = {};\n // the order of attributes depend on the order being added to the vertexAttributes object\n for (const attribute of attributesOrder) {\n if (vertexAttributes[attribute]) {\n const { valueType, valuesPerElement } = vertexAttributes[attribute];\n // protect from arrayBuffer read overunns by NOT assuming node has regions always even though its declared in defaultGeometrySchema.\n // In i3s 1.6: client is required to decide that based on ./shared resource of the node (materialDefinitions.[Mat_id].params.vertexRegions == true)\n // In i3s 1.7 the property has been rolled into the 3d scene layer json/node pages.\n // Code below does not account when the bytelength is actually bigger than\n // the calculated value (b\\c the tile potentially could have mesh segmentation information).\n // In those cases tiles without regions could fail or have garbage values.\n if (byteOffset + attributeCount * valuesPerElement * sizeOf(valueType) <=\n arrayBuffer.byteLength) {\n const buffer = arrayBuffer.slice(byteOffset);\n let value;\n if (valueType === 'UInt64') {\n value = parseUint64Values(buffer, attributeCount * valuesPerElement, sizeOf(valueType));\n }\n else {\n const TypedArrayType = getConstructorForDataFormat(valueType);\n value = new TypedArrayType(buffer, 0, attributeCount * valuesPerElement);\n }\n attributes[attribute] = {\n value,\n type: GL_TYPE_MAP[valueType],\n size: valuesPerElement\n };\n switch (attribute) {\n case 'color':\n attributes.color.normalized = true;\n break;\n case 'position':\n case 'region':\n case 'normal':\n default:\n }\n byteOffset = byteOffset + attributeCount * valuesPerElement * sizeOf(valueType);\n }\n else if (attribute !== 'uv0') {\n break;\n }\n }\n }\n return { attributes, byteOffset };\n}\n/**\n * Parse buffer to return array of uint64 values\n *\n * @param buffer\n * @param elementsCount\n * @returns 64-bit array of values until precision is lost after Number.MAX_SAFE_INTEGER\n */\nfunction parseUint64Values(buffer, elementsCount, attributeSize) {\n const values = [];\n const dataView = new DataView(buffer);\n let offset = 0;\n for (let index = 0; index < elementsCount; index++) {\n // split 64-bit number into two 32-bit parts\n const left = dataView.getUint32(offset, true);\n const right = dataView.getUint32(offset + 4, true);\n // combine the two 32-bit values\n const value = left + 2 ** 32 * right;\n values.push(value);\n offset += attributeSize;\n }\n return new Uint32Array(values);\n}\nfunction parsePositions(attribute, options) {\n const mbs = options.mbs;\n const value = attribute.value;\n const metadata = attribute.metadata;\n const enuMatrix = new Matrix4();\n const cartographicOrigin = new Vector3(mbs[0], mbs[1], mbs[2]);\n const cartesianOrigin = new Vector3();\n Ellipsoid.WGS84.cartographicToCartesian(cartographicOrigin, cartesianOrigin);\n Ellipsoid.WGS84.eastNorthUpToFixedFrame(cartesianOrigin, enuMatrix);\n attribute.value = offsetsToCartesians(value, metadata, cartographicOrigin);\n return enuMatrix;\n}\n/**\n * Converts position coordinates to absolute cartesian coordinates\n * @param vertices - \"position\" attribute data\n * @param metadata - When the geometry is DRACO compressed, contain position attribute's metadata\n * https://github.com/Esri/i3s-spec/blob/master/docs/1.7/compressedAttributes.cmn.md\n * @param cartographicOrigin - Cartographic origin coordinates\n * @returns - converted \"position\" data\n */\nfunction offsetsToCartesians(vertices, metadata = {}, cartographicOrigin) {\n const positions = new Float64Array(vertices.length);\n const scaleX = (metadata['i3s-scale_x'] && metadata['i3s-scale_x'].double) || 1;\n const scaleY = (metadata['i3s-scale_y'] && metadata['i3s-scale_y'].double) || 1;\n for (let i = 0; i < positions.length; i += 3) {\n positions[i] = vertices[i] * scaleX + cartographicOrigin.x;\n positions[i + 1] = vertices[i + 1] * scaleY + cartographicOrigin.y;\n positions[i + 2] = vertices[i + 2] + cartographicOrigin.z;\n }\n for (let i = 0; i < positions.length; i += 3) {\n // @ts-ignore\n Ellipsoid.WGS84.cartographicToCartesian(positions.subarray(i, i + 3), scratchVector);\n positions[i] = scratchVector.x;\n positions[i + 1] = scratchVector.y;\n positions[i + 2] = scratchVector.z;\n }\n return positions;\n}\n/**\n * Get model matrix for loaded vertices\n * @param positions positions attribute\n * @returns Matrix4 - model matrix for geometry transformation\n */\nfunction getModelMatrix(positions) {\n const metadata = positions.metadata;\n const scaleX = metadata?.['i3s-scale_x']?.double || 1;\n const scaleY = metadata?.['i3s-scale_y']?.double || 1;\n const modelMatrix = new Matrix4();\n modelMatrix[0] = scaleX;\n modelMatrix[5] = scaleY;\n return modelMatrix;\n}\n/**\n * Makes a glTF-compatible PBR material from an I3S material definition\n * @param materialDefinition - i3s material definition\n * https://github.com/Esri/i3s-spec/blob/master/docs/1.7/materialDefinitions.cmn.md\n * @param texture - texture image\n * @returns {object}\n */\nfunction makePbrMaterial(materialDefinition, texture) {\n let pbrMaterial;\n if (materialDefinition) {\n pbrMaterial = {\n ...materialDefinition,\n pbrMetallicRoughness: materialDefinition.pbrMetallicRoughness\n ? { ...materialDefinition.pbrMetallicRoughness }\n : { baseColorFactor: [255, 255, 255, 255] }\n };\n }\n else {\n pbrMaterial = {\n pbrMetallicRoughness: {}\n };\n if (texture) {\n pbrMaterial.pbrMetallicRoughness.baseColorTexture = { texCoord: 0 };\n }\n else {\n pbrMaterial.pbrMetallicRoughness.baseColorFactor = [255, 255, 255, 255];\n }\n }\n // Set default 0.25 per spec https://github.com/Esri/i3s-spec/blob/master/docs/1.7/materialDefinitions.cmn.md\n pbrMaterial.alphaCutoff = pbrMaterial.alphaCutoff || 0.25;\n if (pbrMaterial.alphaMode) {\n // I3S contain alphaMode in lowerCase\n pbrMaterial.alphaMode = pbrMaterial.alphaMode.toUpperCase();\n }\n // Convert colors from [255,255,255,255] to [1,1,1,1]\n if (pbrMaterial.emissiveFactor) {\n pbrMaterial.emissiveFactor = convertColorFormat(pbrMaterial.emissiveFactor);\n }\n if (pbrMaterial.pbrMetallicRoughness && pbrMaterial.pbrMetallicRoughness.baseColorFactor) {\n pbrMaterial.pbrMetallicRoughness.baseColorFactor = convertColorFormat(pbrMaterial.pbrMetallicRoughness.baseColorFactor);\n }\n if (texture) {\n setMaterialTexture(pbrMaterial, texture);\n }\n return pbrMaterial;\n}\n/**\n * Convert color from [255,255,255,255] to [1,1,1,1]\n * @param colorFactor - color array\n * @returns - new color array\n */\nfunction convertColorFormat(colorFactor) {\n const normalizedColor = [...colorFactor];\n for (let index = 0; index < colorFactor.length; index++) {\n normalizedColor[index] = colorFactor[index] / 255;\n }\n return normalizedColor;\n}\n/**\n * Set texture in PBR material\n * @param {object} material - i3s material definition\n * @param image - texture image\n * @returns\n */\nfunction setMaterialTexture(material, image) {\n const texture = { source: { image } };\n // I3SLoader now support loading only one texture. This elseif sequence will assign this texture to one of\n // properties defined in materialDefinition\n if (material.pbrMetallicRoughness && material.pbrMetallicRoughness.baseColorTexture) {\n material.pbrMetallicRoughness.baseColorTexture = {\n ...material.pbrMetallicRoughness.baseColorTexture,\n texture\n };\n }\n else if (material.emissiveTexture) {\n material.emissiveTexture = { ...material.emissiveTexture, texture };\n }\n else if (material.pbrMetallicRoughness &&\n material.pbrMetallicRoughness.metallicRoughnessTexture) {\n material.pbrMetallicRoughness.metallicRoughnessTexture = {\n ...material.pbrMetallicRoughness.metallicRoughnessTexture,\n texture\n };\n }\n else if (material.normalTexture) {\n material.normalTexture = { ...material.normalTexture, texture };\n }\n else if (material.occlusionTexture) {\n material.occlusionTexture = { ...material.occlusionTexture, texture };\n }\n}\n/**\n * Flatten feature ids using face ranges\n * @param normalizedFeatureAttributes\n * @returns\n */\nfunction flattenFeatureIdsByFaceRanges(normalizedFeatureAttributes) {\n const { id, faceRange } = normalizedFeatureAttributes;\n if (!id || !faceRange) {\n return;\n }\n const featureIds = id.value;\n const range = faceRange.value;\n const featureIdsLength = range[range.length - 1] + 1;\n const orderedFeatureIndices = new Uint32Array(featureIdsLength * 3);\n let featureIndex = 0;\n let startIndex = 0;\n for (let index = 1; index < range.length; index += 2) {\n const fillId = Number(featureIds[featureIndex]);\n const endValue = range[index];\n const prevValue = range[index - 1];\n const trianglesCount = endValue - prevValue + 1;\n const endIndex = startIndex + trianglesCount * 3;\n orderedFeatureIndices.fill(fillId, startIndex, endIndex);\n featureIndex++;\n startIndex = endIndex;\n }\n normalizedFeatureAttributes.id.value = orderedFeatureIndices;\n}\n/**\n * Flatten feature ids using featureIndices\n * @param attributes\n * @param featureIds\n * @returns\n */\nfunction flattenFeatureIdsByFeatureIndices(attributes, featureIds) {\n const featureIndices = attributes.id.value;\n const result = new Float32Array(featureIndices.length);\n for (let index = 0; index < featureIndices.length; index++) {\n result[index] = featureIds[featureIndices[index]];\n }\n attributes.id.value = result;\n}\n/**\n * Flatten feature ids using featureIndices\n * @param featureIndex\n * @returns\n */\nfunction getFeatureIdsFromFeatureIndexMetadata(featureIndex) {\n return featureIndex?.metadata?.['i3s-feature-ids']?.intArray;\n}\n", "export var HeaderAttributeProperty;\n(function (HeaderAttributeProperty) {\n HeaderAttributeProperty[\"vertexCount\"] = \"vertexCount\";\n HeaderAttributeProperty[\"featureCount\"] = \"featureCount\";\n})(HeaderAttributeProperty || (HeaderAttributeProperty = {}));\n", "/**\n * Return URL seperated from search params\n * @param url - URL that might have search params\n * @returns url without search params\n */\nexport function getUrlWithoutParams(url) {\n let urlWithoutParams;\n try {\n const urlObj = new URL(url);\n urlWithoutParams = `${urlObj.origin}${urlObj.pathname}`;\n // On Windows `new URL(url)` makes `C:\\...` -> `null\\...`\n if (urlWithoutParams.startsWith('null')) {\n urlWithoutParams = null;\n }\n }\n catch (e) {\n urlWithoutParams = null;\n }\n return urlWithoutParams || url;\n}\n/**\n * Generates url with token if it is exists.\n * @param url\n * @param token\n * @returns\n */\nexport function getUrlWithToken(url, token = null) {\n return token ? `${url}?token=${token}` : url;\n}\n/**\n * Generates attribute urls for tile.\n * @param tile\n * @returns list of attribute urls\n */\nexport function generateTileAttributeUrls(url, tile) {\n const { attributeData = [] } = tile;\n const attributeUrls = [];\n for (let index = 0; index < attributeData.length; index++) {\n const attributeUrl = attributeData[index].href.replace('./', '');\n attributeUrls.push(`${url}/${attributeUrl}`);\n }\n return attributeUrls;\n}\n/**\n * Generates attribute urls for tileset based on tileset and resource\n * @param tileset - tileset metadata\n * @param url - tileset base url\n * @param resource - resource id per I3S spec\n * @returns {Array}\n */\nexport function generateTilesetAttributeUrls(tileset, url, resource) {\n const attributeUrls = [];\n const { attributeStorageInfo = [] } = tileset;\n for (let index = 0; index < attributeStorageInfo.length; index++) {\n const fileName = attributeStorageInfo[index].key;\n attributeUrls.push(`${url}/nodes/${resource}/attributes/${fileName}/0`);\n }\n return attributeUrls;\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright vis.gl contributors\nimport { parseI3STileContent } from \"./lib/parsers/parse-i3s-tile-content.js\";\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof \"4.3.2\" !== 'undefined' ? \"4.3.2\" : 'latest';\n/**\n * Loader for I3S - Indexed 3D Scene Layer\n */\nexport const I3SContentLoader = {\n dataType: null,\n batchType: null,\n name: 'I3S Content (Indexed Scene Layers)',\n id: 'i3s-content',\n module: 'i3s',\n worker: true,\n version: VERSION,\n mimeTypes: ['application/octet-stream'],\n parse,\n extensions: ['bin'],\n options: {\n 'i3s-content': {}\n }\n};\nasync function parse(data, options, context) {\n const { tile, _tileOptions, tileset, _tilesetOptions } = options?.i3s || {};\n const tileOptions = _tileOptions || tile;\n const tilesetOptions = _tilesetOptions || tileset;\n if (!tileOptions || !tilesetOptions) {\n return null;\n }\n return await parseI3STileContent(data, tileOptions, tilesetOptions, options, context);\n}\n", "import { OrientedBoundingBox } from '@math.gl/culling';\nimport { Ellipsoid } from '@math.gl/geospatial';\nimport { load } from '@loaders.gl/core';\nimport { TILE_TYPE, TILE_REFINEMENT, TILESET_TYPE } from '@loaders.gl/tiles';\nimport I3SNodePagesTiles from \"../helpers/i3s-nodepages-tiles.js\";\nimport { generateTileAttributeUrls, getUrlWithToken, getUrlWithoutParams } from \"../utils/url-utils.js\";\nimport { I3SLoader } from \"../../i3s-loader.js\";\nexport function normalizeTileData(tile, context) {\n const url = context.url || '';\n let contentUrl;\n if (tile.geometryData) {\n contentUrl = `${url}/${tile.geometryData[0].href}`;\n }\n let textureUrl;\n if (tile.textureData) {\n textureUrl = `${url}/${tile.textureData[0].href}`;\n }\n let attributeUrls;\n if (tile.attributeData) {\n attributeUrls = generateTileAttributeUrls(url, tile);\n }\n const children = tile.children || [];\n return normalizeTileNonUrlData({\n ...tile,\n children,\n url,\n contentUrl,\n textureUrl,\n textureFormat: 'jpg', // `jpg` format will cause `ImageLoader` usage that will be able to handle `png` as well\n attributeUrls,\n isDracoGeometry: false\n });\n}\nexport function normalizeTileNonUrlData(tile) {\n const boundingVolume = {};\n let mbs = [0, 0, 0, 1];\n if (tile.mbs) {\n mbs = tile.mbs;\n boundingVolume.sphere = [\n ...Ellipsoid.WGS84.cartographicToCartesian(tile.mbs.slice(0, 3)), // cartesian center of sphere\n tile.mbs[3] // radius of sphere\n ];\n }\n else if (tile.obb) {\n boundingVolume.box = [\n ...Ellipsoid.WGS84.cartographicToCartesian(tile.obb.center), // cartesian center of box\n ...tile.obb.halfSize, // halfSize\n ...tile.obb.quaternion // quaternion\n ];\n const obb = new OrientedBoundingBox().fromCenterHalfSizeQuaternion(boundingVolume.box.slice(0, 3), tile.obb.halfSize, tile.obb.quaternion);\n const boundingSphere = obb.getBoundingSphere();\n boundingVolume.sphere = [...boundingSphere.center, boundingSphere.radius];\n mbs = [...tile.obb.center, boundingSphere.radius];\n }\n const lodMetricType = tile.lodSelection?.[0].metricType;\n const lodMetricValue = tile.lodSelection?.[0].maxError;\n const type = TILE_TYPE.MESH;\n /**\n * I3S specification supports only REPLACE\n */\n const refine = TILE_REFINEMENT.REPLACE;\n return { ...tile, mbs, boundingVolume, lodMetricType, lodMetricValue, type, refine };\n}\nexport async function normalizeTilesetData(tileset, options, context) {\n const url = getUrlWithoutParams(context.url || '');\n let nodePagesTile;\n let root;\n if (tileset.nodePages) {\n nodePagesTile = new I3SNodePagesTiles(tileset, url, options);\n root = await nodePagesTile.formTileFromNodePages(0);\n }\n else {\n const parseOptions = options.i3s;\n const rootNodeUrl = getUrlWithToken(`${url}/nodes/root`, parseOptions.token);\n // eslint-disable-next-line no-use-before-define\n root = await load(rootNodeUrl, I3SLoader, {\n ...options,\n i3s: {\n // @ts-expect-error options is not properly typed\n ...options.i3s,\n loadContent: false, isTileHeader: true, isTileset: false\n }\n });\n }\n return {\n ...tileset,\n loader: I3SLoader,\n url,\n basePath: url,\n type: TILESET_TYPE.I3S,\n nodePagesTile,\n // @ts-expect-error\n root,\n lodMetricType: root.lodMetricType,\n lodMetricValue: root.lodMetricValue\n };\n}\n", "import { load } from '@loaders.gl/core';\nimport { getSupportedGPUTextureFormats, selectSupportedBasisFormat } from '@loaders.gl/textures';\nimport { I3SNodePageLoader } from \"../../i3s-node-page-loader.js\";\nimport { normalizeTileNonUrlData } from \"../parsers/parse-i3s.js\";\nimport { getUrlWithToken, generateTilesetAttributeUrls } from \"../utils/url-utils.js\";\n/**\n * class I3SNodePagesTiles - loads nodePages and form i3s tiles from them\n */\nexport default class I3SNodePagesTiles {\n tileset;\n nodePages = [];\n pendingNodePages = [];\n nodesPerPage;\n options;\n lodSelectionMetricType;\n textureDefinitionsSelectedFormats = [];\n nodesInNodePages;\n url;\n textureLoaderOptions = {};\n /**\n * @constructs\n * Create a I3SNodePagesTiles instance.\n * @param tileset - i3s tileset header ('layers/0')\n * @param url - tileset url\n * @param options - i3s loader options\n */\n constructor(tileset, url = '', options) {\n this.tileset = { ...tileset }; // spread the tileset to avoid circular reference\n this.url = url;\n this.nodesPerPage = tileset.nodePages?.nodesPerPage || 64;\n this.lodSelectionMetricType = tileset.nodePages?.lodSelectionMetricType;\n this.options = options;\n this.nodesInNodePages = 0;\n this.initSelectedFormatsForTextureDefinitions(tileset);\n }\n /**\n * Loads some nodePage and return a particular node from it\n * @param id - id of node through all node pages\n */\n async getNodeById(id) {\n const pageIndex = Math.floor(id / this.nodesPerPage);\n if (!this.nodePages[pageIndex] && !this.pendingNodePages[pageIndex]) {\n const nodePageUrl = getUrlWithToken(`${this.url}/nodepages/${pageIndex}`, \n // @ts-expect-error this.options is not properly typed\n this.options.i3s?.token);\n this.pendingNodePages[pageIndex] = {\n status: 'Pending',\n promise: load(nodePageUrl, I3SNodePageLoader, this.options)\n };\n this.nodePages[pageIndex] = await this.pendingNodePages[pageIndex].promise;\n this.nodesInNodePages += this.nodePages[pageIndex].nodes.length;\n this.pendingNodePages[pageIndex].status = 'Done';\n }\n if (this.pendingNodePages[pageIndex].status === 'Pending') {\n this.nodePages[pageIndex] = await this.pendingNodePages[pageIndex].promise;\n }\n const nodeIndex = id % this.nodesPerPage;\n return this.nodePages[pageIndex].nodes[nodeIndex];\n }\n /**\n * Forms tile header using node and tileset data\n * @param id - id of node through all node pages\n */\n // eslint-disable-next-line complexity, max-statements\n async formTileFromNodePages(id) {\n const node = await this.getNodeById(id);\n const children = [];\n const childNodesPromises = [];\n for (const child of node.children || []) {\n childNodesPromises.push(this.getNodeById(child));\n }\n const childNodes = await Promise.all(childNodesPromises);\n for (const childNode of childNodes) {\n children.push({\n id: childNode.index.toString(),\n obb: childNode.obb\n });\n }\n let contentUrl;\n let textureUrl;\n let materialDefinition;\n let textureFormat = 'jpg';\n let attributeUrls = [];\n let isDracoGeometry = false;\n if (node && node.mesh) {\n // Get geometry resource URL and type (compressed / non-compressed)\n const { url, isDracoGeometry: isDracoGeometryResult } = (node.mesh.geometry &&\n this.getContentUrl(node.mesh.geometry)) || { isDracoGeometry: false };\n contentUrl = url;\n isDracoGeometry = isDracoGeometryResult;\n const { textureData, materialDefinition: nodeMaterialDefinition } = this.getInformationFromMaterial(node.mesh.material);\n materialDefinition = nodeMaterialDefinition;\n textureFormat = textureData.format || textureFormat;\n if (textureData.name) {\n textureUrl = `${this.url}/nodes/${node.mesh.material.resource}/textures/${textureData.name}`;\n }\n if (this.tileset.attributeStorageInfo) {\n attributeUrls = generateTilesetAttributeUrls(this.tileset, this.url, node.mesh.attribute.resource);\n }\n }\n const lodSelection = this.getLodSelection(node);\n return normalizeTileNonUrlData({\n id: id.toString(),\n lodSelection,\n obb: node.obb,\n contentUrl,\n textureUrl,\n attributeUrls,\n materialDefinition,\n textureFormat,\n textureLoaderOptions: this.textureLoaderOptions,\n children,\n isDracoGeometry\n });\n }\n /**\n * Forms url and type of geometry resource by nodepage's data and `geometryDefinitions` in the tileset\n * @param - data about the node's mesh from the nodepage\n * @returns -\n * {string} url - url to the geometry resource\n * {boolean} isDracoGeometry - whether the geometry resource contain DRACO compressed geometry\n */\n getContentUrl(meshGeometryData) {\n let result = null;\n // @ts-ignore\n const geometryDefinition = this.tileset.geometryDefinitions[meshGeometryData.definition];\n let geometryIndex = -1;\n // Try to find DRACO geometryDefinition of `useDracoGeometry` option is set\n // @ts-expect-error this.options is not properly typed\n if (this.options.i3s && this.options.i3s.useDracoGeometry) {\n geometryIndex = geometryDefinition.geometryBuffers.findIndex((buffer) => buffer.compressedAttributes && buffer.compressedAttributes.encoding === 'draco');\n }\n // If DRACO geometry is not applicable try to select non-compressed geometry\n if (geometryIndex === -1) {\n geometryIndex = geometryDefinition.geometryBuffers.findIndex((buffer) => !buffer.compressedAttributes);\n }\n if (geometryIndex !== -1) {\n const isDracoGeometry = Boolean(geometryDefinition.geometryBuffers[geometryIndex].compressedAttributes);\n result = {\n url: `${this.url}/nodes/${meshGeometryData.resource}/geometries/${geometryIndex}`,\n isDracoGeometry\n };\n }\n return result;\n }\n /**\n * Forms 1.6 compatible LOD selection object from a nodepage's node data\n * @param node - a node from nodepage\n * @returns- Array of LodSelection\n */\n getLodSelection(node) {\n const lodSelection = [];\n if (this.lodSelectionMetricType === 'maxScreenThresholdSQ') {\n lodSelection.push({\n metricType: 'maxScreenThreshold',\n // @ts-ignore\n maxError: Math.sqrt(node.lodThreshold / (Math.PI * 0.25))\n });\n }\n lodSelection.push({\n metricType: this.lodSelectionMetricType,\n // @ts-ignore\n maxError: node.lodThreshold\n });\n return lodSelection;\n }\n /**\n * Returns information about texture and material from `materialDefinitions`\n * @param material - material data from nodepage\n * @returns - Couple {textureData, materialDefinition}\n * {string} textureData.name - path name of the texture\n * {string} textureData.format - format of the texture\n * materialDefinition - PBR-like material definition from `materialDefinitions`\n */\n getInformationFromMaterial(material) {\n const informationFromMaterial = { textureData: { name: null } };\n if (material) {\n const materialDefinition = this.tileset.materialDefinitions?.[material.definition];\n if (materialDefinition) {\n informationFromMaterial.materialDefinition = materialDefinition;\n const textureSetDefinitionIndex = materialDefinition?.pbrMetallicRoughness?.baseColorTexture?.textureSetDefinitionId;\n if (typeof textureSetDefinitionIndex === 'number') {\n informationFromMaterial.textureData =\n this.textureDefinitionsSelectedFormats[textureSetDefinitionIndex] ||\n informationFromMaterial.textureData;\n }\n }\n }\n return informationFromMaterial;\n }\n /**\n * Sets preferable and supported format for each textureDefinition of the tileset\n * @param tileset - I3S layer data\n * @returns\n */\n initSelectedFormatsForTextureDefinitions(tileset) {\n this.textureDefinitionsSelectedFormats = [];\n const possibleI3sFormats = this.getSupportedTextureFormats();\n const textureSetDefinitions = tileset.textureSetDefinitions || [];\n for (const textureSetDefinition of textureSetDefinitions) {\n const formats = (textureSetDefinition && textureSetDefinition.formats) || [];\n let selectedFormat = null;\n for (const i3sFormat of possibleI3sFormats) {\n const format = formats.find((value) => value.format === i3sFormat);\n if (format) {\n selectedFormat = format;\n break;\n }\n }\n // For I3S 1.8 need to define basis target format to decode\n if (selectedFormat && selectedFormat.format === 'ktx2') {\n this.textureLoaderOptions.basis = {\n format: selectSupportedBasisFormat(),\n containerFormat: 'ktx2',\n module: 'encoder'\n };\n }\n this.textureDefinitionsSelectedFormats.push(selectedFormat);\n }\n }\n /**\n * Returns the array of supported texture format\n * @returns list of format strings\n */\n getSupportedTextureFormats() {\n const formats = [];\n // @ts-expect-error this.options is not properly typed\n if (!this.options.i3s || this.options.i3s.useCompressedTextures) {\n // I3S 1.7 selection\n const supportedCompressedFormats = getSupportedGPUTextureFormats();\n // List of possible in i3s formats:\n // https://github.com/Esri/i3s-spec/blob/master/docs/1.7/textureSetDefinitionFormat.cmn.md\n if (supportedCompressedFormats.has('etc2')) {\n formats.push('ktx-etc2');\n }\n if (supportedCompressedFormats.has('dxt')) {\n formats.push('dds');\n }\n // I3S 1.8 selection\n // ktx2 wraps basis texture which at the edge case can be decoded as uncompressed image\n formats.push('ktx2');\n }\n formats.push('jpg');\n formats.push('png');\n return formats;\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright vis.gl contributors\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof \"4.3.2\" !== 'undefined' ? \"4.3.2\" : 'latest';\n/**\n * Loader for I3S node pages\n */\nexport const I3SNodePageLoader = {\n dataType: null,\n batchType: null,\n name: 'I3S Node Page',\n id: 'i3s-node-page',\n module: 'i3s',\n version: VERSION,\n mimeTypes: ['application/json'],\n parse: parseNodePage,\n extensions: ['json'],\n options: {\n i3s: {}\n }\n};\nasync function parseNodePage(data, options) {\n return JSON.parse(new TextDecoder().decode(data));\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright vis.gl contributors\nimport { DataViewFile } from '@loaders.gl/loader-utils';\nimport { parseSLPKArchive } from \"./lib/parsers/parse-slpk/parse-slpk.js\";\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof \"4.3.2\" !== 'undefined' ? \"4.3.2\" : 'latest';\n/**\n * Loader for SLPK - Scene Layer Package (Archive I3S format)\n * @todo - this reloads the entire archive for every tile, should be optimized\n * @todo - this should be updated to use `parseFile` and ReadableFile\n */\nexport const SLPKLoader = {\n dataType: null,\n batchType: null,\n name: 'I3S SLPK (Scene Layer Package)',\n id: 'slpk',\n module: 'i3s',\n version: VERSION,\n mimeTypes: ['application/octet-stream'],\n extensions: ['slpk'],\n options: {},\n parse: async (data, options = {}) => {\n const archive = await parseSLPKArchive(new DataViewFile(new DataView(data)));\n return archive.getFile(options.slpk?.path ?? '', options.slpk?.pathMode);\n }\n};\n", "import { parseZipCDFileHeader, CD_HEADER_SIGNATURE, parseZipLocalFileHeader, searchFromTheEnd, parseHashTable, makeHashTableFromZipHeaders } from '@loaders.gl/zip';\nimport { SLPKArchive } from \"./slpk-archieve.js\";\n/**\n * Creates slpk file handler from raw file\n * @param fileProvider raw file data\n * @param cb is called with information message during parsing\n * @returns slpk file handler\n */\nexport async function parseSLPKArchive(fileProvider, cb, fileName) {\n const hashCDOffset = await searchFromTheEnd(fileProvider, CD_HEADER_SIGNATURE);\n const cdFileHeader = await parseZipCDFileHeader(hashCDOffset, fileProvider);\n let hashTable;\n if (cdFileHeader?.fileName !== '@specialIndexFileHASH128@') {\n