UNPKG

terriajs

Version:

Geospatial data visualization platform.

284 lines (255 loc) 7.96 kB
"use strict"; import classNames from "classnames"; import { action, autorun, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import PropTypes from "prop-types"; import React from "react"; import { withTranslation } from "react-i18next"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import filterOutUndefined from "../../Core/filterOutUndefined"; import MappableMixin, { ImageryParts } from "../../ModelMixins/MappableMixin"; import CommonStrata from "../../Models/Definition/CommonStrata"; import CreateModel from "../../Models/Definition/CreateModel"; import GeoJsonCatalogItem from "../../Models/Catalog/CatalogItems/GeoJsonCatalogItem"; import ViewerMode from "../../Models/ViewerMode"; import MappableTraits from "../../Traits/TraitsClasses/MappableTraits"; import TerriaViewer from "../../ViewModels/TerriaViewer"; import Styles from "./data-preview-map.scss"; class AdaptForPreviewMap extends MappableMixin(CreateModel(MappableTraits)) { previewed; async forceLoadMapItems() {} // Make all imagery 0 or 100% opacity @computed get mapItems() { return ( this.previewed?.mapItems.map((m) => ImageryParts.is(m) ? { ...m, alpha: m.alpha !== 0.0 ? 1.0 : 0.0, show: true } : m ) ?? [] ); } } /** * Leaflet-based preview map that sits within the preview. */ /** * @typedef {object} Props * @prop {Terria} terria * @prop {Mappable} previewed * @prop {boolean} showMap * */ // TODO: Can this.props.previewed be undefined? /** * * @extends {React.Component<Props>} */ @observer class DataPreviewMap extends React.Component { @observable isZoomedToExtent = false; /** * @type {TerriaViewer} * @readonly */ previewViewer; static propTypes = { terria: PropTypes.object.isRequired, previewed: PropTypes.object, showMap: PropTypes.bool, t: PropTypes.func.isRequired }; @computed get previewBadgeState() { if (this.props.previewed?.isLoading) return "loading"; if ( this.props.previewed?.loadMetadataResult?.error || this.props.previewed?.loadMapItemsResult?.error ) return "dataPreviewError"; if (this.props.previewed?.mapItems?.length === 0) return "noPreviewAvailable"; return "dataPreview"; } constructor(props) { super(props); /** * @param {HTMLElement | null} container */ this.containerRef = action((container) => { this.previewViewer.attached && this.previewViewer.detach(); if (container !== null) { this.initPreview(container); } }); this.previewViewer = new TerriaViewer( this.props.terria, computed(() => { const previewItem = new AdaptForPreviewMap(); previewItem.previewed = this.props.previewed; // Can previewed be undefined? return filterOutUndefined([ previewItem, this.boundingRectangleCatalogItem ]); }) ); runInAction(() => { this.previewViewer.viewerMode = ViewerMode.Leaflet; this.previewViewer.disableInteraction = true; this.previewViewer.homeCamera = this.props.terria.mainViewer.homeCamera; }); // Not yet implemented // previewViewer.hideTerriaLogo = true; // previewViewer.homeView = terria.homeView; // previewViewer.initialView = terria.homeView; } /** * @param {HTMLElement} container */ @action initPreview(container) { console.log( "Initialising preview map. This might be expensive, so this should only show up when the preview map disappears and reappears" ); this.isZoomedToExtent = false; const baseMapItems = this.props.terria.baseMapsModel.baseMapItems; // Find preview basemap using `terria.previewBaseMapId` const initPreviewBaseMap = baseMapItems.find( (baseMap) => baseMap.item.uniqueId === this.props.terria.baseMapsModel.previewBaseMapId ); if (initPreviewBaseMap !== undefined) { this.previewViewer.setBaseMap(initPreviewBaseMap.item); } else { this.previewViewer.setBaseMap( baseMapItems.length > 0 ? baseMapItems[0].item : undefined ); } this.previewViewer.attach(container); this._disposeZoomToExtentSubscription = autorun(() => { if (this.isZoomedToExtent) { this.previewViewer.currentViewer.zoomTo(this.props.previewed); } else { this.previewViewer.currentViewer.zoomTo(this.previewViewer.homeCamera); } }); } componentWillUnmount() { this._disposeZoomToExtentSubscription && this._disposeZoomToExtentSubscription(); this.previewViewer.detach(); if (this._unsubscribeErrorHandler) { this._unsubscribeErrorHandler(); this._unsubscribeErrorHandler = undefined; } } @computed get boundingRectangleCatalogItem() { const rectangle = this.props.previewed.rectangle; if (rectangle === undefined) { return undefined; } let west = rectangle.west; let south = rectangle.south; let east = rectangle.east; let north = rectangle.north; if ( west === undefined || south === undefined || east === undefined || north === undefined ) { return undefined; } if (!this.isZoomedToExtent) { // When zoomed out, make sure the dataset rectangle is at least 5% of the width and height // the home view, so that it is actually visible. const minimumFraction = 0.05; const homeView = this.previewViewer.homeCamera; const minimumWidth = CesiumMath.toDegrees(homeView.rectangle.width) * minimumFraction; if (east - west < minimumWidth) { const center = (east + west) * 0.5; west = center - minimumWidth * 0.5; east = center + minimumWidth * 0.5; } const minimumHeight = CesiumMath.toDegrees(homeView.rectangle.height) * minimumFraction; if (north - south < minimumHeight) { const center = (north + south) * 0.5; south = center - minimumHeight * 0.5; north = center + minimumHeight * 0.5; } } const rectangleCatalogItem = new GeoJsonCatalogItem( "__preview-data-extent", this.props.terria ); rectangleCatalogItem.setTrait(CommonStrata.user, "geoJsonData", { type: "FeatureCollection", features: [ { type: "Feature", properties: { stroke: "#08ABD5", "stroke-width": 2, "stroke-opacity": 1 }, geometry: { type: "LineString", coordinates: [ [west, south], [west, north], [east, north], [east, south], [west, south] ] } } ] }); rectangleCatalogItem.loadMapItems(); return rectangleCatalogItem; } @action.bound clickMap(evt) { this.isZoomedToExtent = !this.isZoomedToExtent; } render() { const { t } = this.props; const previewBadgeLabels = { loading: t("preview.loading"), noPreviewAvailable: t("preview.noPreviewAvailable"), dataPreview: t("preview.dataPreview"), dataPreviewError: t("preview.dataPreviewError") }; return ( <div className={Styles.map} onClick={this.clickMap}> <Choose> <When condition={this.props.showMap}> <div className={classNames(Styles.terriaPreview)} ref={this.containerRef} /> </When> <Otherwise> <div className={classNames(Styles.terriaPreview, Styles.placeholder)} /> </Otherwise> </Choose> <label className={Styles.badge}> {previewBadgeLabels[this.previewBadgeState]} </label> </div> ); } } export default withTranslation()(DataPreviewMap);