UNPKG

terriajs

Version:

Geospatial data visualization platform.

200 lines (188 loc) 7.26 kB
import { action, runInAction, makeObservable } from "mobx"; import Cartesian2 from "terriajs-cesium/Source/Core/Cartesian2"; import JulianDate from "terriajs-cesium/Source/Core/JulianDate"; import Resource from "terriajs-cesium/Source/Core/Resource"; import ConstantProperty from "terriajs-cesium/Source/DataSources/ConstantProperty"; import PropertyBag from "terriajs-cesium/Source/DataSources/PropertyBag"; import ImageryProvider from "terriajs-cesium/Source/Scene/ImageryProvider"; import AbstractConstructor from "../Core/AbstractConstructor"; import isDefined from "../Core/isDefined"; import loadJson from "../Core/loadJson"; import proxyCatalogItemUrl from "../Models/Catalog/proxyCatalogItemUrl"; import Model from "../Models/Definition/Model"; import TerriaFeature from "../Models/Feature/Feature"; import { generateCesiumInfoHTMLFromProperties } from "../ReactViews/FeatureInfo/generateCesiumInfoHTMLFromProperties"; import FeatureInfoUrlTemplateTraits from "../Traits/TraitsClasses/FeatureInfoTraits"; import MappableMixin from "./MappableMixin"; import TimeVarying from "./TimeVarying"; type BaseType = Model<FeatureInfoUrlTemplateTraits>; function FeatureInfoUrlTemplateMixin<T extends AbstractConstructor<BaseType>>( Base: T ) { abstract class FeatureInfoUrlTemplateMixin extends Base { constructor(...args: any[]) { super(...args); makeObservable(this); } get hasFeatureInfoUrlTemplateMixin() { return true; } /** * Returns a {@link Feature} for the pick result. */ abstract buildFeatureFromPickResult( screenPosition: Cartesian2 | undefined, pickResult: any ): Promise<TerriaFeature> | TerriaFeature | undefined; /** * Returns a {@link Feature} for the pick result. If `featureInfoUrlTemplate` is set, * it asynchronously loads additional info from the url. */ @action async getFeaturesFromPickResult( screenPosition: Cartesian2 | undefined, pickResult: any, loadExternal = true ): Promise<TerriaFeature | undefined> { const feature = await this.buildFeatureFromPickResult( screenPosition, pickResult ); if (isDefined(feature)) { feature._catalogItem = this; feature.loadingFeatureInfoUrl = true; (async () => { if (loadExternal && isDefined(this.featureInfoUrlTemplate)) { const resource = new Resource({ url: proxyCatalogItemUrl(this, this.featureInfoUrlTemplate, "0d"), templateValues: feature.properties ? feature.properties.getValue(new JulianDate()) : undefined }); try { const featureInfo = await loadJson(resource); Object.keys(featureInfo).forEach((property) => { if (!feature.properties) { feature.properties = new PropertyBag(); } feature.properties.addProperty(property, featureInfo[property]); }); // Update description of the feature after it is resolved from // feature info template url feature.description = new ConstantProperty( generateCesiumInfoHTMLFromProperties( feature.properties, (TimeVarying.is(this) ? this.currentTimeAsJulianDate : undefined) ?? JulianDate.now(), MappableMixin.isMixedInto(this) ? this.showStringIfPropertyValueIsNull : undefined ) ); } catch (_e) { if (!feature.properties) { feature.properties = new PropertyBag(); } feature.properties.addProperty( "Error", "Unable to retrieve feature details from:\n\n" + resource.url ); } } runInAction(() => (feature.loadingFeatureInfoUrl = false)); })(); } return feature; } wrapImageryPickFeatures<T extends ImageryProvider>(imageryProvider: T) { const realPickFeatures = imageryProvider.pickFeatures; // Get CatalogItem properties, so we aren't reading them outside of a reaction const featureInfoUrlTemplate = this.featureInfoUrlTemplate; const proxiedFeatureInfoUrl = proxyCatalogItemUrl( this, featureInfoUrlTemplate ?? "", "0d" ); const showStringIfPropertyValueIsNull = MappableMixin.isMixedInto(this) ? this.showStringIfPropertyValueIsNull : undefined; const maxRequests = this.maxRequests; const currentTime = TimeVarying.is(this) ? this.currentTimeAsJulianDate : undefined; imageryProvider.pickFeatures = async ( x: number, y: number, level: number, longitude: number, latitude: number ) => { const features = await realPickFeatures.call( imageryProvider, x, y, level, longitude, latitude ); if ( isDefined(featureInfoUrlTemplate) && isDefined(features) && features.length < maxRequests ) { for (let i = 0; i < features.length; i++) { const feature = features[i]; const resource = new Resource({ url: proxiedFeatureInfoUrl, templateValues: feature.properties ? feature.properties : undefined }); try { const featureInfo = await loadJson(resource); Object.keys(featureInfo).forEach((property) => { if (!feature.properties) { feature.properties = {}; } if (feature.properties instanceof PropertyBag) { feature.properties.addProperty( property, featureInfo[property] ); } else { feature.properties[property] = featureInfo[property]; } }); // Update description of the feature after it is resolved from // feature info template url feature.description = generateCesiumInfoHTMLFromProperties( feature.properties, currentTime ?? JulianDate.now(), showStringIfPropertyValueIsNull ); } catch (_e) { if (!feature.properties) { feature.properties = {}; } feature.properties["Error"] = "Unable to retrieve feature details from:\n\n" + resource.url; } } } return Promise.resolve(features!); }; return imageryProvider; } } return FeatureInfoUrlTemplateMixin; } namespace FeatureInfoUrlTemplateMixin { export interface Instance extends InstanceType< ReturnType<typeof FeatureInfoUrlTemplateMixin> > {} export function isMixedInto(model: any): model is Instance { return model && model.hasFeatureInfoUrlTemplateMixin; } } export default FeatureInfoUrlTemplateMixin;