UNPKG

terriajs

Version:

Geospatial data visualization platform.

203 lines (173 loc) 6.05 kB
"use strict"; import React from "react"; import PropTypes from "prop-types"; import L from "leaflet"; import Cartesian2 from "terriajs-cesium/Source/Core/Cartesian2"; import defined from "terriajs-cesium/Source/Core/defined"; import EllipsoidGeodesic from "terriajs-cesium/Source/Core/EllipsoidGeodesic"; import getTimestamp from "terriajs-cesium/Source/Core/getTimestamp"; import Styles from "./legend.scss"; import { observer, disposeOnUnmount } from "mobx-react"; import { autorun, runInAction } from "mobx"; const geodesic = new EllipsoidGeodesic(); const distances = [ 1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500, 1000, 2000, 3000, 5000, 10000, 20000, 30000, 50000, 100000, 200000, 300000, 500000, 1000000, 2000000, 3000000, 5000000, 10000000, 20000000, 30000000, 50000000 ]; @observer class DistanceLegend extends React.Component { constructor(props) { super(props); this.state = { distanceLabel: undefined, barWidth: 0 }; } static displayName = "DistanceLegend"; static propTypes = { terria: PropTypes.object, scale: PropTypes.number, isPrintMode: PropTypes.bool }; /* eslint-disable-next-line camelcase */ UNSAFE_componentWillMount() { this.viewerSubscriptions = []; this.removeUpdateSubscription = undefined; this._lastLegendUpdate = undefined; this.viewerSubscriptions.push( this.props.terria.mainViewer.beforeViewerChanged.addEventListener(() => { if (defined(this.removeUpdateSubscription)) { this.removeUpdateSubscription(); this.removeUpdateSubscription = undefined; } }) ); disposeOnUnmount( this, autorun(() => this.addUpdateSubscription()) ); } componentWillUnmount() { this.removeUpdateSubscription && this.removeUpdateSubscription(); this.viewerSubscriptions.forEach((remove) => remove()); } addUpdateSubscription() { const that = this; if (defined(this.props.terria.cesium)) { const scene = this.props.terria.cesium.scene; this.removeUpdateSubscription = scene.postRender.addEventListener(() => { this.updateDistanceLegendCesium(scene); if (this.props.isPrintMode) { this.removeUpdateSubscription(); this.removeUpdateSubscription = null; } }); } else if (defined(this.props.terria.leaflet)) { const map = this.props.terria.leaflet.map; const potentialChangeCallback = function potentialChangeCallback() { that.updateDistanceLegendLeaflet(map); }; if (!this.props.isPrintMode) { that.removeUpdateSubscription = function () { map.off("zoomend", potentialChangeCallback); map.off("moveend", potentialChangeCallback); }; map.on("zoomend", potentialChangeCallback); map.on("moveend", potentialChangeCallback); } that.updateDistanceLegendLeaflet(map); } } updateDistanceLegendCesium(scene) { const now = getTimestamp(); if (now < this._lastLegendUpdate + 250) { return; } this._lastLegendUpdate = now; // Find the distance between two pixels at the bottom center of the screen. const width = scene.canvas.clientWidth; const height = scene.canvas.clientHeight; const left = scene.camera.getPickRay( new Cartesian2((width / 2) | 0, height - 1) ); const right = scene.camera.getPickRay( new Cartesian2((1 + width / 2) | 0, height - 1) ); const globe = scene.globe; const leftPosition = globe.pick(left, scene); const rightPosition = globe.pick(right, scene); if (!defined(leftPosition) || !defined(rightPosition)) { this.setState({ barWidth: undefined, distanceLabel: undefined }); return; } const leftCartographic = globe.ellipsoid.cartesianToCartographic(leftPosition); const rightCartographic = globe.ellipsoid.cartesianToCartographic(rightPosition); geodesic.setEndPoints(leftCartographic, rightCartographic); const pixelDistance = geodesic.surfaceDistance; runInAction(() => (this.props.terria.mainViewer.scale = pixelDistance)); // Find the first distance that makes the scale bar less than 100 pixels. const maxBarWidth = 100; let distance; for (let i = distances.length - 1; !defined(distance) && i >= 0; --i) { if (distances[i] / pixelDistance < maxBarWidth) { distance = distances[i]; } } if (defined(distance)) { let label; if (distance >= 1000) { label = (distance / 1000).toString() + " km"; } else { label = distance.toString() + " m"; } this.setState({ barWidth: ((distance / pixelDistance) * this.props.scale) | 0, distanceLabel: label }); } else { this.setState({ barWidth: undefined, distanceLabel: undefined }); } } updateDistanceLegendLeaflet(map) { const halfHeight = map.getSize().y / 2; const maxPixelWidth = 100; const maxMeters = map .containerPointToLatLng([0, halfHeight]) .distanceTo(map.containerPointToLatLng([maxPixelWidth, halfHeight])); runInAction(() => (this.props.terria.mainViewer.scale = maxMeters / 100)); const meters = L.control.scale()._getRoundNum(maxMeters); const label = meters < 1000 ? meters + " m" : meters / 1000 + " km"; this.setState({ barWidth: (meters / maxMeters) * maxPixelWidth * this.props.scale, distanceLabel: label }); } render() { const barStyle = { width: this.state.barWidth + "px", left: 5 + (125 - this.state.barWidth) / 2 + "px", height: "2px" }; const distanceLabel = this.state.distanceLabel ? ( <div className={Styles.distanceLegend}> <label>{this.state.distanceLabel}</label> <div className={Styles.bar} style={barStyle} /> </div> ) : null; return distanceLabel; } } DistanceLegend.defaultProps = { scale: 1, isPrintMode: false }; export default DistanceLegend;