UNPKG

terriajs

Version:

Geospatial data visualization platform.

256 lines 11.4 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import dateFormat from "dateformat"; import { get as _get, map as _map } from "lodash-es"; import { computed, observable, runInAction, makeObservable } from "mobx"; import URI from "urijs"; import isDefined from "../../../Core/isDefined"; import loadJson from "../../../Core/loadJson"; import AutoRefreshingMixin from "../../../ModelMixins/AutoRefreshingMixin"; import TableMixin from "../../../ModelMixins/TableMixin"; import TableAutomaticStylesStratum from "../../../Table/TableAutomaticStylesStratum"; import ApiTableCatalogItemTraits from "../../../Traits/TraitsClasses/ApiTableCatalogItemTraits"; import TableStyleTraits from "../../../Traits/TraitsClasses/Table/StyleTraits"; import TableTimeStyleTraits from "../../../Traits/TraitsClasses/Table/TimeStyleTraits"; import CreateModel from "../../Definition/CreateModel"; import createStratumInstance from "../../Definition/createStratumInstance"; import LoadableStratum from "../../Definition/LoadableStratum"; import saveModelToJson from "../../Definition/saveModelToJson"; import StratumOrder from "../../Definition/StratumOrder"; import proxyCatalogItemUrl from "../proxyCatalogItemUrl"; export class ApiTableStratum extends LoadableStratum(ApiTableCatalogItemTraits) { catalogItem; static stratumName = "apiTable"; duplicateLoadableStratum(model) { return new ApiTableStratum(model); } constructor(catalogItem) { super(); this.catalogItem = catalogItem; makeObservable(this); } // Set time id columns to `idKey` get defaultStyle() { return createStratumInstance(TableStyleTraits, { time: createStratumInstance(TableTimeStyleTraits, { idColumns: this.catalogItem.idKey ? [this.catalogItem.idKey] : undefined }) }); } } __decorate([ computed ], ApiTableStratum.prototype, "defaultStyle", null); StratumOrder.addLoadStratum(ApiTableStratum.stratumName); /** * THE API AND TRAITS OF THIS EXPERIMENTAL CATALOG ITEM SHOULD BE CONSIDERED IN * ALPHA. EXPECT BREAKING CHANGES. * * This is a generic, one-size-fits-most catalog item for deriving tables from * external APIs. Currently only supports JSON APIs, and doesn't support region * mapping. Also currently only supports a single API to get values from, and a * single API to get positions from. */ export class ApiTableCatalogItem extends AutoRefreshingMixin(TableMixin(CreateModel(ApiTableCatalogItemTraits))) { static type = "api-table"; get type() { return ApiTableCatalogItem.type; } apiResponses = []; hasData = false; constructor(id, terria) { super(id, terria); makeObservable(this); this.strata.set(TableAutomaticStylesStratum.stratumName, new TableAutomaticStylesStratum(this)); this.strata.set(ApiTableStratum.stratumName, new ApiTableStratum(this)); } get apiDataIsLoaded() { return this.apiResponses.length > 0; } loadDataFromApis() { const apisWithUrl = this.apis.filter((api) => api.url); const apiUrls = apisWithUrl.map((api) => proxyCatalogItemUrl(this, this.addQueryParams(api))); return Promise.all(apisWithUrl.map(async (api, idx) => { let data = await loadJson(apiUrls[idx], undefined, api.requestData ? saveModelToJson(api.requestData) : undefined, api.postRequestDataAsFormData); if (api.responseDataPath !== undefined) { data = getResponseDataPath(data, api.responseDataPath); } return Promise.resolve({ data, api }); })).then((values) => { runInAction(() => { const columnMajorData = new Map(); values .filter((val) => val.api.kind === "COLUMN_MAJOR") // column major rows only .map((val, i) => { // add the column name to each column val.data["TERRIA_columnName"] = val.api.columnMajorColumnNames[i]; return val.data; }) .flat() // make row id/data pairs for columnMajorData map .map((data) => Object.entries(data)) .flat() // merge rows with the same id .forEach((rowPart) => { const id = rowPart[0]; const value = rowPart[1]; const row = {}; row["value"] = value; // add the id to the row's data row[this.idKey] = id; if (columnMajorData.has(id)) { const currentRow = columnMajorData.get(id); columnMajorData.set(id, { currentRow, ...value }); } else { columnMajorData.set(id, row); } }); if (columnMajorData.size !== 0) { this.apiResponses = Array.from(columnMajorData.values()); return; } // Make map of ids to values that are constant for that id const perIdData = new Map(values .filter((val) => val.api.kind === "PER_ID") // per id only .map((val) => val.data) // throw away api, keep data .reduce((curr, prev) => curr.concat(prev), []) // flatten // make id/data pair for perIdData map .map((data) => [data[this.idKey], data])); // Merge PER_ID data with *all* PER_ROW data (this may result in the same PER_ID data row being added to multiple PER_ROW data row) const perRowData = values .filter((val) => val.api.kind === "PER_ROW") .map((val) => val.data) .reduce((curr, prev) => curr.concat(prev), []) .map((row) => Object.assign(row, isDefined(row[this.idKey]) ? perIdData.get(row[this.idKey]) : {})); this.apiResponses = perRowData; }); }); } makeTableColumns(addHeaders) { return this.columns.map((col) => (addHeaders ? [col.name ?? ""] : [])); } apiResponseToTable() { const columnMajorTable = this.makeTableColumns(!this.hasData); if (!this.apiDataIsLoaded) { // No data yet, just return the headers return columnMajorTable; } // Fill in column values from the API response this.apiResponses.forEach((response) => { this.columns.forEach((col, mappingIdx) => { if (!isDefined(col.name)) return; // If ApiColumnTraits has a responseDataPath, use that to get the value const dataPath = this.apiColumns.find((c) => c.name === col.name)?.responseDataPath; if (dataPath) { columnMajorTable[mappingIdx].push(`${getResponseDataPath(response, dataPath) ?? ""}`); } // Otherwise, use column name as the path else { columnMajorTable[mappingIdx].push(`${response[col.name] ?? ""}`); } }); }); return columnMajorTable; } async forceLoadMetadata() { return Promise.resolve(); } async forceLoadTableData() { return this.loadDataFromApis() .then(() => { runInAction(() => { const newTableData = this.apiResponseToTable(); if (this.shouldAppendNewData) { this.append(newTableData); } else { this.dataColumnMajor = newTableData; } this.hasData = true; }); }) .then(() => undefined); } refreshData() { this.loadDataFromApis().then(() => { runInAction(() => { const newTableData = this.apiResponseToTable(); if (this.shouldAppendNewData) { this.append(newTableData); } else { this.dataColumnMajor = newTableData; } }); }); } addQueryParams(api) { const uri = new URI(api.url); const substituteDateTimesInQueryParam = (param) => { if (param.startsWith("DATE!")) { const dateFormatString = param.slice(param.indexOf("!") + 1); const now = new Date(); return dateFormat(now, dateFormatString); } return param; }; // Add common query parameters let useUpdateParams = this.hasData && this.updateQueryParameters.length > 0; const commonQueryParameters = useUpdateParams ? this.updateQueryParameters : this.queryParameters; commonQueryParameters.forEach((query) => { uri.addQuery(query.name, substituteDateTimesInQueryParam(query.value)); }); // Add API-specific query parameters useUpdateParams = this.hasData && api.updateQueryParameters.length > 0; const specificQueryParameters = useUpdateParams ? api.updateQueryParameters : api.queryParameters; specificQueryParameters.forEach((query) => { uri.addQuery(query.name, substituteDateTimesInQueryParam(query.value)); }); return uri.toString(); } } __decorate([ observable ], ApiTableCatalogItem.prototype, "apiResponses", void 0); __decorate([ observable ], ApiTableCatalogItem.prototype, "hasData", void 0); __decorate([ computed ], ApiTableCatalogItem.prototype, "apiDataIsLoaded", null); /** * Return the value at json path of the data object. * * This works exactly like the lodash.get() function but adds support for * traversing array objects. For eg, the lodash.get() does not support a path * like: `a.users[].name`, but this function will correctly return a `{name}[]` * array if they exist. The particular syntax for array traversal * is borrowed from `jq` CLI tool. */ function getResponseDataPath(data, jsonPath) { // Split the path at `[].` or `[]` const pathSegments = jsonPath.split(/\[\]\.?/); const getPath = (data, path) => path === "" ? data : Array.isArray(data) ? _map(data, path) : _get(data, path); return pathSegments.reduce((nextData, segment) => getPath(nextData, segment), data); } StratumOrder.addLoadStratum(TableAutomaticStylesStratum.stratumName); //# sourceMappingURL=ApiTableCatalogItem.js.map