HttpDataSetReader

Usage

import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader';

const reader = vtkHttpDataSetReader.newInstance();
reader.setURL('/Data/can.ex2/index.json').then((reader, dataset) => {
console.log('Metadata loaded with the geometry', dataset);

reader.getArrays().forEach(array => {
console.log('-', array.name, array.location, ':', array.enable);
});

reader.update()
.then((reader, dataset) => {
console.log('dataset fully loaded', dataset);
});
});

The vtkHttpDataSetReader is using a custom format that only exist in vtk.js which aims to simplify data fetching in an HTTP context. Basically the format is composed of a JSON metadata file referencing all the required data array as side binary files along with all the dataset configuration (i.e.: type, extent…).

newInstance({ enableArray = true, fetchGzip = false })

Create a reader instance while enabeling a default behavior regarding the
data array and the way they should be fetched from the server.

The enableArray argument allow you to choose if you want to activate
all data array by default or if you will have to manually enable them before
downloading them.

setURL(url) : Promise()

Set the URL for the dataset to load.

const reader = HttpDataSetReader.newInstance();
isReady = reader.setURL('/Data/can.ex2/index.json');

// Same as
const reader = HttpDataSetReader.newInstance({ url: '/Data/can.ex2/index.json' });
isReady = reader.updateMetadata();

update() : Promise()

Load all data arrays that have been enabled unsing the enableArray method.

reader
.update()
.then((reader, dataset) => console.log('Fully loaded dataset', dataset));

getArrays() : [{ name, location, enable }, …]

Return the list of available array with their location and if they are enable or not for download using the update() method.

getBlocks(): { BlockName: { type: ‘MultiBlock’, enable: true, SubBlockName: { type: ‘UnstructuredGrid’, enable: true }}}

List the blocks and their state for data loading. Each hierachy have its state.

{
"Element Blocks": {
"Unnamed block ID: 1 Type: HEX8": {
"type": "UnstructuredGrid",
"enable": true
},
"enable": true
},
"Node Sets": {
"enable": true
},
"Element Sets": {
"enable": true
},
"Edge Blocks": {
"enable": true
},
"Edge Sets": {
"enable": true
},
"Face Blocks": {
"enable": true
},
"Face Sets": {
"enable": true
},
"Side Sets": {
"enable": true
}
}

enableArray(location, name, enable = true)

Enable or disable a given array.

reader.enableArray('pointData', 'Temperature');
reader.enableArray('pointData', 'Pressure', false);
reader.enableArray('cellData', 'CellId', true);
reader.enableArray('fieldData', 'labels', true);

enableBlock(blockPath, enable = true, pathSeparator = ‘.’)

Enable or disable a given block.

reader.enableBlock('Element Blocks.Unnamed block ID: 1 Type: HEX8');
reader.enableBlock('Face Sets', false);
reader.enableBlock('Edge Sets', false);

getOutputData() : { dataset }

Return the dataset in its current state.
Some arrays could be loaded (no more ref), while others could still be remote and have their ref.

delete()

Free memory and remove any listener.

onBusy(callback) : subscription

Attach listener to monitor when the reader is downloading data or not.

const subscription = reader.onBusy(busy => {
console.log('Reader is', busy ? 'downloading' : 'idle');
})

reader.update();

// much later
subscription.unsubscribe();

isBusy() : Boolean

Return the current status of the reader. True means busy and False means idle.

getBaseURL() : String

Return the base url to use to download arrays or other data from the given dataset.

reader.setURL('/Data/can.ex2/index.json');

if (reader.getBaseURL() === '/Data/can.ex2') {
console.log('Good guess...');
}

Source

index.js
// 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 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 = {}) {
return model.dataAccessHelper.fetchArray(
publicAPI,
model.baseURL,
array,
options
);
}

// 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
model.arrays
.filter((array) => array.registration)
.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 };