qwc2-lts
Version:
QGIS Web Client
481 lines (473 loc) • 21.7 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/**
* Copyright 2024 Sourcepole AG
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import Coordinates from '@giro3d/giro3d/core/geographic/Coordinates';
import ColorLayer from '@giro3d/giro3d/core/layer/ColorLayer';
import Shape from '@giro3d/giro3d/entities/Shape';
import DrawTool, { conditions } from "@giro3d/giro3d/interactions/DrawTool.js";
import VectorSource from '@giro3d/giro3d/sources/VectorSource';
import ol from 'openlayers';
import pointInPolygon from 'point-in-polygon';
import PropTypes from 'prop-types';
import { CurvePath, LineCurve, Vector2, Vector3 } from 'three';
import ConfigUtils from '../../utils/ConfigUtils';
import CoordinatesUtils from '../../utils/CoordinatesUtils';
import LocaleUtils from '../../utils/LocaleUtils';
import MeasureUtils from '../../utils/MeasureUtils';
import TaskBar from '../TaskBar';
import ButtonBar from '../widgets/ButtonBar';
import CopyButton from '../widgets/CopyButton';
import HeightProfile3D from './HeightProfile3D';
import '../../plugins/style/Measure.css';
var Measure3D = /*#__PURE__*/function (_React$Component) {
function Measure3D(props) {
var _this;
_classCallCheck(this, Measure3D);
_this = _callSuper(this, Measure3D, [props]);
_defineProperty(_this, "state", {
mode: null,
result: null,
lenUnit: 'metric',
areaUnit: 'metric',
elevUnit: 'absolute'
});
_defineProperty(_this, "onShow", function (mode) {
_this.setState({
mode: mode !== null && mode !== void 0 ? mode : 'Point'
});
_this.abortController = new AbortController();
_this.measureTool = new DrawTool({
instance: _this.props.sceneContext.scene
});
_this.drawLayer = new ColorLayer({
source: new VectorSource({
data: [],
format: new ol.format.GeoJSON(),
style: _this.featureStyleFunction
})
});
_this.props.sceneContext.map.addLayer(_this.drawLayer);
});
_defineProperty(_this, "onHide", function () {
_this.clearResult();
_this.setState({
mode: null
});
_this.abortController.abort();
_this.abortController = null;
_this.measureTool.dispose();
_this.measureTool = null;
_this.props.sceneContext.map.removeLayer(_this.drawLayer, {
dispose: true
});
});
_defineProperty(_this, "renderModeSwitcher", function () {
var buttons = [{
key: "Point",
label: LocaleUtils.tr("measureComponent.pointLabel")
}, {
key: "LineString",
label: LocaleUtils.tr("measureComponent.lengthLabel")
}, {
key: "Polygon",
label: LocaleUtils.tr("measureComponent.areaLabel")
}];
return /*#__PURE__*/React.createElement(ButtonBar, {
active: _this.state.mode,
buttons: buttons,
onClick: function onClick(mode) {
return _this.setState({
mode: mode,
result: null
});
}
});
});
_defineProperty(_this, "renderResult", function () {
if (!_this.state.result) {
return null;
}
var text = "";
var unitSelector = null;
if (_this.state.mode === "Point") {
text = CoordinatesUtils.getFormattedCoordinate(_this.state.result.pos.slice(0, 2), _this.props.sceneContext.mapCrs);
var prec = ConfigUtils.getConfigProp("measurementPrecision");
text += ", " + (_this.state.result.ground > 0 && _this.state.elevUnit === 'ground' ? _this.state.result.ground : _this.state.result.pos[2]).toFixed(prec);
if (_this.state.result.ground > 0) {
unitSelector = /*#__PURE__*/React.createElement("select", {
onChange: function onChange(ev) {
return _this.setState({
elevUnit: ev.target.value
});
},
value: _this.state.elevUnit
}, /*#__PURE__*/React.createElement("option", {
value: "ground"
}, LocaleUtils.tr("measureComponent.ground")), /*#__PURE__*/React.createElement("option", {
value: "absolute"
}, LocaleUtils.tr("measureComponent.absolute")));
} else {
unitSelector = /*#__PURE__*/React.createElement("span", {
className: "measure-unit-label"
}, LocaleUtils.tr("measureComponent.absolute"));
}
} else if (_this.state.mode === "LineString") {
text = MeasureUtils.formatMeasurement(_this.state.result.length, false, _this.state.lenUnit);
unitSelector = /*#__PURE__*/React.createElement("select", {
onChange: function onChange(ev) {
return _this.setState({
lenUnit: ev.target.value
});
},
value: _this.state.lenUnit
}, /*#__PURE__*/React.createElement("option", {
value: "metric"
}, LocaleUtils.tr("measureComponent.metric")), /*#__PURE__*/React.createElement("option", {
value: "imperial"
}, LocaleUtils.tr("measureComponent.imperial")), /*#__PURE__*/React.createElement("option", {
value: "m"
}, "m"), /*#__PURE__*/React.createElement("option", {
value: "km"
}, "km"), /*#__PURE__*/React.createElement("option", {
value: "ft"
}, "ft"), /*#__PURE__*/React.createElement("option", {
value: "mi"
}, "mi"));
} else if (_this.state.mode === "Polygon") {
text = MeasureUtils.formatMeasurement(_this.state.result, true, _this.state.areaUnit);
unitSelector = /*#__PURE__*/React.createElement("select", {
onChange: function onChange(ev) {
return _this.setState({
areaUnit: ev.target.value
});
},
value: _this.state.areaUnit
}, /*#__PURE__*/React.createElement("option", {
value: "metric"
}, LocaleUtils.tr("measureComponent.metric")), /*#__PURE__*/React.createElement("option", {
value: "imperial"
}, LocaleUtils.tr("measureComponent.imperial")), /*#__PURE__*/React.createElement("option", {
value: "sqm"
}, "m\xB2"), /*#__PURE__*/React.createElement("option", {
value: "ha"
}, "ha"), /*#__PURE__*/React.createElement("option", {
value: "sqkm"
}, "km\xB2"), /*#__PURE__*/React.createElement("option", {
value: "sqft"
}, "ft\xB2"), /*#__PURE__*/React.createElement("option", {
value: "acre"
}, "acre"), /*#__PURE__*/React.createElement("option", {
value: "sqmi"
}, "mi\xB2"));
}
return /*#__PURE__*/React.createElement("div", {
className: "controlgroup"
}, /*#__PURE__*/React.createElement("input", {
className: "measure-result",
readOnly: true,
type: "text",
value: text
}), unitSelector, /*#__PURE__*/React.createElement(CopyButton, {
text: text
}));
});
_defineProperty(_this, "featureStyleFunction", function () {
return [new ol.style.Style({
fill: new ol.style.Fill({
color: [41, 120, 180, 0.5]
})
}), new ol.style.Style({
stroke: new ol.style.Stroke({
color: [255, 255, 255],
width: 4
})
}), new ol.style.Style({
stroke: new ol.style.Stroke({
color: [41, 120, 180],
width: 1.5
})
})];
});
_defineProperty(_this, "clearResult", function () {
_this.drawLayer.source.clear();
_this.measurementObjects.forEach(function (object) {
_this.props.sceneContext.scene.remove(object);
});
_this.measurementObjects = [];
_this.setState({
result: null
});
});
_defineProperty(_this, "restart", function () {
if (_this.abortController) {
_this.abortController.abort();
}
_this.abortController = new AbortController();
var options = {
signal: _this.abortController.signal,
endCondition: conditions.doubleClick
};
if (_this.state.mode === 'Point') {
_this.measureTool.createPoint(options).then(_this.measurePoint);
} else if (_this.state.mode === 'LineString') {
_this.measureTool.createLineString(options).then(_this.measureLine);
} else if (_this.state.mode === 'Polygon') {
_this.measureTool.createPolygon(options).then(_this.measureArea);
} else if (_this.state.mode === 'Height') {
_this.measureTool.createVerticalMeasure(options).then(_this.measureHeight);
}
});
_defineProperty(_this, "measurePoint", function (point) {
if (point === null) {
_this.restart();
return;
}
_this.clearResult();
var pos = point.points[0];
// Measure point above terrain
var elevation = _this.getElevation([pos.x, pos.y]);
var ground = pos.z - elevation > 0.3 ? pos.z - elevation : 0;
var elevationLabelFormatter = function elevationLabelFormatter(options) {
if (options.index === 0) {
return MeasureUtils.formatMeasurement(elevation, false, LocaleUtils.tr("measureComponent.absolute"));
} else if (ground > 0 && _this.state.elevUnit === "ground") {
return MeasureUtils.formatMeasurement(pos.z - elevation, false, LocaleUtils.tr("measureComponent.ground"));
} else {
return MeasureUtils.formatMeasurement(pos.z, false, LocaleUtils.tr("measureComponent.absolute"));
}
};
var shape = null;
if (ground > 0) {
// Add line
shape = new Shape({
showVertexLabels: true,
showLine: true,
showVertices: true,
vertexLabelFormatter: elevationLabelFormatter
});
shape.setPoints([new Vector3(pos.x, pos.y, elevation), pos]);
} else {
// Add point
shape = new Shape({
showVertexLabels: true,
showLine: false,
showVertices: true,
vertexLabelFormatter: elevationLabelFormatter
});
shape.setPoints([new Vector3(pos.x, pos.y, pos.z)]);
}
_this.props.sceneContext.scene.add(shape);
_this.measurementObjects.push(shape);
_this.props.sceneContext.scene.remove(point);
_this.setState({
result: {
pos: [pos.x, pos.y, pos.z],
ground: ground
}
});
// Setup for next measurement
_this.restart();
});
_defineProperty(_this, "measureLine", function (lineString) {
if (lineString === null) {
_this.restart();
return;
}
_this.clearResult();
var features = new ol.format.GeoJSON().readFeatures(lineString.toGeoJSON(), {
dataProjection: "EPSG:4326",
featureProjection: _this.props.sceneContext.mapCrs
});
_this.drawLayer.source.addFeatures(features);
_this.props.sceneContext.scene.remove(lineString);
// Compute 2d length and nSamples spaced points
var path = new CurvePath();
var len2d = 0;
for (var i = 0; i < lineString.points.length - 1; i++) {
var v0 = lineString.points[i];
var v1 = lineString.points[i + 1];
var line = new LineCurve(new Vector2(v0.x, v0.y), new Vector2(v1.x, v1.y));
path.add(line);
len2d += Math.sqrt((v1.x - v0.x) * (v1.x - v0.x) + (v1.y - v0.y) * (v1.y - v0.y));
}
var nSamples = Math.min(_this.props.maxSampleCount, Math.round(len2d / _this.props.minMeasureLength));
var points = path.getSpacedPoints(nSamples - 1);
var line3d = new Array(nSamples);
line3d[0] = [points[0].x, points[0].y, _this.getElevation([points[0].x, points[0].y]), 0];
var len3d = 0;
for (var _i = 1; _i < nSamples; ++_i) {
line3d[_i] = [points[_i].x, points[_i].y, _this.getElevation([points[_i].x, points[_i].y]), 0];
var dx = line3d[_i][0] - line3d[_i - 1][0];
var dy = line3d[_i][1] - line3d[_i - 1][1];
var dz = line3d[_i][2] - line3d[_i - 1][2];
len3d += Math.sqrt(dx * dx + dy * dy + dz * dz);
line3d[_i][3] = len3d; // Also store incremental length for height profie
}
_this.setState({
result: {
length: len3d,
profile: line3d
}
});
// Setup for next measurement
_this.restart();
});
_defineProperty(_this, "measureArea", function (polygon) {
if (polygon === null) {
_this.restart();
return;
}
_this.clearResult();
var features = new ol.format.GeoJSON().readFeatures(polygon.toGeoJSON(), {
dataProjection: "EPSG:4326",
featureProjection: _this.props.sceneContext.mapCrs
});
_this.drawLayer.source.addFeatures(features);
_this.props.sceneContext.scene.remove(polygon);
// Compute boundingbox of polygon, divide boundingbox into quads,
// compute quad area on terrain for each quad in polygon
var bbox = [polygon.points[0].x, polygon.points[0].y, polygon.points[0].x, polygon.points[0].y];
var coordinates = polygon.points.map(function (v) {
bbox[0] = Math.min(bbox[0], v.x);
bbox[1] = Math.min(bbox[1], v.y);
bbox[2] = Math.max(bbox[2], v.x);
bbox[3] = Math.max(bbox[3], v.y);
return [v.x, v.y];
});
var quadSize = _this.props.minMeasureLength;
var numX = Math.min(_this.props.maxSampleCount, Math.round((bbox[2] - bbox[0]) / quadSize));
var numY = Math.min(_this.props.maxSampleCount, Math.round((bbox[3] - bbox[1]) / quadSize));
var deltaX = (bbox[2] - bbox[0]) / numX;
var deltaY = (bbox[3] - bbox[1]) / numY;
var area = 0;
var elevationCache = new Array(numX * numY);
for (var iX = 0; iX < numX - 1; ++iX) {
for (var iY = 0; iY < numY - 1; ++iY) {
var _elevationCache, _elevationCache2, _elevationCache3, _elevationCache4;
// If quad center lies in polygon, consider it
var p = [bbox[0] + iX * deltaX, bbox[1] + iY * deltaY];
var c = [p[0] + 0.5 * deltaX, p[1] + 0.5 * deltaY];
if (!pointInPolygon(c, coordinates)) {
continue;
}
// Get elevations
var z1 = (_elevationCache = elevationCache[iY * numX + iX]) !== null && _elevationCache !== void 0 ? _elevationCache : elevationCache[iY * numX + iX] = _this.getElevation(p);
var z2 = (_elevationCache2 = elevationCache[iY * numX + iX + 1]) !== null && _elevationCache2 !== void 0 ? _elevationCache2 : elevationCache[iY * numX + iX + 1] = _this.getElevation([p[0] + deltaX, p[1]]);
var z3 = (_elevationCache3 = elevationCache[(iY + 1) * numX + iX + 1]) !== null && _elevationCache3 !== void 0 ? _elevationCache3 : elevationCache[(iY + 1) * numX + iX + 1] = _this.getElevation([p[0] + deltaX, p[1] + deltaY]);
var z4 = (_elevationCache4 = elevationCache[(iY + 1) * numX + iX]) !== null && _elevationCache4 !== void 0 ? _elevationCache4 : elevationCache[(iY + 1) * numX + iX] = _this.getElevation([p[0], p[1] + deltaY]);
// Divide quad along diagonal with smaller elevation difference
var dz1 = Math.abs(z3 - z1);
var dz2 = Math.abs(z4 - z2);
if (dz1 < dz2) {
var area1 = _this.triangleArea([-deltaX, 0, z1 - z2], [0, deltaY, z3 - z2]);
var area2 = _this.triangleArea([0, -deltaY, z1 - z4], [deltaX, 0, z3 - z4]);
area += area1 + area2;
} else {
var _area = _this.triangleArea([deltaX, 0, z2 - z1], [0, deltaY, z4 - z1]);
var _area2 = _this.triangleArea([-deltaX, 0, z4 - z3], [0, -deltaY, z1 - z3]);
area += _area + _area2;
}
}
}
_this.setState({
result: area
});
// Setup for next measurement
_this.restart();
});
_defineProperty(_this, "measureHeight", function (lineString) {
if (lineString === null) {
_this.restart();
return;
}
_this.clearResult();
_this.measurementObjects.push(lineString);
// Setup for next measurement
_this.restart();
});
_defineProperty(_this, "getElevation", function (point) {
var coordinates = new Coordinates(_this.props.sceneContext.mapCrs, point[0], point[1], 0);
var result = _this.props.sceneContext.map.getElevation({
coordinates: coordinates
});
if (result.samples.length > 0) {
result.samples.sort(function (a, b) {
return a.resolution - b.resolution;
});
return result.samples[0].elevation;
}
return null;
});
_defineProperty(_this, "triangleArea", function (u, v) {
var cross = [u[1] * v[2] - u[2] * v[1], u[0] * v[2] - u[2] * v[0], u[0] * v[1] - u[1] * v[0]];
return 0.5 * Math.sqrt(cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]);
});
_this.measureTool = null;
_this.measurementObjects = [];
return _this;
}
_inherits(Measure3D, _React$Component);
return _createClass(Measure3D, [{
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
if (this.state.mode !== prevState.mode) {
this.clearResult();
this.restart();
}
if (this.state.elevUnit !== prevState.elevUnit) {
// Re-render height label
this.measurementObjects[0].rebuildLabels();
this.props.sceneContext.scene.notifyChange();
}
}
}, {
key: "render",
value: function render() {
var _this2 = this,
_this$state$result;
return [/*#__PURE__*/React.createElement(TaskBar, {
key: "TaskBar",
onHide: this.onHide,
onShow: this.onShow,
task: "Measure3D"
}, function () {
return {
body: /*#__PURE__*/React.createElement("div", null, _this2.renderModeSwitcher(), _this2.renderResult())
};
}), (_this$state$result = this.state.result) !== null && _this$state$result !== void 0 && _this$state$result.profile ? /*#__PURE__*/React.createElement(HeightProfile3D, {
data: this.state.result.profile,
key: "HeightProfile",
sceneContext: this.props.sceneContext
}) : null];
}
}]);
}(React.Component);
_defineProperty(Measure3D, "propTypes", {
maxSampleCount: PropTypes.number,
minMeasureLength: PropTypes.number,
sceneContext: PropTypes.object
});
_defineProperty(Measure3D, "defaultProps", {
maxSampleCount: 500,
minMeasureLength: 5
});
export { Measure3D as default };