terriajs
Version:
Geospatial data visualization platform.
333 lines (284 loc) • 12.8 kB
JSX
'use strict';
const CesiumMath = require('terriajs-cesium/Source/Core/Math');
const ConsoleAnalytics = require('../../Core/ConsoleAnalytics');
const defaultValue = require('terriajs-cesium/Source/Core/defaultValue');
const defined = require('terriajs-cesium/Source/Core/defined');
const GeoJsonCatalogItem = require('../../Models/GeoJsonCatalogItem');
const ObserveModelMixin = require('../ObserveModelMixin');
const OpenStreetMapCatalogItem = require('../../Models/OpenStreetMapCatalogItem');
const React = require('react');
const createReactClass = require('create-react-class');
const PropTypes = require('prop-types');
const Terria = require('../../Models/Terria');
const TerriaViewer = require('../../ViewModels/TerriaViewer.js');
const ViewerMode = require('../../Models/ViewerMode');
const when = require('terriajs-cesium/Source/ThirdParty/when');
import classNames from 'classnames';
import Styles from './data-preview-map.scss';
/**
* Leaflet-based preview map that sits within the preview.
*/
const DataPreviewMap = createReactClass({
displayName: 'DataPreviewMap',
mixins: [ObserveModelMixin],
propTypes: {
terria: PropTypes.object.isRequired,
previewedCatalogItem: PropTypes.object,
showMap: PropTypes.bool
},
getInitialState() {
return {
previewBadgeText: 'PREVIEW LOADING...',
};
},
/* eslint-disable-next-line camelcase */
UNSAFE_componentWillMount() {
const terria = this.props.terria;
this.terriaPreview = new Terria({
appName: terria.appName + ' preview',
supportEmail: terria.supportEmail,
baseUrl: terria.baseUrl,
cesiumBaseUrl: terria.cesiumBaseUrl,
analytics: new ConsoleAnalytics()
});
this.terriaPreview.viewerMode = ViewerMode.Leaflet;
this.terriaPreview.homeView = terria.homeView;
this.terriaPreview.initialView = terria.homeView;
this.terriaPreview.regionMappingDefinitionsUrl = terria.regionMappingDefinitionsUrl;
this._unsubscribeErrorHandler = this.terriaPreview.error.addEventListener(e => {
if (e.sender === this.props.previewedCatalogItem ||
(e.sender && e.sender.nowViewingCatalogItem === this.props.previewedCatalogItem)) {
this._errorPreviewingCatalogItem = true;
this.setState({
previewBadgeText: 'NO PREVIEW AVAILABLE'
});
}
});
// TODO: we shouldn't hard code the base map here. (copied from branch analyticsWithCharts)
const positron = new OpenStreetMapCatalogItem(this.terriaPreview);
positron.name = 'Positron (Light)';
positron.url = '//global.ssl.fastly.net/light_all/';
positron.attribution = '© OpenStreetMap contributors ODbL, © CartoDB CC-BY 3.0';
positron.opacity = 1.0;
positron.subdomains = ['cartodb-basemaps-a', 'cartodb-basemaps-b', 'cartodb-basemaps-c', 'cartodb-basemaps-d'];
this.terriaPreview.baseMap = positron;
this.isZoomedToExtent = false;
this.lastPreviewedCatalogItem = undefined;
this.removePreviewFromMap = undefined;
},
componentWillUnmount() {
this.destroyPreviewMap();
if (this._unsubscribeErrorHandler) {
this._unsubscribeErrorHandler();
this._unsubscribeErrorHandler = undefined;
}
},
/* eslint-disable-next-line camelcase */
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.showMap && !this.props.showMap) {
this.initMap(newProps.previewedCatalogItem);
} else {
this.updatePreview(newProps.previewedCatalogItem);
}
},
updatePreview(previewedCatalogItem) {
if (this.lastPreviewedCatalogItem === previewedCatalogItem) {
return;
}
if (previewedCatalogItem) {
this.props.terria.analytics.logEvent('dataSource', 'preview', previewedCatalogItem.name);
}
this.lastPreviewedCatalogItem = previewedCatalogItem;
this.setState({
previewBadgeText: 'DATA PREVIEW LOADING...'
});
this.isZoomedToExtent = false;
this.terriaPreview.currentViewer.zoomTo(this.terriaPreview.homeView);
if (defined(this.removePreviewFromMap)) {
this.removePreviewFromMap();
this.removePreviewFromMap = undefined;
}
if (defined(this.rectangleCatalogItem)) {
this.rectangleCatalogItem.isEnabled = false;
}
const previewed = previewedCatalogItem;
if (previewed && defined(previewed.type) && previewed.isMappable) {
const that = this;
return when(previewed.load()).then(() => {
// If this item has a separate now viewing item, load it before continuing.
let nowViewingItem;
let loadNowViewingItemPromise;
if (defined(previewed.nowViewingCatalogItem)) {
nowViewingItem = previewed.nowViewingCatalogItem;
loadNowViewingItemPromise = when(nowViewingItem.load());
} else {
nowViewingItem = previewed;
loadNowViewingItemPromise = when();
}
return loadNowViewingItemPromise.then(() => {
// Now that the item is loaded, add it to the map.
// Unless we've started previewing something else in the meantime!
if (!that._unsubscribeErrorHandler || previewed !== that.lastPreviewedCatalogItem) {
return;
}
if (defined(nowViewingItem.showOnSeparateMap)) {
if (defined(nowViewingItem.clock) && defined(nowViewingItem.clock.currentTime)) {
that.terriaPreview.clock.currentTime = nowViewingItem.clock.currentTime;
}
this._errorPreviewingCatalogItem = false;
that.removePreviewFromMap = nowViewingItem.showOnSeparateMap(that.terriaPreview.currentViewer);
if (this._errorPreviewingCatalogItem) {
this.setState({
previewBadgeText: 'NO PREVIEW AVAILABLE'
});
} else if (that.removePreviewFromMap) {
this.setState({
previewBadgeText: 'DATA PREVIEW'
});
} else {
this.setState({
previewBadgeText: 'NO PREVIEW AVAILABLE'
});
}
} else {
this.setState({
previewBadgeText: 'NO PREVIEW AVAILABLE'
});
}
that.updateBoundingRectangle(previewed);
});
}).otherwise((err) => {
console.error(err);
this.setState({
previewBadgeText: 'DATA PREVIEW ERROR'
});
});
}
},
clickMap() {
if (!defined(this.props.previewedCatalogItem)) {
return;
}
this.isZoomedToExtent = !this.isZoomedToExtent;
if (this.isZoomedToExtent) {
const catalogItem = defaultValue(this.props.previewedCatalogItem.nowViewingCatalogItem, this.props.previewedCatalogItem);
if (defined(catalogItem.rectangle)) {
this.terriaPreview.currentViewer.zoomTo(catalogItem.rectangle);
}
} else {
this.terriaPreview.currentViewer.zoomTo(this.terriaPreview.homeView);
}
this.updateBoundingRectangle(this.props.previewedCatalogItem);
},
updateBoundingRectangle(catalogItem) {
if (defined(this.rectangleCatalogItem)) {
this.rectangleCatalogItem.isEnabled = false;
this.rectangleCatalogItem = undefined;
}
catalogItem = defaultValue(catalogItem.nowViewingCatalogItem, catalogItem);
if (!defined(catalogItem) || !defined(catalogItem.rectangle)) {
return;
}
let west = catalogItem.rectangle.west;
let south = catalogItem.rectangle.south;
let east = catalogItem.rectangle.east;
let north = catalogItem.rectangle.north;
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.terriaPreview.homeView.rectangle;
const minimumWidth = (homeView.east - homeView.west) * minimumFraction;
if ((east - west) < minimumWidth) {
const center = (east + west) * 0.5;
west = center - minimumWidth * 0.5;
east = center + minimumWidth * 0.5;
}
const minimumHeight = (homeView.north - homeView.south) * minimumFraction;
if ((north - south) < minimumHeight) {
const center = (north + south) * 0.5;
south = center - minimumHeight * 0.5;
north = center + minimumHeight * 0.5;
}
}
west = CesiumMath.toDegrees(west);
south = CesiumMath.toDegrees(south);
east = CesiumMath.toDegrees(east);
north = CesiumMath.toDegrees(north);
this.rectangleCatalogItem = new GeoJsonCatalogItem(this.terriaPreview);
this.rectangleCatalogItem.data = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
stroke: '#08ABD5',
'stroke-width': 2,
'stroke-opacity': 1,
fill: '#555555',
'fill-opacity': 0
},
geometry: {
type: 'Polygon',
coordinates: [
[
[west, south],
[west, north],
[east, north],
[east, south],
[west, south]
]
]
}
}
]
};
this.rectangleCatalogItem.isEnabled = true;
},
mapIsReady(mapContainer) {
if (mapContainer) {
this.mapElement = mapContainer;
if (this.props.showMap) {
this.initMap(this.props.previewedCatalogItem);
}
}
},
destroyPreviewMap() {
this.terriaViewer && this.terriaViewer.destroy();
if (this.mapElement) {
this.mapElement.innerHTML = '';
}
},
initMap(previewedCatalogItem) {
if (this.mapElement) {
this.terriaViewer = TerriaViewer.create(this.terriaPreview, {
mapContainer: this.mapElement
});
// disable preview map interaction
const map = this.terriaViewer.terria.leaflet.map;
map.touchZoom.disable();
map.doubleClickZoom.disable();
map.scrollWheelZoom.disable();
map.boxZoom.disable();
map.keyboard.disable();
map.dragging.disable();
this.updatePreview(previewedCatalogItem);
}
},
render() {
return (
<div className={Styles.map} onClick={this.clickMap}>
<Choose>
<When condition={this.props.showMap}>
<div className={classNames(Styles.terriaPreview)} ref={this.mapIsReady}/>
</When>
<Otherwise>
<div className={classNames(Styles.terriaPreview, Styles.placeholder)}/>
</Otherwise>
</Choose>
<label className={Styles.badge}>{this.state.previewBadgeText}</label>
</div>
);
},
});
module.exports = DataPreviewMap;