terriajs
Version:
Geospatial data visualization platform.
171 lines (160 loc) • 5.25 kB
text/typescript
import papaparse from "papaparse";
import loadWithXhr from "../Core/loadWithXhr";
// We'd like to use `FeatureDetection.supportsWebWorkers()` here, but
// PapaParse doesn't get along with our webpack configuration. It ends up
// trying to load the entire bundle into a web worker. This is inefficient
// to begin with, but also breaks because we use `require` and `window` in
// places and these are not defined in web workers.
const useWorker = false;
export default class Csv {
/**
* Parses a CSV from a string.
* @param csv The string to parse as a CSV.
* @param columnMajor True if the returned array is an array of columns; False if the returned array is an array of rows.
* @returns A promise that resolves to the CSV data.
*/
static parseString(
csv: string,
columnMajor: boolean = false,
filterOutComments: boolean = false,
options: papaparse.ParseConfig = {}
): Promise<string[][]> {
return new Promise<string[][]>((resolve, reject) => {
papaparse.parse(csv, {
...getParseOptions(columnMajor, filterOutComments, resolve, reject),
...options
});
});
}
/**
* Parses a CSV from a file.
* @param csv The file to parse as a CSV.
* @param columnMajor True if the returned array is an array of columns; False if the returned array is an array of rows.
* @returns A promise that resolves to the CSV data.
*/
static parseFile(
file: File,
columnMajor: boolean = false,
filterOutComments: boolean = false,
options: papaparse.ParseConfig = {}
): Promise<string[][]> {
return new Promise<string[][]>((resolve, reject) => {
papaparse.parse(file, {
...getParseOptions(columnMajor, filterOutComments, resolve, reject),
...options
});
});
}
/**
* Parses a CSV from a URL.
* @param csv The URL to parse as a CSV.
* @param columnMajor True if the returned array is an array of columns; False if the returned array is an array of rows.
* @returns A promise that resolves to the CSV data.
*/
static parseUrl(
url: string,
columnMajor: boolean = false,
filterOutComments: boolean = false,
options: papaparse.ParseConfig = {}
): Promise<string[][]> {
return loadWithXhr({ url }).then((csv: any) => {
if (typeof csv === "string") {
return Csv.parseString(csv, columnMajor, filterOutComments, options);
} else {
throw "Request failed";
}
});
// There is currently a bug when using papaparse to fetch CSV URLs
// See: https://github.com/mholt/PapaParse/pull/832
// return new Promise<string[][]>((resolve, reject) => {
// papaparse.parse(url, {
// ...getParseOptions(columnMajor, resolve, reject),
// download: true,
// });
// });
}
}
function getParseOptions(
columnMajor: boolean,
filterOutComments: boolean,
resolve: (value: string[][]) => void,
reject: (reason?: any) => void
): papaparse.ParseConfig {
return columnMajor
? getParseOptionsColumnMajor(filterOutComments, resolve, reject)
: getParseOptionsRowMajor(filterOutComments, resolve, reject);
}
function getParseOptionsRowMajor(
filterOutComments: boolean,
resolve: (value: string[][]) => void,
reject: (reason?: any) => void
): papaparse.ParseConfig {
const result: string[][] = [];
let parser: any = null;
return {
comments: filterOutComments,
skipEmptyLines: true,
worker: useWorker,
chunk: function (results, p) {
parser = p;
const data = results.data;
for (let i = 0; i < data.length; ++i) {
result.push(data[i]);
}
},
complete: function () {
resolve(result);
},
error: function (e) {
// If we did manage to get some data lets use what we've got
// Perhaps there was an error because there was no Content-Length header
if (result.length > 0) parser.abort();
else reject(e);
}
};
}
function getParseOptionsColumnMajor(
filterOutComments: boolean,
resolve: (value: string[][]) => void,
reject: (reason?: any) => void
): papaparse.ParseConfig {
const result: string[][] = [];
let parser: any = null;
return {
comments: filterOutComments,
skipEmptyLines: true,
worker: useWorker,
chunk: function (results, p) {
parser = p;
const data = results.data;
for (let i = 0; i < data.length; ++i) {
const row = data[i];
if (i === 0) cleanColumnNames(row);
for (let j = 0; j < row.length; ++j) {
let destColumn = result[j];
if (destColumn === undefined) {
destColumn = [];
result.push(destColumn);
}
destColumn.push(row[j]);
}
}
},
complete: function () {
resolve(result);
},
error: function (e) {
// If we did manage to get some data lets use what we've got
// Perhaps there was an error because there was no Content-Length header
if (result.length > 0) parser.abort();
else reject(e);
}
};
}
function cleanColumnNames(columnNames: string[]) {
for (let i = 0; i < columnNames.length; ++i) {
if (typeof columnNames[i] === "string") {
columnNames[i] = columnNames[i].trim();
}
}
}