UNPKG

terriajs

Version:

Geospatial data visualization platform.

620 lines 28.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 { computed, toJS, makeObservable, override } from "mobx"; import { createTransformer } from "mobx-utils"; import filterOutUndefined from "../../../Core/filterOutUndefined"; import isDefined from "../../../Core/isDefined"; import { isJsonObject, isJsonString } from "../../../Core/Json"; import loadJson from "../../../Core/loadJson"; import runLater from "../../../Core/runLater"; import TerriaError from "../../../Core/TerriaError"; import AccessControlMixin from "../../../ModelMixins/AccessControlMixin"; import GroupMixin from "../../../ModelMixins/GroupMixin"; import ReferenceMixin from "../../../ModelMixins/ReferenceMixin"; import UrlMixin from "../../../ModelMixins/UrlMixin"; import MagdaDistributionFormatTraits from "../../../Traits/TraitsClasses/MagdaDistributionFormatTraits"; import MagdaReferenceTraits from "../../../Traits/TraitsClasses/MagdaReferenceTraits"; import CommonStrata from "../../Definition/CommonStrata"; import CreateModel from "../../Definition/CreateModel"; import createStratumInstance from "../../Definition/createStratumInstance"; import { BaseModel } from "../../Definition/Model"; import StratumOrder from "../../Definition/StratumOrder"; import updateModelFromJson from "../../Definition/updateModelFromJson"; import CatalogMemberFactory from "../CatalogMemberFactory"; import proxyCatalogItemUrl from "../proxyCatalogItemUrl"; const magdaRecordStratum = "magda-record"; StratumOrder.addDefaultStratum(magdaRecordStratum); export default class MagdaReference extends AccessControlMixin(UrlMixin(ReferenceMixin(CreateModel(MagdaReferenceTraits)))) { static defaultDistributionFormats = [ createStratumInstance(MagdaDistributionFormatTraits, { id: "WMS", formatRegex: "^wms$", definition: { type: "wms" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "WMS-GROUP", formatRegex: "^wms-group$", definition: { type: "wms-group" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "EsriMapServer", formatRegex: "^esri (mapserver|map server|rest|tiled map service)$", urlRegex: "MapServer", definition: { type: "esri-mapServer" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "CSV", formatRegex: "^csv(-geo-)?", definition: { type: "csv" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "CZML", formatRegex: "^czml$", definition: { type: "czml" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "KML", formatRegex: "^km[lz]$", definition: { type: "kml" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "GeoJSON", formatRegex: "^geojson$", definition: { type: "geojson" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "WFS", formatRegex: "^wfs$", definition: { type: "wfs" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "EsriFeatureServer", formatRegex: "ESRI (MAPSERVER|FEATURESERVER)", // We still allow `ESRI MAPSERVER` to be considered for compatibility reason urlRegex: "FeatureServer$|FeatureServer/$", // url Regex will exclude MapServer urls definition: { type: "esri-featureServer-group" } }), createStratumInstance(MagdaDistributionFormatTraits, { id: "EsriFeatureServer", formatRegex: "ESRI (MAPSERVER|FEATURESERVER)", // We still allow `ESRI MAPSERVER` to be considered for compatibility reason urlRegex: "FeatureServer/d", definition: { type: "esri-featureServer" } }) ]; static type = "magda"; get type() { return MagdaReference.type; } constructor(id, terria, sourceReference, strata) { super(id, terria, sourceReference, strata); makeObservable(this); this.setTrait(CommonStrata.defaults, "distributionFormats", MagdaReference.defaultDistributionFormats); } get registryUri() { const uri = this.uri; if (uri === undefined) { return undefined; } return uri.clone().segment("api/v0/registry"); } get preparedDistributionFormats() { return (this.distributionFormats && this.distributionFormats.map(prepareDistributionFormat)); } get accessType() { return this.magdaRecordAcessType ?? super.accessType; } get magdaRecordAcessType() { return this.magdaRecord ? getAccessTypeFromMagdaRecord(this.magdaRecord) : undefined; } async forceLoadReference(previousTarget) { const existingRecord = this.magdaRecord ? toJS(this.magdaRecord) : undefined; const magdaUri = this.uri; const override = toJS(this.override); const addOrOverrideAspects = toJS(this.addOrOverrideAspects); const distributionFormats = this.preparedDistributionFormats; // `runLater` is needed due to no actions in `AsyncLoader` computed promise (See AsyncLoader.ts) return await runLater(async () => { const target = MagdaReference.createMemberFromRecord(this.terria, this, distributionFormats, magdaUri, this.uniqueId, existingRecord, override, previousTarget, addOrOverrideAspects); if (target !== undefined) { return target; } if (this.recordId === undefined && this.terria.catalogProvider?.isProviderFor(this)) { // Occurs if record is no longer found inside containing group // (i.e moved, deleted or user no longer has access, including if not logged in) throw this.terria.catalogProvider.createLoadError(this); } const record = await this.loadMagdaRecord({ id: this.recordId, optionalAspects: [ "terria", "group", "dcat-dataset-strings", "dcat-distribution-strings", "dataset-distributions", "dataset-format" ], dereference: true, magdaReferenceHeaders: this.terria.configParameters.magdaReferenceHeaders }); return MagdaReference.createMemberFromRecord(this.terria, this, distributionFormats, magdaUri, this.uniqueId, record, override, previousTarget, addOrOverrideAspects); }); } static overrideRecordAspects(record, override) { if (record && override && isJsonObject(override.aspects)) { if (isJsonObject(record.aspects)) { for (const key in override.aspects) record.aspects[key] = override.aspects[key]; } else { record.aspects = override.aspects; } } } static createMemberFromRecord(terria, sourceReference, distributionFormats, magdaUri, id, record, override, previousTarget, addOrOverrideAspects = undefined) { if (record === undefined) { return undefined; } this.overrideRecordAspects(record, addOrOverrideAspects); const aspects = record.aspects; if (!isJsonObject(aspects)) { return undefined; } if (isJsonObject(aspects.group) && Array.isArray(aspects.group.members)) { const members = aspects.group.members; if (members.every((member) => isJsonObject(member))) { // Every member has been dereferenced, so we're good to go. return MagdaReference.createGroupFromRecord(terria, sourceReference, distributionFormats, magdaUri, id, record, override, previousTarget); } else { // Not enough information to create a group yet. return undefined; } } if (isJsonObject(aspects.terria) && isJsonString(aspects.terria.type)) { // A terria aspect is really all we need, _except_ if the terria aspect indicates // this is a group and we don't have a dereferenced group aspect to tell us what's // in the group. if (aspects.terria.type === "group") { // TODO: could be other types of groups! // If we had a dereferenced group aspect, we would have returned above. return undefined; } else { return MagdaReference.createMemberFromTerriaAspect(terria, sourceReference, magdaUri, id, record, aspects.terria, override, previousTarget); } } // If this is a dataset, we need the distributions to be dereferenced. let distributions; if (isJsonObject(aspects["dcat-dataset-strings"])) { const datasetDistributions = aspects["dataset-distributions"]; if (!isJsonObject(datasetDistributions) || !Array.isArray(datasetDistributions.distributions)) { // Distributions not present return undefined; } distributions = datasetDistributions.distributions; if (!distributions.every((distribution) => isJsonObject(distribution))) { // Some of the distributions are not dereferenced. return undefined; } } // A distribution is already ready to go if (isJsonObject(aspects["dcat-distribution-strings"])) { distributions = distributions ? distributions.slice() : []; distributions.push(record); } if (distributions) { const match = MagdaReference.findPreparedDistributionFormat(distributionFormats, distributions); if (match !== undefined && match.format.definition && isJsonString(match.format.definition.type)) { return MagdaReference.createMemberFromDistributionFormat(terria, sourceReference, magdaUri, id, record, match.distribution, match.format, override, previousTarget); } } return undefined; } static createGroupFromRecord(terria, sourceReference, distributionFormats, magdaUri, id, record, override, previousTarget) { const aspects = record.aspects; if (!isJsonObject(aspects)) { return undefined; } const terriaAspect = aspects.terria; const type = isJsonObject(terriaAspect) && isJsonString(terriaAspect.type) ? terriaAspect.type : "group"; const ModelClass = CatalogMemberFactory.find(type); if (ModelClass === undefined) { throw new TerriaError({ sender: this, title: i18next.t("models.catalog.unsupportedTypeTitle"), message: i18next.t("models.catalog.unsupportedTypeMessage", { type }) }); } let group; if (previousTarget && previousTarget instanceof ModelClass) { group = previousTarget; } else { group = new ModelClass(id, terria, sourceReference); } if (isJsonObject(aspects.group) && Array.isArray(aspects.group.members)) { // Create models for members. There are at least the following different cases: // 1. The JSON can be used to create the full model, and it is a new model, // or the same type as the previous model with the same id. // 2. JSON used to create full model, but there is an existing model with a different type // 3. No details to create full model, instead create MagdaReference // 4. No details to create full model and a MagdaReference with its id exists, so update existing const members = aspects.group.members; const ids = members.map((member) => { if (!isJsonObject(member) || !isJsonString(member.id)) { return undefined; } const memberId = member.id; let overriddenMember; if (override && Array.isArray(override.members)) { overriddenMember = override.members.find((member) => isJsonObject(member) && member.id === memberId); } const model = MagdaReference.createMemberFromRecord(terria, undefined, distributionFormats, magdaUri, member.id, member, overriddenMember, terria.getModelById(BaseModel, member.id)); let shareKeys; if (isJsonObject(member.aspects, false) && isJsonObject(member.aspects.terria, false) && Array.isArray(member.aspects.terria.shareKeys)) { shareKeys = member.aspects.terria.shareKeys.filter(isJsonString); } if (!model) { // Can't create an item or group yet, so create or update a reference. let ref = terria.getModelById(MagdaReference, member.id); if (!ref) { ref = new MagdaReference(member.id, terria, undefined); terria.addModel(ref, shareKeys); } if (!(isDefined(ref.url) && isDefined(ref.recordId)) && !isDefined(ref.magdaRecord)) { // MagdaReference has just been created, or comes from share data with // not enough information to load itself if (magdaUri) { ref.setTrait(CommonStrata.definition, "url", magdaUri.toString()); } ref.setTrait(CommonStrata.definition, "recordId", memberId); if (isJsonObject(member.aspects, false) && isJsonObject(member.aspects.group, false)) { // This is most likely a group. ref.setTrait(CommonStrata.definition, "isGroup", true); } else { // This is most likely a mappable or chartable item. ref.setTrait(CommonStrata.definition, "isMappable", true); ref.setTrait(CommonStrata.definition, "isChartable", true); } // Use the name from the terria aspect if there is one. if (isJsonObject(member.aspects, false) && isJsonObject(member.aspects.terria, false) && isJsonObject(member.aspects.terria.definition, false) && isJsonString(member.aspects.terria.definition.name)) { ref.setTrait(CommonStrata.definition, "name", member.aspects.terria.definition.name); } else if (isJsonString(member.name)) { ref.setTrait(CommonStrata.definition, "name", member.name); } } if (overriddenMember) { ref.setTrait(CommonStrata.definition, "override", overriddenMember); } if (AccessControlMixin.isMixedInto(ref)) { ref.setAccessType(getAccessTypeFromMagdaRecord(member)); } return ref.uniqueId; } else { const prevModel = terria.getModelById(BaseModel, member.id); if (prevModel === undefined) { terria.addModel(model, shareKeys); } else if (prevModel.type !== model.type) { terria.removeModelReferences(prevModel); prevModel.dispose(); terria.addModel(model, shareKeys); } if (AccessControlMixin.isMixedInto(model)) { model.setAccessType(getAccessTypeFromMagdaRecord(member)); } return model.uniqueId; } }); if (isJsonString(record.name)) { group.setTrait(magdaRecordStratum, "name", record.name); } group.setTrait(magdaRecordStratum, "members", filterOutUndefined(ids)); if (GroupMixin.isMixedInto(group) && group.uniqueId) { console.log(`Refreshing ids for ${group.uniqueId}`); group.refreshKnownContainerUniqueIds(group.uniqueId); } } if (isJsonObject(aspects.terria, false)) { const terriaAspect = aspects.terria; Object.keys(terriaAspect).forEach((key) => { const terriaStratum = terriaAspect[key]; if (key === "id" || key === "type" || key === "shareKeys" || !isJsonObject(terriaStratum, false)) { return; } updateModelFromJson(group, key, terriaStratum, true).logError(); }); } if (override) { updateModelFromJson(group, CommonStrata.override, override, true).logError(); } return group; } static createMemberFromTerriaAspect(terria, sourceReference, _magdaUri, id, record, terriaAspect, override, previousTarget) { if (!isJsonString(terriaAspect.type)) { return undefined; } let result; if (previousTarget && previousTarget.type === terriaAspect.type) { result = previousTarget; } else { // Couldn't re-use the previous target, so create a new one. const newMember = CatalogMemberFactory.create(terriaAspect.type, id, terria, sourceReference); if (newMember === undefined) { console.error(`Could not create unknown model type ${terriaAspect.type}.`); // don't create a stub here, as magda should rarely create unknown model types // and we'll let the UI highlight that it's bad rather than bandaging an unknown type return undefined; } result = newMember; } if (isJsonString(record.name)) { result.setTrait(magdaRecordStratum, "name", record.name); } Object.entries(terriaAspect).forEach(([stratumName, stratum]) => { if (stratumName === "id" || stratumName === "type" || stratumName === "shareKeys" || !isJsonObject(stratum, false)) { return; } updateModelFromJson(result, stratumName, stratum, true).catchError((error) => { error.log(); result.setTrait(CommonStrata.underride, "isExperiencingIssues", true); }); }); if (override) { updateModelFromJson(result, CommonStrata.override, override, true).logError(); } if (terria.workbench.contains(result)) { // Reload after updating traits // Adding an item already in the workbench does all relevant loading, but doesn't change the workbench terria.workbench.add(result).then((res) => res.raiseError(terria)); } return result; } static createMemberFromDistributionFormat(terria, sourceReference, _magdaUri, id, datasetRecord, distributionRecord, format, override, previousTarget) { if (!isJsonString(format.definition.type) || !isJsonObject(datasetRecord.aspects) || !isJsonObject(distributionRecord.aspects)) { return undefined; } let result; if (previousTarget && previousTarget.type === format.definition.type) { result = previousTarget; } else { // Couldn't re-use the previous target, so create a new one. const newMember = CatalogMemberFactory.create(format.definition.type, id, terria, sourceReference); if (newMember === undefined) { throw new TerriaError({ sender: this, title: i18next.t("models.catalog.unsupportedTypeTitle"), message: i18next.t("models.catalog.unsupportedTypeMessage", { type: format.definition.type }) }); } result = newMember; } const datasetDcat = datasetRecord.aspects["dcat-dataset-strings"]; const distributionDcat = distributionRecord.aspects["dcat-distribution-strings"]; let url; if (isJsonObject(distributionDcat)) { if (isJsonString(distributionDcat.downloadURL)) { url = distributionDcat.downloadURL; } if (url === undefined && isJsonString(distributionDcat.accessURL)) { url = distributionDcat.accessURL; } } const definition = { url: url, info: [], ...format.definition }; if (isJsonObject(datasetDcat) && isJsonString(datasetDcat.description) && !definition.info.find((section) => section.name === "Dataset Description")) { definition.info.push({ name: "Dataset Description", content: datasetDcat.description }); } if (isJsonObject(distributionDcat) && isJsonString(distributionDcat.description) && !definition.info.find((section) => section.name === "Distribution Description")) { definition.info.push({ name: "Distribution Description", content: distributionDcat.description }); } if (isJsonString(datasetRecord.name)) updateModelFromJson(result, magdaRecordStratum, { name: datasetRecord.name }, true).logError(); updateModelFromJson(result, CommonStrata.definition, definition, true).logError(); if (override) { updateModelFromJson(result, CommonStrata.override, override, true).logError(); } return result; } static findPreparedDistributionFormat(distributionFormats, distributions) { for (let i = 0; i < distributionFormats.length; ++i) { const distributionFormat = distributionFormats[i]; const formatRegex = distributionFormat.formatRegex; const urlRegex = distributionFormat.urlRegex; // Find distributions that match this format for (let j = 0; j < distributions.length; ++j) { const distribution = distributions[j]; if (!isJsonObject(distribution)) { continue; } const aspects = distribution.aspects; if (!isJsonObject(aspects)) { continue; } const dcatJson = aspects["dcat-distribution-strings"]; const datasetFormat = aspects["dataset-format"]; let format; let url; if (isJsonObject(dcatJson)) { if (typeof dcatJson.format === "string") { format = dcatJson.format; } if (typeof dcatJson.downloadURL === "string") { url = dcatJson.downloadURL; } if (url === undefined && typeof dcatJson.accessURL === "string") { url = dcatJson.accessURL; } } if (isJsonObject(datasetFormat) && typeof datasetFormat.format === "string") { format = datasetFormat.format; } if (format === undefined || url === undefined) { continue; } if ((formatRegex !== undefined && !formatRegex.test(format)) || (urlRegex !== undefined && !urlRegex.test(url))) { continue; } // We have a match! return { distribution: distribution, format: distributionFormat }; } } return undefined; } get cacheDuration() { if (isDefined(super.cacheDuration)) { return super.cacheDuration; } return "0d"; } loadMagdaRecord(options) { const recordUri = this.buildMagdaRecordUri(options); if (recordUri === undefined) { return Promise.reject(new TerriaError({ sender: this, title: i18next.t("models.magda.idsNotSpecifiedTitle"), message: i18next.t("models.magda.idsNotSpecifiedMessage") })); } const proxiedUrl = proxyCatalogItemUrl(this, recordUri.toString()); return loadJson(proxiedUrl, options.magdaReferenceHeaders); } buildMagdaRecordUri(options) { const registryUri = this.registryUri; if (options.id === undefined || registryUri === undefined) { return undefined; } const recordUri = registryUri .clone() .segment(`records/${encodeURIComponent(options.id)}`); if (options.aspects) { recordUri.addQuery("aspect", options.aspects); } if (options.optionalAspects) { recordUri.addQuery("optionalAspect", options.optionalAspects); } if (options.dereference) { recordUri.addQuery("dereference", "true"); } return recordUri; } } __decorate([ computed ], MagdaReference.prototype, "registryUri", null); __decorate([ computed ], MagdaReference.prototype, "preparedDistributionFormats", null); __decorate([ override ], MagdaReference.prototype, "accessType", null); __decorate([ computed ], MagdaReference.prototype, "magdaRecordAcessType", null); __decorate([ override ], MagdaReference.prototype, "cacheDuration", null); const prepareDistributionFormat = createTransformer((format) => { return { formatRegex: format.formatRegex ? new RegExp(format.formatRegex, "i") : undefined, urlRegex: format.urlRegex ? new RegExp(format.urlRegex, "i") : undefined, definition: format.definition || {} }; }); function getAccessTypeFromMagdaRecord(magdaRecord) { const record = toJS(magdaRecord); // Magda V2 access control has higher priority. if (record?.aspects?.["access-control"]) { return record.aspects["access-control"].orgUnitId ? record.aspects["access-control"].constraintExemption ? "public" : "non-public" : "public"; } else if (record?.aspects?.["esri-access-control"]) { return record.aspects["esri-access-control"].access; } else { return "public"; } } //# sourceMappingURL=MagdaReference.js.map