import { CsvLoader } from "../../../data/CsvLoader.js";
import { CsvLayerSchema } from "./CsvLayer.schema.js";
import { Folder } from "../Folder/Folder.js";
import { MeasurementsLayer } from "../MeasurementsLayer/MeasurementsLayer.js";
import { FILE_DATA_UNAVAILABLE } from "../../utils/messageTemplates.js";
export { CsvLayer }
/**
* Layer for displaying Data from the DWS in the viewer
*
* @author rhess <robin.hess@awi.de>
* @memberof vef.map.layer
*/
class CsvLayer extends MeasurementsLayer {
static promises = {};
static layers = [];
/**
* Set all properties for the layer based on the options object.
*
* @param {object} config
* @param {object} cache
* @param {string} id
*/
constructor(config, cache, id) {
// remove "file" if an actual File instance
// is given to avoid trying to serializing it
// when the viewer is saved. only URL references
// as strings should be saved
const file = config?.file || cache?.file || null;
if (config?.file instanceof File) delete config.file;
if (cache?.file instanceof File) delete cache.file;
// calling parent constructor
super(config, cache, id);
// init default values and define getters and setters
this.setSchema_(CsvLayerSchema.getSchema());
this.defaults.defaultYAxis = this.columnData;
this.defaults.title = this.columnData;
// generate a file id that is similar across all layers based on the same file
if (!this.fileIdentifier && file) this.fileIdentifier = CsvLayer.generateFileIdentifier(file);
if (!this.dataFrame && file) {
CsvLayer.parseData(file, this.fileIdentifier).then(dataFrame => {
this.setDataFrame(dataFrame);
})
} else if (!this.dataFrame) {
this.addMessage(FILE_DATA_UNAVAILABLE);
}
CsvLayer.layers.push(this);
}
/**
* Reload the data from a file or a url
*
* @param {File | string} file file or url
* @param {string} identifier optionally override the identifier
*/
reloadFile(file, identifier) {
CsvLayer.parseData(file, identifier || this.fileIdentifier).then(dataFrame => {
if (identifier) this.fileIdentifier = identifier;
this.setDataFrame(dataFrame);
this.removeMessage(FILE_DATA_UNAVAILABLE);
})
}
/**
* open a file upload dialog and reload the content for each layer
* containing the same fileIdentifier
*
* @param {string} fileId override the identifier to ignore changes to the file
*/
static requestFileUpload(fileId) {
const input = document.createElement("input");
input.type = "file";
input.oninput = () => {
if (input.files.length > 0) {
const file = input.files[0];
// update the identifier with the new file metadata
const newId = CsvLayer.generateFileIdentifier(file);
const oldId = fileId || newId;
for (let i = 0; i < CsvLayer.layers.length; ++i) {
const l = CsvLayer.layers[i];
if (l.fileIdentifier == oldId) l.reloadFile(input.files[0], newId);
}
}
}
input.click();
}
/**
* @param {string | File} file File object or path to a file
* @returns {string} identifier
*/
static generateFileIdentifier(file) {
if (file instanceof File) {
return file.name + ":" + file.size + ":" + file.lastModified;
} else if (typeof file == "string") {
return file
}
}
/**
* @param {string | File} file File object or path to a file
* @param {string} identifier
* @returns {Promise} dataframe
*/
static parseData(file, identifier) {
if (identifier && (identifier in CsvLayer.promises)) {
return CsvLayer.promises[identifier];
} else {
const promise = new Promise((resolve, reject) => {
const load = data => {
new CsvLoader(data).data().then(dataFrame => resolve(dataFrame));
};
if (typeof file == "string") {
fetch(file).then(res => res.text()).then(text => load(text));
} else if (file instanceof File) {
const reader = new FileReader();
reader.onload = e => load(e.target.result);
reader.readAsText(file);
} else {
reject("unsupported type");
}
});
if (identifier) CsvLayer.promises[identifier] = promise;
return promise;
}
}
/**
* @param {string | File} file File object or path to a file
* @returns {Promise} array of layers
*/
static createLayers(file, options) {
const filename = (file instanceof File) ? file.name : file;
return new Promise((resolve, reject) => {
const identifier = CsvLayer.generateFileIdentifier(file);
CsvLayer.parseData(file, identifier).then(dataFrame => {
const layers = {};
// prepare options
options = Object.assign({
groupBy: "Event",
defaultYAxis: "Depth water [m]",
invertYAxis: true,
hiddenColumns: [
"Event",
"Latitude",
"Longitude"
]
}, options);
// find grouping columns, hidden columns
if (!options.groupBy) {
// possible column names to group for, from more to less specific
const GROUPS = ['Sample code', 'Sample name', 'Sample label', 'Sample code/label', 'Sample', 'Event'];
let groupBy = -1;
for (let g = 0; g < GROUPS.length; g++) {
groupBy = dataFrame.columns.indexOf(GROUPS[g]);
if (groupBy > -1) {
console.log('Groupping by', GROUPS[g]);
options.groupBy = GROUPS[g];
options.hiddenColumns.push(GROUPS[g]);
break;
}
}
}
for (let col in dataFrame.columnMap) {
if (options.hiddenColumns.includes(col)) continue;
const layer = new CsvLayer({
file: file,
fileIdentifier: identifier,
columnData: col,
dataFrame: dataFrame,
groupByColumn: options.groupBy,
defaultYAxis: options.defaultYAxis,
invertYAxis: options.invertYAxis,
defaultXAxis: options.defaultXAxis,
invertXAxis: options.invertXAxis,
hiddenColumns: options.hiddenColumns
});
layers[layer.uniqueId] = layer;
}
const folder = new Folder({ title: filename });
const structure = { "#": [folder.uniqueId] };
structure[folder.uniqueId] = Object.keys(layers);
layers[folder.uniqueId] = folder;
resolve({
structure: structure,
layers: layers
});
}).catch(error => {
console.warn(error);
resolve(null);
})
});
}
}