Source: map/layer/CsvLayer/CsvLayer.js

  1. import { CsvLoader } from "../../../data/CsvLoader.js";
  2. import { CsvLayerSchema } from "./CsvLayer.schema.js";
  3. import { Folder } from "../Folder/Folder.js";
  4. import { MeasurementsLayer } from "../MeasurementsLayer/MeasurementsLayer.js";
  5. import { FILE_DATA_UNAVAILABLE } from "../../utils/messageTemplates.js";
  6. export { CsvLayer }
  7. /**
  8. * Layer for displaying Data from the DWS in the viewer
  9. *
  10. * @author rhess <robin.hess@awi.de>
  11. * @memberof vef.map.layer
  12. */
  13. class CsvLayer extends MeasurementsLayer {
  14. static promises = {};
  15. static layers = [];
  16. /**
  17. * Set all properties for the layer based on the options object.
  18. *
  19. * @param {object} config
  20. * @param {object} cache
  21. * @param {string} id
  22. */
  23. constructor(config, cache, id) {
  24. // remove "file" if an actual File instance
  25. // is given to avoid trying to serializing it
  26. // when the viewer is saved. only URL references
  27. // as strings should be saved
  28. const file = config?.file || cache?.file || null;
  29. if (config?.file instanceof File) delete config.file;
  30. if (cache?.file instanceof File) delete cache.file;
  31. // calling parent constructor
  32. super(config, cache, id);
  33. // init default values and define getters and setters
  34. this.setSchema_(CsvLayerSchema.getSchema());
  35. this.defaults.defaultYAxis = this.columnData;
  36. this.defaults.title = this.columnData;
  37. // generate a file id that is similar across all layers based on the same file
  38. if (!this.fileIdentifier && file) this.fileIdentifier = CsvLayer.generateFileIdentifier(file);
  39. if (!this.dataFrame && file) {
  40. CsvLayer.parseData(file, this.fileIdentifier).then(dataFrame => {
  41. this.setDataFrame(dataFrame);
  42. })
  43. } else if (!this.dataFrame) {
  44. this.addMessage(FILE_DATA_UNAVAILABLE);
  45. }
  46. CsvLayer.layers.push(this);
  47. }
  48. /**
  49. * Reload the data from a file or a url
  50. *
  51. * @param {File | string} file file or url
  52. * @param {string} identifier optionally override the identifier
  53. */
  54. reloadFile(file, identifier) {
  55. CsvLayer.parseData(file, identifier || this.fileIdentifier).then(dataFrame => {
  56. if (identifier) this.fileIdentifier = identifier;
  57. this.setDataFrame(dataFrame);
  58. this.removeMessage(FILE_DATA_UNAVAILABLE);
  59. })
  60. }
  61. /**
  62. * open a file upload dialog and reload the content for each layer
  63. * containing the same fileIdentifier
  64. *
  65. * @param {string} fileId override the identifier to ignore changes to the file
  66. */
  67. static requestFileUpload(fileId) {
  68. const input = document.createElement("input");
  69. input.type = "file";
  70. input.oninput = () => {
  71. if (input.files.length > 0) {
  72. const file = input.files[0];
  73. // update the identifier with the new file metadata
  74. const newId = CsvLayer.generateFileIdentifier(file);
  75. const oldId = fileId || newId;
  76. for (let i = 0; i < CsvLayer.layers.length; ++i) {
  77. const l = CsvLayer.layers[i];
  78. if (l.fileIdentifier == oldId) l.reloadFile(input.files[0], newId);
  79. }
  80. }
  81. }
  82. input.click();
  83. }
  84. /**
  85. * @param {string | File} file File object or path to a file
  86. * @returns {string} identifier
  87. */
  88. static generateFileIdentifier(file) {
  89. if (file instanceof File) {
  90. return file.name + ":" + file.size + ":" + file.lastModified;
  91. } else if (typeof file == "string") {
  92. return file
  93. }
  94. }
  95. /**
  96. * @param {string | File} file File object or path to a file
  97. * @param {string} identifier
  98. * @returns {Promise} dataframe
  99. */
  100. static parseData(file, identifier) {
  101. if (identifier && (identifier in CsvLayer.promises)) {
  102. return CsvLayer.promises[identifier];
  103. } else {
  104. const promise = new Promise((resolve, reject) => {
  105. const load = data => {
  106. new CsvLoader(data).data().then(dataFrame => resolve(dataFrame));
  107. };
  108. if (typeof file == "string") {
  109. fetch(file).then(res => res.text()).then(text => load(text));
  110. } else if (file instanceof File) {
  111. const reader = new FileReader();
  112. reader.onload = e => load(e.target.result);
  113. reader.readAsText(file);
  114. } else {
  115. reject("unsupported type");
  116. }
  117. });
  118. if (identifier) CsvLayer.promises[identifier] = promise;
  119. return promise;
  120. }
  121. }
  122. /**
  123. * @param {string | File} file File object or path to a file
  124. * @returns {Promise} array of layers
  125. */
  126. static createLayers(file, options) {
  127. const filename = (file instanceof File) ? file.name : file;
  128. return new Promise((resolve, reject) => {
  129. const identifier = CsvLayer.generateFileIdentifier(file);
  130. CsvLayer.parseData(file, identifier).then(dataFrame => {
  131. const layers = {};
  132. // prepare options
  133. options = Object.assign({
  134. groupBy: "Event",
  135. defaultYAxis: "Depth water [m]",
  136. invertYAxis: true,
  137. hiddenColumns: [
  138. "Event",
  139. "Latitude",
  140. "Longitude"
  141. ]
  142. }, options);
  143. // find grouping columns, hidden columns
  144. if (!options.groupBy) {
  145. // possible column names to group for, from more to less specific
  146. const GROUPS = ['Sample code', 'Sample name', 'Sample label', 'Sample code/label', 'Sample', 'Event'];
  147. let groupBy = -1;
  148. for (let g = 0; g < GROUPS.length; g++) {
  149. groupBy = dataFrame.columns.indexOf(GROUPS[g]);
  150. if (groupBy > -1) {
  151. console.log('Groupping by', GROUPS[g]);
  152. options.groupBy = GROUPS[g];
  153. options.hiddenColumns.push(GROUPS[g]);
  154. break;
  155. }
  156. }
  157. }
  158. for (let col in dataFrame.columnMap) {
  159. if (options.hiddenColumns.includes(col)) continue;
  160. const layer = new CsvLayer({
  161. file: file,
  162. fileIdentifier: identifier,
  163. columnData: col,
  164. dataFrame: dataFrame,
  165. groupByColumn: options.groupBy,
  166. defaultYAxis: options.defaultYAxis,
  167. invertYAxis: options.invertYAxis,
  168. defaultXAxis: options.defaultXAxis,
  169. invertXAxis: options.invertXAxis,
  170. hiddenColumns: options.hiddenColumns
  171. });
  172. layers[layer.uniqueId] = layer;
  173. }
  174. const folder = new Folder({ title: filename });
  175. const structure = { "#": [folder.uniqueId] };
  176. structure[folder.uniqueId] = Object.keys(layers);
  177. layers[folder.uniqueId] = folder;
  178. resolve({
  179. structure: structure,
  180. layers: layers
  181. });
  182. }).catch(error => {
  183. console.warn(error);
  184. resolve(null);
  185. })
  186. });
  187. }
  188. }