import 'vtk.js/Sources/Common/DataModel/ImageData'; import 'vtk.js/Sources/Common/DataModel/PolyData';
import vtk from 'vtk.js/Sources/vtk'; import macro from 'vtk.js/Sources/macros'; import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkStringArray from 'vtk.js/Sources/Common/Core/StringArray';
import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper';
const fieldDataLocations = ['pointData', 'cellData', 'fieldData']; const ARRAY_BUILDERS = { vtkDataArray, vtkStringArray, };
const cachedArraysAndPromises = {}; const cachedArraysMetaData = {}; const MiB = 1024 * 1024;
const GEOMETRY_ARRAYS = { vtkPolyData(dataset) { const arrayToDownload = []; arrayToDownload.push(dataset.points); ['verts', 'lines', 'polys', 'strips'].forEach((cellName) => { if (dataset[cellName]) { arrayToDownload.push(dataset[cellName]); } });
return arrayToDownload; },
vtkImageData(dataset) { return []; },
vtkUnstructuredGrid(dataset) { const arrayToDownload = []; arrayToDownload.push(dataset.points); arrayToDownload.push(dataset.cells); arrayToDownload.push(dataset.cellTypes);
return arrayToDownload; },
vtkRectilinearGrid(dataset) { const arrayToDownload = []; arrayToDownload.push(dataset.xCoordinates); arrayToDownload.push(dataset.yCoordinates); arrayToDownload.push(dataset.zCoordinates);
return arrayToDownload; }, };
function processDataSet( publicAPI, model, dataset, fetchArray, resolve, reject, loadData ) { const enable = model.enableArray;
model.arrays = [];
fieldDataLocations.forEach((location) => { if (dataset[location]) { dataset[location].arrays .map((i) => i.data) .forEach((array) => { model.arrays.push({ name: array.name, enable, location, array, registration: array.ref.registration || 'addArray', }); });
dataset[location].arrays = []; } });
const pendingPromises = []; const { progressCallback } = model; const compression = model.fetchGzip ? 'gz' : null; GEOMETRY_ARRAYS[dataset.vtkClass](dataset).forEach((array) => { pendingPromises.push(fetchArray(array, { compression, progressCallback })); });
function success() { model.dataset = vtk(dataset); if (!loadData) { model.output[0] = model.dataset; resolve(publicAPI, model.output[0]); } else { publicAPI.loadData().then(() => { model.output[0] = model.dataset; resolve(publicAPI, model.output[0]); }); } }
if (pendingPromises.length) { Promise.all(pendingPromises).then(success, (err) => { reject(err); }); } else { success(); } }
function vtkHttpDataSetReader(publicAPI, model) { model.classHierarchy.push('vtkHttpDataSetReader');
model.output[0] = vtk({ vtkClass: 'vtkPolyData' });
if (!model.dataAccessHelper) { model.dataAccessHelper = DataAccessHelper.get('http'); }
function fetchArray(array, options = {}) { const arrayId = `${array.ref.id}|${array.vtkClass}`; if (!cachedArraysAndPromises[arrayId]) { cachedArraysAndPromises[arrayId] = model.dataAccessHelper .fetchArray(publicAPI, model.baseURL, array, options) .then((newArray) => { if (!model.maxCacheSize) { delete cachedArraysAndPromises[arrayId]; return newArray; }
cachedArraysAndPromises[arrayId] = newArray; cachedArraysMetaData[arrayId] = { lastAccess: new Date() };
if (model.maxCacheSize < 0) { return newArray; }
const cachedArrays = {}; Object.keys(cachedArraysMetaData).forEach((arrId) => { cachedArrays[arrId] = { array: cachedArraysAndPromises[arrId], lastAccess: cachedArraysMetaData[arrId].lastAccess, }; }); const sortedArrayCache = Object.entries(cachedArrays).sort((a, b) => Math.sign(b[1].lastAccess - a[1].lastAccess) );
const cacheSizeLimit = model.maxCacheSize * MiB; let cacheSize = Object.values(cachedArrays).reduce( (accSize, entry) => accSize + entry.array.values.byteLength, 0 );
while (cacheSize > cacheSizeLimit && sortedArrayCache.length > 0) { const [oldId, entry] = sortedArrayCache.pop(); delete cachedArraysAndPromises[oldId]; delete cachedArraysMetaData[oldId]; cacheSize -= entry.array.values.byteLength; }
if (!cachedArraysMetaData[arrayId]) { macro.vtkWarningMacro('Cache size is too small for the dataset'); }
return newArray; }); } else { Promise.resolve(cachedArraysAndPromises[arrayId]).then((cachedArray) => { if (array !== cachedArray) { if (model.maxCacheSize) { cachedArraysMetaData[arrayId].lastAccess = new Date(); }
Object.assign(array, cachedArray); delete array.ref; } }); }
return Promise.resolve(cachedArraysAndPromises[arrayId]); }
publicAPI.updateMetadata = (loadData = false) => { if (model.compression === 'zip') { return new Promise((resolve, reject) => { DataAccessHelper.get('http') .fetchBinary(model.url) .then( (zipContent) => { model.dataAccessHelper = DataAccessHelper.get('zip', { zipContent, callback: (zip) => { model.baseURL = ''; model.dataAccessHelper .fetchJSON(publicAPI, 'index.json') .then( (dataset) => { publicAPI .parseObject(dataset, { loadData, deepCopy: false, }) .then(resolve, reject); }, (error) => { reject(error); } ); }, }); }, (error) => { reject(error); } ); }); }
return new Promise((resolve, reject) => { model.dataAccessHelper.fetchJSON(publicAPI, model.url).then( (dataset) => { publicAPI .parseObject(dataset, { loadData, deepCopy: false }) .then(resolve, reject); }, (error) => { reject(error); } ); }); };
publicAPI.setUrl = (url, options = {}) => { if (url.indexOf('index.json') === -1 && !options.fullpath) { model.baseURL = url; model.url = `${url}/index.json`; } else { model.url = url;
const path = url.split('/'); path.pop(); model.baseURL = path.join('/'); }
model.compression = options.compression;
return publicAPI.updateMetadata(!!options.loadData); };
publicAPI.parseObject = ( manifest, { loadData, baseUrl, deepCopy = true } ) => { if (baseUrl) { model.baseURL = baseUrl; }
const dataset = deepCopy ? structuredClone(manifest) : manifest;
return new Promise((resolve, reject) => { processDataSet( publicAPI, model, dataset, fetchArray, resolve, reject, loadData ); }); };
publicAPI.loadData = () => { const datasetObj = model.dataset; const arrayToFecth = model.arrays .filter((array) => array.enable) .filter((array) => array.array.ref) .map((array) => array.array);
return new Promise((resolve, reject) => { const error = (e) => { reject(e); };
const processNext = () => { if (arrayToFecth.length) { const { progressCallback } = model; const compression = model.fetchGzip ? 'gz' : null; fetchArray(arrayToFecth.pop(), { compression, progressCallback, }).then(processNext, error); } else if (datasetObj) { model.arrays .filter( (metaArray) => metaArray.registration && !metaArray.array.ref ) .forEach((metaArray) => { const newArray = ARRAY_BUILDERS[ metaArray.array.vtkClass ].newInstance(metaArray.array); datasetObj[`get${macro.capitalize(metaArray.location)}`]()[ metaArray.registration ](newArray); delete metaArray.registration; }); datasetObj.modified(); resolve(publicAPI, datasetObj); } };
processNext(); }); };
publicAPI.requestData = (inData, outData) => { };
publicAPI.enableArray = (location, name, enable = true) => { const activeArray = model.arrays.filter( (array) => array.name === name && array.location === location ); if (activeArray.length === 1) { activeArray[0].enable = enable; } };
publicAPI.getCachedArrayIds = () => Object.keys(cachedArraysMetaData);
publicAPI.clearCache = () => Object.keys(cachedArraysMetaData).forEach((k) => { delete cachedArraysAndPromises[k]; delete cachedArraysMetaData[k]; });
publicAPI.isBusy = () => !!model.requestCount; }
const DEFAULT_VALUES = { enableArray: true, fetchGzip: false, arrays: [], url: null, baseURL: null, requestCount: 0, arrayCachingEnabled: true, maxCacheSize: 2048, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); macro.get(publicAPI, model, [ 'enableArray', 'fetchGzip', 'url', 'baseURL', 'dataAccessHelper', 'maxCacheSize', ]); macro.set(publicAPI, model, [ 'dataAccessHelper', 'progressCallback', 'maxCacheSize', ]); macro.getArray(publicAPI, model, ['arrays']); macro.algo(publicAPI, model, 0, 1); macro.event(publicAPI, model, 'busy');
vtkHttpDataSetReader(publicAPI, model);
if (model.progressCallback === undefined) { model.progressCallback = null; } }
export const newInstance = macro.newInstance(extend, 'vtkHttpDataSetReader');
export default { newInstance, extend };
|