UNPKG

terriajs

Version:

Geospatial data visualization platform.

343 lines 17.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 i18next from "i18next"; import { action, computed, makeObservable } from "mobx"; import RequestErrorEvent from "terriajs-cesium/Source/Core/RequestErrorEvent"; import Resource from "terriajs-cesium/Source/Core/Resource"; import filterOutUndefined from "../../../Core/filterOutUndefined"; import flatten from "../../../Core/flatten"; import isDefined from "../../../Core/isDefined"; import { regexMatches } from "../../../Core/regexMatches"; import TerriaError from "../../../Core/TerriaError"; import SdmxCatalogGroupTraits from "../../../Traits/TraitsClasses/SdmxCatalogGroupTraits"; import CatalogGroup from "../CatalogGroup"; import CommonStrata from "../../Definition/CommonStrata"; import LoadableStratum from "../../Definition/LoadableStratum"; import proxyCatalogItemUrl from "../proxyCatalogItemUrl"; import StratumOrder from "../../Definition/StratumOrder"; import SdmxJsonCatalogItem from "./SdmxJsonCatalogItem"; export class SdmxServerStratum extends LoadableStratum(SdmxCatalogGroupTraits) { catalogGroup; sdmxServer; static stratumName = "sdmxServer"; static async load(catalogGroup) { // Load agency schemes (may be undefined) const agencySchemes = (await loadSdmxJsonStructure(proxyCatalogItemUrl(catalogGroup, `${catalogGroup.url}/agencyscheme/`), true))?.data?.agencySchemes; // Load category schemes (may be undefined) const categorySchemeResponse = await loadSdmxJsonStructure(proxyCatalogItemUrl(catalogGroup, `${catalogGroup.url}/categoryscheme?references=parentsandsiblings`), true); let dataflows = categorySchemeResponse?.data?.dataflows; // If no dataflows from category schemes -> try getting all of them through `dataflow` endpoint if (!isDefined(dataflows)) { dataflows = (await loadSdmxJsonStructure(proxyCatalogItemUrl(catalogGroup, `${catalogGroup.url}/dataflow/`), true))?.data?.dataflows; if (!isDefined(dataflows)) { throw new TerriaError({ title: i18next.t("models.sdmxServerStratum.loadDataErrorTitle"), message: i18next.t("models.sdmxServerStratum.loadDataErrorMessage") }); } } return new SdmxServerStratum(catalogGroup, { agencySchemes, categorySchemes: categorySchemeResponse?.data?.categorySchemes, categorisations: categorySchemeResponse?.data?.categorisations, dataflows }); } duplicateLoadableStratum(model) { return new SdmxServerStratum(model, this.sdmxServer); } dataflowTree = {}; constructor(catalogGroup, sdmxServer) { super(); this.catalogGroup = catalogGroup; this.sdmxServer = sdmxServer; makeObservable(this); // If categorisations exist => organise Dataflows into a tree! if (isDefined(this.sdmxServer.categorisations)) { this.sdmxServer.categorisations.forEach((categorisiation) => { const categorySchemeUrn = parseSdmxUrn(categorisiation.target); const agencyId = categorySchemeUrn?.agencyId; const categorySchemeId = categorySchemeUrn?.resourceId; const categoryIds = categorySchemeUrn?.descendantIds; // Only support 1 level of categorisiation for now const dataflowId = parseSdmxUrn(categorisiation.source)?.resourceId; if (!isDefined(agencyId) || !isDefined(categorySchemeId) || !isDefined(categoryIds) || !isDefined(dataflowId)) return; let agencyNode = this.dataflowTree[agencyId]; // Create agency node if it doesn't exist if (!isDefined(agencyNode)) { const agency = this.getAgency(agencyId); if (!isDefined(agency)) return; this.dataflowTree[agencyId] = { type: "agencyScheme", item: agency, members: {} }; agencyNode = this.dataflowTree[agencyId]; } let categorySchemeNode = agencyNode.members[categorySchemeId]; // Create categoryScheme node if it doesn't exist if (!isDefined(categorySchemeNode)) { const categoryScheme = this.getCategoryScheme(categorySchemeId); if (!isDefined(categoryScheme)) return; agencyNode.members[categorySchemeId] = { type: "categoryScheme", item: categoryScheme, members: {} }; categorySchemeNode = agencyNode.members[categorySchemeId]; } let categoryParentNode = categorySchemeNode; // Make category nodes (may be nested) categoryIds.forEach((categoryId) => { // Create category node if it doesn't exist if (!isDefined(categoryParentNode.members[categoryId])) { let category; // Find current categoryId in parent categoryScheme or parent category if (categoryParentNode.type === "categoryScheme") { category = this.getCategoryFromCatagoryScheme(categorySchemeId, categoryId); } else if (categoryParentNode.type === "category") { category = this.getCategoryFromCategories(categoryParentNode.item?.categories, categoryId); } if (!isDefined(category)) return; categoryParentNode.members[categoryId] = { type: "category", item: category, members: {} }; } // Swap parent node to newly created category node categoryParentNode = categoryParentNode.members[categoryId]; }); // Create dataflow! const dataflow = this.getDataflow(dataflowId); if (!isDefined(dataflow)) return; categoryParentNode.members[dataflowId] = { type: "dataflow", item: dataflow }; }); // No categorisations exist => add flat list of dataflows } else { this.dataflowTree = this.sdmxServer.dataflows.reduce((tree, dataflow) => { if (isDefined(dataflow.id)) { tree[dataflow.id] = { type: "dataflow", item: dataflow }; } return tree; }, {}); } } get members() { // Find first node in tree which has more than 1 child const findRootGroup = (node) => { const children = isDefined(node?.members) ? Object.values(node.members) : undefined; // If only 1 child -> keep searching return isDefined(children) && children.length === 1 ? findRootGroup(children[0]) : node; }; let rootTreeNodes = Object.values(this.dataflowTree); // If only a single group -> try to find next nested group with more than 1 child if (rootTreeNodes.length === 1 && isDefined(rootTreeNodes[0])) { rootTreeNodes = Object.values(findRootGroup(rootTreeNodes[0]).members ?? this.dataflowTree); } return rootTreeNodes.map((node) => this.getMemberId(node)); } createMembers() { Object.values(this.dataflowTree).forEach((node) => this.createMemberFromLayer(node)); } createMemberFromLayer(node) { const layerId = this.getMemberId(node); if (!layerId) { return; } // Replace the stratum inherited from the parent group. // If has nested layers -> create model for CatalogGroup if (node.members && Object.keys(node.members).length > 0) { // Create nested layers Object.values(node.members).forEach((member) => this.createMemberFromLayer(member)); // Create group const existingGroupModel = this.catalogGroup.terria.getModelById(CatalogGroup, layerId); let groupModel; if (existingGroupModel === undefined) { groupModel = new CatalogGroup(layerId, this.catalogGroup.terria); this.catalogGroup.terria.addModel(groupModel); } else { groupModel = existingGroupModel; } groupModel.setTrait(CommonStrata.definition, "name", node.item.name || node.item.id); groupModel.setTrait(CommonStrata.definition, "members", filterOutUndefined(Object.values(node.members).map((member) => this.getMemberId(member)))); // Set group description if (node.item.description) { groupModel.setTrait(CommonStrata.definition, "description", node.item.description); } return; } // No nested layers (and type is dataflow) -> create model for SdmxJsonCatalogItem if (node.type !== "dataflow" || !isDefined(node.item.id) || !isDefined(node.item.agencyID)) return; const existingItemModel = this.catalogGroup.terria.getModelById(SdmxJsonCatalogItem, layerId); let itemModel; if (existingItemModel === undefined) { itemModel = new SdmxJsonCatalogItem(layerId, this.catalogGroup.terria, undefined); this.catalogGroup.terria.addModel(itemModel); } else { itemModel = existingItemModel; } itemModel.strata.delete(CommonStrata.definition); itemModel.setTrait(CommonStrata.definition, "name", node.item.name || node.item.id); itemModel.setTrait(CommonStrata.definition, "url", this.catalogGroup.url); // Set group description if (node.item.description) { itemModel.setTrait(CommonStrata.definition, "description", node.item.description); } itemModel.setTrait(CommonStrata.definition, "agencyId", node.item.agencyID); itemModel.setTrait(CommonStrata.definition, "dataflowId", node.item.id); itemModel.setTrait(CommonStrata.definition, "modelOverrides", this.catalogGroup.traits.modelOverrides.toJson(this.catalogGroup.modelOverrides)); } getMemberId(node) { return `${this.catalogGroup.uniqueId}/${node.type}-${node.item.id}`; } getDataflow(id) { if (!isDefined(id)) return; return this.sdmxServer.dataflows.find((d) => d.id === id); } getCategoryScheme(id) { if (!isDefined(id)) return; return this.sdmxServer.categorySchemes?.find((d) => d.id === id); } getCategoryFromCatagoryScheme(categoryScheme, id) { if (!isDefined(id)) return; const resolvedCategoryScheme = typeof categoryScheme === "string" ? this.getCategoryScheme(categoryScheme) : categoryScheme; return this.getCategoryFromCategories(resolvedCategoryScheme?.categories, id); } getCategoryFromCategories(categories, id) { return isDefined(id) ? categories?.find((c) => c.id === id) : undefined; } getAgency(id) { if (!isDefined(id)) return; const agencies = this.sdmxServer.agencySchemes?.map((agencyScheme) => agencyScheme.agencies); if (!isDefined(agencies)) return; return flatten(filterOutUndefined(agencies)).find((d) => d.id === id); } } __decorate([ computed ], SdmxServerStratum.prototype, "members", null); __decorate([ action ], SdmxServerStratum.prototype, "createMemberFromLayer", null); StratumOrder.addLoadStratum(SdmxServerStratum.stratumName); export function parseSdmxUrn(urn) { if (!isDefined(urn)) return; // Format urn:sdmx:org.sdmx.infomodel.xxx.xxx=AGENCY:RESOURCEID(VERSION).SUBRESOURCEID.SUBSUBRESOURCEID... // Example urn:sdmx:org.sdmx.infomodel.categoryscheme.Category=SPC:CAS_COM_TOPIC(1.0).ECO // Sub resource ID and (and sub sub...) are optional const matches = regexMatches(/.+=(.+):(.+)\((.+)\)\.*(.*)/g, urn); if (matches.length >= 1 && matches[0].length >= 3 && !isDefined([0, 1, 2].find((idx) => matches[0][idx] === null))) { return { agencyId: matches[0][0], resourceId: matches[0][1], version: matches[0][2], descendantIds: matches[0][3] !== null ? matches[0][3].split(".") : undefined }; } } export async function loadSdmxJsonStructure(url, allowNotImplemeted) { try { return JSON.parse(await new Resource({ url, headers: { Accept: "application/vnd.sdmx.structure+json; charset=utf-8; version=1.0" } }).fetch()); } catch (error) { // If SDMX server has returned an error message if (error instanceof RequestErrorEvent && isDefined(error.response)) { if (!allowNotImplemeted) { throw new TerriaError({ title: i18next.t("models.sdmxServerStratum.sdmxStructureLoadErrorTitle"), message: sdmxErrorString.has(error.statusCode) ? `${sdmxErrorString.get(error.statusCode)}: ${error.response}` : `${error.response}` }); } // Not sure what happened (maybe CORS) } else if (!allowNotImplemeted) { throw new TerriaError({ title: i18next.t("models.sdmxServerStratum.sdmxStructureLoadErrorTitle"), message: `Unkown error occurred${isDefined(error) ? typeof error === "string" ? `: ${error}` : `: ${JSON.stringify(error)}` : ""}` }); } } } export var SdmxHttpErrorCodes; (function (SdmxHttpErrorCodes) { // SDMX to HTTP Error Mapping - taken from https://github.com/sdmx-twg/sdmx-rest/blob/7366f56ac08fe4eed758204e32d2e1ca62c78acf/v2_1/ws/rest/docs/4_7_errors.md#sdmx-to-http-error-mapping SdmxHttpErrorCodes[SdmxHttpErrorCodes["NoChanges"] = 304] = "NoChanges"; // 100 No results found = 404 Not found SdmxHttpErrorCodes[SdmxHttpErrorCodes["NoResults"] = 404] = "NoResults"; // 110 Unauthorized = 401 Unauthorized SdmxHttpErrorCodes[SdmxHttpErrorCodes["Unauthorized"] = 401] = "Unauthorized"; // 130 Response too large due to client request = 413 Request entity too large // 510 Response size exceeds service limit = 413 Request entity too large SdmxHttpErrorCodes[SdmxHttpErrorCodes["ReponseTooLarge"] = 413] = "ReponseTooLarge"; // 140 Syntax error = 400 Bad syntax SdmxHttpErrorCodes[SdmxHttpErrorCodes["SyntaxError"] = 400] = "SyntaxError"; // 150 Semantic error = 403 Forbidden SdmxHttpErrorCodes[SdmxHttpErrorCodes["SemanticError"] = 403] = "SemanticError"; SdmxHttpErrorCodes[SdmxHttpErrorCodes["UriTooLong"] = 414] = "UriTooLong"; // 500 Internal Server error = 500 Internal server error // 1000+ = 500 Internal server error SdmxHttpErrorCodes[SdmxHttpErrorCodes["ServerError"] = 500] = "ServerError"; // 501 Not implemented = 501 Not implemented SdmxHttpErrorCodes[SdmxHttpErrorCodes["NotImplemented"] = 501] = "NotImplemented"; // 503 Service unavailable = 503 Service unavailable SdmxHttpErrorCodes[SdmxHttpErrorCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; })(SdmxHttpErrorCodes || (SdmxHttpErrorCodes = {})); export const sdmxErrorString = new Map(); sdmxErrorString.set(SdmxHttpErrorCodes.NoChanges, "No changes"); sdmxErrorString.set(SdmxHttpErrorCodes.NoResults, "No results"); sdmxErrorString.set(SdmxHttpErrorCodes.Unauthorized, "Unauthorised"); sdmxErrorString.set(SdmxHttpErrorCodes.ReponseTooLarge, "Response too large"); sdmxErrorString.set(SdmxHttpErrorCodes.SyntaxError, "Syntax error"); sdmxErrorString.set(SdmxHttpErrorCodes.SemanticError, "Semantic error"); sdmxErrorString.set(SdmxHttpErrorCodes.UriTooLong, "URI too long"); sdmxErrorString.set(SdmxHttpErrorCodes.ServerError, "Server error"); sdmxErrorString.set(SdmxHttpErrorCodes.NotImplemented, "Not implemented"); sdmxErrorString.set(SdmxHttpErrorCodes.ServiceUnavailable, "Service unavailable"); //# sourceMappingURL=SdmxJsonServerStratum.js.map