terriajs
Version:
Geospatial data visualization platform.
202 lines (175 loc) • 8.57 kB
JSX
;
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import ObserveModelMixin from '../../ObserveModelMixin';
import Styles from './tool_button.scss';
import Icon from "../../Icon.jsx";
const UserDrawing = require('../../../Models/UserDrawing');
const EllipsoidGeodesic = require('terriajs-cesium/Source/Core/EllipsoidGeodesic.js');
const Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid.js');
const EllipsoidTangentPlane = require('terriajs-cesium/Source/Core/EllipsoidTangentPlane.js');
const CesiumMath = require('terriajs-cesium/Source/Core/Math.js');
const PolygonGeometryLibrary = require('terriajs-cesium/Source/Core/PolygonGeometryLibrary.js');
const PolygonHierarchy = require('terriajs-cesium/Source/Core/PolygonHierarchy.js');
const Cartesian3 = require('terriajs-cesium/Source/Core/Cartesian3.js');
const VertexFormat = require('terriajs-cesium/Source/Core/VertexFormat.js');
const MeasureTool = createReactClass({
displayName: 'MeasureTool',
mixins: [ObserveModelMixin],
propTypes: {
terria: PropTypes.object
},
getInitialState() {
return {
totalDistanceMetres: 0,
totalAreaMetresSquared: 0,
userDrawing: new UserDrawing(
{
terria: this.props.terria,
messageHeader: "Measure Tool",
allowPolygon: false,
onPointClicked: this.onPointClicked,
onPointMoved: this.onPointMoved,
onCleanUp: this.onCleanUp,
onMakeDialogMessage: this.onMakeDialogMessage
})
};
},
prettifyNumber(number, squared) {
if (number <= 0) {
return "";
}
// Given a number representing a number in metres, make it human readable
let label = "m";
if (squared) {
if (number > 999999) {
label = "km";
number = number/1000000.0;
}
} else {
if (number > 999) {
label = "km";
number = number/1000.0;
}
}
number = number.toFixed(2);
// http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
number = number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
let numberStr = number + " " + label;
if (squared) {
numberStr += "\u00B2";
}
return numberStr;
},
updateDistance(pointEntities) {
this.setState({ totalDistanceMetres: 0 });
if (pointEntities.entities.values.length < 1) {
return;
}
const prevPoint = pointEntities.entities.values[0];
let prevPointPos = prevPoint.position.getValue(this.props.terria.clock.currentTime);
for (let i=1; i < pointEntities.entities.values.length; i++) {
const currentPoint = pointEntities.entities.values[i];
const currentPointPos = currentPoint.position.getValue(this.props.terria.clock.currentTime);
this.setState({ totalDistanceMetres: this.state.totalDistanceMetres + this.getGeodesicDistance(prevPointPos,
currentPointPos)});
prevPointPos = currentPointPos;
}
if (this.state.userDrawing.closeLoop) {
const firstPoint = pointEntities.entities.values[0];
const firstPointPos = firstPoint.position.getValue(this.props.terria.clock.currentTime);
this.setState({ totalDistanceMetres: this.state.totalDistanceMetres + this.getGeodesicDistance(prevPointPos,
firstPointPos)});
}
},
updateArea(pointEntities) {
this.setState({ totalAreaMetresSquared: 0 });
if (!this.state.userDrawing.closeLoop) {
// Not a closed polygon? Don't calculate area.
return;
}
if (pointEntities.entities.values.length < 3) {
return;
}
const perPositionHeight = true;
const positions = [];
for (let i=0; i < pointEntities.entities.values.length; i++) {
const currentPoint = pointEntities.entities.values[i];
const currentPointPos = currentPoint.position.getValue(this.props.terria.clock.currentTime);
positions.push(currentPointPos);
}
// Request the triangles that make up the polygon from Cesium.
const tangentPlane = EllipsoidTangentPlane.fromPoints(positions, Ellipsoid.WGS84);
const polygons = PolygonGeometryLibrary.polygonsFromHierarchy(new PolygonHierarchy(positions), tangentPlane.projectPointsOntoPlane.bind(tangentPlane), !perPositionHeight, Ellipsoid.WGS84);
const geom = PolygonGeometryLibrary.createGeometryFromPositions(Ellipsoid.WGS84,
polygons.polygons[0],
CesiumMath.RADIANS_PER_DEGREE,
perPositionHeight,
VertexFormat.POSITION_ONLY);
if (geom.indices.length % 3 !== 0 || geom.attributes.position.values.length % 3 !== 0) {
// Something has gone wrong. We expect triangles. Can't calcuate area.
return;
}
const coords = [];
for (let i = 0; i < geom.attributes.position.values.length; i+=3) {
coords.push(new Cartesian3(geom.attributes.position.values[i],
geom.attributes.position.values[i+1],
geom.attributes.position.values[i+2]));
}
let area = 0;
for (let i=0; i < geom.indices.length; i+=3) {
const ind1 = geom.indices[i];
const ind2 = geom.indices[i+1];
const ind3 = geom.indices[i+2];
const a = Cartesian3.distance(coords[ind1], coords[ind2]);
const b = Cartesian3.distance(coords[ind2], coords[ind3]);
const c = Cartesian3.distance(coords[ind3], coords[ind1]);
// Heron's formula
const s = (a + b + c)/2.0;
area += Math.sqrt(s*(s-a)*(s-b)*(s-c));
}
this.setState({ totalAreaMetresSquared: area });
},
getGeodesicDistance(pointOne, pointTwo) {
// Note that Cartesian.distance gives the straight line distance between the two points, ignoring
// curvature. This is not what we want.
const pickedPointCartographic = Ellipsoid.WGS84.cartesianToCartographic(pointOne);
const lastPointCartographic = Ellipsoid.WGS84.cartesianToCartographic(pointTwo);
const geodesic = new EllipsoidGeodesic(pickedPointCartographic, lastPointCartographic);
return geodesic.surfaceDistance;
},
onCleanUp() {
this.setState({totalDistanceMetres: 0});
this.setState({totalAreaMetresSquared: 0});
},
onPointClicked(pointEntities) {
this.updateDistance(pointEntities);
this.updateArea(pointEntities);
},
onPointMoved(pointEntities) {
// This is no different to clicking a point.
this.onPointClicked(pointEntities);
},
onMakeDialogMessage() {
const distance = this.prettifyNumber(this.state.totalDistanceMetres, false);
let message = distance;
if (this.state.totalAreaMetresSquared !== 0) {
message += "<br>" + this.prettifyNumber(this.state.totalAreaMetresSquared, true);
}
return message;
},
handleClick() {
this.state.userDrawing.enterDrawMode();
},
render() {
return <div className={Styles.toolButton}>
<button type='button' className={Styles.btn}
title='measure distance between two points'
onClick={this.handleClick}>
<Icon glyph={Icon.GLYPHS.measure}/>
</button>
</div>;
},
});
export default MeasureTool;