qwc2-lts
Version:
QGIS Web Client
447 lines (444 loc) • 22.5 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 classNames from 'classnames';
import PropTypes from 'prop-types';
import { Raycaster, Vector2, Vector3 } from 'three';
import { MapControls } from 'three/addons/controls/MapControls';
import Icon from '../Icon';
import './style/MapControls3D.css';
var MapControls3D = /*#__PURE__*/function (_React$Component) {
function MapControls3D(props) {
var _this;
_classCallCheck(this, MapControls3D);
_this = _callSuper(this, MapControls3D, [props]);
_defineProperty(_this, "state", {
firstPerson: false
});
_defineProperty(_this, "keyHandler", function (event) {
if (event.repeat) {
return;
} else if (event.key === "ArrowUp") {
if (event.ctrlKey) _this.tilt(event, 0, 1, true);else _this.pan(event, 0, 1, true);
} else if (event.key === "ArrowDown") {
if (event.ctrlKey) _this.tilt(event, 0, -1, true);else _this.pan(event, 0, -1, true);
} else if (event.key === "ArrowLeft") {
if (event.ctrlKey) _this.tilt(event, 1, 0, true);else _this.pan(event, 1, 0, true);
} else if (event.key === "ArrowRight") {
if (event.ctrlKey) _this.tilt(event, -1, 0, true);else _this.pan(event, -1, 0, true);
}
});
_defineProperty(_this, "home", function () {
_this.leaveFirstPerson();
var extent = _this.props.sceneContext.map.extent;
var bounds = [extent.west, extent.south, extent.east, extent.north];
_this.setViewToExtent(bounds);
});
_defineProperty(_this, "pan", function (ev, dx, dy) {
var keyboard = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
// Pan faster the heigher one is above the terrain
var d = _this.state.firstPerson ? 200000 : 100 + (_this.props.sceneContext.scene.view.camera.position.z - _this.props.sceneContext.scene.view.controls.target.z) / 250;
var delta = new Vector2(dx, dy).multiplyScalar(d);
_this.animationInterrupted = false;
var lastTimestamp = new Date() / 1000;
var _animate = function animate() {
if (_this.animationInterrupted) {
return;
}
// Pan <delta> distance per second
var timestamp = new Date() / 1000;
var k = timestamp - lastTimestamp;
lastTimestamp = timestamp;
_this.props.sceneContext.scene.view.controls._pan(delta.x * k, delta.y * k);
_this.props.sceneContext.scene.notifyChange();
requestAnimationFrame(_animate);
};
requestAnimationFrame(_animate);
var element = keyboard ? ev.target : ev.view;
var event = keyboard ? "keyup" : "mouseup";
element.addEventListener(event, function () {
_this.animationInterrupted = true;
}, {
once: true
});
});
_defineProperty(_this, "tilt", function (ev, yaw, az) {
var keyboard = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
if (_this.state.firstPerson) {
az *= -1;
yaw *= -1;
}
// Pan faster the heigher one is above the terrain
_this.animationInterrupted = false;
var lastTimestamp = new Date() / 1000;
var _animate2 = function animate() {
if (_this.animationInterrupted) {
return;
}
// Pan <delta> distance per second
var timestamp = new Date() / 1000;
var k = timestamp - lastTimestamp;
lastTimestamp = timestamp;
if (az) {
_this.props.sceneContext.scene.view.controls._rotateUp(az * k);
}
if (yaw) {
_this.props.sceneContext.scene.view.controls._rotateLeft(yaw * k);
}
_this.props.sceneContext.scene.notifyChange();
requestAnimationFrame(_animate2);
};
requestAnimationFrame(_animate2);
var element = keyboard ? ev.target : ev.view;
var event = keyboard ? "keyup" : "mouseup";
element.addEventListener(event, function () {
_this.animationInterrupted = true;
}, {
once: true
});
});
_defineProperty(_this, "resetTilt", function () {
if (_this.state.firstPerson) {
var target = _this.props.sceneContext.scene.view.controls.target;
var camerapos = _this.props.sceneContext.scene.view.camera.position;
_this.props.sceneContext.scene.view.controls.target.set(target.x, target.y, camerapos.z);
} else {
// Animate from old to new position
var _target = _this.props.sceneContext.scene.view.controls.target;
var oldPosition = _this.props.sceneContext.scene.view.camera.position.clone();
var oldYaw = _this.props.sceneContext.scene.view.controls.getAzimuthalAngle();
var newPosition = new Vector3(_target.x, _target.y, _target.distanceTo(oldPosition));
var startTime = new Date() / 1000;
_this.animationInterrupted = false;
var _animate3 = function animate() {
if (!_this.props.sceneContext.scene || _this.animationInterrupted) {
return;
}
var duration = 2;
var elapsed = new Date() / 1000 - startTime;
var x = elapsed / duration;
var k = 0.5 * (1 - Math.cos(x * Math.PI));
var currentPosition = new Vector3().lerpVectors(oldPosition, newPosition, k);
currentPosition.x -= _target.x;
currentPosition.y -= _target.y;
currentPosition.applyAxisAngle(new Vector3(0, 0, 1), -oldYaw * k);
currentPosition.x += _target.x;
currentPosition.y += _target.y;
_this.props.sceneContext.scene.view.camera.position.copy(currentPosition);
_this.props.sceneContext.scene.notifyChange();
if (elapsed < duration) {
requestAnimationFrame(_animate3);
} else {
_this.props.sceneContext.scene.view.camera.position.copy(newPosition);
_this.props.sceneContext.scene.notifyChange();
}
};
requestAnimationFrame(_animate3);
}
});
_defineProperty(_this, "setViewToExtent", function (bounds) {
var _elevationResult$samp;
var angle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (_this.state.firstPerson) {
_this.leaveFirstPerson();
}
var center = {
x: 0.5 * (bounds[0] + bounds[2]),
y: 0.5 * (bounds[1] + bounds[3])
};
var elevationResult = _this.props.sceneContext.map.getElevation({
coordinates: new Coordinates(_this.props.sceneContext.mapCrs, center.x, center.y)
});
elevationResult.samples.sort(function (a, b) {
return a.resolution > b.resolution;
});
center.z = ((_elevationResult$samp = elevationResult.samples[0]) === null || _elevationResult$samp === void 0 ? void 0 : _elevationResult$samp.elevation) || 0;
// Camera height to width bbox width
var fov = 35 / 180 * Math.PI;
var cameraHeight = (bounds[2] - bounds[0]) / (2 * Math.tan(fov / 2));
// Animate from old to new position/target
var oldPosition = _this.props.sceneContext.scene.view.camera.position.clone();
var oldTarget = _this.props.sceneContext.scene.view.controls.target.clone();
var oldYaw = _this.props.sceneContext.scene.view.controls.getAzimuthalAngle();
var newPosition = new Vector3(center.x, center.y, center.z + cameraHeight);
var newTarget = new Vector3(center.x, center.y, center.z);
var rotateAngle = -oldYaw + angle;
while (rotateAngle > Math.PI) {
rotateAngle -= 2 * Math.PI;
}
while (rotateAngle < -Math.PI) {
rotateAngle += 2 * Math.PI;
}
var startTime = new Date() / 1000;
_this.animationInterrupted = false;
var _animate4 = function animate() {
if (!_this.props.sceneContext.scene || _this.animationInterrupted) {
return;
}
var duration = 2;
var elapsed = new Date() / 1000 - startTime;
var x = elapsed / duration;
var k = 0.5 * (1 - Math.cos(x * Math.PI));
var currentPosition = new Vector3().lerpVectors(oldPosition, newPosition, k);
var currentTarget = new Vector3().lerpVectors(oldTarget, newTarget, k);
currentPosition.x -= currentTarget.x;
currentPosition.y -= currentTarget.y;
currentPosition.applyAxisAngle(new Vector3(0, 0, 1), rotateAngle * k);
currentPosition.x += currentTarget.x;
currentPosition.y += currentTarget.y;
_this.props.sceneContext.scene.view.camera.position.copy(currentPosition);
_this.props.sceneContext.scene.view.controls.target.copy(currentTarget);
_this.props.sceneContext.scene.notifyChange();
if (elapsed < duration) {
requestAnimationFrame(_animate4);
} else {
_this.props.sceneContext.scene.view.camera.position.copy(newPosition);
_this.props.sceneContext.scene.view.controls.target.copy(newTarget);
_this.props.sceneContext.scene.view.controls._rotateLeft(-angle);
_this.props.sceneContext.scene.notifyChange();
}
};
requestAnimationFrame(_animate4);
});
_defineProperty(_this, "updateControlsTarget", function () {
var _terrInter$point$z, _terrInter$point;
var target = _this.props.sceneContext.scene.view.controls.target;
if (_this.prevTarget && _this.prevTarget.distanceToSquared(target) < 50) {
return;
}
_this.prevTarget = target.clone();
var x = target.x;
var y = target.y;
var raycaster = new Raycaster();
// Query highest resolution terrain tile (i.e. tile with no children)
raycaster.set(new Vector3(x, y, target.z + 100000), new Vector3(0, 0, -1));
var terrInter = raycaster.intersectObjects([_this.props.sceneContext.map.object3d]).filter(function (result) {
return result.object.children.length === 0;
})[0];
var terrainHeight = (_terrInter$point$z = terrInter === null || terrInter === void 0 || (_terrInter$point = terrInter.point) === null || _terrInter$point === void 0 ? void 0 : _terrInter$point.z) !== null && _terrInter$point$z !== void 0 ? _terrInter$point$z : 0;
// FIXME: Why does raycaster.intersectObjects on terrain return 0-ish even when above terrain?
if (terrainHeight <= 1) {
return;
}
if (_this.state.firstPerson) {
var delta = terrainHeight + _this.personHeight - _this.props.sceneContext.scene.view.camera.position.z;
_this.props.sceneContext.scene.view.camera.position.z += delta;
_this.props.sceneContext.scene.view.controls.target.z += delta;
return;
}
var cameraHeight = _this.props.sceneContext.scene.view.camera.position.z;
// If camera height is at terrain height, target height should be at terrain height
// If camera height is at twice the terrain height or further, target height should be zero
var targetHeight = terrainHeight > 0 ? terrainHeight * Math.max(0, 1 - (cameraHeight - terrainHeight) / terrainHeight) : 0;
_this.props.sceneContext.scene.view.controls.target.z = targetHeight;
});
_defineProperty(_this, "stopAnimations", function () {
_this.animationInterrupted = true;
});
_defineProperty(_this, "switchToFirstPersonView", function (ev) {
if (!_this.state.firstPerson) {
_this.setupFirstPerson(ev);
}
});
_defineProperty(_this, "toggleFirstPersonControls", function () {
if (_this.state.firstPerson) {
_this.leaveFirstPerson();
} else {
_this.props.sceneContext.scene.domElement.addEventListener('click', _this.setupFirstPerson, {
once: true
});
}
});
_defineProperty(_this, "setupFirstPerson", function (ev) {
var rect = ev.target.getBoundingClientRect();
var mouseX = (ev.clientX - rect.left) / rect.width * 2 - 1;
var mouseY = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
var intersection = _this.props.sceneContext.getSceneIntersection(mouseX, mouseY, false);
if (!intersection) {
return;
}
var pos = intersection.point;
_this.props.sceneContext.getTerrainHeight([pos.x, pos.y]).then(function (z) {
// Animate from old to new position/target
var oldPosition = _this.props.sceneContext.scene.view.camera.position.clone();
var oldTarget = _this.props.sceneContext.scene.view.controls.target.clone();
var newPosition = new Vector3(pos.x, pos.y, z + _this.personHeight);
var newTarget = new Vector3(pos.x, pos.y + 300, z + _this.personHeight);
var startTime = new Date() / 1000;
_this.animationInterrupted = false;
var _animate5 = function animate() {
if (!_this.props.sceneContext.scene || _this.animationInterrupted) {
return;
}
var duration = 2;
var elapsed = new Date() / 1000 - startTime;
var x = elapsed / duration;
var k = 0.5 * (1 - Math.cos(x * Math.PI));
var currentPosition = new Vector3().lerpVectors(oldPosition, newPosition, k);
var currentTarget = new Vector3().lerpVectors(oldTarget, newTarget, k);
_this.props.sceneContext.scene.view.camera.position.copy(currentPosition);
_this.props.sceneContext.scene.view.controls.target.copy(currentTarget);
_this.props.sceneContext.scene.notifyChange();
if (elapsed < duration) {
requestAnimationFrame(_animate5);
} else {
_this.props.sceneContext.scene.view.camera.position.copy(newPosition);
_this.props.sceneContext.scene.view.controls.target.set(pos.x, pos.y + 0.1, z + _this.personHeight);
_this.props.sceneContext.scene.notifyChange();
_this.controls.maxPolarAngle = 0.8 * Math.PI;
_this.controls.panSpeed = 600;
_this.controls.enableZoom = false;
_this.setState({
firstPerson: true
});
}
};
requestAnimationFrame(_animate5);
});
});
_defineProperty(_this, "leaveFirstPerson", function () {
_this.controls.maxPolarAngle = Math.PI * 0.5;
_this.controls.panSpeed = 1;
_this.controls.enableZoom = true;
_this.setState({
firstPerson: false
}, function () {
var cameraPos = _this.props.sceneContext.scene.view.camera.position;
var bounds = [cameraPos.x - 1000, cameraPos.y - 1000, cameraPos.x + 1000, cameraPos.y + 1000];
_this.setViewToExtent(bounds);
});
});
_this.animationInterrupted = false;
_this.personHeight = 2;
_this.prevTarget = null;
return _this;
}
_inherits(MapControls3D, _React$Component);
return _createClass(MapControls3D, [{
key: "componentDidMount",
value: function componentDidMount() {
var sceneElement = this.props.sceneContext.scene.domElement;
sceneElement.tabIndex = 0;
this.controls = new MapControls(this.props.sceneContext.scene.view.camera, sceneElement);
this.controls.zoomToCursor = true;
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.2;
this.controls.maxPolarAngle = Math.PI * 0.5;
sceneElement.addEventListener('keydown', this.keyHandler);
this.props.sceneContext.scene.view.setControls(this.controls);
var targetPos = this.props.sceneContext.scene.view.camera.position.clone();
targetPos.z = 0;
this.controls.target = targetPos;
this.controls.addEventListener('change', this.updateControlsTarget);
sceneElement.addEventListener('dblclick', this.switchToFirstPersonView);
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.animationInterrupted = true;
var sceneElement = this.props.sceneContext.scene.domElement;
sceneElement.addEventListener('keydown', this.keyHandler);
this.controls.removeEventListener('change', this.updateControlsTarget);
this.props.sceneContext.scene.view.setControls(null);
this.controls.dispose();
this.controls = null;
this.props.sceneContext.scene.domElement.removeEventListener('dblclick', this.switchToFirstPersonView);
}
}, {
key: "render",
value: function render() {
var _this2 = this;
var firstPersonButtonClasses = classNames({
"map3d-firstperson-button": true,
"map3d-firstperson-button-active": this.state.firstPerson
});
return [/*#__PURE__*/React.createElement("div", {
className: "map3d-nav-pan",
key: "MapPanWidget"
}, /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "chevron-up",
onMouseDown: function onMouseDown(ev) {
return _this2.pan(ev, 0, 1);
}
}), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "chevron-left",
onMouseDown: function onMouseDown(ev) {
return _this2.pan(ev, 1, 0);
}
}), /*#__PURE__*/React.createElement(Icon, {
icon: "home",
onClick: function onClick() {
return _this2.home();
}
}), /*#__PURE__*/React.createElement(Icon, {
icon: "chevron-right",
onMouseDown: function onMouseDown(ev) {
return _this2.pan(ev, -1, 0);
}
}), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "chevron-down",
onMouseDown: function onMouseDown(ev) {
return _this2.pan(ev, 0, -1);
}
}), /*#__PURE__*/React.createElement("span", null)), /*#__PURE__*/React.createElement("div", {
className: "map3d-nav-rotate",
key: "MapRotateWidget"
}, /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "tilt-up",
onMouseDown: function onMouseDown(ev) {
return _this2.tilt(ev, 0, 1);
}
}), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "tilt-left",
onMouseDown: function onMouseDown(ev) {
return _this2.tilt(ev, 1, 0);
}
}), /*#__PURE__*/React.createElement(Icon, {
icon: "point",
onClick: function onClick() {
return _this2.resetTilt();
}
}), /*#__PURE__*/React.createElement(Icon, {
icon: "tilt-right",
onMouseDown: function onMouseDown(ev) {
return _this2.tilt(ev, -1, 0);
}
}), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement(Icon, {
icon: "tilt-down",
onMouseDown: function onMouseDown(ev) {
return _this2.tilt(ev, 0, -1);
}
}), /*#__PURE__*/React.createElement("span", null)), /*#__PURE__*/React.createElement("div", {
className: firstPersonButtonClasses,
key: "FirstPersonButton",
onClick: this.toggleFirstPersonControls
}, /*#__PURE__*/React.createElement(Icon, {
icon: "person"
}))];
}
}]);
}(React.Component);
_defineProperty(MapControls3D, "propTypes", {
sceneContext: PropTypes.object
});
export { MapControls3D as default };