UNPKG

terriajs

Version:

Geospatial data visualization platform.

310 lines (269 loc) 9.52 kB
import { ApiClient, fromCatalog } from "@opendatasoft/api-client"; import { Dataset, Facet } from "@opendatasoft/api-client/dist/client/types"; import i18next from "i18next"; import { action, computed, runInAction, makeObservable } from "mobx"; import URI from "urijs"; import isDefined from "../../../Core/isDefined"; import runLater from "../../../Core/runLater"; import CatalogMemberMixin from "../../../ModelMixins/CatalogMemberMixin"; import GroupMixin from "../../../ModelMixins/GroupMixin"; import UrlMixin from "../../../ModelMixins/UrlMixin"; import ModelReference from "../../../Traits/ModelReference"; import { MetadataUrlTraits } from "../../../Traits/TraitsClasses/CatalogMemberTraits"; import OpenDataSoftCatalogGroupTraits, { RefineTraits } from "../../../Traits/TraitsClasses/OpenDataSoftCatalogGroupTraits"; import CommonStrata from "../../Definition/CommonStrata"; import CreateModel from "../../Definition/CreateModel"; import createStratumInstance from "../../Definition/createStratumInstance"; import LoadableStratum from "../../Definition/LoadableStratum"; import { BaseModel } from "../../Definition/Model"; import StratumOrder from "../../Definition/StratumOrder"; import OpenDataSoftCatalogItem from "../CatalogItems/OpenDataSoftCatalogItem"; // "Valid" types which force some properties to be defined export type ValidDataset = Dataset & { dataset_id: string }; export type ValidFacet = Facet & { name: string; facets?: ValidFacet[] }; export class OpenDataSoftCatalogStratum extends LoadableStratum( OpenDataSoftCatalogGroupTraits ) { static stratumName = "openDataSoftCatalog"; static async load( catalogGroup: OpenDataSoftCatalogGroup ): Promise<OpenDataSoftCatalogStratum> { if (!catalogGroup.url) throw "`url` must be set"; const client = new ApiClient({ domain: catalogGroup.url }); const datasets: ValidDataset[] = []; let facets: ValidFacet[] | undefined; // If no facetFilters, try to get some facets if ( catalogGroup.facetFilters && catalogGroup.facetFilters.length === 0 && !catalogGroup.flatten ) { facets = (await client.get(fromCatalog().facets())).facets?.filter((f) => isValidFacet(f) ) as ValidFacet[]; } // If no facets (or we have facetFiles) - get datasets if (!facets || facets.length === 0) { // We can only fetch datasets in batch of 100 let offset = 0; let totalDatasets: number | undefined; const limit = 100; let q = fromCatalog() .datasets() .limit(limit) .orderBy("title asc") // Filter dataset with 'geo' or 'timeserie' features. // Possible values: calendar, geo, image, apiproxy, timeserie, and aggregate .where(`features = "geo" OR features = "timeserie"`); // If facet filters, use them to filter datasets if (catalogGroup.facetFilters && catalogGroup.facetFilters.length > 0) { q = q.refine( catalogGroup.facetFilters.map((f) => `${f.name}:${f.value}`).join(",") ); } while (!isDefined(totalDatasets) || offset < totalDatasets) { q = q.offset(offset); const catalog = await client.get(q); totalDatasets = catalog.total_count ?? 0; catalog.datasets?.forEach((response) => { if (isValidDataset(response.dataset)) { datasets.push(response.dataset); } }); offset += limit; } } return new OpenDataSoftCatalogStratum( catalogGroup, undefined, facets ?? [], datasets ?? [] ); } duplicateLoadableStratum(model: BaseModel): this { return new OpenDataSoftCatalogStratum( model as OpenDataSoftCatalogGroup, this.facetName, this.facets, this.datasets ) as this; } constructor( private readonly catalogGroup: OpenDataSoftCatalogGroup, readonly facetName: string | undefined, readonly facets: ValidFacet[], readonly datasets: ValidDataset[] ) { super(); makeObservable(this); } @computed get members(): ModelReference[] { return [ ...this.facets.map((f) => this.getFacetId(f)), ...this.datasets.map((d) => this.getDatasetId(d)) ]; } createMembers() { this.facets.forEach((facet) => this.createGroupFromFacet(facet)); this.datasets.forEach((dataset) => this.createMemberFromDataset(dataset)); } /** Turn facet into OpenDataSoftCatalogGroup */ @action createGroupFromFacet(facet: ValidFacet) { const layerId = this.getFacetId(facet); if (!layerId) { return; } const existingGroupModel = this.catalogGroup.terria.getModelById( OpenDataSoftCatalogGroup, layerId ); let groupModel: OpenDataSoftCatalogGroup; if (existingGroupModel === undefined) { groupModel = new OpenDataSoftCatalogGroup( layerId, this.catalogGroup.terria, undefined ); this.catalogGroup.terria.addModel(groupModel); } else { groupModel = existingGroupModel; } // Replace the stratum inherited from the parent group. groupModel.strata.delete(CommonStrata.definition); groupModel.setTrait( CommonStrata.definition, "name", `${facet.name}${facet.count ? ` (${facet.count ?? 0})` : ""}` ); groupModel.setTrait(CommonStrata.definition, "url", this.catalogGroup.url); // Set OpenDataSoftDatasetStratum so it doesn't have to be loaded gain groupModel.strata.delete(OpenDataSoftCatalogStratum.stratumName); // If no more facets, set facetFilter if ( !facet.facets || !Array.isArray(facet.facets) || facet.facets.length === 0 ) { groupModel.setTrait(CommonStrata.definition, "facetFilters", [ createStratumInstance(RefineTraits, { name: this.facetName, value: facet.name }) ]); } else { groupModel.strata.set( OpenDataSoftCatalogStratum.stratumName, new OpenDataSoftCatalogStratum(groupModel, facet.name, facet.facets, []) ); } } /** Turn dataset into OpenDataSoftCatalogItem */ @action createMemberFromDataset(dataset: ValidDataset) { const layerId = this.getDatasetId(dataset); if (!layerId) { return; } const existingItemModel = this.catalogGroup.terria.getModelById( OpenDataSoftCatalogItem, layerId ); let itemModel: OpenDataSoftCatalogItem; if (existingItemModel === undefined) { itemModel = new OpenDataSoftCatalogItem( layerId, this.catalogGroup.terria, undefined ); this.catalogGroup.terria.addModel(itemModel); // Add older shareKey this.catalogGroup.terria.addShareKey( layerId, `${this.catalogGroup.uniqueId}/${dataset.dataset_id}` ); } else { itemModel = existingItemModel; } // Replace the stratum inherited from the parent group. itemModel.strata.delete(CommonStrata.definition); itemModel.setTrait( CommonStrata.definition, "datasetId", dataset.dataset_id ); itemModel.setTrait(CommonStrata.definition, "url", this.catalogGroup.url); itemModel.setTrait( CommonStrata.definition, "name", dataset.metas?.default?.title ?? dataset.dataset_id ); itemModel.setTrait( CommonStrata.definition, "description", dataset.metas?.default?.description ?? undefined ); itemModel.setTrait(CommonStrata.definition, "metadataUrls", [ createStratumInstance(MetadataUrlTraits, { title: i18next.t("models.openDataSoft.viewDatasetPage"), url: `${this.catalogGroup.url}/explore/dataset/${dataset.dataset_id}/information/` }) ]); } getDatasetId(dataset: ValidDataset) { // Use OpenDataSoft server hostname for datasets, so we don't create multiple across facets return `${ this.catalogGroup.url ? URI(this.catalogGroup.url).hostname() : this.catalogGroup.uniqueId }/${dataset.dataset_id}`; } getFacetId(facet: ValidFacet) { return `${this.catalogGroup.uniqueId}/${facet.name}`; } } StratumOrder.addLoadStratum(OpenDataSoftCatalogStratum.stratumName); export default class OpenDataSoftCatalogGroup extends UrlMixin( GroupMixin(CatalogMemberMixin(CreateModel(OpenDataSoftCatalogGroupTraits))) ) { static readonly type = "opendatasoft-group"; get type() { return OpenDataSoftCatalogGroup.type; } protected async forceLoadMetadata(): Promise<void> { if (!this.strata.has(OpenDataSoftCatalogStratum.stratumName)) { const stratum = await OpenDataSoftCatalogStratum.load(this); runInAction(() => { this.strata.set(OpenDataSoftCatalogStratum.stratumName, stratum); }); } } protected async forceLoadMembers() { const opendatasoftServerStratum = this.strata.get( OpenDataSoftCatalogStratum.stratumName ) as OpenDataSoftCatalogStratum | undefined; if (opendatasoftServerStratum) { await runLater(() => opendatasoftServerStratum.createMembers()); } } } export function isValidDataset( dataset: Dataset | undefined ): dataset is ValidDataset { return isDefined(dataset) && isDefined(dataset.dataset_id); } export function isValidFacet(facet: Facet | undefined): facet is ValidFacet { return ( isDefined(facet) && isDefined(facet.name) && (facet.facets ?? []).reduce<boolean>( (valid, current) => valid && isValidFacet(current), true ) ); }