@acransac/vtk.js
Version:
Visualization Toolkit for the Web
354 lines (305 loc) • 9.78 kB
JavaScript
// For vtk factory
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/macro';
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';
const fieldDataLocations = ['pointData', 'cellData', 'fieldData'];
const HTTP_DATA_ACCESS = DataAccessHelper.get('http');
const ARRAY_BUILDERS = {
vtkDataArray,
vtkStringArray,
};
// ----------------------------------------------------------------------------
// Global methods
// ----------------------------------------------------------------------------
const cachedArrays = {};
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;
// Generate array list
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',
});
});
// Reset data arrays
dataset[location].arrays = [];
}
});
// Fetch geometry 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]);
});
}
}
// Wait for all geometry array to be fetched
if (pendingPromises.length) {
Promise.all(pendingPromises).then(success, (err) => {
reject(err);
});
} else {
success();
}
}
// ----------------------------------------------------------------------------
// vtkHttpDataSetReader methods
// ----------------------------------------------------------------------------
function vtkHttpDataSetReader(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkHttpDataSetReader');
// Empty output by default
model.output[0] = vtk({ vtkClass: 'vtkPolyData' });
// Create default dataAccessHelper if not available
if (!model.dataAccessHelper) {
model.dataAccessHelper = HTTP_DATA_ACCESS;
}
// Internal method to fetch Array
function fetchArray(array, options = {}) {
const arrayId = array.ref.id;
if (!cachedArrays[arrayId]) {
// Cache the promise while fetching
cachedArrays[arrayId] = model.dataAccessHelper
.fetchArray(publicAPI, model.baseURL, array, options)
.then((newArray) => {
// Replace the promise with the array in cache once downloaded
cachedArrays[arrayId] = newArray;
return newArray;
});
} else {
// cacheArrays[arrayId] can be a promise or value
Promise.resolve(cachedArrays[arrayId]).then((cachedArray) => {
if (array !== cachedArray) {
Object.assign(array, cachedArray);
delete array.ref;
}
});
}
return Promise.resolve(cachedArrays[arrayId]);
}
// Fetch dataset (metadata)
publicAPI.updateMetadata = (loadData = false) => {
if (model.compression === 'zip') {
return new Promise((resolve, reject) => {
HTTP_DATA_ACCESS.fetchBinary(model.url).then(
(zipContent) => {
model.dataAccessHelper = DataAccessHelper.get('zip', {
zipContent,
callback: (zip) => {
model.baseURL = '';
model.dataAccessHelper.fetchJSON(publicAPI, 'index.json').then(
(dataset) => {
processDataSet(
publicAPI,
model,
dataset,
fetchArray,
resolve,
reject,
loadData
);
},
(error) => {
reject(error);
}
);
},
});
},
(error) => {
reject(error);
}
);
});
}
return new Promise((resolve, reject) => {
model.dataAccessHelper.fetchJSON(publicAPI, model.url).then(
(dataset) => {
processDataSet(
publicAPI,
model,
dataset,
fetchArray,
resolve,
reject,
loadData
);
},
(error) => {
reject(error);
}
);
});
};
// Set DataSet url
publicAPI.setUrl = (url, options = {}) => {
if (url.indexOf('index.json') === -1 && !options.fullpath) {
model.baseURL = url;
model.url = `${url}/index.json`;
} else {
model.url = url;
// Remove the file in the URL
const path = url.split('/');
path.pop();
model.baseURL = path.join('/');
}
model.compression = options.compression;
// Fetch metadata
return publicAPI.updateMetadata(!!options.loadData);
};
// Fetch the actual data arrays
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) {
// Perform array registration on new arrays
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);
}
};
// Start processing queue
processNext();
});
};
publicAPI.requestData = (inData, outData) => {
// do nothing loadData will eventually load up the data
};
// Toggle arrays to load
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;
}
};
// return Busy state
publicAPI.isBusy = () => !!model.requestCount;
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
enableArray: true,
fetchGzip: false,
arrays: [],
url: null,
baseURL: null,
requestCount: 0,
// dataAccessHelper: null,
};
// ----------------------------------------------------------------------------
export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Build VTK API
macro.obj(publicAPI, model);
macro.get(publicAPI, model, [
'enableArray',
'fetchGzip',
'url',
'baseURL',
'dataAccessHelper',
]);
macro.set(publicAPI, model, ['dataAccessHelper', 'progressCallback']);
macro.getArray(publicAPI, model, ['arrays']);
macro.algo(publicAPI, model, 0, 1);
macro.event(publicAPI, model, 'busy');
// Object methods
vtkHttpDataSetReader(publicAPI, model);
// Make sure we can destructuring progressCallback from model
if (model.progressCallback === undefined) {
model.progressCallback = null;
}
}
// ----------------------------------------------------------------------------
export const newInstance = macro.newInstance(extend, 'vtkHttpDataSetReader');
// ----------------------------------------------------------------------------
export default { newInstance, extend };